##// END OF EJS Templates
Merge tip
Bohdan Horbeshko -
r2144:1765cedc merge lite
parent child Browse files
Show More
@@ -0,0 +1,31 b''
1 import xml.etree.ElementTree as et
2
3 from boards.models import Post
4
5 TAG_THREAD = 'thread'
6
7
8 class PostFilter:
9 def __init__(self, content=None):
10 self.content = content
11
12 def filter(self, posts):
13 return posts
14
15 def add_filter(self, model_tag, value):
16 return model_tag
17
18
19 class ThreadFilter(PostFilter):
20 def filter(self, posts):
21 op_id = self.content.text
22
23 op = Post.objects.filter(opening=True, id=op_id).first()
24 if op:
25 return posts.filter(thread=op.get_thread())
26 else:
27 return posts.none()
28
29 def add_filter(self, model_tag, value):
30 thread_tag = et.SubElement(model_tag, TAG_THREAD)
31 thread_tag.text = str(value)
@@ -0,0 +1,24 b''
1 # -*- coding: utf-8 -*-
2 # Generated by Django 1.10.5 on 2017-01-23 14:20
3 from __future__ import unicode_literals
4
5 from django.db import migrations, models
6
7
8 class Migration(migrations.Migration):
9
10 dependencies = [
11 ('boards', '0055_auto_20161229_1132'),
12 ]
13
14 operations = [
15 migrations.AlterModelOptions(
16 name='attachment',
17 options={'ordering': ('id',)},
18 ),
19 migrations.AlterField(
20 model_name='post',
21 name='uid',
22 field=models.TextField(),
23 ),
24 ]
@@ -18,6 +18,7 b' PostingDelay = 30'
18 Autoban = false
18 Autoban = false
19 DefaultTag = test
19 DefaultTag = test
20 MaxFileCount = 1
20 MaxFileCount = 1
21 AdditionalSpoilerSpaces = false
21
22
22 [Messages]
23 [Messages]
23 # Thread bumplimit
24 # Thread bumplimit
@@ -24,6 +24,8 b' class Command(BaseCommand):'
24 parser.add_argument('--split-query', type=int, default=1,
24 parser.add_argument('--split-query', type=int, default=1,
25 help='Split GET query into separate by the given'
25 help='Split GET query into separate by the given'
26 ' number of posts in one')
26 ' number of posts in one')
27 parser.add_argument('--thread', type=int,
28 help='Get posts of one specific thread')
27
29
28 def handle(self, *args, **options):
30 def handle(self, *args, **options):
29 logger = logging.getLogger('boards.sync')
31 logger = logging.getLogger('boards.sync')
@@ -45,7 +47,7 b' class Command(BaseCommand):'
45 global_id = GlobalId(key_type=key_type, key=key,
47 global_id = GlobalId(key_type=key_type, key=key,
46 local_id=local_id)
48 local_id=local_id)
47
49
48 xml = GlobalId.objects.generate_request_get([global_id])
50 xml = SyncManager.generate_request_get([global_id])
49 h = httplib2.Http()
51 h = httplib2.Http()
50 response, content = h.request(get_url, method="POST", body=xml)
52 response, content = h.request(get_url, method="POST", body=xml)
51
53
@@ -55,7 +57,8 b' class Command(BaseCommand):'
55 else:
57 else:
56 logger.info('Running LIST request...')
58 logger.info('Running LIST request...')
57 h = httplib2.Http()
59 h = httplib2.Http()
58 xml = GlobalId.objects.generate_request_list()
60 xml = SyncManager.generate_request_list(
61 opening_post=options.get('thread'))
59 response, content = h.request(list_url, method="POST", body=xml)
62 response, content = h.request(list_url, method="POST", body=xml)
60 logger.info('Processing response...')
63 logger.info('Processing response...')
61
64
@@ -83,7 +86,7 b' class Command(BaseCommand):'
83 if len(ids_to_sync) > 0:
86 if len(ids_to_sync) > 0:
84 limit = options.get('split_query', len(ids_to_sync))
87 limit = options.get('split_query', len(ids_to_sync))
85 for offset in range(0, len(ids_to_sync), limit):
88 for offset in range(0, len(ids_to_sync), limit):
86 xml = GlobalId.objects.generate_request_get(ids_to_sync[offset:offset+limit])
89 xml = SyncManager.generate_request_get(ids_to_sync[offset:offset + limit])
87 h = httplib2.Http()
90 h = httplib2.Http()
88 logger.info('Running GET request...')
91 logger.info('Running GET request...')
89 response, content = h.request(get_url, method="POST", body=xml)
92 response, content = h.request(get_url, method="POST", body=xml)
@@ -10,6 +10,7 b' from django.core.exceptions import Objec'
10 from django.core.urlresolvers import reverse
10 from django.core.urlresolvers import reverse
11
11
12 import boards
12 import boards
13 from boards import settings
13
14
14
15
15 __author__ = 'neko259'
16 __author__ = 'neko259'
@@ -24,6 +25,7 b' LINE_BREAK_HTML = \'<div class="br"></div'
24 SPOILER_SPACE = '&nbsp;'
25 SPOILER_SPACE = '&nbsp;'
25
26
26 MAX_SPOILER_MULTIPLIER = 2
27 MAX_SPOILER_MULTIPLIER = 2
28 MAX_SPOILER_SPACE_COUNT = 20
27
29
28
30
29 class TextFormatter():
31 class TextFormatter():
@@ -202,11 +204,15 b' def render_tag(tag_name, value, options,'
202
204
203
205
204 def render_spoiler(tag_name, value, options, parent, context):
206 def render_spoiler(tag_name, value, options, parent, context):
205 text_len = len(value)
207 if settings.get_bool('Forms', 'AdditionalSpoilerSpaces'):
206 space_count = random.randint(0, text_len * MAX_SPOILER_MULTIPLIER)
208 text_len = len(value)
207 side_spaces = SPOILER_SPACE * (space_count // 2)
209 space_count = min(random.randint(0, text_len * MAX_SPOILER_MULTIPLIER),
208 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, value,
210 MAX_SPOILER_SPACE_COUNT)
209 side_spaces)
211 side_spaces = SPOILER_SPACE * (space_count // 2)
212 else:
213 side_spaces = ''
214 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces,
215 value, side_spaces)
210
216
211
217
212 formatters = [
218 formatters = [
@@ -30,15 +30,15 b' class Downloader:'
30 return True
30 return True
31
31
32 @staticmethod
32 @staticmethod
33 def download(url: str):
33 def download(url: str, validate):
34 # Verify content headers
34 # Verify content headers
35 response_head = requests.head(url, verify=False)
35 response_head = requests.head(url, verify=False)
36 content_type = response_head.headers[HEADER_CONTENT_TYPE].split(';')[0]
36 content_type = response_head.headers[HEADER_CONTENT_TYPE].split(';')[0]
37 if content_type in TYPE_URL_ONLY:
37 if validate and content_type in TYPE_URL_ONLY:
38 return None
38 return None
39
39
40 length_header = response_head.headers.get(HEADER_CONTENT_LENGTH)
40 length_header = response_head.headers.get(HEADER_CONTENT_LENGTH)
41 if length_header:
41 if validate and length_header:
42 length = int(length_header)
42 length = int(length_header)
43 validate_file_size(length)
43 validate_file_size(length)
44 # Get the actual content into memory
44 # Get the actual content into memory
@@ -63,7 +63,7 b' class Downloader:'
63
63
64 class YouTubeDownloader(Downloader):
64 class YouTubeDownloader(Downloader):
65 @staticmethod
65 @staticmethod
66 def download(url: str):
66 def download(url: str, validate):
67 yt = YouTube()
67 yt = YouTube()
68 yt.from_url(url)
68 yt.from_url(url)
69 videos = yt.filter(YOUTUBE_VIDEO_FORMAT)
69 videos = yt.filter(YOUTUBE_VIDEO_FORMAT)
@@ -82,7 +82,7 b' class NothingDownloader(Downloader):'
82 return REGEX_MAGNET.match(url)
82 return REGEX_MAGNET.match(url)
83
83
84 @staticmethod
84 @staticmethod
85 def download(url: str):
85 def download(url: str, validate):
86 return None
86 return None
87
87
88
88
@@ -93,9 +93,9 b' DOWNLOADERS = ('
93 )
93 )
94
94
95
95
96 def download(url):
96 def download(url, validate=True):
97 for downloader in DOWNLOADERS:
97 for downloader in DOWNLOADERS:
98 if downloader.handles(url):
98 if downloader.handles(url):
99 return downloader.download(url)
99 return downloader.download(url, validate=validate)
100 raise Exception('No downloader supports this URL.')
100 raise Exception('No downloader supports this URL.')
101
101
@@ -90,7 +90,7 b' class Post(models.Model, Viewable):'
90 thread = models.ForeignKey('Thread', db_index=True, related_name='replies')
90 thread = models.ForeignKey('Thread', db_index=True, related_name='replies')
91
91
92 url = models.TextField()
92 url = models.TextField()
93 uid = models.TextField(db_index=True)
93 uid = models.TextField()
94
94
95 # Global ID with author key. If the message was downloaded from another
95 # Global ID with author key. If the message was downloaded from another
96 # server, this indicates the server.
96 # server, this indicates the server.
@@ -1,9 +1,13 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2 import logging
2 import logging
3 from xml.etree import ElementTree
3
4
4 from boards.abstracts.exceptions import SyncException
5 from boards.abstracts.exceptions import SyncException
6 from boards.abstracts.sync_filters import ThreadFilter
5 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
7 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
6 from boards.models.attachment.downloaders import download
8 from boards.models.attachment.downloaders import download
9 from boards.models.signature import TAG_REQUEST, ATTR_TYPE, TYPE_GET, \
10 ATTR_VERSION, TAG_MODEL, ATTR_NAME, TAG_ID, TYPE_LIST
7 from boards.utils import get_file_mimetype, get_file_hash
11 from boards.utils import get_file_mimetype, get_file_hash
8 from django.db import transaction
12 from django.db import transaction
9 from django import forms
13 from django import forms
@@ -238,7 +242,7 b' class SyncManager:'
238 TAG_ATTACHMENT_REF, attachment.text))
242 TAG_ATTACHMENT_REF, attachment.text))
239 url = tag_ref.get(ATTR_URL)
243 url = tag_ref.get(ATTR_URL)
240 try:
244 try:
241 attached_file = download(hostname + url)
245 attached_file = download(hostname + url, validate=False)
242
246
243 if attached_file is None:
247 if attached_file is None:
244 raise SyncException(EXCEPTION_DOWNLOAD)
248 raise SyncException(EXCEPTION_DOWNLOAD)
@@ -269,7 +273,7 b' class SyncManager:'
269 logger.debug('Parsed new post {}'.format(global_id))
273 logger.debug('Parsed new post {}'.format(global_id))
270
274
271 @staticmethod
275 @staticmethod
272 def generate_response_list():
276 def generate_response_list(filters):
273 response = et.Element(TAG_RESPONSE)
277 response = et.Element(TAG_RESPONSE)
274
278
275 status = et.SubElement(response, TAG_STATUS)
279 status = et.SubElement(response, TAG_STATUS)
@@ -277,7 +281,11 b' class SyncManager:'
277
281
278 models = et.SubElement(response, TAG_MODELS)
282 models = et.SubElement(response, TAG_MODELS)
279
283
280 for post in Post.objects.prefetch_related('global_id').all():
284 posts = Post.objects.prefetch_related('global_id')
285 for post_filter in filters:
286 posts = post_filter.filter(posts)
287
288 for post in posts:
281 tag_model = et.SubElement(models, TAG_MODEL)
289 tag_model = et.SubElement(models, TAG_MODEL)
282 tag_id = et.SubElement(tag_model, TAG_ID)
290 tag_id = et.SubElement(tag_model, TAG_ID)
283 post.global_id.to_xml_element(tag_id)
291 post.global_id.to_xml_element(tag_id)
@@ -334,4 +342,4 b' class SyncManager:'
334 if tag_refs is not None:
342 if tag_refs is not None:
335 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
343 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
336 attachment_ref.set(ATTR_REF, attachment.hash)
344 attachment_ref.set(ATTR_REF, attachment.hash)
337 attachment_ref.set(ATTR_URL, attachment.file.url)
345 attachment_ref.set(ATTR_URL, attachment.file.url)
@@ -1,8 +1,9 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2
2 from django.db import models
3 from django.db import models
4
3 from boards.models import KeyPair
5 from boards.models import KeyPair
4
6
5
6 TAG_MODEL = 'model'
7 TAG_MODEL = 'model'
7 TAG_REQUEST = 'request'
8 TAG_REQUEST = 'request'
8 TAG_ID = 'id'
9 TAG_ID = 'id'
@@ -20,40 +21,6 b" ATTR_LOCAL_ID = 'local-id'"
20
21
21
22
22 class GlobalIdManager(models.Manager):
23 class GlobalIdManager(models.Manager):
23 def generate_request_get(self, global_id_list: list):
24 """
25 Form a get request from a list of ModelId objects.
26 """
27
28 request = et.Element(TAG_REQUEST)
29 request.set(ATTR_TYPE, TYPE_GET)
30 request.set(ATTR_VERSION, '1.0')
31
32 model = et.SubElement(request, TAG_MODEL)
33 model.set(ATTR_VERSION, '1.0')
34 model.set(ATTR_NAME, 'post')
35
36 for global_id in global_id_list:
37 tag_id = et.SubElement(model, TAG_ID)
38 global_id.to_xml_element(tag_id)
39
40 return et.tostring(request, 'unicode')
41
42 def generate_request_list(self):
43 """
44 Form a pull request from a list of ModelId objects.
45 """
46
47 request = et.Element(TAG_REQUEST)
48 request.set(ATTR_TYPE, TYPE_LIST)
49 request.set(ATTR_VERSION, '1.0')
50
51 model = et.SubElement(request, TAG_MODEL)
52 model.set(ATTR_VERSION, '1.0')
53 model.set(ATTR_NAME, 'post')
54
55 return et.tostring(request, 'unicode')
56
57 def global_id_exists(self, global_id):
24 def global_id_exists(self, global_id):
58 """
25 """
59 Checks if the same global id already exists in the system.
26 Checks if the same global id already exists in the system.
@@ -23,47 +23,50 b' THUMB_SIZES = ((200, 150),)'
23
23
24 @receiver(post_save, sender=Post)
24 @receiver(post_save, sender=Post)
25 def connect_replies(instance, **kwargs):
25 def connect_replies(instance, **kwargs):
26 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
26 if not kwargs['update_fields']:
27 post_id = reply_number.group(1)
27 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
28 post_id = reply_number.group(1)
28
29
29 try:
30 try:
30 referenced_post = Post.objects.get(id=post_id)
31 referenced_post = Post.objects.get(id=post_id)
31
32
32 if not referenced_post.referenced_posts.filter(
33 if not referenced_post.referenced_posts.filter(
33 id=instance.id).exists():
34 id=instance.id).exists():
34 referenced_post.referenced_posts.add(instance)
35 referenced_post.referenced_posts.add(instance)
35 referenced_post.last_edit_time = instance.pub_time
36 referenced_post.last_edit_time = instance.pub_time
36 referenced_post.build_refmap()
37 referenced_post.build_refmap()
37 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
38 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
38 except Post.DoesNotExist:
39 except Post.DoesNotExist:
39 pass
40 pass
40
41
41
42
42 @receiver(post_save, sender=Post)
43 @receiver(post_save, sender=Post)
43 @receiver(post_import_deps, sender=Post)
44 @receiver(post_import_deps, sender=Post)
44 def connect_global_replies(instance, **kwargs):
45 def connect_global_replies(instance, **kwargs):
45 for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()):
46 if not kwargs['update_fields']:
46 key_type = reply_number.group(1)
47 for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()):
47 key = reply_number.group(2)
48 key_type = reply_number.group(1)
48 local_id = reply_number.group(3)
49 key = reply_number.group(2)
50 local_id = reply_number.group(3)
49
51
50 try:
52 try:
51 global_id = GlobalId.objects.get(key_type=key_type, key=key,
53 global_id = GlobalId.objects.get(key_type=key_type, key=key,
52 local_id=local_id)
54 local_id=local_id)
53 referenced_post = Post.objects.get(global_id=global_id)
55 referenced_post = Post.objects.get(global_id=global_id)
54 referenced_post.referenced_posts.add(instance)
56 referenced_post.referenced_posts.add(instance)
55 referenced_post.last_edit_time = instance.pub_time
57 referenced_post.last_edit_time = instance.pub_time
56 referenced_post.build_refmap()
58 referenced_post.build_refmap()
57 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
59 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
58 except (GlobalId.DoesNotExist, Post.DoesNotExist):
60 except (GlobalId.DoesNotExist, Post.DoesNotExist):
59 pass
61 pass
60
62
61
63
62 @receiver(post_save, sender=Post)
64 @receiver(post_save, sender=Post)
63 def connect_notifications(instance, **kwargs):
65 def connect_notifications(instance, **kwargs):
64 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
66 if not kwargs['update_fields']:
65 user_name = reply_number.group(1).lower()
67 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
66 Notification.objects.get_or_create(name=user_name, post=instance)
68 user_name = reply_number.group(1).lower()
69 Notification.objects.get_or_create(name=user_name, post=instance)
67
70
68
71
69 @receiver(pre_save, sender=Post)
72 @receiver(pre_save, sender=Post)
@@ -57,40 +57,35 b' function showFormAfter(blockToInsertAfte'
57 }
57 }
58
58
59 function addQuickReply(postId) {
59 function addQuickReply(postId) {
60 // If we click "reply" on the same post, it means "cancel"
60 var blockToInsert = null;
61 if (getForm().prev().attr('id') == postId) {
61 var textAreaJq = getPostTextarea();
62 resetFormPosition();
62 var postLinkRaw = '[post]' + postId + '[/post]'
63 } else {
63 var textToAdd = '';
64 var blockToInsert = null;
65 var textAreaJq = getPostTextarea();
66 var postLinkRaw = '[post]' + postId + '[/post]'
67 var textToAdd = '';
68
64
69 if (postId != null) {
65 if (postId != null) {
70 var post = $('#' + postId);
66 var post = $('#' + postId);
71
67
72 // If this is not OP, add reflink to the post. If there already is
68 // If this is not OP, add reflink to the post. If there already is
73 // the same reflink, don't add it again.
69 // the same reflink, don't add it again.
74 var postText = textAreaJq.val();
70 var postText = textAreaJq.val();
75 if (!post.is(':first-child') && postText.indexOf(postLinkRaw) < 0) {
71 if (!post.is(':first-child') && postText.indexOf(postLinkRaw) < 0) {
76 // Insert line break if none is present.
72 // Insert line break if none is present.
77 if (postText.length > 0 && !postText.endsWith('\n') && !postText.endsWith('\r')) {
73 if (postText.length > 0 && !postText.endsWith('\n') && !postText.endsWith('\r')) {
78 textToAdd += '\n';
74 textToAdd += '\n';
79 }
80 textToAdd += postLinkRaw + '\n';
81 }
75 }
76 textToAdd += postLinkRaw + '\n';
77 }
82
78
83 textAreaJq.val(textAreaJq.val()+ textToAdd);
79 textAreaJq.val(textAreaJq.val()+ textToAdd);
84 blockToInsert = post;
80 blockToInsert = post;
85 } else {
81 } else {
86 blockToInsert = $('.thread');
82 blockToInsert = $('.thread');
87 }
83 }
88 showFormAfter(blockToInsert);
84 showFormAfter(blockToInsert);
89
90 textAreaJq.focus();
91
85
92 moveCaretToEnd(textAreaJq);
86 textAreaJq.focus();
93 }
87
88 moveCaretToEnd(textAreaJq);
94 }
89 }
95
90
96 function addQuickQuote() {
91 function addQuickQuote() {
@@ -98,7 +93,7 b' function addQuickQuote() {'
98
93
99 var quoteButton = $("#quote-button");
94 var quoteButton = $("#quote-button");
100 var postId = quoteButton.attr('data-post-id');
95 var postId = quoteButton.attr('data-post-id');
101 if (postId != null && getForm().prev().attr('id') != postId) {
96 if (postId != null) {
102 addQuickReply(postId);
97 addQuickReply(postId);
103 }
98 }
104
99
@@ -1,4 +1,3 b''
1 from base64 import b64encode
2 import logging
1 import logging
3
2
4 from django.test import TestCase
3 from django.test import TestCase
@@ -38,7 +37,7 b' class KeyTest(TestCase):'
38 def test_request_get(self):
37 def test_request_get(self):
39 post = self._create_post_with_key()
38 post = self._create_post_with_key()
40
39
41 request = GlobalId.objects.generate_request_get([post.global_id])
40 request = SyncManager.generate_request_get([post.global_id])
42 logger.debug(request)
41 logger.debug(request)
43
42
44 key = KeyPair.objects.get(primary=True)
43 key = KeyPair.objects.get(primary=True)
@@ -3,7 +3,7 b' from django.test import TestCase'
3 from boards.models import KeyPair, Post, Tag
3 from boards.models import KeyPair, Post, Tag
4 from boards.models.post.sync import SyncManager
4 from boards.models.post.sync import SyncManager
5 from boards.tests.mocks import MockRequest
5 from boards.tests.mocks import MockRequest
6 from boards.views.sync import response_get
6 from boards.views.sync import response_get, response_list
7
7
8 __author__ = 'neko259'
8 __author__ = 'neko259'
9
9
@@ -103,3 +103,112 b' class SyncTest(TestCase):'
103 post.version,
103 post.version,
104 ) in response,
104 ) in response,
105 'Wrong response generated for the GET request.')
105 'Wrong response generated for the GET request.')
106
107 def test_list_all(self):
108 key = KeyPair.objects.generate_key(primary=True)
109 tag = Tag.objects.create(name='tag1')
110 post = Post.objects.create_post(title='test_title',
111 text='test_text\rline two',
112 tags=[tag])
113 post2 = Post.objects.create_post(title='test title 2',
114 text='test text 2',
115 tags=[tag])
116
117 request_all = MockRequest()
118 request_all.body = (
119 '<request type="list" version="1.0">'
120 '<model name="post" version="1.0">'
121 '</model>'
122 '</request>'
123 )
124
125 response_all = response_list(request_all).content.decode()
126 self.assertTrue(
127 '<status>success</status>'
128 '<models>'
129 '<model>'
130 '<id key="{}" local-id="{}" type="{}" />'
131 '<version>{}</version>'
132 '</model>'
133 '<model>'
134 '<id key="{}" local-id="{}" type="{}" />'
135 '<version>{}</version>'
136 '</model>'
137 '</models>'.format(
138 post.global_id.key,
139 post.global_id.local_id,
140 post.global_id.key_type,
141 post.version,
142 post2.global_id.key,
143 post2.global_id.local_id,
144 post2.global_id.key_type,
145 post2.version,
146 ) in response_all,
147 'Wrong response generated for the LIST request for all posts.')
148
149 def test_list_existing_thread(self):
150 key = KeyPair.objects.generate_key(primary=True)
151 tag = Tag.objects.create(name='tag1')
152 post = Post.objects.create_post(title='test_title',
153 text='test_text\rline two',
154 tags=[tag])
155 post2 = Post.objects.create_post(title='test title 2',
156 text='test text 2',
157 tags=[tag])
158
159 request_thread = MockRequest()
160 request_thread.body = (
161 '<request type="list" version="1.0">'
162 '<model name="post" version="1.0">'
163 '<thread>{}</thread>'
164 '</model>'
165 '</request>'.format(
166 post.id,
167 )
168 )
169
170 response_thread = response_list(request_thread).content.decode()
171 self.assertTrue(
172 '<status>success</status>'
173 '<models>'
174 '<model>'
175 '<id key="{}" local-id="{}" type="{}" />'
176 '<version>{}</version>'
177 '</model>'
178 '</models>'.format(
179 post.global_id.key,
180 post.global_id.local_id,
181 post.global_id.key_type,
182 post.version,
183 ) in response_thread,
184 'Wrong response generated for the LIST request for posts of '
185 'existing thread.')
186
187 def test_list_non_existing_thread(self):
188 key = KeyPair.objects.generate_key(primary=True)
189 tag = Tag.objects.create(name='tag1')
190 post = Post.objects.create_post(title='test_title',
191 text='test_text\rline two',
192 tags=[tag])
193 post2 = Post.objects.create_post(title='test title 2',
194 text='test text 2',
195 tags=[tag])
196
197 request_thread = MockRequest()
198 request_thread.body = (
199 '<request type="list" version="1.0">'
200 '<model name="post" version="1.0">'
201 '<thread>{}</thread>'
202 '</model>'
203 '</request>'.format(
204 0,
205 )
206 )
207
208 response_thread = response_list(request_thread).content.decode()
209 self.assertTrue(
210 '<status>success</status>'
211 '<models />'
212 in response_thread,
213 'Wrong response generated for the LIST request for posts of '
214 'non-existing thread.')
@@ -1,17 +1,41 b''
1 import logging
2
1 import xml.etree.ElementTree as et
3 import xml.etree.ElementTree as et
2
4
3 from django.http import HttpResponse, Http404
5 from django.http import HttpResponse, Http404
6
7 from boards.abstracts.sync_filters import ThreadFilter, TAG_THREAD
4 from boards.models import GlobalId, Post
8 from boards.models import GlobalId, Post
5 from boards.models.post.sync import SyncManager
9 from boards.models.post.sync import SyncManager
6
10
7
11
12 logger = logging.getLogger('boards.sync')
13
14
15 FILTERS = {
16 TAG_THREAD: ThreadFilter,
17 }
18
19
8 def response_list(request):
20 def response_list(request):
9 request_xml = request.body
21 request_xml = request.body
10
22
23 filters = []
24
11 if request_xml is None or len(request_xml) == 0:
25 if request_xml is None or len(request_xml) == 0:
12 return HttpResponse(content='Use the API')
26 return HttpResponse(content='Use the API')
27 else:
28 root_tag = et.fromstring(request_xml)
29 model_tag = root_tag[0]
13
30
14 response_xml = SyncManager.generate_response_list()
31 for tag_filter in model_tag:
32 filter_name = tag_filter.tag
33 model_filter = FILTERS.get(filter_name)(tag_filter)
34 if not model_filter:
35 logger.warning('Unavailable filter: {}'.format(filter_name))
36 filters.append(model_filter)
37
38 response_xml = SyncManager.generate_response_list(filters)
15
39
16 return HttpResponse(content=response_xml)
40 return HttpResponse(content=response_xml)
17
41
@@ -33,11 +33,11 b' 3. Setup a database in `neboard/settings'
33
33
34 Depending on configured database and search engine, you need to install corresponding dependencies manually.
34 Depending on configured database and search engine, you need to install corresponding dependencies manually.
35
35
36 Default database is *sqlite*, default search engine is *simple*.
36 Default database is *sqlite*. If you want to change the database backend, refer to the django documentation for the correct settings. Please note that sqlite accepts only one connection at a time, so you won't be able to run 2 servers or a server and a sync at the same time.
37
37
38 4. Setup SECRET_KEY to a secret value in `neboard/settings.py
38 4. Setup SECRET_KEY to a secret value in `neboard/settings.py
39 5. Run `./manage.py migrate` to apply all migrations
39 5. Run `./manage.py migrate` to apply all migrations
40 6. Apply config changes to `boards/config/config.ini`. You can see the default settings in `boards/config/default_config.ini`
40 6. Apply config changes to `boards/config/settings.ini`. You can see the default settings in `boards/config/default_config.ini`(do not delete or overwrite it).
41 7. If you want to use decetral engine, run `./manage.py generate_keypair` to generate keys
41 7. If you want to use decetral engine, run `./manage.py generate_keypair` to generate keys
42
42
43 # RUNNING #
43 # RUNNING #
General Comments 0
You need to be logged in to leave comments. Login now