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 = |
|
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 = |
|
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 = |
|
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 = ' ' |
|
25 | SPOILER_SPACE = ' ' | |
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( |
|
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 |
|
|
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( |
|
48 | key_type = reply_number.group(1) | |
48 |
|
|
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 |
|
|
65 | if (postId != null) { | |
70 |
|
|
66 | var post = $('#' + postId); | |
71 |
|
67 | |||
72 |
|
|
68 | // If this is not OP, add reflink to the post. If there already is | |
73 |
|
|
69 | // the same reflink, don't add it again. | |
74 |
|
|
70 | var postText = textAreaJq.val(); | |
75 |
|
|
71 | if (!post.is(':first-child') && postText.indexOf(postLinkRaw) < 0) { | |
76 |
|
|
72 | // Insert line break if none is present. | |
77 |
|
|
73 | if (postText.length > 0 && !postText.endsWith('\n') && !postText.endsWith('\r')) { | |
78 |
|
|
74 | textToAdd += '\n'; | |
79 | } |
|
|||
80 | textToAdd += postLinkRaw + '\n'; |
|
|||
81 | } |
|
75 | } | |
|
76 | textToAdd += postLinkRaw + '\n'; | |||
|
77 | } | |||
82 |
|
78 | |||
83 |
|
|
79 | textAreaJq.val(textAreaJq.val()+ textToAdd); | |
84 |
|
|
80 | blockToInsert = post; | |
85 |
|
|
81 | } else { | |
86 |
|
|
82 | blockToInsert = $('.thread'); | |
87 |
|
|
83 | } | |
88 |
|
|
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 |
|
|
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 = |
|
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/ |
|
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