[feature] Global Search (again) (#2710)

* [start] global search frappe/erpnext#6674

* [fix] setup before running test

* [start] global search frappe/erpnext#6674

* Display result as rudimentary list, rebuild old doctypes

* Media view, child tables, delete document updates, searchable fields

* More results UI

* Code clean up

* remove msgprint from document.py to resolve merge conflict

* Modularization stage 1, get show more to work with it

* Dedicated modal Search bar works, some clean up needed

* Can't data-dismiss on links, Bootstrap issue, use hashchange

* Accomodate missing field content syndrome

* Search in boolean mode, make GS default in awesome bar, fix double modal bug and cleanup

* Add in Meta

* Add in customize form

* Modularise Global Search

* Search object

* Commonify Search UI: Stage I

* II: save list state, UI, default condensed view, refactor

* Fix SQL bug, Refactor awesome bar, Fix unicode bug, add nav results

* Refactor using separate search objects, some async issues

* Fix async flow

* Fix preceding more list bug

* UI additions

* another async fix, back link

* Help: Stage I

* Help: Stage II

* Background jobs, fix route options bug

* Fix GS syncing on install

* Add GS options in awesome bar: test

* Input now remembers search type state

* More UI updates

* Add description for GS results in awesome bar

* Fix help modal bug

* Fix: not commit during install

* Test cases, some fixes

* Update in_test flag in enqueue

* Disable GS sync when not install_db

* Add flag check

* Disable field in child tables

* Cleanups

* Create table fix

* Fix redis exception, remove commit enqueue, add gs in migrate

* Fix tests

* Single enqueue

* cleanups

* Fix tests

* Fix event test

* Fix duplication, search as first option

* Add show name in global search

* fix event tests and desk.less

* Fix communication.json

* [fixes] wip

* [fix] tests

* [minor] for tests

* [minor] for tests

* [minor] for tests

* [minor] for tests
This commit is contained in:
Rushabh Mehta 2017-02-13 14:50:54 +05:30 committed by GitHub
parent 1f58e36d37
commit 86ceb21005
38 changed files with 4350 additions and 2702 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
@ -52,6 +53,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Module",
@ -83,6 +85,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Is Child Table",
@ -114,6 +117,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Is Single",
@ -145,6 +149,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Editable Grid",
@ -175,6 +180,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Quick Entry",
@ -205,6 +211,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Track Changes",
@ -233,6 +240,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -260,6 +268,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Show in Module Section",
@ -290,6 +299,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Icon",
@ -317,6 +327,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Custom?",
@ -344,6 +355,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Beta",
@ -374,6 +386,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image View",
@ -402,6 +415,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Plugin",
@ -429,6 +443,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Fields",
@ -457,6 +472,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Fields",
@ -487,6 +503,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Naming",
@ -515,6 +532,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Auto Name",
@ -544,6 +562,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Name Case",
@ -574,6 +593,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
@ -604,6 +624,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -632,6 +653,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title Field",
@ -660,6 +682,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Search Fields",
@ -690,6 +713,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image Field",
@ -721,6 +745,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sort Field",
@ -750,6 +775,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sort Order",
@ -780,6 +806,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Timeline Field",
@ -809,6 +836,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Permission Rules",
@ -837,6 +865,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Permissions",
@ -868,6 +897,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -894,6 +924,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Permissions Settings",
@ -921,6 +952,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "User Cannot Create",
@ -950,6 +982,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "User Cannot Search",
@ -979,6 +1012,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Submittable",
@ -1007,6 +1041,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Import",
@ -1034,6 +1069,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Rename",
@ -1063,6 +1099,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "In Dialog",
@ -1092,6 +1129,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Show Print First",
@ -1110,6 +1148,35 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "show_name_in_global_search",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Make \"name\" searchable in Global Search",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -1121,6 +1188,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Max Attachments",
@ -1150,6 +1218,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Other Settings",
@ -1177,6 +1246,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Hide Heading",
@ -1206,6 +1276,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Hide Toolbar",
@ -1235,6 +1306,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Hide Copy",
@ -1264,6 +1336,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Track Seen",
@ -1292,6 +1365,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Print Format",
@ -1319,6 +1393,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Advanced",
@ -1349,6 +1424,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Database Engine",
@ -1379,7 +1455,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-12-29 14:39:46.191331",
"modified": "2017-02-13 12:09:33.488890",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",
@ -1395,7 +1471,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -1416,7 +1491,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -1432,6 +1506,7 @@
"read_only": 0,
"read_only_onload": 0,
"search_fields": "module",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,

View file

@ -33,7 +33,7 @@ class DocType(Document):
- Validate series
- Check fieldnames (duplication etc)
- Clear permission table for child tables
- Add `amended_from` and `ameneded_by` if Amendable"""
- Add `amended_from` and `amended_by` if Amendable"""
self.check_developer_mode()
self.validate_name()
@ -59,6 +59,12 @@ class DocType(Document):
self.make_amendable()
self.validate_website()
try:
self.before_update = frappe.get_doc('DocType', self.name)
except frappe.DoesNotExistError:
pass
if not self.is_new():
self.setup_fields_to_fetch()
@ -162,6 +168,7 @@ class DocType(Document):
self.autoname = "naming_series:"
# validate field name if autoname field:fieldname is used
if autoname and autoname.startswith('field:'):
field = autoname.split(":")[1]
if not field or field not in [ df.fieldname for df in self.fields ]:
@ -201,13 +208,31 @@ class DocType(Document):
delete_notification_count_for(doctype=self.name)
frappe.clear_cache(doctype=self.name)
if not frappe.flags.in_install and hasattr(self, 'before_update'):
self.sync_global_search()
def sync_global_search(self):
'''If global search settings are changed, rebuild search properties for this table'''
global_search_fields_before_update = [d.fieldname for d in
self.before_update.fields if d.in_global_search]
if self.before_update.show_name_in_global_search:
global_search_fields_before_update.append('name')
global_search_fields_after_update = [d.fieldname for d in
self.fields if d.in_global_search]
if self.show_name_in_global_search:
global_search_fields_after_update.append('name')
if set(global_search_fields_before_update) != set(global_search_fields_after_update):
frappe.enqueue('frappe.utils.global_search.rebuild_for_doctype',
now=frappe.flags.in_test, doctype=self.name)
def run_module_method(self, method):
from frappe.modules import load_doctype_module
module = load_doctype_module(self.name, self.module)
if hasattr(module, method):
getattr(module, method)()
def before_rename(self, old, new, merge=False):
"""Throw exception if merge. DocTypes cannot be merged."""
if not self.custom and frappe.session.user != "Administrator":
@ -263,7 +288,7 @@ class DocType(Document):
import_from_files(record_list=[[self.module, 'doctype', self.name]])
def make_controller_template(self):
"""Make boilderplate controller template."""
"""Make boilerplate controller template."""
make_boilerplate("controller.py", self)
if not (self.istable or self.issingle):

View file

@ -43,6 +43,7 @@ docfield_properties = {
'ignore_user_permissions': 'Check',
'in_list_view': 'Check',
'in_standard_filter': 'Check',
'in_global_search': 'Check',
'hidden': 'Check',
'collapsible': 'Check',
'collapsible_depends_on': 'Data',

View file

@ -51,7 +51,8 @@ class TestCustomizeForm(unittest.TestCase):
d = self.get_customize_form("User")
self.assertEquals(d.doc_type, "User")
self.assertEquals(len(d.get("fields")), len(frappe.get_doc("DocType", d.doc_type).fields) + 1)
self.assertEquals(len(d.get("fields")),
len(frappe.get_doc("DocType", d.doc_type).fields) + 1)
self.assertEquals(d.get("fields")[-1].fieldname, "test_custom_field")
self.assertEquals(d.get("fields", {"fieldname": "location"})[0].in_list_view, 1)

View file

@ -22,6 +22,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Label and Type",
@ -50,6 +51,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Label",
@ -80,6 +82,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Type",
@ -110,6 +113,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Name",
@ -139,7 +143,8 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mandatory",
"length": 0,
@ -170,6 +175,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Unique",
@ -198,6 +204,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "In List View",
@ -225,6 +232,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "In Standard Filter",
@ -242,6 +250,36 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "In Global Search",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -253,6 +291,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -282,6 +321,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Precision",
@ -312,6 +352,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Length",
@ -341,6 +382,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Options",
@ -370,6 +412,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Permissions",
@ -399,6 +442,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Depends On",
@ -429,6 +473,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Perm Level",
@ -458,6 +503,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Hidden",
@ -489,6 +535,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Read Only",
@ -518,6 +565,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Collapsible",
@ -547,6 +595,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Collapsible Depends On",
@ -575,6 +624,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -602,6 +652,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Ignore User Permissions",
@ -629,6 +680,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow on Submit",
@ -658,6 +710,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Report Hide",
@ -688,6 +741,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Remember Last Selected Value",
@ -716,6 +770,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Display",
@ -744,6 +799,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default",
@ -762,6 +818,38 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "in_filter",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "In Filter",
"length": 0,
"no_copy": 0,
"oldfieldname": "in_filter",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "50px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50px"
},
{
"allow_on_submit": 0,
"bold": 0,
@ -773,6 +861,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -800,6 +889,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
@ -831,6 +921,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Hide",
@ -861,6 +952,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Hide If No Value",
@ -890,6 +982,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Width",
@ -921,6 +1014,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Columns",
@ -949,6 +1043,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Width",
@ -980,6 +1075,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Custom Field",

View file

@ -57,7 +57,8 @@ class PropertySetter(Document):
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
validate_fields_for_doctype(self.doc_type)
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False, validate_fields_for_doctype=True):
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False,
validate_fields_for_doctype=True):
# WARNING: Ignores Permissions
property_setter = frappe.get_doc({
"doctype":"Property Setter",

View file

@ -31,6 +31,7 @@ CREATE TABLE `tabDocField` (
`report_hide` int(1) NOT NULL DEFAULT 0,
`reqd` int(1) NOT NULL DEFAULT 0,
`bold` int(1) NOT NULL DEFAULT 0,
`in_global_search` int(1) NOT NULL DEFAULT 0,
`collapsible` int(1) NOT NULL DEFAULT 0,
`unique` int(1) NOT NULL DEFAULT 0,
`no_copy` int(1) NOT NULL DEFAULT 0,
@ -149,6 +150,7 @@ CREATE TABLE `tabDocType` (
`engine` varchar(20) DEFAULT 'InnoDB',
`default_print_format` varchar(255) DEFAULT NULL,
`is_submittable` int(1) NOT NULL DEFAULT 0,
`show_name_in_global_search` int(1) NOT NULL DEFAULT 0,
`_user_tags` varchar(255) DEFAULT NULL,
`custom` int(1) NOT NULL DEFAULT 0,
`beta` int(1) NOT NULL DEFAULT 0,

View file

@ -17,7 +17,8 @@ import re
import frappe.model.meta
from frappe.utils import now, get_datetime, cstr
from frappe import _
from types import StringType, UnicodeType
from types import StringType, UnicodeType
from frappe.utils.global_search import sync_global_search
class Database:
"""
@ -722,6 +723,8 @@ class Database:
self.sql("commit")
frappe.local.rollback_observers = []
self.flush_realtime_log()
if frappe.flags.update_global_search:
sync_global_search()
def flush_realtime_log(self):
for args in frappe.local.realtime_log:
@ -729,7 +732,6 @@ class Database:
frappe.local.realtime_log = []
def rollback(self):
"""`ROLLBACK` current transaction."""
self.sql("rollback")

View file

@ -23,6 +23,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
@ -51,6 +52,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Subject",
@ -78,6 +80,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Event Type",
@ -109,6 +112,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send an email reminder in the morning",
@ -136,6 +140,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -162,6 +167,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Starts on",
@ -189,6 +195,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Ends on",
@ -216,6 +223,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "All Day",
@ -243,6 +251,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -269,6 +278,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat this Event",
@ -297,6 +307,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -324,6 +335,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat On",
@ -354,6 +366,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat Till",
@ -381,6 +394,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -408,6 +422,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Monday",
@ -436,6 +451,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Tuesday",
@ -464,6 +480,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Wednesday",
@ -492,6 +509,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Thursday",
@ -520,6 +538,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Friday",
@ -548,6 +567,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Saturday",
@ -576,6 +596,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sunday",
@ -603,6 +624,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -629,6 +651,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
@ -660,6 +683,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Participants",
@ -688,6 +712,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Groups",
@ -718,6 +743,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Roles",
@ -748,6 +774,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Ref Type",
@ -778,6 +805,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Ref Name",
@ -809,7 +837,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-12-29 14:40:19.097127",
"modified": "2017-02-09 03:32:53.484696",
"modified_by": "Administrator",
"module": "Desk",
"name": "Event",
@ -825,7 +853,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -846,7 +873,6 @@
"export": 1,
"if_owner": 0,
"import": 1,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -861,6 +887,7 @@
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "DESC",
"title_field": "subject",
"track_changes": 1,

View file

@ -10,36 +10,43 @@ import unittest
import json
from frappe.desk.doctype.event.event import get_events
from frappe.test_runner import make_test_objects
test_records = frappe.get_test_records('Event')
test_user = "test1@example.com"
class TestEvent(unittest.TestCase):
def setUp(self):
frappe.db.sql('delete from tabEvent')
frappe.db.sql('delete from `tabEvent Role`')
make_test_objects('Event', reset=True)
self.test_records = frappe.get_test_records('Event')
self.test_user = "test1@example.com"
def tearDown(self):
frappe.set_user("Administrator")
def test_allowed_public(self):
frappe.set_user(test_user)
frappe.set_user(self.test_user)
doc = frappe.get_doc("Event", frappe.db.get_value("Event", {"subject":"_Test Event 1"}))
self.assertTrue(frappe.has_permission("Event", doc=doc))
def test_not_allowed_private(self):
frappe.set_user(test_user)
frappe.set_user(self.test_user)
doc = frappe.get_doc("Event", frappe.db.get_value("Event", {"subject":"_Test Event 2"}))
self.assertFalse(frappe.has_permission("Event", doc=doc))
def test_allowed_private_if_in_event_user(self):
name = frappe.db.get_value("Event", {"subject":"_Test Event 3"})
frappe.share.add("Event", name, test_user, "read")
frappe.set_user(test_user)
frappe.share.add("Event", name, self.test_user, "read")
frappe.set_user(self.test_user)
doc = frappe.get_doc("Event", name)
self.assertTrue(frappe.has_permission("Event", doc=doc))
frappe.set_user("Administrator")
frappe.share.remove("Event", name, test_user)
frappe.share.remove("Event", name, self.test_user)
def test_event_list(self):
frappe.set_user(test_user)
frappe.set_user(self.test_user)
res = frappe.get_list("Event", filters=[["Event", "subject", "like", "_Test Event%"]], fields=["name", "subject"])
self.assertEquals(len(res), 1)
subjects = [r.subject for r in res]
@ -48,13 +55,13 @@ class TestEvent(unittest.TestCase):
self.assertFalse("_Test Event 2" in subjects)
def test_revert_logic(self):
ev = frappe.get_doc(test_records[0]).insert()
ev = frappe.get_doc(self.test_records[0]).insert()
name = ev.name
frappe.delete_doc("Event", ev.name)
# insert again
ev = frappe.get_doc(test_records[0]).insert()
ev = frappe.get_doc(self.test_records[0]).insert()
# the name should be same!
self.assertEquals(ev.name, name)
@ -62,7 +69,7 @@ class TestEvent(unittest.TestCase):
def test_assign(self):
from frappe.desk.form.assign_to import add
ev = frappe.get_doc(test_records[0]).insert()
ev = frappe.get_doc(self.test_records[0]).insert()
add({
"assign_to": "test@example.com",
@ -77,7 +84,7 @@ class TestEvent(unittest.TestCase):
# add another one
add({
"assign_to": test_user,
"assign_to": self.test_user,
"doctype": "Event",
"name": ev.name,
"description": "Test Assignment"
@ -85,11 +92,11 @@ class TestEvent(unittest.TestCase):
ev = frappe.get_doc("Event", ev.name)
self.assertEquals(set(json.loads(ev._assign)), set(["test@example.com", test_user]))
self.assertEquals(set(json.loads(ev._assign)), set(["test@example.com", self.test_user]))
# close an assignment
todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name,
"owner": test_user})
"owner": self.test_user})
todo.status = "Closed"
todo.save()
@ -120,5 +127,4 @@ class TestEvent(unittest.TestCase):
self.assertFalse(filter(lambda e: e.name==ev.name, ev_list2))
ev_list3 = get_events("2015-02-01", "2015-02-01", "Administrator", for_reminder=True)
self.assertTrue(filter(lambda e: e.name==ev.name, ev_list3))
self.assertTrue(filter(lambda e: e.name==ev.name, ev_list3))

View file

@ -14,7 +14,7 @@
{
"doctype": "Event",
"starts_on": "2014-01-01",
"subject":"_Test Event 3",
"subject": "_Test Event 3",
"event_type": "Private",
"roles": [
{"role": "System Manager"}

View file

@ -8,19 +8,23 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "role",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Role",
"length": 0,
"no_copy": 0,
@ -32,6 +36,7 @@
"print_hide_if_no_value": 0,
"print_width": "240px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -50,7 +55,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:00.333899",
"modified": "2017-01-30 06:39:06.384904",
"modified_by": "Administrator",
"module": "Desk",
"name": "Event Role",
@ -59,5 +64,6 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"track_changes": 0,
"track_seen": 0
}

View file

@ -22,6 +22,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
@ -50,6 +51,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "First Name",
@ -79,6 +81,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Last Name",
@ -108,6 +111,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email Address",
@ -138,6 +142,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "User Id",
@ -167,6 +172,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -194,6 +200,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
@ -222,6 +229,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Phone",
@ -251,6 +259,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mobile No",
@ -280,6 +289,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
@ -308,6 +318,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference",
@ -338,6 +349,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Primary Contact",
@ -367,6 +379,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Links",
@ -396,6 +409,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "More Information",
@ -425,6 +439,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
@ -453,6 +468,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Designation",
@ -480,6 +496,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -507,6 +524,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Unsubscribed",
@ -536,7 +554,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-01-13 06:59:06.417300",
"modified": "2017-01-31 00:15:30.298287",
"modified_by": "Administrator",
"module": "Email",
"name": "Contact",
@ -552,7 +570,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -573,7 +590,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -594,7 +610,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -615,7 +630,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -636,7 +650,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -657,7 +670,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -678,7 +690,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -699,7 +710,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -720,7 +730,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -741,7 +750,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -762,7 +770,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -783,7 +790,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"match": "",
"permlevel": 1,
"print": 0,

View file

@ -17,6 +17,7 @@ from frappe.utils.fixtures import sync_fixtures
from frappe.website import render
from frappe.desk.doctype.desktop_icon.desktop_icon import sync_from_app
from frappe.utils.password import create_auth_table
from frappe.utils.global_search import setup_global_search_table
from frappe.modules.utils import sync_customizations
def install_db(root_login="root", root_password=None, db_name=None, source_sql=None,
@ -41,6 +42,7 @@ def install_db(root_login="root", root_password=None, db_name=None, source_sql=N
remove_missing_apps()
create_auth_table()
setup_global_search_table()
create_list_settings_table()
frappe.flags.in_install_db = False

View file

@ -14,7 +14,7 @@ from frappe.website import render
from frappe.desk.doctype.desktop_icon.desktop_icon import sync_desktop_icons
from frappe.core.doctype.language.language import sync_languages
from frappe.modules.utils import sync_customizations
import frappe.utils.help
import frappe.utils.help
def migrate(verbose=True, rebuild_website=False):
'''Migrate all apps to the latest version, will:

View file

@ -12,9 +12,6 @@ no_value_fields = ('Section Break', 'Column Break', 'HTML', 'Table', 'Button', '
display_fieldtypes = ('Section Break', 'Column Break', 'HTML', 'Button', 'Image', 'Fold', 'Heading')
default_fields = ('doctype','name','owner','creation','modified','modified_by',
'parent','parentfield','parenttype','idx','docstatus')
integer_docfield_properties = ("reqd", "search_index", "in_list_view", "permlevel",
"hidden", "read_only", "ignore_user_permissions", "allow_on_submit", "report_hide",
"no_copy", "print_hide", "unique")
optional_fields = ("_user_tags", "_comments", "_assign", "_liked_by", "_seen")
def rename(doctype, old, new, debug=False):

View file

@ -11,6 +11,7 @@ from frappe.utils.file_manager import remove_all
from frappe.utils.password import delete_all_passwords_for
from frappe import _
from frappe.model.naming import revert_series_if_last
from frappe.utils.global_search import delete_for_document
def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False,
ignore_permissions=False, flags=None, ignore_on_trash=False):
@ -87,6 +88,9 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa
# delete attachments
remove_all(doctype, name, from_delete=True)
# delete global search entry
delete_for_document(doc)
if doc and not for_reload:
add_to_deleted_document(doc)
if not frappe.flags.in_patch:

View file

@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
import time
import redis
from frappe import _, msgprint
from frappe.utils import flt, cstr, now, get_datetime_str, file_lock
from frappe.utils.background_jobs import enqueue
@ -13,7 +14,7 @@ from werkzeug.exceptions import NotFound, Forbidden
import hashlib, json
from frappe.model import optional_fields
from frappe.utils.file_manager import save_url
from frappe.utils.global_search import update_global_search
# once_only validation
# methods
@ -789,6 +790,11 @@ class Document(BaseDocument):
self.clear_cache()
self.notify_update()
try:
frappe.enqueue('frappe.utils.global_search.update_global_search', now=frappe.flags.in_test, doc=self)
except redis.exceptions.ConnectionError:
update_global_search(self)
if self._doc_before_save and not self.flags.ignore_version:
self.save_version()

View file

@ -18,7 +18,7 @@ Example:
from __future__ import unicode_literals
import frappe, json
from frappe.utils import cstr, cint
from frappe.model import integer_docfield_properties, default_fields, no_value_fields, optional_fields
from frappe.model import default_fields, no_value_fields, optional_fields
from frappe.model.document import Document
from frappe.model.base_document import BaseDocument
from frappe.model.db_schema import type_map
@ -95,6 +95,14 @@ class Meta(Document):
return self._table_fields
def get_global_search_fields(self):
'''Returns list of fields with `in_global_search` set and `name` if set'''
fields = self.get("fields", {"in_global_search": 1 })
if getattr(self, 'show_name_in_global_search', None):
fields.append(frappe._dict(fieldtype='Data', fieldname='name', label='Name'))
return fields
def get_valid_columns(self):
if not hasattr(self, "_valid_columns"):
if self.name in ("DocType", "DocField", "DocPerm", "Property Setter"):
@ -221,8 +229,15 @@ class Meta(Document):
raise
def apply_property_setters(self):
for ps in frappe.db.sql("""select * from `tabProperty Setter` where
doc_type=%s""", (self.name,), as_dict=1):
property_setters = frappe.db.sql("""select * from `tabProperty Setter` where
doc_type=%s""", (self.name,), as_dict=1)
if not property_setters: return
integer_docfield_properties = [d.fieldname for d in frappe.get_meta('DocField').fields
if d.fieldtype in ('Int', 'Check')]
for ps in property_setters:
if ps.doctype_or_field=='DocType':
if ps.property_type in ('Int', 'Check'):
ps.value = cint(ps.value)

View file

@ -1,5 +1,6 @@
execute:frappe.db.sql("""update `tabPatch Log` set patch=replace(patch, '.4_0.', '.v4_0.')""") #2014-05-12
frappe.patches.v5_0.convert_to_barracuda_and_utf8mb4
execute:frappe.utils.global_search.setup_global_search_table()
frappe.patches.v7_0.update_auth
frappe.patches.v7_1.rename_scheduler_log_to_error_log
frappe.patches.v6_1.rename_file_data

View file

@ -135,6 +135,9 @@
"public/js/frappe/views/pageview.js",
"public/js/frappe/ui/toolbar/awesome_bar.js",
"public/js/frappe/ui/toolbar/search.js",
"public/js/frappe/ui/toolbar/search.html",
"public/js/frappe/ui/toolbar/search_header.html",
"public/js/frappe/ui/toolbar/about.js",
"public/js/frappe/ui/toolbar/navbar.html",
"public/js/frappe/ui/toolbar/toolbar.js",

View file

@ -660,7 +660,79 @@ fieldset[disabled] .form-control {
.note-editor .dropdown-menu {
z-index: 100;
max-height: 300px;
overflow: scroll;
overflow: auto;
}
.search-dialog .modal-dialog {
width: 768px;
height: 500px;
}
.search-dialog .modal-body {
padding: 0px 15px;
}
.search-dialog input.form-control,
.search-dialog .input-group-addon {
border: none;
border-left-style: none;
}
.search-dialog input.form-control:focus {
outline: none;
box-shadow: none;
}
.search-dialog .input-group-addon {
background-color: #FFF;
}
.search-dialog .layout-side-section,
.search-dialog .layout-main-section {
height: 500px;
padding: 0px;
overflow-y: auto;
}
.search-dialog .layout-side-section {
padding-left: 15px;
}
.search-dialog .module-section .back-link {
margin-bottom: 20px;
margin-top: -10px;
}
.search-dialog .module-section .all-results-link:before {
font-family: 'Octicons';
content: '\f0a4';
}
.search-dialog .dual-section .result a {
color: black;
}
.search-dialog .dual-section .result a b {
color: #4e6161;
}
.search-dialog .result-status {
margin-top: 30px;
text-align: center;
}
.search-dialog .more-results {
display: none;
}
@media (max-width: 767px) {
.search-dialog .modal-dialog {
width: auto;
}
.search-dialog .modal-content {
height: auto !important;
}
}
@media (max-width: 991px) {
.search-dialog .module-body {
margin: 0px;
border-top: none;
}
.search-dialog .layout-side-section .sidebar-menu {
margin: 30px 0px;
}
}
.result p {
margin-top: 0.2em;
}
.search-result {
margin-bottom: 24px;
}
.note-editor.note-frame .note-editing-area .note-editable {
color: #36414C;

View file

@ -1,7 +1,7 @@
frappe.provide('frappe.ui.misc');
frappe.ui.misc.about = function() {
if(!frappe.ui.misc.about_dialog) {
var d = new frappe.ui.Dialog({title: __('Frappe Framework')})
var d = new frappe.ui.Dialog({title: __('Frappe Framework')});
$(d.body).html(repl("<div>\
<p>"+__("Open Source Applications for the Web")+"</p> \

View file

@ -1,19 +1,38 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.provide('frappe.search');
frappe.search = {
frappe.search.AwesomeBar = Class.extend({
setup: function(element) {
var $input = $(element);
var input = $input.get(0);
var me = this;
this.search = new frappe.search.UnifiedSearch();
this.global = new frappe.search.GlobalSearch();
this.nav = new frappe.search.NavSearch();
this.help = new frappe.search.HelpSearch();
var awesomplete = new Awesomplete(input, {
minChars: 0,
maxItems: 99,
autoFirst: true,
list: [],
filter: function (text, term) { return true; },
filter: function (text, term) {
return true;
},
data: function (item, input) {
var label = item.label + "%%%" + item.value + "%%%" +
(item.description || "") + "%%%" + (item.index || "");
return {
label: label,
value: item.value
};
},
item: function(item, term) {
var d = item;
var parts = item.split("%%%"),
d = { label: parts[0], value: parts[1], description: parts[2] };
var html = "<span>" + __(d.label || d.value) + "</span>";
if(d.description && d.value!==d.description) {
html += '<br><span class="text-muted">' + __(d.description) + '</span>';
@ -23,57 +42,64 @@ frappe.search = {
.html('<a style="font-weight:normal"><p>' + html + '</p></a>')
.get(0);
},
sort: function(a, b) { return 0; }
sort: function(a, b) {
var a_index = a.split("%%%")[3];
var b_index = b.split("%%%")[3];
return (a_index - b_index);
}
});
$input.on("input", function(e) {
var value = e.target.value;
var txt = strip(value);
frappe.search.options = [];
if(txt) {
var lower = strip(txt.toLowerCase());
$.each(frappe.search.verbs, function(i, action) {
action(lower);
});
}
var $this = $(this);
clearTimeout($this.data('timeout'));
// sort options
frappe.search.options.sort(function(a, b) {
return (a.match || "").length - (b.match || "").length; });
frappe.search.add_recent(txt || "");
frappe.search.add_help();
// de-duplicate
var out = [], routes = [];
frappe.search.options.forEach(function(option) {
if(option.route) {
var str_route = (typeof option.route==='string') ?
option.route : option.route.join('/');
if(routes.indexOf(str_route)===-1) {
out.push(option);
routes.push(str_route);
$this.data('timeout', setTimeout(function(){
var value = e.target.value;
var txt = strip(value);
me.options = [];
if(txt) {
var keywords = strip(txt.toLowerCase());
me.build_options(keywords);
if(me.options.length < 2) {
me.global.get_awesome_bar_options(keywords, me);
}
} else {
out.push(option);
}
});
awesomplete.list = out;
me.add_recent(txt || "");
me.add_help();
// de-duplicate
var out = [], routes = [];
me.options.forEach(function(option) {
if(option.route) {
var str_route = (typeof option.route==='string') ?
option.route : option.route.join('/');
if(routes.indexOf(str_route)===-1) {
out.push(option);
routes.push(str_route);
}
} else {
out.push(option);
}
});
awesomplete.list = out;
}, 200));
});
var open_recent = function() {
if (!frappe.search.autocomplete_open) {
if (!this.autocomplete_open) {
$(this).trigger("input");
}
}
$input.on("focus", open_recent);
$input.on("awesomplete-open", function(e) {
frappe.search.autocomplete_open = e.target;
me.autocomplete_open = e.target;
});
$input.on("awesomplete-close", function(e) {
frappe.search.autocomplete_open = false;
me.autocomplete_open = false;
});
$input.on("awesomplete-select", function(e) {
@ -101,14 +127,15 @@ frappe.search = {
$input.on("awesomplete-selectcomplete", function(e) {
$input.val("");
});
frappe.search.make_page_title_map();
frappe.search.setup_recent();
this.setup_recent();
this.search.setup();
},
add_help: function() {
frappe.search.options.push({
this.options.push({
label: __("Help on Search"),
value: "Help on Search",
index: 20,
onclick: function() {
var txt = '<table class="table table-bordered">\
<tr><td style="width: 50%">'+__("Make a new record")+'</td><td>'+
@ -126,9 +153,11 @@ frappe.search = {
}
});
},
add_recent: function(txt) {
var me = this;
values = [];
$.each(frappe.search.recent, function(i, doctype) {
$.each(me.recent, function(i, doctype) {
values.push([doctype[1], ['Form', doctype[0], doctype[1]]]);
});
@ -148,7 +177,7 @@ frappe.search = {
}
});
frappe.search.find(values, txt, function(match) {
this.find(values, txt, function(match) {
out = {
route: match[1]
}
@ -165,20 +194,13 @@ frappe.search = {
out.label = match[0].bold();
out.value = match[0];
}
out.index = 10
return out;
}, true);
},
make_page_title_map: function() {
frappe.search.pages = {};
$.each(frappe.boot.page_info, function(name, p) {
frappe.search.pages[p.title] = p;
p.name = name;
});
},
setup_recent: function() {
frappe.search.recent = JSON.parse(frappe.boot.user.recent || "[]") || [];
},
find: function(list, txt, process, prepend) {
var me = this;
$.each(list, function(i, item) {
if($.isArray(item)) {
_item = item[0];
@ -197,19 +219,60 @@ frappe.search = {
option.forEach(function(o) { o.match = item; });
if(prepend) {
frappe.search.options = option.concat(frappe.search.options);
me.options = option.concat(me.options);
} else {
frappe.search.options = frappe.search.options.concat(option);
me.options = me.options.concat(option);
}
}
}
});
}
}
},
frappe.search.verbs = [
// search in list if current
function(txt) {
setup_recent: function() {
this.recent = JSON.parse(frappe.boot.user.recent || "[]") || [];
},
is_present: function(txt, item) {
($.isArray(item)) ? _item = item[0] : _item = item;
_item = __(_item || '').toLowerCase().replace(/-/g, " ");
if(txt===_item || _item.indexOf(txt) !== -1) {
return item;
}
},
set_global_results: function(global_results){
this.options = this.options.concat(global_results);
},
build_options: function(txt) {
this.options =
this.make_global_search(txt).concat(
this.make_search_in_current(txt),
this.make_calculator(txt),
this.make_new_doc(txt),
this.make_search_in_list(txt),
this.get_doctypes(txt),
this.get_reports(txt),
this.get_pages(txt),
this.get_modules(txt)
);
},
make_global_search: function(txt) {
var me = this;
return [{
label: __("Search for '" + txt.bold() + "'"),
value: __("Search for '" + txt + "'"),
match: txt,
index: 5,
onclick: function() {
me.search.search_dialog.show();
me.search.setup_search(txt, [me.global, me.nav, me.help]);
}
}];
},
make_search_in_current: function(txt) {
var route = frappe.get_route();
if(route[0]==="List" && txt.indexOf(" in") === -1) {
// search in title field
@ -217,183 +280,224 @@ frappe.search.verbs = [
var search_field = meta.title_field || "name";
var options = {};
options[search_field] = ["like", "%" + txt + "%"];
frappe.search.options.push({
return [{
label: __('Find {0} in {1}', [txt.bold(), route[1].bold()]),
value: __('Find {0} in {1}', [txt, route[1]]),
route_options: options,
index: 10,
onclick: function() {
cur_list.refresh();
},
match: txt
});
}
}];
} else { return []; }
},
// new doc
function(txt) {
var ret = false;
if(txt.split(" ")[0]==="new") {
frappe.search.find(frappe.boot.user.can_create, txt.substr(4), function(match) {
return {
label: __("New {0}", [match.bold()]),
value: __("New {0}", [match]),
onclick: function() { frappe.new_doc(match, true); }
}
});
}
},
// doctype list
function(txt) {
if (txt.toLowerCase().indexOf(" list")) {
// remove list keyword
txt = txt.replace(/ list/ig, "").trim();
}
frappe.search.find(frappe.boot.user.can_read, txt, function(match) {
if(in_list(frappe.boot.single_types, match)) {
return {
label: __("{0}", [__(match).bold()]),
value: __(match),
route:["Form", match, match]
}
} else if(in_list(frappe.boot.treeviews, match)) {
return {
label: __("{0} Tree", [__(match).bold()]),
value: __(match),
route:["Tree", match]
}
} else {
out = [
{
label: __("{0} List", [__(match).bold()]),
value: __("{0} List", [__(match)]),
route:["List", match]
},
];
if(frappe.model.can_get_report(match)) {
out.push({
label: __("{0} Report", [__(match).bold()]),
value: __("{0} Report", [__(match)]),
route:["Report", match]
});
}
if(frappe.boot.calendars.indexOf(match) !== -1) {
out.push({
label: __("{0} Calendar", [__(match).bold()]),
value: __("{0} Calendar", [__(match)]),
route:["List", match, "Calendar"]
});
out.push({
label: __("{0} Gantt", [__(match).bold()]),
value: __("{0} Gantt", [__(match)]),
route:["List", match, "Gantt"]
});
}
return out;
}
});
},
// reports
function(txt) {
frappe.search.find(keys(frappe.boot.user.all_reports), txt, function(match) {
var report = frappe.boot.user.all_reports[match];
var route = [];
if(report.report_type == "Report Builder")
route = ["Report", report.ref_doctype, match];
else
route = ["query-report", match];
return {
label: __("Report {0}", [__(match).bold()]),
value: __("Report {0}", [__(match)]),
route: route
}
});
},
// pages
function(txt) {
frappe.search.find(keys(frappe.search.pages), txt, function(match) {
return {
label: __("Open {0}", [__(match).bold()]),
value: __("Open {0}", [__(match)]),
route: [frappe.search.pages[match].route || frappe.search.pages[match].name]
}
});
// calendar
var match = 'Calendar';
if(__('calendar').indexOf(txt.toLowerCase()) === 0) {
frappe.search.options.push({
label: __("Open {0}", [__(match).bold()]),
value: __("Open {0}", [__(match)]),
route: ['List', 'Event', 'Calendar'],
match: match
});
}
},
// modules
function(txt) {
frappe.search.find(keys(frappe.modules), txt, function(match) {
var module = frappe.modules[match];
if(module._doctype) return;
ret = {
label: __("Open {0}", [__(match).bold()]),
value: __("Open {0}", [__(match)]),
}
if(module.link) {
ret.route = [module.link];
} else {
ret.route = ["Module", match];
}
return ret;
});
},
// in
function(txt) {
if(in_list(txt.split(" "), "in")) {
parts = txt.split(" in ");
frappe.search.find(frappe.boot.user.can_read, parts[1], function(match) {
return {
label: __('Find {0} in {1}', [__(parts[0]).bold(), __(match).bold()]),
value: __('Find {0} in {1}', [__(parts[0]), __(match)]),
route_options: {"name": ["like", "%" + parts[0] + "%"]},
route: ["List", match]
}
});
}
},
// calculator
function(txt) {
make_calculator: function(txt) {
var first = txt.substr(0,1);
if(first==parseInt(first) || first==="(" || first==="=") {
if(first==="=") {
txt = txt.substr(1);
}
try {
var val = eval(txt);
var formatted_value = __('{0} = {1}', [txt, (val + '').bold()]);
frappe.search.options.push({
return [{
label: formatted_value,
value: __('{0} = {1}', [txt, val]),
match: val,
onclick: function(match) {
index: 10,
onclick: function() {
msgprint(formatted_value, "Result");
}
});
}];
} catch(e) {
// pass
}
} else { return []; }
},
make_new_doc: function(txt) {
var me = this;
var out = [];
if(txt.split(" ")[0]==="new") {
frappe.boot.user.can_create.forEach(function (item) {
var target = me.is_present(txt.substr(4), item);
if(target) {
out.push({
label: __("New {0}", [target.bold()]),
value: __("New {0}", [target]),
index: 10,
match: target,
onclick: function() { frappe.new_doc(target, true); }
});
}
});
}
return out;
},
make_search_in_list: function(txt) {
var me = this;
var out = [];
if(in_list(txt.split(" "), "in")) {
parts = txt.split(" in ");
frappe.boot.user.can_read.forEach(function (item) {
target = me.is_present(parts[1], item);
if(target) {
out.push({
label: __('Find {0} in {1}', [__(parts[0]).bold(), __(target).bold()]),
value: __('Find {0} in {1}', [__(parts[0]), __(target)]),
route_options: {"name": ["like", "%" + parts[0] + "%"]},
index: 10,
route: ["List", target]
});
}
});
}
return out;
},
get_doctypes: function(txt) {
var me = this;
var out = [];
var target, index;
var option = function(type, route) {
return {
label: __("{0} " + type, [__(target).bold()]),
value: __(target),
route: route,
index: index,
match: target
}
};
}
];
frappe.boot.user.can_read.forEach(function (item) {
target = me.is_present(txt, item);
if(target) {
var match_ratio = txt.length / item.length;
index = (match_ratio > 0.7) ? 10 : 12;
// include 'making new' option
if(in_list(frappe.boot.user.can_create, target)) {
out.push({
label: __("New {0}", [target.bold()]),
value: __("New {0}", [target]),
match: target,
index: 12,
onclick: function() { frappe.new_doc(target, true); }
});
}
if(in_list(frappe.boot.single_types, target)) {
out.push(option("", ["Form", target, target]));
} else if(in_list(frappe.boot.treeviews, target)) {
out.push(option("Tree", ["Tree", target]));
} else {
out.push(option("List", ["List", target]));
if(frappe.model.can_get_report(target)) {
out.push(option("Report", ["Report", target]));
}
if(frappe.boot.calendars.indexOf(target) !== -1) {
out.push(option("Calendar", ["List", target, "Calendar"]));
out.push(option("Gantt", ["List", target, "Gantt"]));
}
}
}
});
return out;
},
get_reports: function(txt) {
var me = this;
var out = [];
Object.keys(frappe.boot.user.all_reports).forEach(function(item) {
var target = me.is_present(txt, item);
if(target) {
var report = frappe.boot.user.all_reports[target];
var match_ratio = txt.length / item.length;
var index = (match_ratio > 0.7) ? 10 : 13;
var route = [];
if(report.report_type == "Report Builder")
route = ["Report", report.ref_doctype, target];
else
route = ["query-report", target];
out.push({
label: __("Report {0}", [__(target).bold()]),
value: __("Report {0}" , [__(target)]),
match: txt,
index: index,
route: route
});
}
});
return out;
},
get_pages: function(txt) {
var me = this;
var out = [];
this.pages = {};
$.each(frappe.boot.page_info, function(name, p) {
me.pages[p.title] = p;
p.name = name;
});
Object.keys(this.pages).forEach(function(item) {
var target = me.is_present(txt, item);
if(target) {
var match_ratio = txt.length / item.length;
var index = (match_ratio > 0.7) ? 10 : 14;
var page = me.pages[target];
out.push({
label: __("Open {0}", [__(target).bold()]),
value: __("Open {0}", [__(target)]),
match: txt,
index: index,
route: [page.route || page.name]
});
}
});
// calendar
var target = 'Calendar';
if(__('calendar').indexOf(txt.toLowerCase()) === 0) {
out.push({
label: __("Open {0}", [__(target).bold()]),
value: __("Open {0}", [__(target)]),
route: [target, 'Event'],
index: 14,
match: target
});
}
return out;
},
get_modules: function(txt) {
var me = this;
var out = [];
Object.keys(frappe.modules).forEach(function(item) {
var target = me.is_present(txt, item);
if(target) {
var match_ratio = txt.length / item.length;
var index = (match_ratio > 0.7) ? 10 : 15;
var module = frappe.modules[target];
if(module._doctype) return;
ret = {
label: __("Open {0}", [__(target).bold()]),
value: __("Open {0}", [__(target)]),
match: txt,
index: index
}
if(module.link) {
ret.route = [module.link];
} else {
ret.route = ["Module", target];
}
out.push(ret);
}
});
return out;
},
});

View file

@ -0,0 +1,11 @@
<div class="row">
<div class="col-md-2 col-sm-2 hidden-xs layout-side-section search-sidebar">
</div>
<div class="col-md-10 col-sm-10 layout-main-section results-area">
</div>
</div>
</div>

View file

@ -0,0 +1,644 @@
frappe.provide('frappe.search');
frappe.search.UnifiedSearch = Class.extend({
setup: function() {
var d = new frappe.ui.Dialog();
$(frappe.render_template("search")).appendTo($(d.body));
$(d.header).html($(frappe.render_template("search_header")));
this.search_dialog = d;
this.search_modal = $(d.$wrapper);
this.search_modal.addClass('search-dialog');
this.input = this.search_modal.find(".search-input");
this.sidebar = this.search_modal.find(".search-sidebar");
this.results_area = this.search_modal.find(".results-area");
},
setup_search: function(keywords, search_objects) {
var me = this;
this.search_objects = search_objects;
this.search_types = search_objects.map(function(s) {
return s.search_type;
});
this.current_type = '';
this.reset();
this.input.val(keywords);
this.input.on("input", function() {
var $this = $(this);
clearTimeout($this.data('timeout'));
$this.data('timeout', setTimeout(function(){
var keywords = me.input.val();
me.reset();
if(keywords.length > 1) {
me.build_results(keywords);
} else {
me.current_type = '';
}
}, 250));
});
this.build_results(keywords);
setTimeout(function() { me.input.select(); }, 500);
},
reset: function() {
this.sidebar.empty();
this.results_area.empty();
},
build_results: function(keywords) {
var me = this;
this.summary = $('<div class="module-body summary"></div>');
this.full_lists = {};
this.current = 0;
this.search_objects[me.current].build_results_object(me, keywords);
},
render_results: function(results_obj, keywords){
var me = this;
if(this.current === 0) { this.reset() }
this.sidebar.append(results_obj.sidelist);
this.results_area.find('.results-status').remove();
results_obj.sections.forEach(function(section) {
me.summary.append(section);
});
this.full_lists = Object.assign(this.full_lists, results_obj.lists);
this.render_next_search(keywords);
},
bind_events: function() {
var me = this;
this.sidebar.find('.list-link').on('click', function() {
me.set_sidebar_link_action($(this));
});
this.results_area.find('.section-more').on('click', function() {
var type = $(this).attr('data-category');
me.sidebar.find('*[data-category="'+ type +'"]').trigger('click');
});
},
set_sidebar_link_action: function(link) {
this.sidebar.find(".list-link a").removeClass("disabled");
link.find('a').addClass("disabled");
var type = link.attr('data-category');
this.results_area.empty().html(this.full_lists[type]);
this.current_type = type;
this.set_back_link();
this.set_list_more_link(type);
},
set_back_link: function() {
var me = this;
var back_link = this.results_area.find('.all-results-link');
back_link.on('click', function() {
me.show_summary();
});
},
show_summary: function() {
this.current_type = '';
this.results_area.empty().html(this.summary);
this.bind_events();
},
set_list_more_link: function(type) {
var me = this;
var more_link = this.results_area.find('.list-more');
more_link.on('click', function() {
var more_search_type = $(this).attr('data-search');
var s_obj = me.search_objects[me.search_types.indexOf(more_search_type)];
s_obj.get_more_results(type);
});
},
add_more_results: function(more_data) {
var me = this;
var more_results = more_data[0];
var more = more_data[1];
this.results_area.find('.list-more').before(more_results);
if(!more) {
this.results_area.find('.list-more').hide();
var no_of_results = this.results_area.find('.result').length;
var no_of_results_cue = $('<p class="results-status text-muted small">'+
no_of_results +' results found</p>');
this.results_area.find(".result:last").append(no_of_results_cue);
}
this.results_area.find('.more-results.last').slideDown(200, function() {
var height = me.results_area.find('.module-body').height() - 750;
if(me.results_area.find('.list-more').is(":visible")) {
me.results_area.animate({
scrollTop: height
}, 300);
}
$(this).removeClass('last');
});
},
render_next_search: function(keywords) {
this.current += 1;
if(this.current < this.search_objects.length){
// More searches to go
this.search_objects[this.current].build_results_object(this, keywords);
} else {
// If there's only one link in sidebar, there's no summary (show its full list)
if(this.sidebar.find('.list-link').length === 1) {
this.bind_events();
this.sidebar.find('.list-link').trigger('click');
this.results_area.find('.all-results-link').hide();
} else if (this.sidebar.find('.list-link').length === 0) {
this.results_area.html('<p class="results-status text-muted" style="text-align: center;">'+
'No results found for: '+ "'"+ keywords +"'" +'</p>');
} else {
var list_types = this.get_all_list_types();
if(list_types.indexOf(this.current_type) >= 0) {
this.bind_events();
this.sidebar.find('*[data-category="'+ this.current_type +'"]').trigger('click');
} else {
this.show_summary();
}
}
}
},
get_all_list_types: function() {
var types = [];
this.sidebar.find('.list-link').each(function() {
types.push($(this).attr('data-category'));
});
return types;
}
});
frappe.search.GlobalSearch = Class.extend({
init: function() {
this.search_type = 'Global Search';
},
setup: function() {
this.types = [];
this.sections = [];
this.lists = {};
this.more_length = 15;
this.start = {};
this.section_length = 3;
this.set_types();
},
set_types: function() {
var me = this;
this.current_type = 0;
frappe.call({
method: "frappe.utils.global_search.get_search_doctypes",
args: { text: me.keywords },
callback: function(r) {
if(r.message) {
r.message.forEach(function(d) {
me.types.push(d.doctype);
me.start[d.doctype] = 0;
});
me.sidelist = me.make_sidelist();
me.get_result_set(me.types[me.current_type]);
} else {
me.render_object.render_next_search(me.keywords);
}
}
});
},
make_sidelist: function() {
var me = this;
var sidelist = $('<ul class="list-unstyled sidebar-menu nav-list"></ul>');
sidelist.append('<li class="h6">'+ me.search_type +'</li>');
this.types.forEach(function(type) {
sidelist.append(me.make_sidelist_item(type));
});
sidelist.append('<li class="divider"></li>');
return sidelist;
},
make_sidelist_item: function(type) {
var sidelist_item = '<li class="list-link" data-search="{0}"' +
'data-category="{1}"><a>{1}</a></li>';
return $(__(sidelist_item, [this.search_type, type]));
},
get_result_set: function(doctype){
var me = this;
var more = true;
frappe.call({
method: "frappe.utils.global_search.search_in_doctype",
args: {
doctype: doctype,
text: me.keywords,
start: me.start[doctype],
limit: me.more_length,
},
callback: function(r) {
if(r.message) {
me.start[doctype] += me.more_length;
if(r.message.length < me.more_length) {
more = false;
}
me.make_type_results(doctype, r.message, more);
}
}
});
},
make_type_results: function(type, results, more) {
var last_type = (type == this.types.slice(-1));
this.sections.push(this.make_section(type, results));
this.lists[type] = this.make_full_list(type, results, more);
if(!last_type) {
this.current_type += 1;
this.get_result_set(this.types[this.current_type]);
} else {
this.render_results();
}
},
build_results_object: function(r, keywords) {
this.render_object = r;
this.keywords = keywords;
this.setup();
},
render_results: function() {
var me = this;
if(this.sections.length > 0) {
this.render_object.render_results({
sidelist: me.sidelist,
sections: me.sections,
lists: me.lists
}, me.keywords);
}
},
make_result_item: function(type, result) {
var link_html = '<div class="result '+ type +'-result">' +
'<a href="{0}" class="module-section-link small">{1}</a>' +
'<p class="small">{2}</p>' +
'</div>';
var formatted_result = this.format_result(result);
return $(__(link_html, formatted_result));
},
format_result: function(result) {
var route = '#Form/' + result.doctype + '/' + result.name;
return [route, this.bold_keywords(result.name),
this.get_finds(result.content, this.keywords)]
},
get_finds: function(searchables, keywords) {
var me = this;
parts = searchables.split("|||");
content = "";
parts.forEach(function(part) {
if(part.toLowerCase().indexOf(keywords) !== -1) {
var colon_index = part.indexOf(':');
part = '<span class="field-name text-muted">' +
part.slice(0, colon_index + 1) + '</span>' +
me.bold_keywords(part.slice(colon_index + 1), keywords);
content += part + ', ';
}
});
return content.slice(0, -2);
},
bold_keywords: function(str, keywords) {
var regEx = new RegExp("("+ keywords +")", "ig");
return str.replace(regEx, '<b>$1</b>');
},
make_section: function(type, results) {
var me = this;
var results_section = $('<div class="row module-section '+type+'-section">'+
'<div class="col-sm-12 module-section-column">' +
'<div class="h4 section-head">'+type+'</div>' +
'<div class="section-body"></div>'+
'</div></div>');
var results_col = results_section.find('.module-section-column');
results.slice(0, this.section_length).forEach(function(result) {
results_col.append(me.make_result_item(type, result));
});
if(results.length > this.section_length) {
results_col.append('<a class="small section-more" data-category="'
+ type + '">More...</a>');
}
return results_section;
},
make_full_list: function(type, results, more) {
var me = this;
var results_list = $(' <div class="module-body"><div class="row module-section '+
type+'-section">'+'<div class="col-sm-12 module-section-column">' +
'<div class="back-link"><a class="all-results-link small"> All Results</a></div>' +
'<div class="h4 section-head">'+type+'</div>' +
'<div class="section-body"></div>'+
'</div></div></div>');
var results_col = results_list.find('.module-section-column');
results.forEach(function(result) {
results_col.append(me.make_result_item(type, result));
});
if(more) {
results_col.append('<a class="small list-more" data-search="'+ this.search_type +'" data-category="'
+ type + '">More...</a>');
}
return results_list;
},
get_more_results: function(doctype) {
var me = this;
var more = true;
frappe.call({
method: "frappe.utils.global_search.search_in_doctype",
args: {
doctype: doctype,
text: me.keywords,
start: me.start[doctype],
limit: me.more_length,
},
callback: function(r) {
if(r.message) {
me.start[doctype] += me.more_length;
me.make_more_list(doctype, r.message, more);
}
}
});
},
make_more_list: function(type, results, more) {
var me = this;
if(results.length < this.more_length) { more = false; }
var more_results = $('<div class="more-results last"></div>');
results.forEach(function(result) {
more_results.append(me.make_result_item(type, result));
});
this.render_object.add_more_results([more_results, more]);
},
get_awesome_bar_options: function(keywords, ref) {
var me = this;
var doctypes = [];
var current = 0;
var results = [];
var get_doctypes = function(){
var me = this;
frappe.call({
method: "frappe.utils.global_search.get_search_doctypes",
args: { text: keywords },
callback: function(r) {
if(r.message) {
r.message.forEach(function(d) {
doctypes.push(d.doctype);
});
get_results();
}
}
});
};
var get_results = function() {
frappe.call({
method: "frappe.utils.global_search.search_in_doctype",
args: {
doctype: doctypes[current],
text: keywords,
start: 0,
limit: 4,
},
callback: function(r) {
if(r.message) {
r.message.forEach(function(d) {
results.push(make_option(d));
});
current += 1;
if(current < doctypes.length) {
get_results();
} else {
ref.set_global_results(results);
}
}
}
});
};
var make_option = function(data) {
console.log("GS search", me.get_finds(data.content, keywords).slice(0,86) + '...');
return {
label: __("{0}: {1}", [__(data.doctype).bold(), data.name]),
value: __("{0}: {1}", [__(data.doctype), data.name]),
route: ["Form", data.doctype, data.name],
match: data.doctype,
index: 5,
description: me.get_finds(data.content, keywords).slice(0,86) + '...'
}
};
get_doctypes();
}
});
frappe.search.NavSearch = frappe.search.GlobalSearch.extend({
init: function() {
this.search_type = 'Navigation';
},
set_types: function() {
var me = this;
this.section_length = 4;
this.awesome_bar = new frappe.search.AwesomeBar();
this.nav_results = {
"Search in ...": me.awesome_bar.make_search_in_list(me.keywords),
"Lists": me.awesome_bar.get_doctypes(me.keywords),
"Reports": me.awesome_bar.get_reports(me.keywords),
"Pages": me.awesome_bar.get_pages(me.keywords),
"Modules": me.awesome_bar.get_modules(me.keywords)
}
var types = ["Search in ...", "Lists", "Reports", "Pages", "Modules"];
types.forEach(function(type) {
if(me.nav_results[type].length > 0) {
me.types.push(type);
me.start[type] = 0;
}
});
if(this.types.length > 0) {
this.sidelist = this.make_sidelist();
this.get_results();
} else {
this.render_object.render_next_search(me.keywords);
}
},
get_results: function() {
var me = this;
this.types.forEach(function(type) {
me.get_result_set(type);
});
},
get_result_set: function(type) {
var last_type = (type == this.types.slice(-1));
var results = this.nav_results[type].slice(this.start[type], this.more_length);
this.start[type] += this.more_length;
var more = true;
if(results.slice(-1)[0] === this.nav_results[type].slice(-1)[0]) {
more = false;
}
this.make_type_results(type, results, more);
if(last_type) {
this.render_results();
}
},
make_type_results: function(type, results, more) {
this.sections.push(this.make_section(type, results));
this.lists[type] = this.make_full_list(type, results, more);
},
get_more_results: function(type) {
var results = this.nav_results[type].slice(this.start[type],
this.start[type]+this.more_length);
this.start[type] += this.more_length;
var more = true;
if(results.slice(-1)[0] === this.nav_results[type].slice(-1)[0]) {
more = false;
}
this.make_more_list(type, results, more)
},
make_result_item: function(type, result) {
var link_html = '<div class="result '+ type +'-result single-link">' +
'<a href="{0}" class="module-section-link small">{1}</a>' +
'<p class="small"></p></div>';
if(!result.onclick) {
var link = $(__(link_html, ['#', result.label]));
link.on('click', function() {
if(result.route_options) {
frappe.route_options = result.route_options;
}
frappe.set_route(result.route);
return false;
});
return link;
} else {
var link = $(__(link_html, ['#', result.label]));
link.on('click', function() {
frappe.new_doc(result.match, true);
// result.onclick(result.match, true);
});
return link;
}
},
make_dual_sections: function() {
this.dual_sections = [];
var section_html = '<div class="row module-section dual-section"></div>';
for(var i = 0; i < this.sections.length; i++) {
var results_section = $(section_html);
for(var j = 0; j < 2 && i < this.sections.length; j++, i++){
results_section.append(this.sections[i]);
}
i--;
this.dual_sections.push(results_section);
}
},
render_results: function() {
var me = this;
this.make_dual_sections();
if(this.dual_sections.length > 0) {
this.render_object.render_results({
sidelist: me.sidelist,
sections: me.dual_sections,
lists: me.lists
}, me.keywords);
}
},
make_section: function(type, results) {
var me = this;
var results_column = $('<div class="col-sm-6 module-section-column">' +
'<div class="h4 section-head">'+type+'</div>' +
'<div class="section-body"></div>'+
'</div>');
results.slice(0, this.section_length).forEach(function(result) {
results_column.append(me.make_result_item(type, result));
});
if(results.length > this.section_length) {
results_column.append('<a class="small section-more" data-category="'
+ type + '">More...</a>');
}
return results_column;
}
});
frappe.search.HelpSearch = frappe.search.GlobalSearch.extend({
init: function() {
this.search_type = 'Help';
},
set_types: function() {
this.types = [this.search_type];
this.sidelist = this.make_sidelist();
this.get_result_set(this.types[0]);
},
make_sidelist: function() {
var sidelist = $('<ul class="list-unstyled sidebar-menu nav-list"></ul>');
var sidelist_item = '<li class="h6 list-link" data-search="'+ this.search_type + '"' +
'data-category="'+ this.search_type + '"><a style="font-size: 11px;">'+
this.search_type +'</a></li>';
sidelist.append(sidelist_item);
sidelist.append('<li class="divider"></li>');
return sidelist;
},
get_result_set: function(type) {
var me = this;
frappe.call({
method: "frappe.utils.help.get_help",
args: {
text: me.keywords
},
callback: function(r) {
if(r.message) {
// Help search doesn't have a more button for full list
me.make_type_results(type, r.message, false);
me.render_results();
} else {
me.render_object.render_next_search(me.keywords);
}
}
});
},
make_type_results: function(type, results, more) {
this.sections.push(this.make_section(type, results));
this.lists[type] = this.make_full_list(type, results, more);
},
make_result_item: function(type, result) {
var me = this;
var link_html = '<div class="result '+ type +'-result">' +
'<a href="#" data-path="{0}" class="module-section-link small">{1}</a>' +
'<p class="small">{2}</p>' +
'</div>';
var link = $(__(link_html, [result[2], result[0], result[1]]));
link.find('a').on('click', frappe.help.show_results);
return link;
},
});

View file

@ -0,0 +1,7 @@
<div class="input-group has-feedback search-header" style="margin:5px -5px;">
<span class="input-group-addon">
<i class="glyphicon glyphicon-search"></i>
</span>
<input type="text" class="form-control search-input" placeholder="Search for...">
<span class="input-group-addon"><a type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</a></span>
</div>

View file

@ -2,6 +2,7 @@
// MIT License. See license.txt
frappe.provide("frappe.ui.toolbar");
frappe.provide('frappe.search');
frappe.ui.toolbar.Toolbar = Class.extend({
init: function() {
@ -11,6 +12,14 @@ frappe.ui.toolbar.Toolbar = Class.extend({
this.setup_sidebar();
this.awesome_bar = new frappe.search.AwesomeBar();
this.awesome_bar.setup("#navbar-search");
this.awesome_bar.setup("#modal-search");
this.search = new frappe.search.UnifiedSearch();
this.help = new frappe.search.HelpSearch();
this.search.setup();
$(document).on("notification-update", function() {
frappe.ui.notifications.update_notifications();
});
@ -33,9 +42,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({
search_modal.find('#modal-search').focus();
}, 300);
});
frappe.search.setup("#navbar-search");
frappe.search.setup("#modal-search");
},
setup_sidebar: function () {
@ -142,54 +148,28 @@ frappe.ui.toolbar.Toolbar = Class.extend({
$('.dropdown-help .dropdown-menu').on('click', 'a', show_results);
});
var $help_modal = frappe.get_modal("Help", "");
$help_modal.addClass('help-modal');
var $result_modal = frappe.get_modal("", "");
$result_modal.addClass("help-modal");
$(document).on("click", ".help-modal a", show_results);
var me = this;
function show_help_results(keywords) {
frappe.call({
method: "frappe.utils.help.get_help",
args: {
text: keywords
},
callback: function (r) {
var results = r.message || [];
var result_html = "<h4 style='margin-bottom: 25px'>Showing results for '" + keywords + "' </h4>";
for (var i = 0, l = results.length; i < l; i++) {
var title = results[i][0];
var intro = results[i][1];
var fpath = results[i][2];
result_html += "<div class='search-result'>" +
"<a href='#' class='h4' data-path='"+fpath+"'>" + title + "</a>" +
"<p>" + intro + "</p>" +
"</div>";
}
if(results.length === 0) {
result_html += "<p class='padding'>No results found</p>";
}
$help_modal.find('.modal-body').html(result_html);
$help_modal.modal('show');
}
});
me.search.search_dialog.show();
me.search.setup_search(keywords, [me.help]);
}
frappe.provide('frappe.help');
frappe.help.show_results = show_results;
function show_results(e) {
//edit links
href = e.target.href;
if(href.indexOf('blob') > 0) {
window.open(href, '_blank');
}
var converter = new Showdown.converter();
var path = $(this).attr("data-path");
var path = $(e.target).attr("data-path");
if(path) {
e.preventDefault();
frappe.call({
@ -211,6 +191,7 @@ frappe.ui.toolbar.Toolbar = Class.extend({
}
});
$.extend(frappe.ui.toolbar, {
add_dropdown_button: function(parent, label, click, icon) {
var menu = frappe.ui.toolbar.get_menu(parent);

View file

@ -527,10 +527,107 @@ textarea.form-control {
.dropdown-menu {
z-index: 100;
max-height: 300px;
overflow: scroll;
overflow: auto;
}
}
.search-dialog {
.modal-dialog {
width: 768px;
height: 500px;
}
.modal-body {
padding: 0px 15px;
}
input.form-control, .input-group-addon {
border: none;
border-left-style:none;
}
input.form-control:focus {
outline: none;
box-shadow: none;
}
.input-group-addon{
background-color: #FFF;
}
.layout-side-section,
.layout-main-section {
height: 500px;
padding: 0px;
overflow-y: auto;
}
.layout-side-section {
padding-left: 15px;
}
// .results-area a {
// color: #5E64FF;
// }
.module-section {
.back-link {
margin-bottom: 20px;
margin-top: -10px;
}
.all-results-link:before {
font-family: 'Octicons';
content: '\f0a4';
}
}
.dual-section .result a {
color: black;
}
.dual-section .result a b{
color: #4e6161;
}
.result-status {
margin-top: 30px;
text-align: center;
}
.more-results {
display: none;
}
@media (max-width: @screen-xs) {
.modal-dialog {
width: auto;
}
.modal-content {
height: auto !important;
}
}
@media (max-width: @screen-sm) {
.module-body {
margin: 0px;
border-top: none;
}
.layout-side-section .sidebar-menu {
margin: 30px 0px;
}
}
}
.result {
p {
margin-top: 0.2em;
}
}
.search-result {
margin-bottom: 24px;
}
.note-editor.note-frame .note-editing-area .note-editable {
color: @text-color;
}
@ -582,4 +679,3 @@ textarea.form-control {
.c3-tooltip td.value {
text-align: right; }

View file

@ -264,17 +264,12 @@ def make_test_records_for_doctype(doctype, verbose=0, force=False):
print_mandatory_fields(doctype)
def make_test_objects(doctype, test_records, verbose=None):
def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
'''Make test objects from given list of `test_records` or from `test_records.json`'''
records = []
# if not frappe.get_meta(doctype).issingle:
# existing = frappe.get_all(doctype, filters={"name":("like", "_T-" + doctype + "-%")})
# if existing:
# return [d.name for d in existing]
#
# existing = frappe.get_all(doctype, filters={"name":("like", "_Test " + doctype + "%")})
# if existing:
# return [d.name for d in existing]
if test_records is None:
test_records = frappe.get_test_records(doctype)
for doc in test_records:
if not doc.get("doctype"):
@ -285,7 +280,7 @@ def make_test_objects(doctype, test_records, verbose=None):
if doc.get('name'):
d.name = doc.get('name')
if frappe.local.test_objects.get(d.doctype):
if frappe.local.test_objects.get(d.doctype) and not reset:
# do not create test records, if already exists
return []

View file

@ -0,0 +1,117 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import unittest
import frappe
from frappe.utils import global_search
from frappe.test_runner import make_test_objects
import frappe.utils
class TestGlobalSearch(unittest.TestCase):
def setUp(self):
global_search.setup_global_search_table()
self.assertTrue('__global_search' in frappe.db.get_tables())
doctype = "Event"
global_search.reset()
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
make_property_setter(doctype, "subject", "in_global_search", 1, "Int")
make_property_setter(doctype, "event_type", "in_global_search", 1, "Int")
make_property_setter(doctype, "roles", "in_global_search", 1, "Int")
make_property_setter(doctype, "repeat_on", "in_global_search", 0, "Int")
def tearDown(self):
frappe.db.sql('delete from `tabProperty Setter` where doc_type="Event"')
frappe.clear_cache(doctype='Event')
frappe.db.sql('delete from `tabEvent`')
frappe.db.sql('delete from `tabEvent Role`')
frappe.db.sql('delete from __global_search')
make_test_objects('Event')
frappe.db.commit()
def insert_test_events(self):
frappe.db.sql('delete from tabEvent')
phrases = ['"The Sixth Extinction II: Amor Fati" is the second episode of the seventh season of the American science fiction.',
'After Mulder awakens from his coma, he realizes his duty to prevent alien colonization. ',
'Carter explored themes of extraterrestrial involvement in ancient mass extinctions in this episode, the third in a trilogy.']
for text in phrases:
frappe.get_doc(dict(
doctype='Event',
subject=text,
repeat_on='Every Month',
starts_on=frappe.utils.now_datetime())).insert()
frappe.db.commit()
def test_search(self):
self.insert_test_events()
results = global_search.search('awakens')
self.assertTrue('After Mulder awakens from his coma, he realizes his duty to prevent alien colonization. ' in results[0].content)
results = global_search.search('extraterrestrial')
self.assertTrue('Carter explored themes of extraterrestrial involvement in ancient mass extinctions in this episode, the third in a trilogy.' in results[0].content)
def test_update_doc(self):
self.insert_test_events()
test_subject = 'testing global search'
event = frappe.get_doc('Event', frappe.get_all('Event')[0].name)
event.subject = test_subject
event.save()
frappe.db.commit()
results = global_search.search('testing global search')
self.assertTrue('testing global search' in results[0].content)
def test_update_fields(self):
self.insert_test_events()
results = global_search.search('Every Month')
self.assertEquals(len(results), 0)
doctype = "Event"
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
make_property_setter(doctype, "repeat_on", "in_global_search", 1, "Int")
global_search.rebuild_for_doctype(doctype)
results = global_search.search('Every Month')
self.assertEquals(len(results), 3)
def test_delete_doc(self):
self.insert_test_events()
event_name = frappe.get_all('Event')[0].name
event = frappe.get_doc('Event', event_name)
test_subject = event.subject
results = global_search.search(test_subject)
self.assertEquals(len(results), 1)
frappe.delete_doc('Event', event_name)
results = global_search.search(test_subject)
self.assertEquals(len(results), 0)
def test_insert_child_table(self):
frappe.db.sql('delete from tabEvent')
frappe.db.sql('delete from `tabEvent Role`')
phrases = ['Hydrus is a small constellation in the deep southern sky. ',
'It was first depicted on a celestial atlas by Johann Bayer in his 1603 Uranometria. ',
'The French explorer and astronomer Nicolas Louis de Lacaille charted the brighter stars and gave their Bayer designations in 1756. ',
'Its name means "male water snake", as opposed to Hydra, a much larger constellation that represents a female water snake. ',
'It remains below the horizon for most Northern Hemisphere observers.',
'The brightest star is the 2.8-magnitude Beta Hydri, also the closest reasonably bright star to the south celestial pole. ',
'Pulsating between magnitude 3.26 and 3.33, Gamma Hydri is a variable red giant some 60 times the diameter of our Sun. ',
'Lying near it is VW Hydri, one of the brightest dwarf novae in the heavens. ',
'Four star systems have been found to have exoplanets to date, most notably HD 10180, which could bear up to nine planetary companions.']
for text in phrases:
doc = frappe.get_doc({
'doctype':'Event',
'subject': text,
'starts_on': frappe.utils.now_datetime()
})
doc.append('roles', dict(role='Administrator'))
doc.insert()
frappe.db.commit()
results = global_search.search('Administrator')
self.assertEquals(len(results), 9)

View file

@ -25,6 +25,7 @@ def enqueue(method, queue='default', timeout=300, event=None,
:param event: this is passed to enable clearing of jobs from queues
:param async: if async=False, the method is executed immediately, else via a worker
:param job_name: can be used to name an enqueue call, which can be used to prevent duplicate calls
:param now: if now=True, the method is executed via frappe.call
:param kwargs: keyword arguments to be passed to the method
'''
if now:

View file

@ -0,0 +1,151 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def setup_global_search_table():
'''Creates __global_seach table'''
if not '__global_search' in frappe.db.get_tables():
frappe.db.sql('''create table __global_search(
doctype varchar(100),
name varchar(140),
content text,
fulltext(content),
unique (doctype, name))
COLLATE=utf8mb4_unicode_ci
ENGINE=MyISAM
CHARACTER SET=utf8mb4''')
def reset():
'''Deletes all data in __global_search'''
frappe.db.sql('delete from __global_search')
def update_global_search(doc):
'''Add values marked with `in_global_search` to
`frappe.flags.update_global_search` from given doc
:param doc: Document to be added to global search'''
if frappe.flags.update_global_search==None:
frappe.flags.update_global_search = []
content = []
for field in doc.meta.get_global_search_fields():
if doc.get(field.fieldname):
if getattr(field, 'fieldtype', None) == "Table":
# Get children
for d in doc.get(field.fieldname):
if d.parent == doc.name:
for field in d.meta.fields:
if d.get(field.fieldname):
content.append(field.label + ": " + unicode(d.get(field.fieldname)))
else:
content.append(field.label + ": " + unicode(doc.get(field.fieldname)))
if content:
frappe.flags.update_global_search.append(
dict(doctype=doc.doctype, name=doc.name, content='|||'.join(content)))
def sync_global_search():
'''Add values from `frappe.flags.update_global_search` to __global_search.
This is called internally at the end of the request.'''
for value in frappe.flags.update_global_search:
frappe.db.sql('''
insert into __global_search
(doctype, name, content)
values
(%(doctype)s, %(name)s, %(content)s)
on duplicate key update
content = %(content)s''', value)
frappe.flags.update_global_search = []
def rebuild_for_doctype(doctype):
'''Rebuild entries of doctype's documents in __global_search on change of
searchable fields
:param doctype: Doctype '''
frappe.db.sql('''
delete
from __global_search
where
doctype = (%s)''', doctype, as_dict=True)
for d in frappe.get_all(doctype):
update_global_search(frappe.get_doc(doctype, d.name))
sync_global_search()
def delete_for_document(doc):
'''Delete the __global_search entry of a document that has
been deleted
:param doc: Deleted document'''
frappe.db.sql('''
delete
from __global_search
where
name = (%s)''', (doc.name), as_dict=True)
@frappe.whitelist()
def search(text, start=0, limit=20):
'''Search for given text in __global_search
:param text: phrase to be searched
:param start: start results at, default 0
:param limit: number of results to return, default 20
:return: Array of result objects'''
text = "+" + text + "*"
results = frappe.db.sql('''
select
doctype, name, content
from
__global_search
where
match(content) against (%s IN BOOLEAN MODE)
limit {start}, {limit}'''.format(start=start, limit=limit), text, as_dict=True)
return results
@frappe.whitelist()
def get_search_doctypes(text):
'''Search for all t
:param text: phrase to be searched
:return: Array of result objects'''
text = "+" + text + "*"
results = frappe.db.sql('''
select
doctype
from
__global_search
where
match(content) against (%s IN BOOLEAN MODE)
group by
doctype
order by
count(doctype) desc limit 0, 80''', text, as_dict=True)
return results
@frappe.whitelist()
def search_in_doctype(doctype, text, start, limit):
'''Search for given text in given doctype in __global_search
:param doctype: doctype to be searched in
:param text: phrase to be searched
:param start: start results at, default 0
:param limit: number of results to return, default 20
:return: Array of result objects'''
text = "+" + text + "*"
results = frappe.db.sql('''
select
doctype, name, content
from
__global_search
where
doctype = %s AND
match(content) against (%s IN BOOLEAN MODE)
limit {start}, {limit}'''.format(start=start, limit=limit), (doctype, text), as_dict=True)
return results

View file

@ -82,8 +82,8 @@ class HelpDatabase(object):
def search(self, words):
self.connect()
return self.db.sql('''
select title, intro, path from help where title like '%{term}%' union
select title, intro, path from help where match(content) against ('{term}') limit 10'''.format(term=words))
select title, intro, path from help where title like %s union
select title, intro, path from help where match(content) against (%s) limit 10''', ('%'+words+'%', words))
def get_content(self, path):
self.connect()

View file

@ -37,7 +37,7 @@ def after_install():
update_password("Administrator", get_admin_password())
# setup wizard now in frappe
frappe.db.set_default('desktop:home_page', 'setup-wizard');
frappe.db.set_default('desktop:home_page', 'setup-wizard')
frappe.db.commit()

View file

@ -2,7 +2,7 @@
// MIT License. See license.txt
frappe.provide("website");
frappe.provide("frappe.search_path");
frappe.provide("frappe.awesome_bar_path");
cur_frm = null;
$.extend(frappe, {
@ -303,13 +303,13 @@ $.extend(frappe, {
});
},
do_search: function(val) {
var path = (frappe.search_path && frappe.search_path[location.pathname]
var path = (frappe.awesome_bar_path && frappe.awesome_bar_path[location.pathname]
|| window.search_path || location.pathname);
window.location.href = path + "?txt=" + encodeURIComponent(val);
},
set_search_path: function(path) {
frappe.search_path[location.pathname] = path;
frappe.awesome_bar_path[location.pathname] = path;
},
make_navbar_active: function() {
var pathname = window.location.pathname;