# HG changeset patch # User neko259 # Date 2017-01-18 18:46:03 # Node ID e64fc2ba554872a7aff66b582e061f5b36d87f60 # Parent 3c5ca02bc54ed5f0909c634e77c4ef0f84d47019 Added ability to filter posts in the LIST request diff --git a/boards/abstracts/sync_filters.py b/boards/abstracts/sync_filters.py new file mode 100644 --- /dev/null +++ b/boards/abstracts/sync_filters.py @@ -0,0 +1,31 @@ +import xml.etree.ElementTree as et + +from boards.models import Post + +TAG_THREAD = 'thread' + + +class PostFilter: + def __init__(self, content=None): + self.content = content + + def filter(self, posts): + return posts + + def add_filter(self, model_tag, value): + return model_tag + + +class ThreadFilter(PostFilter): + def filter(self, posts): + op_id = self.content.text + + op = Post.objects.filter(opening=True, id=op_id).first() + if op: + return posts.filter(thread=op.get_thread()) + else: + return posts.none() + + def add_filter(self, model_tag, value): + thread_tag = et.SubElement(model_tag, TAG_THREAD) + thread_tag.text = str(value) diff --git a/boards/management/commands/sync_with_server.py b/boards/management/commands/sync_with_server.py --- a/boards/management/commands/sync_with_server.py +++ b/boards/management/commands/sync_with_server.py @@ -24,6 +24,8 @@ class Command(BaseCommand): parser.add_argument('--split-query', type=int, default=1, help='Split GET query into separate by the given' ' number of posts in one') + parser.add_argument('--thread', type=int, + help='Get posts of one specific thread') def handle(self, *args, **options): logger = logging.getLogger('boards.sync') @@ -45,7 +47,7 @@ class Command(BaseCommand): global_id = GlobalId(key_type=key_type, key=key, local_id=local_id) - xml = GlobalId.objects.generate_request_get([global_id]) + xml = SyncManager.generate_request_get([global_id]) h = httplib2.Http() response, content = h.request(get_url, method="POST", body=xml) @@ -55,7 +57,8 @@ class Command(BaseCommand): else: logger.info('Running LIST request...') h = httplib2.Http() - xml = GlobalId.objects.generate_request_list() + xml = SyncManager.generate_request_list( + opening_post=options.get('thread')) response, content = h.request(list_url, method="POST", body=xml) logger.info('Processing response...') @@ -83,7 +86,7 @@ class Command(BaseCommand): if len(ids_to_sync) > 0: limit = options.get('split_query', len(ids_to_sync)) for offset in range(0, len(ids_to_sync), limit): - xml = GlobalId.objects.generate_request_get(ids_to_sync[offset:offset+limit]) + xml = SyncManager.generate_request_get(ids_to_sync[offset:offset + limit]) h = httplib2.Http() logger.info('Running GET request...') response, content = h.request(get_url, method="POST", body=xml) diff --git a/boards/models/post/sync.py b/boards/models/post/sync.py --- a/boards/models/post/sync.py +++ b/boards/models/post/sync.py @@ -1,9 +1,13 @@ import xml.etree.ElementTree as et import logging +from xml.etree import ElementTree from boards.abstracts.exceptions import SyncException +from boards.abstracts.sync_filters import ThreadFilter from boards.models import KeyPair, GlobalId, Signature, Post, Tag from boards.models.attachment.downloaders import download +from boards.models.signature import TAG_REQUEST, ATTR_TYPE, TYPE_GET, \ + ATTR_VERSION, TAG_MODEL, ATTR_NAME, TAG_ID, TYPE_LIST from boards.utils import get_file_mimetype, get_file_hash from django.db import transaction @@ -263,7 +267,7 @@ class SyncManager: logger.debug('Parsed new post {}'.format(global_id)) @staticmethod - def generate_response_list(): + def generate_response_list(filters): response = et.Element(TAG_RESPONSE) status = et.SubElement(response, TAG_STATUS) @@ -271,7 +275,11 @@ class SyncManager: models = et.SubElement(response, TAG_MODELS) - for post in Post.objects.prefetch_related('global_id').all(): + posts = Post.objects.prefetch_related('global_id') + for post_filter in filters: + posts = post_filter.filter(posts) + + for post in posts: tag_model = et.SubElement(models, TAG_MODEL) tag_id = et.SubElement(tag_model, TAG_ID) post.global_id.to_xml_element(tag_id) @@ -329,3 +337,42 @@ class SyncManager: attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF) attachment_ref.set(ATTR_REF, attachment.hash) attachment_ref.set(ATTR_URL, attachment.file.url) + + @staticmethod + def generate_request_get(global_id_list: list): + """ + Form a get request from a list of ModelId objects. + """ + + request = et.Element(TAG_REQUEST) + request.set(ATTR_TYPE, TYPE_GET) + request.set(ATTR_VERSION, '1.0') + + model = et.SubElement(request, TAG_MODEL) + model.set(ATTR_VERSION, '1.0') + model.set(ATTR_NAME, 'post') + + for global_id in global_id_list: + tag_id = et.SubElement(model, TAG_ID) + global_id.to_xml_element(tag_id) + + return et.tostring(request, 'unicode') + + @staticmethod + def generate_request_list(opening_post=None): + """ + Form a pull request from a list of ModelId objects. + """ + + request = et.Element(TAG_REQUEST) + request.set(ATTR_TYPE, TYPE_LIST) + request.set(ATTR_VERSION, '1.0') + + model = et.SubElement(request, TAG_MODEL) + model.set(ATTR_VERSION, '1.0') + model.set(ATTR_NAME, 'post') + + if opening_post: + ThreadFilter().add_filter(model, opening_post) + + return et.tostring(request, 'unicode') \ No newline at end of file diff --git a/boards/models/signature.py b/boards/models/signature.py --- a/boards/models/signature.py +++ b/boards/models/signature.py @@ -1,8 +1,9 @@ import xml.etree.ElementTree as et + from django.db import models + from boards.models import KeyPair - TAG_MODEL = 'model' TAG_REQUEST = 'request' TAG_ID = 'id' @@ -20,40 +21,6 @@ ATTR_LOCAL_ID = 'local-id' class GlobalIdManager(models.Manager): - def generate_request_get(self, global_id_list: list): - """ - Form a get request from a list of ModelId objects. - """ - - request = et.Element(TAG_REQUEST) - request.set(ATTR_TYPE, TYPE_GET) - request.set(ATTR_VERSION, '1.0') - - model = et.SubElement(request, TAG_MODEL) - model.set(ATTR_VERSION, '1.0') - model.set(ATTR_NAME, 'post') - - for global_id in global_id_list: - tag_id = et.SubElement(model, TAG_ID) - global_id.to_xml_element(tag_id) - - return et.tostring(request, 'unicode') - - def generate_request_list(self): - """ - Form a pull request from a list of ModelId objects. - """ - - request = et.Element(TAG_REQUEST) - request.set(ATTR_TYPE, TYPE_LIST) - request.set(ATTR_VERSION, '1.0') - - model = et.SubElement(request, TAG_MODEL) - model.set(ATTR_VERSION, '1.0') - model.set(ATTR_NAME, 'post') - - return et.tostring(request, 'unicode') - def global_id_exists(self, global_id): """ Checks if the same global id already exists in the system. diff --git a/boards/tests/test_keys.py b/boards/tests/test_keys.py --- a/boards/tests/test_keys.py +++ b/boards/tests/test_keys.py @@ -1,4 +1,3 @@ -from base64 import b64encode import logging from django.test import TestCase @@ -38,7 +37,7 @@ class KeyTest(TestCase): def test_request_get(self): post = self._create_post_with_key() - request = GlobalId.objects.generate_request_get([post.global_id]) + request = SyncManager.generate_request_get([post.global_id]) logger.debug(request) key = KeyPair.objects.get(primary=True) diff --git a/boards/tests/test_sync.py b/boards/tests/test_sync.py --- a/boards/tests/test_sync.py +++ b/boards/tests/test_sync.py @@ -3,7 +3,7 @@ from django.test import TestCase from boards.models import KeyPair, Post, Tag from boards.models.post.sync import SyncManager from boards.tests.mocks import MockRequest -from boards.views.sync import response_get +from boards.views.sync import response_get, response_list __author__ = 'neko259' @@ -103,3 +103,112 @@ class SyncTest(TestCase): post.version, ) in response, 'Wrong response generated for the GET request.') + + def test_list_all(self): + key = KeyPair.objects.generate_key(primary=True) + tag = Tag.objects.create(name='tag1') + post = Post.objects.create_post(title='test_title', + text='test_text\rline two', + tags=[tag]) + post2 = Post.objects.create_post(title='test title 2', + text='test text 2', + tags=[tag]) + + request_all = MockRequest() + request_all.body = ( + '' + '' + '' + '' + ) + + response_all = response_list(request_all).content.decode() + self.assertTrue( + 'success' + '' + '' + '' + '{}' + '' + '' + '' + '{}' + '' + ''.format( + post.global_id.key, + post.global_id.local_id, + post.global_id.key_type, + post.version, + post2.global_id.key, + post2.global_id.local_id, + post2.global_id.key_type, + post2.version, + ) in response_all, + 'Wrong response generated for the LIST request for all posts.') + + def test_list_existing_thread(self): + key = KeyPair.objects.generate_key(primary=True) + tag = Tag.objects.create(name='tag1') + post = Post.objects.create_post(title='test_title', + text='test_text\rline two', + tags=[tag]) + post2 = Post.objects.create_post(title='test title 2', + text='test text 2', + tags=[tag]) + + request_thread = MockRequest() + request_thread.body = ( + '' + '' + '{}' + '' + ''.format( + post.id, + ) + ) + + response_thread = response_list(request_thread).content.decode() + self.assertTrue( + 'success' + '' + '' + '' + '{}' + '' + ''.format( + post.global_id.key, + post.global_id.local_id, + post.global_id.key_type, + post.version, + ) in response_thread, + 'Wrong response generated for the LIST request for posts of ' + 'existing thread.') + + def test_list_non_existing_thread(self): + key = KeyPair.objects.generate_key(primary=True) + tag = Tag.objects.create(name='tag1') + post = Post.objects.create_post(title='test_title', + text='test_text\rline two', + tags=[tag]) + post2 = Post.objects.create_post(title='test title 2', + text='test text 2', + tags=[tag]) + + request_thread = MockRequest() + request_thread.body = ( + '' + '' + '{}' + '' + ''.format( + 0, + ) + ) + + response_thread = response_list(request_thread).content.decode() + self.assertTrue( + 'success' + '' + in response_thread, + 'Wrong response generated for the LIST request for posts of ' + 'non-existing thread.') diff --git a/boards/views/sync.py b/boards/views/sync.py --- a/boards/views/sync.py +++ b/boards/views/sync.py @@ -1,17 +1,41 @@ +import logging + import xml.etree.ElementTree as et from django.http import HttpResponse, Http404 + +from boards.abstracts.sync_filters import ThreadFilter, TAG_THREAD from boards.models import GlobalId, Post from boards.models.post.sync import SyncManager +logger = logging.getLogger('boards.sync') + + +FILTERS = { + TAG_THREAD: ThreadFilter, +} + + def response_list(request): request_xml = request.body + filters = [] + if request_xml is None or len(request_xml) == 0: return HttpResponse(content='Use the API') + else: + root_tag = et.fromstring(request_xml) + model_tag = root_tag[0] - response_xml = SyncManager.generate_response_list() + for tag_filter in model_tag: + filter_name = tag_filter.tag + model_filter = FILTERS.get(filter_name)(tag_filter) + if not model_filter: + logger.warning('Unavailable filter: {}'.format(filter_name)) + filters.append(model_filter) + + response_xml = SyncManager.generate_response_list(filters) return HttpResponse(content=response_xml)