diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py index 82a47d24d9..160018c4a2 100644 --- a/frappe/core/doctype/communication/mixins.py +++ b/frappe/core/doctype/communication/mixins.py @@ -89,7 +89,7 @@ class CommunicationEmailMixin: return self._final_cc def get_mail_cc_with_displayname(self, is_inbound_mail_communcation=False, include_sender = False): - cc_list = self.mail_cc(is_inbound_mail_communcation=False, include_sender = False) + cc_list = self.mail_cc(is_inbound_mail_communcation=is_inbound_mail_communcation, include_sender = include_sender) return [self.get_email_with_displayname(email) for email in cc_list] def mail_bcc(self, is_inbound_mail_communcation=False): diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 2f0819ab68..19462e79de 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -120,7 +120,7 @@ "label": "Field Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", "reqd": 1 }, { @@ -417,7 +417,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-29 06:14:43.073329", + "modified": "2021-07-12 04:54:12.042319", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/database/database.py b/frappe/database/database.py index 81e24cc7ad..6012e47445 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -15,7 +15,6 @@ from frappe import _ from time import time from frappe.utils import now, getdate, cast_fieldtype, get_datetime from frappe.model.utils.link_count import flush_local_link_count -from frappe.utils import cint class Database(object): @@ -556,8 +555,7 @@ class Database(object): if not df: frappe.throw(_('Invalid field name: {0}').format(frappe.bold(fieldname)), self.InvalidColumnName) - if df.fieldtype in frappe.model.numeric_fieldtypes: - val = cint(val) + val = cast_fieldtype(df.fieldtype, val) self.value_cache[doctype][fieldname] = val diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index cdab7d6d1b..ca53e6cba4 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -452,6 +452,7 @@ def get_custom_report_list(module): "type": "Link", "link_type": "report", "doctype": r.ref_doctype, + "dependencies": r.ref_doctype, "is_query_report": 1 if r.report_type in ("Query Report", "Script Report", "Custom Report") else 0, "label": _(r.name), "link_to": r.name, diff --git a/frappe/handler.py b/frappe/handler.py index de86c15c8f..8d0c18a00b 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -144,8 +144,8 @@ def upload_file(): file_url = frappe.form_dict.file_url folder = frappe.form_dict.folder or 'Home' method = frappe.form_dict.method + filename = frappe.form_dict.file_name content = None - filename = None if 'file' in files: file = files['file'] @@ -155,7 +155,7 @@ def upload_file(): frappe.local.uploaded_file = content frappe.local.uploaded_filename = filename - if frappe.session.user == 'Guest' or (user and not user.has_desk_access()): + if not file_url and (frappe.session.user == "Guest" or (user and not user.has_desk_access())): import mimetypes filetype = mimetypes.guess_type(filename)[0] if filetype not in ALLOWED_MIMETYPES: diff --git a/frappe/integrations/doctype/google_settings/google_settings.json b/frappe/integrations/doctype/google_settings/google_settings.json index 086c56c020..6f25fa4bf6 100644 --- a/frappe/integrations/doctype/google_settings/google_settings.json +++ b/frappe/integrations/doctype/google_settings/google_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-06-14 00:08:37.255003", "doctype": "DocType", "engine": "InnoDB", @@ -8,7 +9,10 @@ "client_id", "client_secret", "sb_01", - "api_key" + "api_key", + "section_break_7", + "google_drive_picker_enabled", + "app_id" ], "fields": [ { @@ -18,10 +22,12 @@ "label": "Enable" }, { + "description": "The Client ID obtained from the Google Cloud Console under \n\"APIs & Services\" > \"Credentials\"\n", "fieldname": "client_id", "fieldtype": "Data", "in_list_view": 1, - "label": "Client ID" + "label": "Client ID", + "mandatory_depends_on": "google_drive_picker_enabled" }, { "fieldname": "client_secret", @@ -30,10 +36,11 @@ "label": "Client Secret" }, { - "description": "Used For Google Maps Integration.", + "description": "The browser API key obtained from the Google Cloud Console under \n\"APIs & Services\" > \"Credentials\"\n", "fieldname": "api_key", "fieldtype": "Data", - "label": "API Key" + "label": "API Key", + "mandatory_depends_on": "google_drive_picker_enabled" }, { "depends_on": "enable", @@ -46,10 +53,30 @@ "fieldname": "sb_01", "fieldtype": "Section Break", "label": "API Key" + }, + { + "depends_on": "google_drive_picker_enabled", + "description": "The project number obtained from Google Cloud Console under \n\"IAM & Admin\" > \"Settings\"\n", + "fieldname": "app_id", + "fieldtype": "Data", + "label": "App ID", + "mandatory_depends_on": "google_drive_picker_enabled" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Google Drive Picker" + }, + { + "default": "0", + "fieldname": "google_drive_picker_enabled", + "fieldtype": "Check", + "label": "Google Drive Picker Enabled" } ], "issingle": 1, - "modified": "2019-08-06 22:37:41.699703", + "links": [], + "modified": "2021-06-29 18:26:07.094851", "modified_by": "Administrator", "module": "Integrations", "name": "Google Settings", @@ -64,16 +91,6 @@ "role": "System Manager", "share": 1, "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "All", - "share": 1, - "write": 1 } ], "quick_entry": 1, diff --git a/frappe/integrations/doctype/google_settings/google_settings.py b/frappe/integrations/doctype/google_settings/google_settings.py index 9a3f3c8ae2..db65abdb65 100644 --- a/frappe/integrations/doctype/google_settings/google_settings.py +++ b/frappe/integrations/doctype/google_settings/google_settings.py @@ -2,11 +2,26 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document class GoogleSettings(Document): pass def get_auth_url(): - return "https://www.googleapis.com/oauth2/v4/token" \ No newline at end of file + return "https://www.googleapis.com/oauth2/v4/token" + + +@frappe.whitelist() +def get_file_picker_settings(): + """Return all the data FileUploader needs to start the Google Drive Picker.""" + google_settings = frappe.get_single("Google Settings") + if not (google_settings.enable and google_settings.google_drive_picker_enabled): + return {} + + return { + "enabled": True, + "appId": google_settings.app_id, + "developerKey": google_settings.api_key, + "clientId": google_settings.client_id + } diff --git a/frappe/integrations/doctype/google_settings/test_google_settings.py b/frappe/integrations/doctype/google_settings/test_google_settings.py new file mode 100644 index 0000000000..32d43a323b --- /dev/null +++ b/frappe/integrations/doctype/google_settings/test_google_settings.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +from .google_settings import get_file_picker_settings + +class TestGoogleSettings(unittest.TestCase): + + def setUp(self): + settings = frappe.get_single("Google Settings") + settings.client_id = "test_client_id" + settings.app_id = "test_app_id" + settings.api_key = "test_api_key" + settings.save() + + def test_picker_disabled(self): + """Google Drive Picker should be disabled if it is not enabled in Google Settings.""" + frappe.db.set_value("Google Settings", None, "enable", 1) + frappe.db.set_value("Google Settings", None, "google_drive_picker_enabled", 0) + settings = get_file_picker_settings() + + self.assertEqual(settings, {}) + + def test_google_disabled(self): + """Google Drive Picker should be disabled if Google integration is not enabled.""" + frappe.db.set_value("Google Settings", None, "enable", 0) + frappe.db.set_value("Google Settings", None, "google_drive_picker_enabled", 1) + settings = get_file_picker_settings() + + self.assertEqual(settings, {}) + + def test_picker_enabled(self): + """If picker is enabled, get_file_picker_settings should return the credentials.""" + frappe.db.set_value("Google Settings", None, "enable", 1) + frappe.db.set_value("Google Settings", None, "google_drive_picker_enabled", 1) + settings = get_file_picker_settings() + + self.assertEqual(True, settings.get("enabled", False)) + self.assertEqual("test_client_id", settings.get("clientId", "")) + self.assertEqual("test_app_id", settings.get("appId", "")) + self.assertEqual("test_api_key", settings.get("developerKey", "")) diff --git a/frappe/public/icons/social/google_drive.svg b/frappe/public/icons/social/google_drive.svg new file mode 100644 index 0000000000..e1a9378f8d --- /dev/null +++ b/frappe/public/icons/social/google_drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frappe/public/js/frappe/file_uploader/FileUploader.vue b/frappe/public/js/frappe/file_uploader/FileUploader.vue index 5199d98a1d..06f9275711 100644 --- a/frappe/public/js/frappe/file_uploader/FileUploader.vue +++ b/frappe/public/js/frappe/file_uploader/FileUploader.vue @@ -63,6 +63,12 @@
{{ __('Camera') }}
+
{{ upload_notes }} @@ -116,6 +122,7 @@ import FilePreview from './FilePreview.vue'; import FileBrowser from './FileBrowser.vue'; import WebLink from './WebLink.vue'; +import GoogleDrivePicker from '../../integrations/google_drive_picker'; export default { name: 'FileUploader', @@ -173,6 +180,24 @@ export default { currently_uploading: -1, show_file_browser: false, show_web_link: false, + allow_take_photo: false, + google_drive_settings: { + enabled: false + } + } + }, + created() { + this.allow_take_photo = window.navigator.mediaDevices; + if (frappe.user_id !== "Guest") { + frappe.call({ + // method only available after login + method: "frappe.integrations.doctype.google_settings.google_settings.get_file_picker_settings", + callback: (resp) => { + if (!resp.exc) { + this.google_drive_settings = resp.message; + } + } + }); } }, watch: { @@ -187,9 +212,6 @@ export default { return this.files.length > 0 && this.files.every( file => file.total !== 0 && file.progress === file.total); - }, - allow_take_photo() { - return window.navigator.mediaDevices; } }, methods: { @@ -408,6 +430,10 @@ export default { form_data.append('file_url', file.file_url); } + if (file.file_name) { + form_data.append('file_name', file.file_name); + } + if (this.doctype && this.docname) { form_data.append('doctype', this.doctype); form_data.append('docname', this.docname); @@ -437,6 +463,25 @@ export default { ); }); }, + show_google_drive_picker() { + let dialog = cur_dialog; + dialog.hide(); + let google_drive = new GoogleDrivePicker({ + pickerCallback: data => this.google_drive_callback(data, dialog), + ...this.google_drive_settings + }); + google_drive.loadPicker(); + }, + google_drive_callback(data, dialog) { + if (data.action == google.picker.Action.PICKED) { + this.upload_file({ + file_url: data.docs[0].url, + file_name: data.docs[0].name + }); + } else if (data.action == google.picker.Action.CANCEL) { + dialog.show(); + } + }, url_to_file(url, filename, mime_type) { return fetch(url) .then(res => res.arrayBuffer()) diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index 6833f68073..b2b0c11d54 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -5,7 +5,6 @@ frappe.ui.form.Dashboard = class FormDashboard { constructor(opts) { $.extend(this, opts); this.setup_dashboard_sections(); - this.set_open_count = frappe.utils.throttle(this.set_open_count, 500); } setup_dashboard_sections() { @@ -179,7 +178,6 @@ frappe.ui.form.Dashboard = class FormDashboard { return; } this.render_links(); - // this.set_open_count(); show = true; } @@ -206,6 +204,7 @@ frappe.ui.form.Dashboard = class FormDashboard { $(this).removeClass('hidden'); } }); + this.set_open_count(); } init_data() { diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 8064f90a98..faaa3dfbd9 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1265,7 +1265,9 @@ frappe.ui.form.Form = class FrappeForm { if (df && df[property] != value) { df[property] = value; if (table_field && table_row_name) { - this.fields_dict[fieldname].grid.grid_rows_by_docname[table_row_name].refresh_field(fieldname); + if (this.fields_dict[fieldname].grid.grid_rows_by_docname[table_row_name]) { + this.fields_dict[fieldname].grid.grid_rows_by_docname[table_row_name].refresh_field(fieldname); + } } else { this.refresh_field(fieldname); } diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 12caf4ab94..2bfa7c7be6 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -171,8 +171,8 @@ frappe.router = { } else { standard_route = ['List', doctype_route.doctype, frappe.utils.to_title_case(route[2])]; if (route[3]) { - // calendar / kanban / dashboard name - standard_route.push(route[3]); + // calendar / kanban / dashboard / folder name + standard_route.push(...route.splice(3, route.length)); } } return standard_route; @@ -297,8 +297,8 @@ frappe.router = { if (route[2] && route[2] !== 'list' && !$.isPlainObject(route[2])) { new_route = [this.slug(route[1]), 'view', route[2].toLowerCase()]; - // calendar / inbox - if (route[3]) new_route.push(route[3]); + // calendar / inbox / file folder + if (route[3]) new_route.push(...route.slice(3, route.length)); } else { if ($.isPlainObject(route[2])) { frappe.route_options = route[2]; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 8d3d0edd53..65635ec1dd 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1202,6 +1202,8 @@ Object.assign(frappe.utils, { } else if (type === "report") { if (item.is_query_report) { route = "query-report/" + item.name; + } else if (!item.doctype) { + route = "/report/" + item.name; } else { route = frappe.router.slug(item.doctype) + "/view/report/" + item.name; } diff --git a/frappe/public/js/frappe/views/file/file_view.js b/frappe/public/js/frappe/views/file/file_view.js index 6f0cdcc0f1..e020bff4dd 100644 --- a/frappe/public/js/frappe/views/file/file_view.js +++ b/frappe/public/js/frappe/views/file/file_view.js @@ -315,7 +315,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView { acc += "/" + curr; } return acc; - }, "/app/file"); + }, "/app/file/view"); return `${folder}`; }) diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js index 80f8c00705..f16228dca0 100644 --- a/frappe/public/js/frappe/views/workspace/workspace.js +++ b/frappe/public/js/frappe/views/workspace/workspace.js @@ -252,7 +252,7 @@ class DesktopPage { return; } this.refresh(); - }).finally(this.page.find('.workspace_loading_skeleton').remove); + }).finally(() => this.page.find('.workspace_loading_skeleton').remove()); } refresh() { diff --git a/frappe/public/js/frappe/widgets/links_widget.js b/frappe/public/js/frappe/widgets/links_widget.js index 2cc824899f..9d0cadc1d0 100644 --- a/frappe/public/js/frappe/widgets/links_widget.js +++ b/frappe/public/js/frappe/widgets/links_widget.js @@ -68,7 +68,7 @@ export default class LinksWidget extends Widget { is_query_report: item.is_query_report }; - if (item.link_type == "Report" && !item.is_query_report) { + if (item.link_type.toLowerCase() == "report" && !item.is_query_report) { opts.doctype = item.dependencies; } diff --git a/frappe/public/js/integrations/google_drive_picker.js b/frappe/public/js/integrations/google_drive_picker.js new file mode 100644 index 0000000000..9d7971e75c --- /dev/null +++ b/frappe/public/js/integrations/google_drive_picker.js @@ -0,0 +1,77 @@ +/* global gapi:false, google:false */ +export default class GoogleDrivePicker { + constructor({ + pickerCallback, + enabled, + appId, + developerKey, + clientId + } = {}) { + this.scope = ['https://www.googleapis.com/auth/drive.readonly']; + this.pickerApiLoaded = false; + this.enabled = enabled; + this.appId = appId; + this.pickerCallback = pickerCallback; + this.developerKey = developerKey; + this.clientId = clientId; + } + + loadPicker() { + // load the google API library + $.ajax({ + method: "GET", + url: "https://apis.google.com/js/api.js", + dataType: "script", + cache: true + }).done(this.loadGapi.bind(this)); + } + + loadGapi() { + // load auth and picker libraries + if (!frappe.boot.user.google_drive_token) { + gapi.load('auth', this.onAuthApiLoad.bind(this)); + } + + gapi.load('picker', this.onPickerApiLoad.bind(this)); + } + + onAuthApiLoad() { + gapi.auth.authorize({ + 'client_id': this.clientId, + 'scope': this.scope, + 'immediate': false + }, this.handleAuthResult.bind(this)); + } + + handleAuthResult(authResult) { + if (authResult && !authResult.error) { + frappe.boot.user.google_drive_token = authResult.access_token; + this.createPicker(); + } + } + + onPickerApiLoad() { + this.pickerApiLoaded = true; + this.createPicker(); + } + + createPicker() { + // Create and render a Picker object for searching images. + if (this.pickerApiLoaded && frappe.boot.user.google_drive_token) { + var view = new google.picker.DocsView(google.picker.ViewId.DOCS) + .setParent('root') // show the root folder by default + .setIncludeFolders(true); // also show folders, not just files + + var picker = new google.picker.PickerBuilder() + .setAppId(this.appId) + .setDeveloperKey(this.developerKey) + .setOAuthToken(frappe.boot.user.google_drive_token) + .addView(view) + .setLocale(frappe.boot.lang) + .setCallback(this.pickerCallback) + .build(); + + picker.setVisible(true); + } + } +} diff --git a/frappe/public/scss/desk/scrollbar.scss b/frappe/public/scss/desk/scrollbar.scss index d8fb49e5ce..9790ed0116 100644 --- a/frappe/public/scss/desk/scrollbar.scss +++ b/frappe/public/scss/desk/scrollbar.scss @@ -11,6 +11,7 @@ html { /* Works on Chrome, Edge, and Safari */ *::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb-color); + border-radius: 6px; } *::-webkit-scrollbar-track, diff --git a/frappe/public/scss/website.bundle.scss b/frappe/public/scss/website.bundle.scss index bcbb6f3c6a..06ec73c386 100644 --- a/frappe/public/scss/website.bundle.scss +++ b/frappe/public/scss/website.bundle.scss @@ -1 +1 @@ -@import './website/index'; \ No newline at end of file +@import './website/index'; diff --git a/frappe/public/scss/website/page_builder.scss b/frappe/public/scss/website/page_builder.scss index 6eb6dae5d2..ff9f4ae1e6 100644 --- a/frappe/public/scss/website/page_builder.scss +++ b/frappe/public/scss/website/page_builder.scss @@ -145,20 +145,21 @@ .section-with-cards .card { @include transition(); + border: none; + + .card-body { + padding: 0 1.5rem 2rem 0; + } &:hover { border-color: $gray-500; } .card-title { - line-height: 1; + line-height: 1.3; } &.card-sm { - .card-body { - padding: 1.5rem; - } - .card-title { font-size: $font-size-base; font-weight: 600; @@ -169,10 +170,6 @@ } } &.card-md { - .card-body { - padding: 1.75rem; - } - .card-title { font-size: $font-size-lg; font-weight: 600; @@ -186,10 +183,6 @@ } } &.card-lg { - .card-body { - padding: 2rem; - } - .card-title { font-size: $font-size-xl; font-weight: bold; diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index a31a898d73..04c9a525b1 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -5,6 +5,7 @@ import unittest from random import choice +import datetime import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_field @@ -45,11 +46,35 @@ class TestDB(unittest.TestCase): frappe.db.escape("香港濟生堂製藥有限公司 - IT".encode("utf-8")) def test_get_single_value(self): - frappe.db.set_value('System Settings', 'System Settings', 'backup_limit', 5) - frappe.db.commit() + #setup + values_dict = { + "Float": 1.5, + "Int": 1, + "Percent": 55.5, + "Currency": 12.5, + "Data": "Test", + "Date": datetime.datetime.now().date(), + "Datetime": datetime.datetime.now(), + "Time": datetime.timedelta(hours=9, minutes=45, seconds=10) + } + test_inputs = [{ + "fieldtype": fieldtype, + "value": value} for fieldtype, value in values_dict.items()] + for fieldtype in values_dict.keys(): + create_custom_field("Print Settings", { + "fieldname": f"test_{fieldtype.lower()}", + "label": f"Test {fieldtype}", + "fieldtype": fieldtype, + }) - limit = frappe.db.get_single_value('System Settings', 'backup_limit') - self.assertEqual(limit, 5) + #test + for inp in test_inputs: + fieldname = f"test_{inp['fieldtype'].lower()}" + frappe.db.set_value("Print Settings", "Print Settings", fieldname, inp["value"]) + self.assertEqual(frappe.db.get_single_value("Print Settings", fieldname), inp["value"]) + + #teardown + clear_custom_fields("Print Settings") def test_log_touched_tables(self): frappe.flags.in_migrate = True @@ -132,29 +157,29 @@ class TestDB(unittest.TestCase): # Testing read self.assertEqual(list(frappe.get_all("ToDo", fields=[random_field], limit=1)[0])[0], random_field) - self.assertEqual(list(frappe.get_all("ToDo", fields=["`{0}` as total".format(random_field)], limit=1)[0])[0], "total") + self.assertEqual(list(frappe.get_all("ToDo", fields=[f"`{random_field}` as total"], limit=1)[0])[0], "total") # Testing read for distinct and sql functions self.assertEqual(list( frappe.get_all("ToDo", - fields=["`{0}` as total".format(random_field)], + fields=[f"`{random_field}` as total"], distinct=True, limit=1, )[0] )[0], "total") self.assertEqual(list( frappe.get_all("ToDo", - fields=["`{0}`".format(random_field)], + fields=[f"`{random_field}`"], distinct=True, limit=1, )[0] )[0], random_field) self.assertEqual(list( frappe.get_all("ToDo", - fields=["count(`{0}`)".format(random_field)], + fields=[f"count(`{random_field}`)"], limit=1 )[0] - )[0], "count" if frappe.conf.db_type == "postgres" else "count(`{0}`)".format(random_field)) + )[0], "count" if frappe.conf.db_type == "postgres" else f"count(`{random_field}`)") # Testing update frappe.db.set_value(test_doctype, random_doc, random_field, random_value) diff --git a/frappe/workflow/doctype/workflow_action/test_workflow_action.py b/frappe/workflow/doctype/workflow_action/test_workflow_action.py new file mode 100644 index 0000000000..a2f4f26193 --- /dev/null +++ b/frappe/workflow/doctype/workflow_action/test_workflow_action.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt + +# import frappe +import unittest + +class TestWorkflowAction(unittest.TestCase): + pass diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.json b/frappe/workflow/doctype/workflow_action/workflow_action.json index 810f22c184..f1290d001f 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.json +++ b/frappe/workflow/doctype/workflow_action/workflow_action.json @@ -1,262 +1,77 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, + "actions": [], "creation": "2018-05-17 18:29:03.923384", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "status", + "reference_name", + "reference_doctype", + "user", + "workflow_state", + "completed_by" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "status", "fieldtype": "Select", - "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": "Status", - "length": 0, - "no_copy": 0, - "options": "Open\nCompleted", - "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, - "translatable": 0, - "unique": 0 + "options": "Open\nCompleted" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "reference_name", "fieldtype": "Dynamic Link", - "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": "Reference Name", - "length": 0, - "no_copy": 0, "options": "reference_doctype", - "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, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "reference_doctype", "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": "Reference Document Type", - "length": 0, - "no_copy": 0, "options": "DocType", - "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, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "user", "fieldtype": "Link", "hidden": 1, - "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", - "length": 0, - "no_copy": 0, "options": "User", - "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, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "workflow_state", "fieldtype": "Data", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Workflow State", - "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, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "completed_by", "fieldtype": "Link", - "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": "Completed By", - "length": 0, - "no_copy": 0, - "options": "User", - "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, - "translatable": 0, - "unique": 0 + "options": "User" } ], - "has_web_view": 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": "2019-09-05 14:22:27.664645", + "links": [], + "modified": "2021-07-01 09:07:52.848618", "modified_by": "Administrator", "module": "Workflow", "name": "Workflow Action", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, "delete": 1, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "All" } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "title_field": "reference_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ddb5623e5e..da7f89872f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4148,16 +4148,11 @@ minimatch@^3.0.4, minimatch@~3.0.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.3, minimist@^1.2.5: +minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - minipass@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"