diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 6b190fd215..d252af8c09 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -34,8 +34,8 @@ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity, email_allowed_for_realm, email_to_username, display_recipient_cache_key, \ get_user_profile_by_email, get_stream_cache_key, \ UserActivityInterval, get_active_user_dicts_in_realm, get_active_streams, \ - realm_filters_for_domain, RealmFilter, receives_offline_notifications, \ - ScheduledJob, realm_filters_for_domain, get_owned_bot_dicts, \ + realm_filters_for_realm, RealmFilter, receives_offline_notifications, \ + ScheduledJob, get_owned_bot_dicts, \ get_old_unclaimed_attachments, get_cross_realm_emails, receives_online_notifications, \ Reaction @@ -2796,7 +2796,7 @@ def do_update_message(user_profile, message, subject, propagate_mode, content, r message.subject = subject event["stream_id"] = message.recipient.type_id event["subject"] = subject - event['subject_links'] = bugdown.subject_links(message.sender.realm.domain.lower(), subject) + event['subject_links'] = bugdown.subject_links(message.sender.realm_id, subject) edit_history_event["prev_subject"] = orig_subject if propagate_mode in ["change_later", "change_all"]: @@ -3095,7 +3095,7 @@ def fetch_initial_state_data(user_profile, event_types, queue_id): state['realm_emoji'] = user_profile.realm.get_emoji() if want('realm_filters'): - state['realm_filters'] = realm_filters_for_domain(user_profile.realm.domain) + state['realm_filters'] = realm_filters_for_realm(user_profile.realm_id) if want('realm_user'): state['realm_users'] = get_realm_user_dicts(user_profile) @@ -3626,7 +3626,7 @@ def do_set_muted_topics(user_profile, muted_topics): def notify_realm_filters(realm): # type: (Realm) -> None - realm_filters = realm_filters_for_domain(realm.domain) + realm_filters = realm_filters_for_realm(realm.id) user_ids = [userdict['id'] for userdict in get_active_user_dicts_in_realm(realm)] event = dict(type="realm_filters", realm_filters=realm_filters) send_event(event, user_ids) diff --git a/zerver/lib/bugdown/__init__.py b/zerver/lib/bugdown/__init__.py index 56fbcec7ab..193d711da3 100644 --- a/zerver/lib/bugdown/__init__.py +++ b/zerver/lib/bugdown/__init__.py @@ -1022,12 +1022,17 @@ class AtomicLinkPattern(LinkPattern): ret.text = markdown.util.AtomicString(ret.text) return ret +# These are used as keys ("realm_ids") to md_engines and the respective +# realm filter caches +DEFAULT_BUGDOWN_KEY = -1 +ZEPHYR_MIRROR_BUGDOWN_KEY = -2 + class Bugdown(markdown.Extension): def __init__(self, *args, **kwargs): # type: (*Any, **Union[bool, None, Text]) -> None # define default configs self.config = { - "realm_filters": [kwargs['realm_filters'], "Realm-specific filters for domain"], + "realm_filters": [kwargs['realm_filters'], "Realm-specific filters for realm"], "realm": [kwargs['realm'], "Realm name"] } @@ -1163,7 +1168,7 @@ class Bugdown(markdown.Extension): if settings.CAMO_URI: md.treeprocessors.add("rewrite_to_https", InlineHttpsProcessor(md), "_end") - if self.getConfig("realm") == "zephyr_mirror": + if self.getConfig("realm") == ZEPHYR_MIRROR_BUGDOWN_KEY: # Disable almost all inline patterns for zephyr mirror # users' traffic that is mirrored. Note that # inline_interesting_links is a treeprocessor and thus is @@ -1181,8 +1186,8 @@ class Bugdown(markdown.Extension): if k not in ["paragraph"]: del md.parser.blockprocessors[k] -md_engines = {} -realm_filter_data = {} # type: Dict[Text, List[Tuple[Text, Text, int]]] +md_engines = {} # type: Dict[int, markdown.Markdown] +realm_filter_data = {} # type: Dict[int, List[Tuple[Text, Text, int]]] class EscapeHtml(markdown.Extension): def extendMarkdown(self, md, md_globals): @@ -1191,7 +1196,7 @@ class EscapeHtml(markdown.Extension): del md.inlinePatterns['html'] def make_md_engine(key, opts): - # type: (Text, Dict[str, Any]) -> None + # type: (int, Dict[str, Any]) -> None md_engines[key] = markdown.Markdown( output_format = 'html', extensions = [ @@ -1206,12 +1211,12 @@ def make_md_engine(key, opts): Bugdown(realm_filters=opts["realm_filters"][0], realm=opts["realm"][0])]) -def subject_links(domain, subject): - # type: (Text, Text) -> List[Text] - from zerver.models import get_realm, RealmFilter, realm_filters_for_domain +def subject_links(realm_id, subject): + # type: (int, Text) -> List[Text] + from zerver.models import get_realm, RealmFilter, realm_filters_for_realm matches = [] # type: List[Text] - realm_filters = realm_filters_for_domain(domain) + realm_filters = realm_filters_for_realm(realm_id) for realm_filter in realm_filters: pattern = prepare_realm_pattern(realm_filter[0]) @@ -1219,35 +1224,35 @@ def subject_links(domain, subject): matches += [realm_filter[1] % m.groupdict()] return matches -def make_realm_filters(domain, filters): - # type: (Text, List[Tuple[Text, Text, int]]) -> None +def make_realm_filters(realm_id, filters): + # type: (int, List[Tuple[Text, Text, int]]) -> None global md_engines, realm_filter_data - if domain in md_engines: - del md_engines[domain] - realm_filter_data[domain] = filters + if realm_id in md_engines: + del md_engines[realm_id] + realm_filter_data[realm_id] = filters # Because of how the Markdown config API works, this has confusing # large number of layers of dicts/arrays :( - make_md_engine(domain, {"realm_filters": [filters, "Realm-specific filters for %s" % (domain,)], - "realm": [domain, "Realm name"]}) + make_md_engine(realm_id, {"realm_filters": [filters, "Realm-specific filters for realm_id %s" % (realm_id,)], + "realm": [realm_id, "Realm name"]}) -def maybe_update_realm_filters(domain): - # type: (Optional[Text]) -> None - from zerver.models import realm_filters_for_domain, all_realm_filters +def maybe_update_realm_filters(realm_id): + # type: (Optional[int]) -> None + from zerver.models import realm_filters_for_realm, all_realm_filters - # If domain is None, load all filters - if domain is None: + # If realm_id is None, load all filters + if realm_id is None: all_filters = all_realm_filters() - all_filters['default'] = [] - for domain, filters in six.iteritems(all_filters): - make_realm_filters(domain, filters) + all_filters[DEFAULT_BUGDOWN_KEY] = [] + for realm_id, filters in six.iteritems(all_filters): + make_realm_filters(realm_id, filters) # Hack to ensure that getConfig("realm") is right for mirrored Zephyrs - make_realm_filters("zephyr_mirror", []) + make_realm_filters(ZEPHYR_MIRROR_BUGDOWN_KEY, []) else: - realm_filters = realm_filters_for_domain(domain) - if domain not in realm_filter_data or realm_filter_data[domain] != realm_filters: + realm_filters = realm_filters_for_realm(realm_id) + if realm_id not in realm_filter_data or realm_filter_data[realm_id] != realm_filters: # Data has changed, re-load filters - make_realm_filters(domain, realm_filters) + make_realm_filters(realm_id, realm_filters) # We want to log Markdown parser failures, but shouldn't log the actual input # message for privacy reasons. The compromise is to replace all alphanumeric @@ -1278,21 +1283,21 @@ def log_bugdown_error(msg): could cause an infinite exception loop.""" logging.getLogger('').error(msg) -def do_convert(content, realm_domain=None, message=None, possible_words=None): - # type: (Text, Optional[Text], Optional[Message], Optional[Set[Text]]) -> Optional[Text] +def do_convert(content, realm_id=None, message=None, possible_words=None): + # type: (Text, Optional[int], Optional[Message], Optional[Set[Text]]) -> Optional[Text] """Convert Markdown to HTML, with Zulip-specific settings and hacks.""" from zerver.models import get_active_user_dicts_in_realm, get_active_streams, UserProfile if message: - maybe_update_realm_filters(message.get_realm().domain) + maybe_update_realm_filters(message.get_realm().id) - if realm_domain in md_engines: - _md_engine = md_engines[realm_domain] + if realm_id in md_engines: + _md_engine = md_engines[realm_id] else: - if 'default' not in md_engines: - maybe_update_realm_filters(domain=None) + if DEFAULT_BUGDOWN_KEY not in md_engines: + maybe_update_realm_filters(realm_id=None) - _md_engine = md_engines["default"] + _md_engine = md_engines[DEFAULT_BUGDOWN_KEY] # Reset the parser; otherwise it will get slower over time. _md_engine.reset() @@ -1364,9 +1369,9 @@ def bugdown_stats_finish(): bugdown_total_requests += 1 bugdown_total_time += (time.time() - bugdown_time_start) -def convert(content, realm_domain=None, message=None, possible_words=None): - # type: (Text, Optional[Text], Optional[Message], Optional[Set[Text]]) -> Optional[Text] +def convert(content, realm_id=None, message=None, possible_words=None): + # type: (Text, Optional[int], Optional[Message], Optional[Set[Text]]) -> Optional[Text] bugdown_stats_start() - ret = do_convert(content, realm_domain, message, possible_words) + ret = do_convert(content, realm_id, message, possible_words) bugdown_stats_finish() return ret diff --git a/zerver/lib/message.py b/zerver/lib/message.py index a5dca7805d..8fe5622e84 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -18,6 +18,7 @@ from zerver.lib.str_utils import force_bytes, dict_with_str_keys from zerver.lib.timestamp import datetime_to_timestamp from zerver.models import ( + get_realm, get_display_recipient_by_id, Message, Recipient, @@ -189,7 +190,7 @@ class MessageDict(object): avatar_url = avatar_url, client = sending_client_name) - obj['subject_links'] = bugdown.subject_links(sender_realm_domain.lower(), subject) + obj['subject_links'] = bugdown.subject_links(sender_realm_id, subject) if last_edit_time != None: obj['last_edit_timestamp'] = datetime_to_timestamp(last_edit_time) @@ -315,12 +316,14 @@ def render_markdown(message, content, domain=None, realm_alert_words=None, messa message.alert_words = set() message.links_for_preview = set() - if not domain: - domain = message.sender.realm.domain + if domain: + realm_id = get_realm(domain).id + else: + realm_id = message.sender.realm.id if message.sending_client.name == "zephyr_mirror" and message.sender.realm.is_zephyr_mirror_realm: # Use slightly customized Markdown processor for content # delivered via zephyr_mirror - domain = u"zephyr_mirror" + realm_id = bugdown.ZEPHYR_MIRROR_BUGDOWN_KEY possible_words = set() # type: Set[Text] if realm_alert_words is not None: @@ -329,7 +332,8 @@ def render_markdown(message, content, domain=None, realm_alert_words=None, messa possible_words.update(set(words)) # DO MAIN WORK HERE -- call bugdown to convert - rendered_content = bugdown.convert(content, domain, message, possible_words) + rendered_content = bugdown.convert(content, realm_id=realm_id, message=message, + possible_words=possible_words) message.user_ids_with_alert_words = set() @@ -342,4 +346,3 @@ def render_markdown(message, content, domain=None, realm_alert_words=None, messa message.is_me_message = Message.is_status_message(content, rendered_content) return rendered_content - diff --git a/zerver/management/commands/realm_filters.py b/zerver/management/commands/realm_filters.py index 62c7407449..13215c4489 100644 --- a/zerver/management/commands/realm_filters.py +++ b/zerver/management/commands/realm_filters.py @@ -46,7 +46,7 @@ Example: ./manage.py realm_filters --realm=zulip --op=show # type: (*Any, **str) -> None realm = get_realm_by_string_id(options["string_id"]) if options["op"] == "show": - print("%s: %s" % (realm.domain, all_realm_filters().get(realm.domain, []))) + print("%s: %s" % (realm.string_id, all_realm_filters().get(realm.id, []))) sys.exit(0) pattern = options['pattern'] diff --git a/zerver/models.py b/zerver/models.py index 76bfb134fc..7e1e2f1468 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -431,47 +431,46 @@ class RealmFilter(models.Model): # type: () -> Text return u"" % (self.realm.domain, self.pattern, self.url_format_string) -def get_realm_filters_cache_key(domain): - # type: (Text) -> Text - return u'all_realm_filters:%s' % (domain,) +def get_realm_filters_cache_key(realm_id): + # type: (int) -> Text + return u'all_realm_filters:%s' % (realm_id,) # We have a per-process cache to avoid doing 1000 remote cache queries during page load -per_request_realm_filters_cache = {} # type: Dict[Text, List[Tuple[Text, Text, int]]] +per_request_realm_filters_cache = {} # type: Dict[int, List[Tuple[Text, Text, int]]] -def domain_in_local_realm_filters_cache(domain): - # type: (Text) -> bool - return domain in per_request_realm_filters_cache +def realm_in_local_realm_filters_cache(realm_id): + # type: (int) -> bool + return realm_id in per_request_realm_filters_cache -def realm_filters_for_domain(domain): - # type: (Text) -> List[Tuple[Text, Text, int]] - domain = domain.lower() - if not domain_in_local_realm_filters_cache(domain): - per_request_realm_filters_cache[domain] = realm_filters_for_domain_remote_cache(domain) - return per_request_realm_filters_cache[domain] +def realm_filters_for_realm(realm_id): + # type: (int) -> List[Tuple[Text, Text, int]] + if not realm_in_local_realm_filters_cache(realm_id): + per_request_realm_filters_cache[realm_id] = realm_filters_for_realm_remote_cache(realm_id) + return per_request_realm_filters_cache[realm_id] @cache_with_key(get_realm_filters_cache_key, timeout=3600*24*7) -def realm_filters_for_domain_remote_cache(domain): - # type: (Text) -> List[Tuple[Text, Text, int]] +def realm_filters_for_realm_remote_cache(realm_id): + # type: (int) -> List[Tuple[Text, Text, int]] filters = [] - for realm_filter in RealmFilter.objects.filter(realm=get_realm(domain)): + for realm_filter in RealmFilter.objects.filter(realm_id=realm_id): filters.append((realm_filter.pattern, realm_filter.url_format_string, realm_filter.id)) return filters def all_realm_filters(): - # type: () -> Dict[Text, List[Tuple[Text, Text, int]]] - filters = defaultdict(list) # type: Dict[Text, List[Tuple[Text, Text, int]]] + # type: () -> Dict[int, List[Tuple[Text, Text, int]]] + filters = defaultdict(list) # type: Dict[int, List[Tuple[Text, Text, int]]] for realm_filter in RealmFilter.objects.all(): - filters[realm_filter.realm.domain].append((realm_filter.pattern, realm_filter.url_format_string, realm_filter.id)) + filters[realm_filter.realm_id].append((realm_filter.pattern, realm_filter.url_format_string, realm_filter.id)) return filters def flush_realm_filter(sender, **kwargs): # type: (Any, **Any) -> None - realm = kwargs['instance'].realm - cache_delete(get_realm_filters_cache_key(realm.domain)) + realm_id = kwargs['instance'].realm.id + cache_delete(get_realm_filters_cache_key(realm_id)) try: - per_request_realm_filters_cache.pop(realm.domain.lower()) + per_request_realm_filters_cache.pop(realm_id) except KeyError: pass diff --git a/zerver/tests/test_bugdown.py b/zerver/tests/test_bugdown.py index ec0a453171..daad01a289 100644 --- a/zerver/tests/test_bugdown.py +++ b/zerver/tests/test_bugdown.py @@ -22,14 +22,14 @@ from zerver.lib.test_classes import ( ) from zerver.lib.str_utils import force_str from zerver.models import ( - domain_in_local_realm_filters_cache, + realm_in_local_realm_filters_cache, flush_per_request_caches, flush_realm_filter, get_client, get_realm_by_string_id, get_user_profile_by_email, get_stream, - realm_filters_for_domain, + realm_filters_for_realm, Message, Stream, Realm, @@ -159,7 +159,7 @@ class FencedBlockPreprocessorTest(TestCase): def bugdown_convert(text): # type: (Text) -> Text - return bugdown.convert(text, "zulip.com") + return bugdown.convert(text, get_realm_by_string_id('zulip').id) class BugdownTest(TestCase): def common_bugdown_test(self, text, expected): @@ -222,9 +222,9 @@ class BugdownTest(TestCase): domain='file_links_test.example.com', string_id='file_links_test') bugdown.make_md_engine( - realm.domain, + realm.id, {'realm_filters': [[], u'file_links_test.example.com'], 'realm': [u'file_links_test.example.com', 'Realm name']}) - converted = bugdown.convert(msg, realm_domain=realm.domain) + converted = bugdown.convert(msg, realm_id=realm.id) self.assertEqual(converted, '

Check out this file file:///Volumes/myserver/Users/Shared/pi.py

') def test_inline_youtube(self): @@ -410,17 +410,17 @@ class BugdownTest(TestCase): # type: (Text, Text) -> Text return '%s' % (name, get_camo_url(url), name) - zulip_realm = get_realm_by_string_id('zulip') + realm = get_realm_by_string_id('zulip') url = "https://zulip.com/test_realm_emoji.png" - check_add_realm_emoji(zulip_realm, "test", url) + check_add_realm_emoji(realm, "test", url) # Needs to mock an actual message because that's how bugdown obtains the realm msg = Message(sender=get_user_profile_by_email("hamlet@zulip.com")) - converted = bugdown.convert(":test:", "zulip.com", msg) + converted = bugdown.convert(":test:", realm_id=realm.id, message=msg) self.assertEqual(converted, '

%s

' % (emoji_img(':test:', url))) - do_remove_realm_emoji(zulip_realm, 'test') - converted = bugdown.convert(":test:", "zulip.com", msg) + do_remove_realm_emoji(realm, 'test') + converted = bugdown.convert(":test:", realm_id=realm.id, message=msg) self.assertEqual(converted, '

:test:

') def test_unicode_emoji(self): @@ -452,18 +452,18 @@ class BugdownTest(TestCase): flush_per_request_caches() content = "We should fix #224 and #115, but not issue#124 or #1124z or [trac #15](https://trac.zulip.net/ticket/16) today." - converted = bugdown.convert(content, realm_domain='zulip.com', message=msg) - converted_subject = bugdown.subject_links(realm.domain.lower(), msg.subject) + converted = bugdown.convert(content, realm_id=realm.id, message=msg) + converted_subject = bugdown.subject_links(realm.id, msg.subject) self.assertEqual(converted, '

We should fix #224 and #115, but not issue#124 or #1124z or trac #15 today.

') self.assertEqual(converted_subject, [u'https://trac.zulip.net/ticket/444']) - RealmFilter(realm=get_realm_by_string_id('zulip'), pattern=r'#(?P[a-zA-Z]+-[0-9]+)', + RealmFilter(realm=realm, pattern=r'#(?P[a-zA-Z]+-[0-9]+)', url_format_string=r'https://trac.zulip.net/ticket/%(id)s').save() msg = Message(sender=get_user_profile_by_email('hamlet@zulip.com')) content = '#ZUL-123 was fixed and code was deployed to production, also #zul-321 was deployed to staging' - converted = bugdown.convert(content, realm_domain='zulip.com', message=msg) + converted = bugdown.convert(content, realm_id=realm.id, message=msg) self.assertEqual(converted, '

#ZUL-123 was fixed and code was deployed to production, also #zul-321 was deployed to staging

') @@ -477,9 +477,9 @@ class BugdownTest(TestCase): realm_filter.save() bugdown.realm_filter_data = {} - bugdown.maybe_update_realm_filters(domain=None) + bugdown.maybe_update_realm_filters(None) all_filters = bugdown.realm_filter_data - zulip_filters = all_filters['zulip.com'] + zulip_filters = all_filters[realm.id] self.assertEqual(len(zulip_filters), 1) self.assertEqual(zulip_filters[0], (u'#(?P[0-9]{2,8})', u'https://trac.zulip.net/ticket/%(id)s', realm_filter.id)) @@ -509,20 +509,20 @@ class BugdownTest(TestCase): # start fresh for our domain flush() - self.assertFalse(domain_in_local_realm_filters_cache(domain=realm.domain)) + self.assertFalse(realm_in_local_realm_filters_cache(realm.id)) # call this just for side effects of populating the cache - realm_filters_for_domain(domain=realm.domain) - self.assertTrue(domain_in_local_realm_filters_cache(realm.domain)) + realm_filters_for_realm(realm.id) + self.assertTrue(realm_in_local_realm_filters_cache(realm.id)) # Saving a new RealmFilter should have the side effect of # flushing the cache. save_new_realm_filter() - self.assertFalse(domain_in_local_realm_filters_cache(realm.domain)) + self.assertFalse(realm_in_local_realm_filters_cache(realm.id)) # and flush it one more time, to make sure we don't get a KeyError flush() - self.assertFalse(domain_in_local_realm_filters_cache(realm.domain)) + self.assertFalse(realm_in_local_realm_filters_cache(realm.id)) def test_realm_patterns_negative(self): # type: () -> None @@ -531,7 +531,7 @@ class BugdownTest(TestCase): url_format_string=r"https://trac.zulip.net/ticket/%(id)s").save() boring_msg = Message(sender=get_user_profile_by_email("othello@zulip.com"), subject=u"no match here") - converted_boring_subject = bugdown.subject_links(realm.domain.lower(), boring_msg.subject) + converted_boring_subject = bugdown.subject_links(realm.id, boring_msg.subject) self.assertEqual(converted_boring_subject, []) def test_is_status_message(self): @@ -817,19 +817,19 @@ class BugdownTest(TestCase): verifies almost all inline patterns are disabled, but inline_interesting_links is still enabled""" msg = "**test**" - converted = bugdown.convert(msg, "zephyr_mirror") + converted = bugdown.convert(msg, realm_id=bugdown.ZEPHYR_MIRROR_BUGDOWN_KEY) self.assertEqual( converted, "

**test**

", ) msg = "* test" - converted = bugdown.convert(msg, "zephyr_mirror") + converted = bugdown.convert(msg, realm_id=bugdown.ZEPHYR_MIRROR_BUGDOWN_KEY) self.assertEqual( converted, "

* test

", ) msg = "https://lists.debian.org/debian-ctte/2014/02/msg00173.html" - converted = bugdown.convert(msg, "zephyr_mirror") + converted = bugdown.convert(msg, realm_id=bugdown.ZEPHYR_MIRROR_BUGDOWN_KEY) self.assertEqual( converted, '

https://lists.debian.org/debian-ctte/2014/02/msg00173.html

', @@ -868,7 +868,7 @@ class BugdownErrorTests(ZulipTestCase): # type: () -> None with self.simulated_markdown_failure(): with self.assertRaises(bugdown.BugdownRenderingException): - bugdown.convert('', 'zulip.com') + bugdown_convert('') def test_send_message_errors(self): # type: () -> None diff --git a/zerver/views/integrations.py b/zerver/views/integrations.py index 7af0fd24fc..8155aad2ad 100644 --- a/zerver/views/integrations.py +++ b/zerver/views/integrations.py @@ -121,7 +121,7 @@ def api_endpoint_docs(request): extended_response = response.replace(", ", ",\n ") else: extended_response = response - call['rendered_response'] = bugdown.convert("~~~ .py\n" + extended_response + "\n~~~\n", "default") + call['rendered_response'] = bugdown.convert("~~~ .py\n" + extended_response + "\n~~~\n", bugdown.DEFAULT_BUGDOWN_KEY) for example_type in ('request', 'response'): for lang in call.get('example_' + example_type, []): langs.add(lang) diff --git a/zerver/views/realm_filters.py b/zerver/views/realm_filters.py index cbffb482c0..301c9c57db 100644 --- a/zerver/views/realm_filters.py +++ b/zerver/views/realm_filters.py @@ -11,13 +11,13 @@ from zerver.lib.actions import do_add_realm_filter, do_remove_realm_filter from zerver.lib.response import json_success, json_error from zerver.lib.rest import rest_dispatch as _rest_dispatch from zerver.lib.validator import check_string -from zerver.models import realm_filters_for_domain, UserProfile, RealmFilter +from zerver.models import realm_filters_for_realm, UserProfile, RealmFilter # Custom realm filters def list_filters(request, user_profile): # type: (HttpRequest, UserProfile) -> HttpResponse - filters = realm_filters_for_domain(user_profile.realm.domain) + filters = realm_filters_for_realm(user_profile.realm_id) return json_success({'filters': filters})