##// END OF EJS Templates
Added PULL protocol method implementation without any filters
neko259 -
r1321:055856f7 decentral
parent child Browse files
Show More
@@ -1,59 +1,59 b''
1 1 import re
2 import urllib.parse
3 import httplib2
4 2 import xml.etree.ElementTree as ET
5 3
4 import httplib2
6 5 from django.core.management import BaseCommand
6
7 7 from boards.models import GlobalId
8 8 from boards.models.post.sync import SyncManager
9 9
10 10 __author__ = 'neko259'
11 11
12 12
13 13 REGEX_GLOBAL_ID = re.compile(r'(\w+)::([\w\+/]+)::(\d+)')
14 14
15 15
16 16 class Command(BaseCommand):
17 help = 'Send a sync or get request to the server.' + \
18 'sync_with_server <server_url> [post_global_id]'
17 help = 'Send a sync or get request to the server.'
19 18
20 19 def add_arguments(self, parser):
21 20 parser.add_argument('url', type=str)
22 parser.add_argument('global_id', type=str)
21 parser.add_argument('--global_id', type=str, default='',
22 help='Post global ID')
23 23
24 24 def handle(self, *args, **options):
25 25 url = options.get('url')
26 26 global_id_str = options.get('global_id')
27 27 if global_id_str:
28 28 match = REGEX_GLOBAL_ID.match(global_id_str)
29 29 if match:
30 30 key_type = match.group(1)
31 31 key = match.group(2)
32 32 local_id = match.group(3)
33 33
34 34 global_id = GlobalId(key_type=key_type, key=key,
35 35 local_id=local_id)
36 36
37 37 xml = GlobalId.objects.generate_request_get([global_id])
38 38 # body = urllib.parse.urlencode(data)
39 39 h = httplib2.Http()
40 40 response, content = h.request(url, method="POST", body=xml)
41 41
42 42 SyncManager.parse_response_get(content)
43 43 else:
44 44 raise Exception('Invalid global ID')
45 45 else:
46 46 h = httplib2.Http()
47 response, content = h.request(url, method="POST")
47 xml = GlobalId.objects.generate_request_pull()
48 response, content = h.request(url, method="POST", body=xml)
48 49
49 50 print(content)
50 51
51 52 root = ET.fromstring(content)
52 53 status = root.findall('status')[0].text
53 54 if status == 'success':
54 55 models = root.findall('models')[0]
55 56 for model in models:
56 model_content = model[0]
57 print(model_content.findall('text')[0].text)
57 print(ET.tostring(model))
58 58 else:
59 59 raise Exception('Invalid response status')
@@ -1,175 +1,190 b''
1 1 import xml.etree.ElementTree as et
2 2 from django.db import transaction
3 3 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
4 4
5 5 ENCODING_UNICODE = 'unicode'
6 6
7 7 TAG_MODEL = 'model'
8 8 TAG_REQUEST = 'request'
9 9 TAG_RESPONSE = 'response'
10 10 TAG_ID = 'id'
11 11 TAG_STATUS = 'status'
12 12 TAG_MODELS = 'models'
13 13 TAG_TITLE = 'title'
14 14 TAG_TEXT = 'text'
15 15 TAG_THREAD = 'thread'
16 16 TAG_PUB_TIME = 'pub-time'
17 17 TAG_SIGNATURES = 'signatures'
18 18 TAG_SIGNATURE = 'signature'
19 19 TAG_CONTENT = 'content'
20 20 TAG_ATTACHMENTS = 'attachments'
21 21 TAG_ATTACHMENT = 'attachment'
22 22 TAG_TAGS = 'tags'
23 23 TAG_TAG = 'tag'
24 24
25 25 TYPE_GET = 'get'
26 26
27 27 ATTR_VERSION = 'version'
28 28 ATTR_TYPE = 'type'
29 29 ATTR_NAME = 'name'
30 30 ATTR_VALUE = 'value'
31 31 ATTR_MIMETYPE = 'mimetype'
32 32 ATTR_KEY = 'key'
33 33
34 34 STATUS_SUCCESS = 'success'
35 35
36 36
37 37 class SyncManager:
38 38 @staticmethod
39 39 def generate_response_get(model_list: list):
40 40 response = et.Element(TAG_RESPONSE)
41 41
42 42 status = et.SubElement(response, TAG_STATUS)
43 43 status.text = STATUS_SUCCESS
44 44
45 45 models = et.SubElement(response, TAG_MODELS)
46 46
47 47 for post in model_list:
48 48 model = et.SubElement(models, TAG_MODEL)
49 49 model.set(ATTR_NAME, 'post')
50 50
51 51 content_tag = et.SubElement(model, TAG_CONTENT)
52 52
53 53 tag_id = et.SubElement(content_tag, TAG_ID)
54 54 post.global_id.to_xml_element(tag_id)
55 55
56 56 title = et.SubElement(content_tag, TAG_TITLE)
57 57 title.text = post.title
58 58
59 59 text = et.SubElement(content_tag, TAG_TEXT)
60 60 text.text = post.get_sync_text()
61 61
62 62 thread = post.get_thread()
63 63 if post.is_opening():
64 64 tag_tags = et.SubElement(content_tag, TAG_TAGS)
65 65 for tag in thread.get_tags():
66 66 tag_tag = et.SubElement(tag_tags, TAG_TAG)
67 67 tag_tag.text = tag.name
68 68 else:
69 69 tag_thread = et.SubElement(content_tag, TAG_THREAD)
70 70 thread_id = et.SubElement(tag_thread, TAG_ID)
71 71 thread.get_opening_post().global_id.to_xml_element(thread_id)
72 72
73 73 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
74 74 pub_time.text = str(post.get_pub_time_str())
75 75
76 76 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
77 77 post_signatures = post.global_id.signature_set.all()
78 78 if post_signatures:
79 79 signatures = post_signatures
80 80 # TODO Adding signature to a post is not yet added. For now this
81 81 # block is useless
82 82 else:
83 83 # TODO Maybe the signature can be computed only once after
84 84 # the post is added? Need to add some on_save signal queue
85 85 # and add this there.
86 86 key = KeyPair.objects.get(public_key=post.global_id.key)
87 87 signatures = [Signature(
88 88 key_type=key.key_type,
89 89 key=key.public_key,
90 90 signature=key.sign(et.tostring(content_tag, ENCODING_UNICODE)),
91 91 )]
92 92 for signature in signatures:
93 93 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
94 94 signature_tag.set(ATTR_TYPE, signature.key_type)
95 95 signature_tag.set(ATTR_VALUE, signature.signature)
96 96 signature_tag.set(ATTR_KEY, signature.key)
97 97
98 98 return et.tostring(response, ENCODING_UNICODE)
99 99
100 100 @staticmethod
101 101 @transaction.atomic
102 102 def parse_response_get(response_xml):
103 103 tag_root = et.fromstring(response_xml)
104 104 tag_status = tag_root.find(TAG_STATUS)
105 105 if STATUS_SUCCESS == tag_status.text:
106 106 tag_models = tag_root.find(TAG_MODELS)
107 107 for tag_model in tag_models:
108 108 tag_content = tag_model.find(TAG_CONTENT)
109 109
110 110 signatures = SyncManager._verify_model(tag_content, tag_model)
111 111
112 112 tag_id = tag_content.find(TAG_ID)
113 113 global_id, exists = GlobalId.from_xml_element(tag_id)
114 114
115 115 if exists:
116 116 print('Post with same ID already exists')
117 117 else:
118 118 global_id.save()
119 119 for signature in signatures:
120 120 signature.global_id = global_id
121 121 signature.save()
122 122
123 123 title = tag_content.find(TAG_TITLE).text
124 124 text = tag_content.find(TAG_TEXT).text
125 125 pub_time = tag_content.find(TAG_PUB_TIME).text
126 126
127 127 thread = tag_content.find(TAG_THREAD)
128 128 tags = []
129 129 if thread:
130 130 opening_post = Post.objects.get(
131 131 id=thread.find(TAG_ID).text)
132 132 else:
133 133 opening_post = None
134 134 tag_tags = tag_content.find(TAG_TAGS)
135 135 for tag_tag in tag_tags:
136 136 tag, created = Tag.objects.get_or_create(name=tag_tag.text)
137 137 tags.append(tag)
138 138
139 139 # TODO Check that the replied posts are already present
140 140 # before adding new ones
141 141
142 142 # TODO Get images
143 143
144 144 post = Post.objects.import_post(
145 145 title=title, text=text, pub_time=pub_time,
146 146 opening_post=opening_post, tags=tags,
147 147 global_id=global_id)
148 148 else:
149 149 # TODO Throw an exception?
150 150 pass
151 151
152 152 @staticmethod
153 def generate_response_pull():
154 response = et.Element(TAG_RESPONSE)
155
156 status = et.SubElement(response, TAG_STATUS)
157 status.text = STATUS_SUCCESS
158
159 models = et.SubElement(response, TAG_MODELS)
160
161 for post in Post.objects.all():
162 tag_id = et.SubElement(models, TAG_ID)
163 post.global_id.to_xml_element(tag_id)
164
165 return et.tostring(response, ENCODING_UNICODE)
166
167 @staticmethod
153 168 def _verify_model(tag_content, tag_model):
154 169 """
155 170 Verifies all signatures for a single model.
156 171 """
157 172
158 173 signatures = []
159 174
160 175 tag_signatures = tag_model.find(TAG_SIGNATURES)
161 176 for tag_signature in tag_signatures:
162 177 signature_type = tag_signature.get(ATTR_TYPE)
163 178 signature_value = tag_signature.get(ATTR_VALUE)
164 179 signature_key = tag_signature.get(ATTR_KEY)
165 180
166 181 signature = Signature(key_type=signature_type,
167 182 key=signature_key,
168 183 signature=signature_value)
169 184 signatures.append(signature)
170 185
171 186 if not KeyPair.objects.verify(
172 187 signature, et.tostring(tag_content, ENCODING_UNICODE)):
173 188 raise Exception('Invalid model signature')
174 189
175 190 return signatures
@@ -1,121 +1,137 b''
1 1 import xml.etree.ElementTree as et
2 2 from django.db import models
3 3
4 4
5 5 TAG_MODEL = 'model'
6 6 TAG_REQUEST = 'request'
7 7 TAG_ID = 'id'
8 8
9 9 TYPE_GET = 'get'
10 TYPE_PULL = 'pull'
10 11
11 12 ATTR_VERSION = 'version'
12 13 ATTR_TYPE = 'type'
13 14 ATTR_NAME = 'name'
14 15
15 16 ATTR_KEY = 'key'
16 17 ATTR_KEY_TYPE = 'type'
17 18 ATTR_LOCAL_ID = 'local-id'
18 19
19 20
20 21 class GlobalIdManager(models.Manager):
21 22 def generate_request_get(self, global_id_list: list):
22 23 """
23 24 Form a get request from a list of ModelId objects.
24 25 """
25 26
26 27 request = et.Element(TAG_REQUEST)
27 28 request.set(ATTR_TYPE, TYPE_GET)
28 29 request.set(ATTR_VERSION, '1.0')
29 30
30 31 model = et.SubElement(request, TAG_MODEL)
31 32 model.set(ATTR_VERSION, '1.0')
32 33 model.set(ATTR_NAME, 'post')
33 34
34 35 for global_id in global_id_list:
35 36 tag_id = et.SubElement(model, TAG_ID)
36 37 global_id.to_xml_element(tag_id)
37 38
38 39 return et.tostring(request, 'unicode')
39 40
41 def generate_request_pull(self):
42 """
43 Form a pull request from a list of ModelId objects.
44 """
45
46 request = et.Element(TAG_REQUEST)
47 request.set(ATTR_TYPE, TYPE_PULL)
48 request.set(ATTR_VERSION, '1.0')
49
50 model = et.SubElement(request, TAG_MODEL)
51 model.set(ATTR_VERSION, '1.0')
52 model.set(ATTR_NAME, 'post')
53
54 return et.tostring(request, 'unicode')
55
40 56 def global_id_exists(self, global_id):
41 57 """
42 58 Checks if the same global id already exists in the system.
43 59 """
44 60
45 61 return self.filter(key=global_id.key,
46 62 key_type=global_id.key_type,
47 63 local_id=global_id.local_id).exists()
48 64
49 65
50 66 class GlobalId(models.Model):
51 67 class Meta:
52 68 app_label = 'boards'
53 69
54 70 objects = GlobalIdManager()
55 71
56 72 def __init__(self, *args, **kwargs):
57 73 models.Model.__init__(self, *args, **kwargs)
58 74
59 75 if 'key' in kwargs and 'key_type' in kwargs and 'local_id' in kwargs:
60 76 self.key = kwargs['key']
61 77 self.key_type = kwargs['key_type']
62 78 self.local_id = kwargs['local_id']
63 79
64 80 key = models.TextField()
65 81 key_type = models.TextField()
66 82 local_id = models.IntegerField()
67 83
68 84 def __str__(self):
69 85 return '%s::%s::%d' % (self.key_type, self.key, self.local_id)
70 86
71 87 def to_xml_element(self, element: et.Element):
72 88 """
73 89 Exports global id to an XML element.
74 90 """
75 91
76 92 element.set(ATTR_KEY, self.key)
77 93 element.set(ATTR_KEY_TYPE, self.key_type)
78 94 element.set(ATTR_LOCAL_ID, str(self.local_id))
79 95
80 96 @staticmethod
81 97 def from_xml_element(element: et.Element):
82 98 """
83 99 Parses XML id tag and gets global id from it.
84 100
85 101 Arguments:
86 102 element -- the XML 'id' element
87 103
88 104 Returns:
89 105 global_id -- id itself
90 106 exists -- True if the global id was taken from database, False if it
91 107 did not exist and was created.
92 108 """
93 109
94 110 try:
95 111 return GlobalId.objects.get(key=element.get(ATTR_KEY),
96 112 key_type=element.get(ATTR_KEY_TYPE),
97 113 local_id=int(element.get(
98 114 ATTR_LOCAL_ID))), True
99 115 except GlobalId.DoesNotExist:
100 116 return GlobalId(key=element.get(ATTR_KEY),
101 117 key_type=element.get(ATTR_KEY_TYPE),
102 118 local_id=int(element.get(ATTR_LOCAL_ID))), False
103 119
104 120
105 121 class Signature(models.Model):
106 122 class Meta:
107 123 app_label = 'boards'
108 124
109 125 def __init__(self, *args, **kwargs):
110 126 models.Model.__init__(self, *args, **kwargs)
111 127
112 128 if 'key' in kwargs and 'key_type' in kwargs and 'signature' in kwargs:
113 129 self.key_type = kwargs['key_type']
114 130 self.key = kwargs['key']
115 131 self.signature = kwargs['signature']
116 132
117 133 key_type = models.TextField()
118 134 key = models.TextField()
119 135 signature = models.TextField()
120 136
121 137 global_id = models.ForeignKey('GlobalId')
@@ -1,91 +1,91 b''
1 1 from django.conf.urls import patterns, url
2 2 from django.views.i18n import javascript_catalog
3 3
4 4 from boards import views
5 5 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
6 6 from boards.views import api, tag_threads, all_threads, \
7 7 settings, all_tags, feed
8 8 from boards.views.authors import AuthorsView
9 9 from boards.views.notifications import NotificationView
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, response_get
13 from boards.views.sync import get_post_sync_data, response_get, response_pull
14 14 from boards.views.random import RandomImageView
15 15
16 16
17 17 js_info_dict = {
18 18 'packages': ('boards',),
19 19 }
20 20
21 21 urlpatterns = patterns('',
22 22 # /boards/
23 23 url(r'^$', all_threads.AllThreadsView.as_view(), name='index'),
24 24
25 25 # /boards/tag/tag_name/
26 26 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
27 27 name='tag'),
28 28
29 29 # /boards/thread/
30 30 url(r'^thread/(?P<post_id>\d+)/$', views.thread.NormalThreadView.as_view(),
31 31 name='thread'),
32 32 url(r'^thread/(?P<post_id>\d+)/mode/gallery/$', views.thread.GalleryThreadView.as_view(),
33 33 name='thread_gallery'),
34 34 url(r'^thread/(?P<post_id>\d+)/mode/tree/$', views.thread.TreeThreadView.as_view(),
35 35 name='thread_tree'),
36 36 # /feed/
37 37 url(r'^feed/$', views.feed.FeedView.as_view(), name='feed'),
38 38
39 39 url(r'^settings/$', settings.SettingsView.as_view(), name='settings'),
40 40 url(r'^tags/(?P<query>\w+)?/?$', all_tags.AllTagsView.as_view(), name='tags'),
41 41 url(r'^authors/$', AuthorsView.as_view(), name='authors'),
42 42
43 43 url(r'^banned/$', views.banned.BannedView.as_view(), name='banned'),
44 44 url(r'^staticpage/(?P<name>\w+)/$', StaticPageView.as_view(),
45 45 name='staticpage'),
46 46
47 47 url(r'^random/$', RandomImageView.as_view(), name='random'),
48 48
49 49 # RSS feeds
50 50 url(r'^rss/$', AllThreadsFeed()),
51 51 url(r'^page/(?P<page>\d+)/rss/$', AllThreadsFeed()),
52 52 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
53 53 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
54 54 url(r'^thread/(?P<post_id>\d+)/rss/$', ThreadPostsFeed()),
55 55
56 56 # i18n
57 57 url(r'^jsi18n/$', javascript_catalog, js_info_dict,
58 58 name='js_info_dict'),
59 59
60 60 # API
61 61 url(r'^api/post/(?P<post_id>\d+)/$', api.get_post, name="get_post"),
62 62 url(r'^api/diff_thread/$', api.api_get_threaddiff, name="get_thread_diff"),
63 63 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
64 64 name='get_threads'),
65 65 url(r'^api/tags/$', api.api_get_tags, name='get_tags'),
66 66 url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
67 67 name='get_thread'),
68 68 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post,
69 69 name='add_post'),
70 70 url(r'^api/notifications/(?P<username>\w+)/$', api.api_get_notifications,
71 71 name='api_notifications'),
72 72 url(r'^api/preview/$', api.api_get_preview, name='preview'),
73 73
74 74 # Sync protocol API
75 url(r'^api/sync/pull/$', api.sync_pull, name='api_sync_pull'),
75 url(r'^api/sync/pull/$', response_pull, name='api_sync_pull'),
76 76 url(r'^api/sync/get/$', response_get, name='api_sync_pull'),
77 77 # TODO 'get' request
78 78
79 79 # Search
80 80 url(r'^search/$', BoardSearchView.as_view(), name='search'),
81 81
82 82 # Notifications
83 83 url(r'^notifications/(?P<username>\w+)$', NotificationView.as_view(), name='notifications'),
84 84
85 85 # Post preview
86 86 url(r'^preview/$', PostPreviewView.as_view(), name='preview'),
87 87
88 88 url(r'^post_xml/(?P<post_id>\d+)$', get_post_sync_data,
89 89 name='post_sync_data'),
90 90
91 91 )
@@ -1,258 +1,243 b''
1 1 import json
2 2 import logging
3 3
4 4 import xml.etree.ElementTree as ET
5 5
6 6 from django.db import transaction
7 7 from django.http import HttpResponse
8 8 from django.shortcuts import get_object_or_404
9 9 from django.core import serializers
10 10
11 11 from boards.forms import PostForm, PlainErrorList
12 12 from boards.models import Post, Thread, Tag, GlobalId
13 13 from boards.models.post.sync import SyncManager
14 14 from boards.utils import datetime_to_epoch
15 15 from boards.views.thread import ThreadView
16 16 from boards.models.user import Notification
17 17 from boards.mdx_neboard import Parser
18 18
19 19
20 20 __author__ = 'neko259'
21 21
22 22 PARAMETER_TRUNCATED = 'truncated'
23 23 PARAMETER_TAG = 'tag'
24 24 PARAMETER_OFFSET = 'offset'
25 25 PARAMETER_DIFF_TYPE = 'type'
26 26 PARAMETER_POST = 'post'
27 27 PARAMETER_UPDATED = 'updated'
28 28 PARAMETER_LAST_UPDATE = 'last_update'
29 29 PARAMETER_THREAD = 'thread'
30 30 PARAMETER_UIDS = 'uids'
31 31
32 32 DIFF_TYPE_HTML = 'html'
33 33 DIFF_TYPE_JSON = 'json'
34 34
35 35 STATUS_OK = 'ok'
36 36 STATUS_ERROR = 'error'
37 37
38 38 logger = logging.getLogger(__name__)
39 39
40 40
41 41 @transaction.atomic
42 42 def api_get_threaddiff(request):
43 43 """
44 44 Gets posts that were changed or added since time
45 45 """
46 46
47 47 thread_id = request.POST.get(PARAMETER_THREAD)
48 48 uids_str = request.POST.get(PARAMETER_UIDS).strip()
49 49 uids = uids_str.split(' ')
50 50
51 51 thread = get_object_or_404(Post, id=thread_id).get_thread()
52 52
53 53 json_data = {
54 54 PARAMETER_UPDATED: [],
55 55 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
56 56 }
57 57 posts = Post.objects.filter(threads__in=[thread]).exclude(uid__in=uids)
58 58
59 59 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
60 60
61 61 for post in posts:
62 62 json_data[PARAMETER_UPDATED].append(get_post_data(post.id, diff_type,
63 63 request))
64 64 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
65 65
66 66 return HttpResponse(content=json.dumps(json_data))
67 67
68 68
69 69 def api_add_post(request, opening_post_id):
70 70 """
71 71 Adds a post and return the JSON response for it
72 72 """
73 73
74 74 opening_post = get_object_or_404(Post, id=opening_post_id)
75 75
76 76 logger.info('Adding post via api...')
77 77
78 78 status = STATUS_OK
79 79 errors = []
80 80
81 81 if request.method == 'POST':
82 82 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
83 83 form.session = request.session
84 84
85 85 if form.need_to_ban:
86 86 # Ban user because he is suspected to be a bot
87 87 # _ban_current_user(request)
88 88 status = STATUS_ERROR
89 89 if form.is_valid():
90 90 post = ThreadView().new_post(request, form, opening_post,
91 91 html_response=False)
92 92 if not post:
93 93 status = STATUS_ERROR
94 94 else:
95 95 logger.info('Added post #%d via api.' % post.id)
96 96 else:
97 97 status = STATUS_ERROR
98 98 errors = form.as_json_errors()
99 99
100 100 response = {
101 101 'status': status,
102 102 'errors': errors,
103 103 }
104 104
105 105 return HttpResponse(content=json.dumps(response))
106 106
107 107
108 108 def get_post(request, post_id):
109 109 """
110 110 Gets the html of a post. Used for popups. Post can be truncated if used
111 111 in threads list with 'truncated' get parameter.
112 112 """
113 113
114 114 post = get_object_or_404(Post, id=post_id)
115 115 truncated = PARAMETER_TRUNCATED in request.GET
116 116
117 117 return HttpResponse(content=post.get_view(truncated=truncated))
118 118
119 119
120 120 def api_get_threads(request, count):
121 121 """
122 122 Gets the JSON thread opening posts list.
123 123 Parameters that can be used for filtering:
124 124 tag, offset (from which thread to get results)
125 125 """
126 126
127 127 if PARAMETER_TAG in request.GET:
128 128 tag_name = request.GET[PARAMETER_TAG]
129 129 if tag_name is not None:
130 130 tag = get_object_or_404(Tag, name=tag_name)
131 131 threads = tag.get_threads().filter(archived=False)
132 132 else:
133 133 threads = Thread.objects.filter(archived=False)
134 134
135 135 if PARAMETER_OFFSET in request.GET:
136 136 offset = request.GET[PARAMETER_OFFSET]
137 137 offset = int(offset) if offset is not None else 0
138 138 else:
139 139 offset = 0
140 140
141 141 threads = threads.order_by('-bump_time')
142 142 threads = threads[offset:offset + int(count)]
143 143
144 144 opening_posts = []
145 145 for thread in threads:
146 146 opening_post = thread.get_opening_post()
147 147
148 148 # TODO Add tags, replies and images count
149 149 post_data = get_post_data(opening_post.id, include_last_update=True)
150 150 post_data['bumpable'] = thread.can_bump()
151 151 post_data['archived'] = thread.archived
152 152
153 153 opening_posts.append(post_data)
154 154
155 155 return HttpResponse(content=json.dumps(opening_posts))
156 156
157 157
158 158 # TODO Test this
159 159 def api_get_tags(request):
160 160 """
161 161 Gets all tags or user tags.
162 162 """
163 163
164 164 # TODO Get favorite tags for the given user ID
165 165
166 166 tags = Tag.objects.get_not_empty_tags()
167 167
168 168 term = request.GET.get('term')
169 169 if term is not None:
170 170 tags = tags.filter(name__contains=term)
171 171
172 172 tag_names = [tag.name for tag in tags]
173 173
174 174 return HttpResponse(content=json.dumps(tag_names))
175 175
176 176
177 177 # TODO The result can be cached by the thread last update time
178 178 # TODO Test this
179 179 def api_get_thread_posts(request, opening_post_id):
180 180 """
181 181 Gets the JSON array of thread posts
182 182 """
183 183
184 184 opening_post = get_object_or_404(Post, id=opening_post_id)
185 185 thread = opening_post.get_thread()
186 186 posts = thread.get_replies()
187 187
188 188 json_data = {
189 189 'posts': [],
190 190 'last_update': None,
191 191 }
192 192 json_post_list = []
193 193
194 194 for post in posts:
195 195 json_post_list.append(get_post_data(post.id))
196 196 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
197 197 json_data['posts'] = json_post_list
198 198
199 199 return HttpResponse(content=json.dumps(json_data))
200 200
201 201
202 202 def api_get_notifications(request, username):
203 203 last_notification_id_str = request.GET.get('last', None)
204 204 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
205 205
206 206 posts = Notification.objects.get_notification_posts(username=username,
207 207 last=last_id)
208 208
209 209 json_post_list = []
210 210 for post in posts:
211 211 json_post_list.append(get_post_data(post.id))
212 212 return HttpResponse(content=json.dumps(json_post_list))
213 213
214 214
215 215 def api_get_post(request, post_id):
216 216 """
217 217 Gets the JSON of a post. This can be
218 218 used as and API for external clients.
219 219 """
220 220
221 221 post = get_object_or_404(Post, id=post_id)
222 222
223 223 json = serializers.serialize("json", [post], fields=(
224 224 "pub_time", "_text_rendered", "title", "text", "image",
225 225 "image_width", "image_height", "replies", "tags"
226 226 ))
227 227
228 228 return HttpResponse(content=json)
229 229
230 230
231 231 # TODO Remove this method and use post method directly
232 232 def get_post_data(post_id, format_type=DIFF_TYPE_JSON, request=None,
233 233 include_last_update=False):
234 234 post = get_object_or_404(Post, id=post_id)
235 235 return post.get_post_data(format_type=format_type, request=request,
236 236 include_last_update=include_last_update)
237 237
238 238
239 239 def api_get_preview(request):
240 240 raw_text = request.POST['raw_text']
241 241
242 242 parser = Parser()
243 243 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
244
245
246 # TODO Make a separate module for sync API methods
247 def sync_pull(request):
248 """
249 Return 'pull' request response for all posts.
250 """
251 request_xml = request.get('xml')
252 if request_xml is None:
253 posts = Post.objects.all()
254 else:
255 pass # TODO Parse the XML and get filters from it
256
257 xml = SyncManager.generate_response_get(posts)
258 return HttpResponse(content=xml)
@@ -1,49 +1,56 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 4 from boards.models.post.sync import SyncManager
5 5
6 6
7 7 def response_pull(request):
8 pass
8 request_xml = request.body
9
10 if request_xml is None:
11 return HttpResponse(content='Use the API')
12
13 response_xml = SyncManager.generate_response_pull()
14
15 return HttpResponse(content=response_xml)
9 16
10 17
11 18 def response_get(request):
12 19 """
13 20 Processes a GET request with post ID list and returns the posts XML list.
14 21 Request should contain an 'xml' post attribute with the actual request XML.
15 22 """
16 23
17 24 request_xml = request.body
18 25
19 26 if request_xml is None:
20 27 return HttpResponse(content='Use the API')
21 28
22 29 posts = []
23 30
24 31 root_tag = et.fromstring(request_xml)
25 32 model_tag = root_tag[0]
26 33 for id_tag in model_tag:
27 34 global_id, exists = GlobalId.from_xml_element(id_tag)
28 35 if exists:
29 36 posts.append(Post.objects.get(global_id=global_id))
30 37
31 38 response_xml = SyncManager.generate_response_get(posts)
32 39
33 40 return HttpResponse(content=response_xml)
34 41
35 42
36 43 def get_post_sync_data(request, post_id):
37 44 try:
38 45 post = Post.objects.get(id=post_id)
39 46 except Post.DoesNotExist:
40 47 raise Http404()
41 48
42 49 content = 'Global ID: %s\n\nXML: %s' \
43 50 % (post.global_id, SyncManager.generate_response_get([post]))
44 51
45 52
46 53 return HttpResponse(
47 54 content_type='text/plain',
48 55 content=content,
49 56 ) No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now