feat: Global Tags
This commit is contained in:
parent
4510b6eca7
commit
f6d1ce2194
24 changed files with 481 additions and 255 deletions
|
|
@ -1,11 +1,133 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies and contributors
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.global_tags import update_global_tags
|
||||
from frappe import _
|
||||
|
||||
class Tag(Document):
|
||||
def validate(self):
|
||||
self.tag_name = self.tag_name.title()
|
||||
|
||||
def on_trash(self):
|
||||
if self.count > 0:
|
||||
frappe.throw(_("Cannot delete Tag {0} since it is linked to Documents.").format(frappe.bold(self.name)))
|
||||
|
||||
def check_user_tags(dt):
|
||||
"if the user does not have a tags column, then it creates one"
|
||||
try:
|
||||
frappe.db.sql("select `_user_tags` from `tab%s` limit 1" % dt)
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
DocTags(dt).setup()
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_tag(tag, dt, dn, color=None):
|
||||
"adds a new tag to a record, and creates the Tag master"
|
||||
DocTags(dt).add(dn, tag)
|
||||
|
||||
return tag
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_tag(tag, dt, dn):
|
||||
"removes tag from the record"
|
||||
DocTags(dt).remove(dn, tag)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tagged_docs(doctype, tag):
|
||||
frappe.has_permission(doctype, throw=True)
|
||||
|
||||
return frappe.db.sql("""SELECT name
|
||||
FROM `tab{0}`
|
||||
WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tags(doctype, txt, cat_tags):
|
||||
tags = json.loads(cat_tags)
|
||||
tag = frappe.get_list("Tag", filters=[["name", "like", "%{}%".format(txt)]])
|
||||
tags.extend([t.name for t in tag])
|
||||
|
||||
return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags))))
|
||||
|
||||
class DocTags:
|
||||
"""Tags for a particular doctype"""
|
||||
def __init__(self, dt):
|
||||
self.dt = dt
|
||||
|
||||
def get_tag_fields(self):
|
||||
"""returns tag_fields property"""
|
||||
return frappe.db.get_value('DocType', self.dt, 'tag_fields')
|
||||
|
||||
def get_tags(self, dn):
|
||||
"""returns tag for a particular item"""
|
||||
return (frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '').strip()
|
||||
|
||||
def add(self, dn, tag):
|
||||
"""add a new user tag"""
|
||||
tl = self.get_tags(dn).split(',')
|
||||
if not tag in tl:
|
||||
tl.append(tag)
|
||||
if not frappe.db.exists("Tag", tag):
|
||||
frappe.get_doc({"doctype": "Tag", "name": tag, "count": 1}).insert(ignore_permissions=True)
|
||||
else:
|
||||
update_tag_count(tags=tag)
|
||||
self.update(dn, tl)
|
||||
|
||||
def remove(self, dn, tag):
|
||||
"""remove a user tag"""
|
||||
tl = self.get_tags(dn).split(',')
|
||||
update_tag_count(tags=tag, increment=False)
|
||||
self.update(dn, filter(lambda x:x.lower()!=tag.lower(), tl))
|
||||
|
||||
def remove_all(self, dn):
|
||||
"""remove all user tags (call before delete)"""
|
||||
update_tag_count(tags=tag, increment=False, dt=self.dt, dn=dn)
|
||||
self.update(dn, [])
|
||||
|
||||
def update(self, dn, tl):
|
||||
"""updates the _user_tag column in the table"""
|
||||
|
||||
if not tl:
|
||||
tags = ''
|
||||
else:
|
||||
tl = list(set(filter(lambda x: x, tl)))
|
||||
tags = ',' + ','.join(tl)
|
||||
try:
|
||||
frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \
|
||||
(self.dt,'%s','%s'), (tags , dn))
|
||||
doc= frappe.get_doc(self.dt, dn)
|
||||
update_global_tags(doc, tags)
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
if not tags:
|
||||
# no tags, nothing to do
|
||||
return
|
||||
|
||||
self.setup()
|
||||
self.update(dn, tl)
|
||||
else: raise
|
||||
|
||||
def setup(self):
|
||||
"""adds the _user_tags column if not exists"""
|
||||
from frappe.database.schema import add_column
|
||||
add_column(self.dt, "_user_tags", "Data")
|
||||
|
||||
def update_tag_count(tags, increment=True, dt=None, dn=None):
|
||||
"""
|
||||
Used to Increase or Decrease the count of documents linked with a certain tag
|
||||
"""
|
||||
_user_tags = [tags]
|
||||
if tags == [] and dt and dn:
|
||||
_user_tags = frappe.db.get_value(dt, dn, '_user_tags', ignore=1).split(",")
|
||||
_user_tags = [t.strip() for t in _user_tags if t]
|
||||
|
||||
for tag in _user_tags:
|
||||
tag_count = frappe.db.get_value("Tag", tag, "count")
|
||||
if increment:
|
||||
tag_count+=1
|
||||
else:
|
||||
tag_count-=1
|
||||
|
||||
frappe.db.set_value("Tag", tag, "count", tag_count)
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
// Copyright (c) 2016, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.ui.form.on('Tag', {
|
||||
tag_name:function(frm){
|
||||
for (var i = 0 ;i<frm.doc.tags.length;i++){
|
||||
frm.doc.tags[i].tag_name = toTitle(frm.doc.tags[i].tag_name)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:category_name",
|
||||
"beta": 0,
|
||||
"creation": "2016-05-25 09:49:07.125394",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "category_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Category Name",
|
||||
"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,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "tags",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Tags",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tag",
|
||||
"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,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "tagdocs",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Doctypes",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tag Doc Category",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-12-29 14:40:37.489085",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Tag Category",
|
||||
"name_case": "Title Case",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 0,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
# test_records = frappe.get_test_records('Tag Categories')
|
||||
|
||||
class TestTagCategories(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"creation": "2016-05-25 13:09:20.996154",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "tagdoc",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Doctype to Assign Tags",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-05-30 15:04:45.454688",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Tag Doc Category",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TagDocCategory(Document):
|
||||
pass
|
||||
|
|
@ -205,6 +205,18 @@ class MariaDBDatabase(Database):
|
|||
ENGINE=MyISAM
|
||||
CHARACTER SET=utf8mb4'''.format(self.VARCHAR_LEN))
|
||||
|
||||
def create_global_tags_table(self):
|
||||
if not '__global_tags' in self.get_tables():
|
||||
self.sql('''create table __global_tags(
|
||||
doctype varchar(100),
|
||||
name varchar({0}),
|
||||
title varchar({0}),
|
||||
tags varchar({0}),
|
||||
unique `doctype_name` (doctype, name))
|
||||
COLLATE=utf8mb4_unicode_ci
|
||||
ENGINE=MyISAM
|
||||
CHARACTER SET=utf8mb4'''.format(self.VARCHAR_LEN))
|
||||
|
||||
def create_user_settings_table(self):
|
||||
self.sql_ddl("""create table if not exists __UserSettings (
|
||||
`user` VARCHAR(180) NOT NULL,
|
||||
|
|
|
|||
|
|
@ -194,6 +194,15 @@ class PostgresDatabase(Database):
|
|||
published int not null default 0,
|
||||
unique (doctype, name))'''.format(self.VARCHAR_LEN))
|
||||
|
||||
def create_global_tags_table(self):
|
||||
if not '__global_tags' in self.get_tables():
|
||||
self.sql('''create table __global_tags(
|
||||
doctype varchar(100),
|
||||
name varchar({0}),
|
||||
title varchar({0}),
|
||||
tags varchar({0}),
|
||||
unique (doctype, name))'''.format(self.VARCHAR_LEN))
|
||||
|
||||
def create_user_settings_table(self):
|
||||
self.sql_ddl("""create table if not exists "__UserSettings" (
|
||||
"user" VARCHAR(180) NOT NULL,
|
||||
|
|
|
|||
8
frappe/desk/doctype/tag/tag.js
Normal file
8
frappe/desk/doctype/tag/tag.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Tag', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
56
frappe/desk/doctype/tag/tag.json
Normal file
56
frappe/desk/doctype/tag/tag.json
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"autoname": "Prompt",
|
||||
"creation": "2016-05-25 09:43:44.767581",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"description",
|
||||
"count"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "count",
|
||||
"fieldtype": "Int",
|
||||
"label": "Count",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"modified": "2019-09-24 00:53:13.488147",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Tag",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies and contributors
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TagCategory(Document):
|
||||
class Tag(Document):
|
||||
pass
|
||||
10
frappe/desk/doctype/tag/test_tag.py
Normal file
10
frappe/desk/doctype/tag/test_tag.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestTag(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -261,13 +261,8 @@ def delete_bulk(doctype, items):
|
|||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_sidebar_stats(stats, doctype, filters=[]):
|
||||
cat_tags = frappe.db.sql("""select `tag`.parent as `category`, `tag`.tag_name as `tag`
|
||||
from `tabTag Doc Category` as `docCat`
|
||||
INNER JOIN `tabTag` as `tag` on `tag`.parent = `docCat`.parent
|
||||
where `docCat`.tagdoc=%s
|
||||
ORDER BY `tag`.parent asc, `tag`.idx""", doctype, as_dict=1)
|
||||
|
||||
return {"defined_cat":cat_tags, "stats":get_stats(stats, doctype, filters)}
|
||||
return {"defined_cat": [], "stats": get_stats(stats, doctype, filters)}
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ def install_db(root_login="root", root_password=None, db_name=None, source_sql=N
|
|||
|
||||
frappe.db.create_auth_table()
|
||||
frappe.db.create_global_search_table()
|
||||
frappe.db.create_global_tags_table()
|
||||
frappe.db.create_user_settings_table()
|
||||
|
||||
frappe.flags.in_install_db = False
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from frappe.core.doctype.file.file import remove_all
|
|||
from frappe.utils.password import delete_all_passwords_for
|
||||
from frappe.model.naming import revert_series_if_last
|
||||
from frappe.utils.global_search import delete_for_document
|
||||
from frappe.utils.global_tags import delete_tags_for_document
|
||||
from frappe.exceptions import FileNotFoundError
|
||||
|
||||
|
||||
|
|
@ -116,6 +117,8 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa
|
|||
|
||||
# delete global search entry
|
||||
delete_for_document(doc)
|
||||
# delete tags from __global_tags
|
||||
delete_tags_for_document(doc)
|
||||
|
||||
if doc and not for_reload:
|
||||
add_to_deleted_document(doc)
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@
|
|||
"public/js/frappe/ui/toolbar/awesome_bar.js",
|
||||
"public/js/frappe/ui/toolbar/energy_points_notifications.js",
|
||||
"public/js/frappe/ui/toolbar/search.js",
|
||||
"public/js/frappe/ui/toolbar/global_tags.js",
|
||||
"public/js/frappe/ui/toolbar/search.html",
|
||||
"public/js/frappe/ui/toolbar/search_header.html",
|
||||
"public/js/frappe/ui/toolbar/search_utils.js",
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ frappe.Application = Class.extend({
|
|||
});
|
||||
}, 300000); // check every 5 minutes
|
||||
}
|
||||
|
||||
this.set_global_tags();
|
||||
},
|
||||
|
||||
setup_frappe_vue() {
|
||||
|
|
@ -599,6 +601,10 @@ frappe.Application = Class.extend({
|
|||
frappe.show_alert(message);
|
||||
});
|
||||
},
|
||||
|
||||
set_global_tags() {
|
||||
frappe.global_tags.utils.set_tags();
|
||||
}
|
||||
});
|
||||
|
||||
frappe.get_module = function(m, default_module) {
|
||||
|
|
|
|||
|
|
@ -35,13 +35,14 @@ frappe.ui.TagEditor = Class.extend({
|
|||
onTagAdd: (tag) => {
|
||||
if(me.initialized && !me.refreshing) {
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.tags.add_tag',
|
||||
method: "frappe.desk.doctype.tag.tag.add_tag",
|
||||
args: me.get_args(tag),
|
||||
callback: function(r) {
|
||||
var user_tags = me.user_tags ? me.user_tags.split(",") : [];
|
||||
user_tags.push(tag)
|
||||
me.user_tags = user_tags.join(",");
|
||||
me.on_change && me.on_change(me.user_tags);
|
||||
frappe.global_tags.utils.set_tags();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -49,13 +50,14 @@ frappe.ui.TagEditor = Class.extend({
|
|||
onTagRemove: (tag) => {
|
||||
if(!me.refreshing) {
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.tags.remove_tag',
|
||||
method: "frappe.desk.doctype.tag.tag.remove_tag",
|
||||
args: me.get_args(tag),
|
||||
callback: function(r) {
|
||||
var user_tags = me.user_tags.split(",");
|
||||
user_tags.splice(user_tags.indexOf(tag), 1);
|
||||
me.user_tags = user_tags.join(",");
|
||||
me.on_change && me.on_change(me.user_tags);
|
||||
frappe.global_tags.utils.set_tags();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -82,7 +84,7 @@ frappe.ui.TagEditor = Class.extend({
|
|||
$input.on("input", function(e) {
|
||||
var value = e.target.value;
|
||||
frappe.call({
|
||||
method:"frappe.desk.tags.get_tags",
|
||||
method: "frappe.desk.doctype.tag.tag.get_tags",
|
||||
args:{
|
||||
doctype: me.frm.doctype,
|
||||
txt: value.toLowerCase(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
frappe.provide('frappe.search');
|
||||
frappe.provide('frappe.global_tags');
|
||||
|
||||
frappe.search.AwesomeBar = Class.extend({
|
||||
setup: function(element) {
|
||||
|
|
@ -140,6 +141,8 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
__("document type..., e.g. customer")+'</td></tr>\
|
||||
<tr><td>'+__("Search in a document type")+'</td><td>'+
|
||||
__("text in document type")+'</td></tr>\
|
||||
<tr><td>'+__("Tags")+'</td><td>'+
|
||||
__("tag name..., e.g. #tag")+'</td></tr>\
|
||||
<tr><td>'+__("Open a module or tool")+'</td><td>'+
|
||||
__("module name...")+'</td></tr>\
|
||||
<tr><td>'+__("Calculate")+'</td><td>'+
|
||||
|
|
@ -177,6 +180,9 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
frappe.search.utils.get_recent_pages(txt || ""),
|
||||
frappe.search.utils.get_executables(txt)
|
||||
);
|
||||
if (txt.charAt(0) === "#") {
|
||||
options = frappe.global_tags.utils.get_tags(txt);
|
||||
}
|
||||
var out = this.deduplicate(options);
|
||||
return out.sort(function(a, b) {
|
||||
return b.index - a.index;
|
||||
|
|
@ -215,6 +221,11 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
|
||||
make_global_search: function(txt) {
|
||||
var me = this;
|
||||
|
||||
if (txt.charAt(0) === "#") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.push({
|
||||
label: __("Search for '{0}'", [txt.bold()]),
|
||||
value: __("Search for '{0}'", [txt]),
|
||||
|
|
|
|||
136
frappe/public/js/frappe/ui/toolbar/global_tags.js
Normal file
136
frappe/public/js/frappe/ui/toolbar/global_tags.js
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.provide("frappe.global_tags");
|
||||
frappe.provide("locals.global_tags");
|
||||
|
||||
frappe.global_tags.GlobalTagsDialog = class GlobalTags {
|
||||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
if (!this.dialog) {
|
||||
this.make_dialog();
|
||||
}
|
||||
|
||||
$(this.dialog.body).html(
|
||||
`<div class="text-muted text-center" style="padding: 30px 0px">
|
||||
${__("Loading")}...
|
||||
</div>`);
|
||||
|
||||
this.dialog.show();
|
||||
}
|
||||
|
||||
make_dialog() {
|
||||
let title = __("Tag {0}", ["#".concat(this.tag)]);
|
||||
|
||||
this.dialog = new frappe.ui.Dialog({
|
||||
hide_on_page_refresh: true,
|
||||
minimizable: true,
|
||||
title: title
|
||||
});
|
||||
|
||||
this.dialog.on_page_show = () => {
|
||||
this.get_documents_for_tag()
|
||||
.then(() => this.make_html());
|
||||
};
|
||||
}
|
||||
|
||||
make_html() {
|
||||
const results = this.results;
|
||||
let html = '';
|
||||
|
||||
const linked_doctypes = Object.keys(results);
|
||||
|
||||
if (linked_doctypes.length === 0) {
|
||||
html = __("Not Linked to any record");
|
||||
} else {
|
||||
html = linked_doctypes.map(doctype => {
|
||||
const docs = results[doctype];
|
||||
return `
|
||||
<div class="list-item-table margin-bottom">
|
||||
${this.make_doc_head(doctype)}
|
||||
${docs.map(doc => this.make_doc_row(doc.name, doctype, doc.title)).join('')}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
$(this.dialog.body).html(html);
|
||||
}
|
||||
|
||||
get_documents_for_tag() {
|
||||
return new Promise((resolve) => {
|
||||
frappe.call({
|
||||
method: "frappe.utils.global_tags.get_documents_for_tag",
|
||||
args: {
|
||||
tag: this.tag
|
||||
},
|
||||
callback: (r) => {
|
||||
this.results = r.message;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
make_doc_head(heading) {
|
||||
return `
|
||||
<header class="level list-row list-row-head text-muted small">
|
||||
<div>${__(heading)}</div>
|
||||
</header>
|
||||
`;
|
||||
}
|
||||
|
||||
make_doc_row(docname, doctype, title) {
|
||||
return `<div class="list-row-container">
|
||||
<div class="level list-row small">
|
||||
<div class="level-left bold">
|
||||
<a href="#Form/${doctype}/${docname}">${docname}</a>
|
||||
</div>
|
||||
<div class="level-left">
|
||||
<p>${title}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
};
|
||||
|
||||
frappe.global_tags.utils = {
|
||||
get_tags: function(txt) {
|
||||
txt = txt.slice(1);
|
||||
let out = [];
|
||||
|
||||
for (let i in locals.global_tags) {
|
||||
let tag = locals.global_tags[i];
|
||||
let level = frappe.search.utils.fuzzy_search(txt, tag);
|
||||
if (level) {
|
||||
out.push({
|
||||
type: "Tag",
|
||||
label: __("#{0}", [frappe.search.utils.bolden_match_part(__(tag), txt)]),
|
||||
value: __("#{0}", [__(tag)]),
|
||||
index: 1 + level,
|
||||
match: tag,
|
||||
onclick: function() {
|
||||
new frappe.global_tags.GlobalTagsDialog({"tag": tag});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
set_tags: function() {
|
||||
frappe.call({
|
||||
method: "frappe.utils.global_tags.get_tags_list_for_awesomebar",
|
||||
callback: function(r) {
|
||||
if (r && r.message) {
|
||||
locals.global_tags = $.extend([], r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -242,10 +242,6 @@ def update_global_search(doc):
|
|||
if doc.get(field.fieldname) and field.fieldtype not in frappe.model.table_fields:
|
||||
content.append(get_formatted_value(doc.get(field.fieldname), field))
|
||||
|
||||
tags = (doc.get('_user_tags') or '').strip()
|
||||
if tags:
|
||||
content.extend(list(filter(lambda x: x, tags.split(','))))
|
||||
|
||||
# Get children
|
||||
for child in doc.meta.get_table_fields():
|
||||
for d in doc.get(child.fieldname):
|
||||
|
|
|
|||
94
frappe/utils/global_tags.py
Normal file
94
frappe/utils/global_tags.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Copyright (c) 2019, 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_tags_table():
|
||||
"""
|
||||
Creates __global_search table
|
||||
:return:
|
||||
"""
|
||||
frappe.db.create_global_tags_table()
|
||||
|
||||
def reset():
|
||||
"""
|
||||
Deletes all data in __global_tags
|
||||
:return:
|
||||
"""
|
||||
frappe.db.sql('DELETE FROM `__global_tags`')
|
||||
|
||||
def delete_tags_for_document(doc):
|
||||
"""
|
||||
Delete the __global_tags entry of a document that has
|
||||
been deleted
|
||||
:param doc: Deleted document
|
||||
"""
|
||||
frappe.db.sql("""DELETE
|
||||
FROM `__global_search`
|
||||
WHERE doctype = %s
|
||||
AND name = %s""", (doc.doctype, doc.name))
|
||||
|
||||
def update_global_tags(doc, tags):
|
||||
"""
|
||||
Adds tags for documents
|
||||
:param doc: Document to be added to global tags
|
||||
"""
|
||||
if frappe.local.conf.get('disable_global_tags') or not doc.get("_user_tags"):
|
||||
return
|
||||
|
||||
value = {
|
||||
"doctype": doc.doctype,
|
||||
"name": doc.name,
|
||||
"title": (doc.get_title() or '')[:int(frappe.db.VARCHAR_LEN)],
|
||||
"tags": tags.lower()
|
||||
}
|
||||
|
||||
frappe.db.multisql({
|
||||
'mariadb': '''INSERT INTO `__global_tags`
|
||||
(`doctype`, `name`, `title`, `tags`)
|
||||
VALUES (%(doctype)s, %(name)s, %(title)s, %(tags)s)
|
||||
ON DUPLICATE key UPDATE
|
||||
`tags`=%(tags)s
|
||||
''',
|
||||
'postgres': '''INSERT INTO `__global_tags`
|
||||
(`doctype`, `name`, `title`, `tags`)
|
||||
VALUES (%(doctype)s, %(name)s, %(title)s, %(tags)s)
|
||||
ON CONFLICT("doctype", "name") DO UPDATE SET
|
||||
`tags`=%(tags)s
|
||||
'''
|
||||
}, value)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_documents_for_tag(tag):
|
||||
"""
|
||||
Search for given text in __global_tags
|
||||
:param tag: tag to be searched
|
||||
"""
|
||||
# remove hastag # from tag
|
||||
results = {}
|
||||
tag = frappe.db.escape('%{0}%'.format(tag.lower()), False)
|
||||
|
||||
common_query = '''
|
||||
SELECT `doctype`, `name`, `title`, `tags`
|
||||
FROM `__global_tags`
|
||||
WHERE `tags` LIKE {tag}
|
||||
'''
|
||||
|
||||
result = frappe.db.multisql({
|
||||
'mariadb': common_query.format(tag=tag),
|
||||
'postgres': common_query.format(tag=tag)
|
||||
}, as_dict=True)
|
||||
|
||||
for res in result:
|
||||
if res.doctype in results.keys():
|
||||
results[res.doctype].append(res)
|
||||
else:
|
||||
results[res.doctype] = [res]
|
||||
|
||||
return results
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tags_list_for_awesomebar():
|
||||
return [t.name for t in frappe.get_list("Tag")]
|
||||
Loading…
Add table
Reference in a new issue