Merge branch 'develop' of https://github.com/frappe/frappe into auto-generate-rtl-style
This commit is contained in:
commit
c5c79ea2fd
48 changed files with 369 additions and 1465 deletions
|
|
@ -98,8 +98,6 @@ rules:
|
|||
languages: [python]
|
||||
severity: WARNING
|
||||
paths:
|
||||
exclude:
|
||||
- test_*.py
|
||||
include:
|
||||
- "*/**/doctype/*"
|
||||
|
||||
|
|
|
|||
4
.github/helper/semgrep_rules/security.yml
vendored
4
.github/helper/semgrep_rules/security.yml
vendored
|
|
@ -8,10 +8,6 @@ rules:
|
|||
dynamic content. Avoid it or use safe_eval().
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
paths:
|
||||
exclude:
|
||||
- frappe/__init__.py
|
||||
- frappe/commands/utils.py
|
||||
|
||||
- id: frappe-sqli-format-strings
|
||||
patterns:
|
||||
|
|
|
|||
36
.github/workflows/semgrep.yml
vendored
36
.github/workflows/semgrep.yml
vendored
|
|
@ -1,34 +1,18 @@
|
|||
name: Semgrep
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- version-13-hotfix
|
||||
- version-13-pre-release
|
||||
pull_request: { }
|
||||
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Frappe Linter
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup python3
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Setup semgrep
|
||||
run: |
|
||||
python -m pip install -q semgrep
|
||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||
|
||||
- name: Semgrep errors
|
||||
run: |
|
||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
||||
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
||||
|
||||
- name: Semgrep warnings
|
||||
run: |
|
||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
||||
- uses: actions/checkout@v2
|
||||
- uses: returntocorp/semgrep-action@v1
|
||||
env:
|
||||
SEMGREP_TIMEOUT: 120
|
||||
with:
|
||||
config: >-
|
||||
r/python.lang.correctness
|
||||
.github/helper/semgrep_rules
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
|
@ -14,6 +12,13 @@ from frappe.exceptions import SiteNotSpecifiedError
|
|||
from frappe.utils import get_bench_path, update_progress_bar, cint
|
||||
|
||||
|
||||
DATA_IMPORT_DEPRECATION = click.style(
|
||||
"[DEPRECATED] The `import-csv` command used 'Data Import Legacy' which has been deprecated.\n"
|
||||
"Use `data-import` command instead to import data via 'Data Import'.",
|
||||
fg="yellow"
|
||||
)
|
||||
|
||||
|
||||
@click.command('build')
|
||||
@click.option('--app', help='Build assets for app')
|
||||
@click.option('--apps', help='Build assets for specific apps')
|
||||
|
|
@ -350,7 +355,8 @@ def import_doc(context, path, force=False):
|
|||
if not context.sites:
|
||||
raise SiteNotSpecifiedError
|
||||
|
||||
@click.command('import-csv')
|
||||
|
||||
@click.command('import-csv', help=DATA_IMPORT_DEPRECATION)
|
||||
@click.argument('path')
|
||||
@click.option('--only-insert', default=False, is_flag=True, help='Do not overwrite existing records')
|
||||
@click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it')
|
||||
|
|
@ -358,32 +364,8 @@ def import_doc(context, path, force=False):
|
|||
@click.option('--no-email', default=True, is_flag=True, help='Send email if applicable')
|
||||
@pass_context
|
||||
def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False, no_email=True):
|
||||
"Import CSV using data import"
|
||||
from frappe.core.doctype.data_import_legacy import importer
|
||||
from frappe.utils.csvutils import read_csv_content
|
||||
site = get_site(context)
|
||||
|
||||
if not os.path.exists(path):
|
||||
path = os.path.join('..', path)
|
||||
if not os.path.exists(path):
|
||||
print('Invalid path {0}'.format(path))
|
||||
sys.exit(1)
|
||||
|
||||
with open(path, 'r') as csvfile:
|
||||
content = read_csv_content(csvfile.read())
|
||||
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
||||
try:
|
||||
importer.upload(content, submit_after_import=submit_after_import, no_email=no_email,
|
||||
ignore_encoding_errors=ignore_encoding_errors, overwrite=not only_insert,
|
||||
via_console=True)
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
print(frappe.get_traceback())
|
||||
|
||||
frappe.destroy()
|
||||
click.secho(DATA_IMPORT_DEPRECATION)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@click.command('data-import')
|
||||
|
|
@ -767,26 +749,49 @@ def set_config(context, key, value, global_=False, parse=False, as_dict=False):
|
|||
frappe.destroy()
|
||||
|
||||
|
||||
@click.command('version')
|
||||
def get_version():
|
||||
"Show the versions of all the installed apps"
|
||||
@click.command("version")
|
||||
@click.option("-f", "--format", "output",
|
||||
type=click.Choice(["plain", "table", "json", "legacy"]), help="Output format", default="legacy")
|
||||
def get_version(output):
|
||||
"""Show the versions of all the installed apps."""
|
||||
from git import Repo
|
||||
from frappe.utils.commands import render_table
|
||||
from frappe.utils.change_log import get_app_branch
|
||||
frappe.init('')
|
||||
|
||||
frappe.init("")
|
||||
data = []
|
||||
|
||||
for app in sorted(frappe.get_all_apps()):
|
||||
branch_name = get_app_branch(app)
|
||||
module = frappe.get_module(app)
|
||||
app_hooks = frappe.get_module(app + ".hooks")
|
||||
repo = Repo(frappe.get_app_path(app, ".."))
|
||||
branch = repo.head.ref.name
|
||||
commit = repo.head.ref.commit.hexsha[:7]
|
||||
|
||||
if hasattr(app_hooks, '{0}_version'.format(branch_name)):
|
||||
click.echo("{0} {1} {2} ({3})".format(app, getattr(app_hooks, '{0}_version'.format(branch_name)), branch, commit))
|
||||
app_info = frappe._dict()
|
||||
app_info.app = app
|
||||
app_info.branch = get_app_branch(app)
|
||||
app_info.commit = repo.head.object.hexsha[:7]
|
||||
app_info.version = getattr(app_hooks, f"{app_info.branch}_version", None) or module.__version__
|
||||
|
||||
elif hasattr(module, "__version__"):
|
||||
click.echo("{0} {1} {2} ({3})".format(app, module.__version__, branch, commit))
|
||||
data.append(app_info)
|
||||
|
||||
{
|
||||
"legacy": lambda: [
|
||||
click.echo(f"{app_info.app} {app_info.version}")
|
||||
for app_info in data
|
||||
],
|
||||
"plain": lambda: [
|
||||
click.echo(f"{app_info.app} {app_info.version} {app_info.branch} ({app_info.commit})")
|
||||
for app_info in data
|
||||
],
|
||||
"table": lambda: render_table(
|
||||
[["App", "Version", "Branch", "Commit"]] +
|
||||
[
|
||||
[app_info.app, app_info.version, app_info.branch, app_info.commit]
|
||||
for app_info in data
|
||||
]
|
||||
),
|
||||
"json": lambda: click.echo(json.dumps(data, indent=4)),
|
||||
}[output]()
|
||||
|
||||
|
||||
@click.command('rebuild-global-search')
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# imports - standard imports
|
||||
# imports - module imports
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
@ -176,8 +176,8 @@ class CommunicationEmailMixin:
|
|||
def mail_attachments(self, print_format=None, print_html=None):
|
||||
final_attachments = []
|
||||
|
||||
if print_format and print_html:
|
||||
d = {'print_format': print_format, 'print_html': print_html, 'print_format_attachment': 1,
|
||||
if print_format or print_html:
|
||||
d = {'print_format': print_format, 'html': print_html, 'print_format_attachment': 1,
|
||||
'doctype': self.reference_doctype, 'name': self.reference_name}
|
||||
final_attachments.append(d)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import frappe.permissions
|
|||
import re, csv, os
|
||||
from frappe.utils.csvutils import UnicodeWriter
|
||||
from frappe.utils import cstr, formatdate, format_datetime, parse_json, cint, format_duration
|
||||
from frappe.core.doctype.data_import_legacy.importer import get_data_keys
|
||||
from frappe.core.doctype.access_log.access_log import make_access_log
|
||||
|
||||
reflags = {
|
||||
|
|
@ -20,6 +19,15 @@ reflags = {
|
|||
"D": re.DEBUG
|
||||
}
|
||||
|
||||
def get_data_keys():
|
||||
return frappe._dict({
|
||||
"data_separator": _('Start entering data below this line'),
|
||||
"main_table": _("Table") + ":",
|
||||
"parent_table": _("Parent Table") + ":",
|
||||
"columns": _("Column Name") + ":",
|
||||
"doctype": _("DocType") + ":"
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def export_data(doctype=None, parent_doctype=None, all_doctypes=True, with_data=False,
|
||||
select_columns=None, file_type='CSV', template=False, filters=None):
|
||||
|
|
|
|||
|
|
@ -171,9 +171,6 @@ def import_file(
|
|||
i.import_data()
|
||||
|
||||
|
||||
##############
|
||||
|
||||
|
||||
def import_doc(path, pre_process=None):
|
||||
if os.path.isdir(path):
|
||||
files = [os.path.join(path, f) for f in os.listdir(path)]
|
||||
|
|
@ -192,19 +189,8 @@ def import_doc(path, pre_process=None):
|
|||
)
|
||||
frappe.flags.mute_emails = False
|
||||
frappe.db.commit()
|
||||
elif f.endswith(".csv"):
|
||||
validate_csv_import_file(f)
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def validate_csv_import_file(path):
|
||||
if path.endswith(".csv"):
|
||||
print()
|
||||
print("This method is deprecated.")
|
||||
print('Import CSV files using the command "bench --site sitename data-import"')
|
||||
print("Or use the method frappe.core.doctype.data_import.data_import.import_file")
|
||||
print()
|
||||
raise Exception("Method deprecated")
|
||||
else:
|
||||
raise NotImplementedError("Only .json files can be imported")
|
||||
|
||||
|
||||
def export_json(
|
||||
|
|
|
|||
|
|
@ -1,324 +0,0 @@
|
|||
// Copyright (c) 2017, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Data Import Legacy', {
|
||||
onload: function(frm) {
|
||||
if (frm.doc.__islocal) {
|
||||
frm.set_value("action", "");
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.core.doctype.data_import_legacy.data_import_legacy.get_importable_doctypes",
|
||||
callback: function (r) {
|
||||
let importable_doctypes = r.message;
|
||||
frm.set_query("reference_doctype", function () {
|
||||
return {
|
||||
"filters": {
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"name": ['in', importable_doctypes]
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
// should never check public
|
||||
frm.fields_dict["import_file"].df.is_private = 1;
|
||||
|
||||
frappe.realtime.on("data_import_progress", function(data) {
|
||||
if (data.data_import === frm.doc.name) {
|
||||
if (data.reload && data.reload === true) {
|
||||
frm.reload_doc();
|
||||
}
|
||||
if (data.progress) {
|
||||
let progress_bar = $(frm.dashboard.progress_area.body).find(".progress-bar");
|
||||
if (progress_bar) {
|
||||
$(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped");
|
||||
$(progress_bar).css("width", data.progress + "%");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
reference_doctype: function(frm){
|
||||
if (frm.doc.reference_doctype) {
|
||||
frappe.model.with_doctype(frm.doc.reference_doctype);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.dashboard.clear_headline();
|
||||
if (frm.doc.reference_doctype && !frm.doc.import_file) {
|
||||
frm.page.set_indicator(__('Attach file'), 'orange');
|
||||
} else {
|
||||
if (frm.doc.import_status) {
|
||||
const listview_settings = frappe.listview_settings['Data Import Legacy'];
|
||||
const indicator = listview_settings.get_indicator(frm.doc);
|
||||
|
||||
frm.page.set_indicator(indicator[0], indicator[1]);
|
||||
|
||||
if (frm.doc.import_status === "In Progress") {
|
||||
frm.dashboard.add_progress("Data Import Progress", "0");
|
||||
frm.set_read_only();
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frm.doc.reference_doctype) {
|
||||
frappe.model.with_doctype(frm.doc.reference_doctype);
|
||||
}
|
||||
|
||||
if(frm.doc.action == "Insert new records" || frm.doc.action == "Update records") {
|
||||
frm.set_df_property("action", "read_only", 1);
|
||||
}
|
||||
|
||||
frm.add_custom_button(__("Help"), function() {
|
||||
frappe.help.show_video("6wiriRKPhmg");
|
||||
});
|
||||
|
||||
if (frm.doc.reference_doctype && frm.doc.docstatus === 0) {
|
||||
frm.add_custom_button(__("Download template"), function() {
|
||||
frappe.data_import.download_dialog(frm).show();
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.reference_doctype && frm.doc.import_file && frm.doc.total_rows &&
|
||||
frm.doc.docstatus === 0 && (!frm.doc.import_status || frm.doc.import_status == "Failed")) {
|
||||
frm.page.set_primary_action(__("Start Import"), function() {
|
||||
frappe.call({
|
||||
btn: frm.page.btn_primary,
|
||||
method: "frappe.core.doctype.data_import_legacy.data_import_legacy.import_data",
|
||||
args: {
|
||||
data_import: frm.doc.name
|
||||
}
|
||||
});
|
||||
}).addClass('btn btn-primary');
|
||||
}
|
||||
|
||||
if (frm.doc.log_details) {
|
||||
frm.events.create_log_table(frm);
|
||||
} else {
|
||||
$(frm.fields_dict.import_log.wrapper).empty();
|
||||
}
|
||||
},
|
||||
|
||||
action: function(frm) {
|
||||
if(!frm.doc.action) return;
|
||||
if(!frm.doc.reference_doctype) {
|
||||
frappe.msgprint(__("Please select document type first."));
|
||||
frm.set_value("action", "");
|
||||
return;
|
||||
}
|
||||
|
||||
if(frm.doc.action == "Insert new records") {
|
||||
frm.doc.insert_new = 1;
|
||||
} else if (frm.doc.action == "Update records"){
|
||||
frm.doc.overwrite = 1;
|
||||
}
|
||||
frm.save();
|
||||
},
|
||||
|
||||
only_update: function(frm) {
|
||||
frm.save();
|
||||
},
|
||||
|
||||
submit_after_import: function(frm) {
|
||||
frm.save();
|
||||
},
|
||||
|
||||
skip_errors: function(frm) {
|
||||
frm.save();
|
||||
},
|
||||
|
||||
ignore_encoding_errors: function(frm) {
|
||||
frm.save();
|
||||
},
|
||||
|
||||
no_email: function(frm) {
|
||||
frm.save();
|
||||
},
|
||||
|
||||
show_only_errors: function(frm) {
|
||||
frm.events.create_log_table(frm);
|
||||
},
|
||||
|
||||
create_log_table: function(frm) {
|
||||
let msg = JSON.parse(frm.doc.log_details);
|
||||
var $log_wrapper = $(frm.fields_dict.import_log.wrapper).empty();
|
||||
$(frappe.render_template("log_details", {
|
||||
data: msg.messages,
|
||||
import_status: frm.doc.import_status,
|
||||
show_only_errors: frm.doc.show_only_errors,
|
||||
})).appendTo($log_wrapper);
|
||||
}
|
||||
});
|
||||
|
||||
frappe.provide('frappe.data_import');
|
||||
frappe.data_import.download_dialog = function(frm) {
|
||||
var dialog;
|
||||
const filter_fields = df => frappe.model.is_value_type(df) && !df.hidden;
|
||||
const get_fields = dt => frappe.meta.get_docfields(dt).filter(filter_fields);
|
||||
|
||||
const get_doctype_checkbox_fields = () => {
|
||||
return dialog.fields.filter(df => df.fieldname.endsWith('_fields'))
|
||||
.map(df => dialog.fields_dict[df.fieldname]);
|
||||
};
|
||||
|
||||
const doctype_fields = get_fields(frm.doc.reference_doctype)
|
||||
.map(df => {
|
||||
let reqd = (df.reqd || df.fieldname == 'naming_series') ? 1 : 0;
|
||||
return {
|
||||
label: df.label,
|
||||
reqd: reqd,
|
||||
danger: reqd,
|
||||
value: df.fieldname,
|
||||
checked: 1
|
||||
};
|
||||
});
|
||||
|
||||
let fields = [
|
||||
{
|
||||
"label": __("Select Columns"),
|
||||
"fieldname": "select_columns",
|
||||
"fieldtype": "Select",
|
||||
"options": "All\nMandatory\nManually",
|
||||
"reqd": 1,
|
||||
"onchange": function() {
|
||||
const fields = get_doctype_checkbox_fields();
|
||||
fields.map(f => f.toggle(true));
|
||||
if(this.value == 'Mandatory' || this.value == 'Manually') {
|
||||
checkbox_toggle(true);
|
||||
fields.map(multicheck_field => {
|
||||
multicheck_field.options.map(option => {
|
||||
if(!option.reqd) return;
|
||||
$(multicheck_field.$wrapper).find(`:checkbox[data-unit="${option.value}"]`)
|
||||
.prop('checked', false)
|
||||
.trigger('click');
|
||||
});
|
||||
});
|
||||
} else if(this.value == 'All'){
|
||||
$(dialog.body).find(`[data-fieldtype="MultiCheck"] :checkbox`)
|
||||
.prop('disabled', true);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": __("File Type"),
|
||||
"fieldname": "file_type",
|
||||
"fieldtype": "Select",
|
||||
"options": "Excel\nCSV",
|
||||
"default": "Excel"
|
||||
},
|
||||
{
|
||||
"label": __("Download with Data"),
|
||||
"fieldname": "with_data",
|
||||
"fieldtype": "Check",
|
||||
"hidden": !frm.doc.overwrite,
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"label": __("Select All"),
|
||||
"fieldname": "select_all",
|
||||
"fieldtype": "Button",
|
||||
"depends_on": "eval:doc.select_columns=='Manually'",
|
||||
click: function() {
|
||||
checkbox_toggle();
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": __("Unselect All"),
|
||||
"fieldname": "unselect_all",
|
||||
"fieldtype": "Button",
|
||||
"depends_on": "eval:doc.select_columns=='Manually'",
|
||||
click: function() {
|
||||
checkbox_toggle(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": frm.doc.reference_doctype,
|
||||
"fieldname": "doctype_fields",
|
||||
"fieldtype": "MultiCheck",
|
||||
"options": doctype_fields,
|
||||
"columns": 2,
|
||||
"hidden": 1
|
||||
}
|
||||
];
|
||||
|
||||
const child_table_fields = frappe.meta.get_table_fields(frm.doc.reference_doctype)
|
||||
.map(df => {
|
||||
return {
|
||||
"label": df.options,
|
||||
"fieldname": df.fieldname + '_fields',
|
||||
"fieldtype": "MultiCheck",
|
||||
"options": frappe.meta.get_docfields(df.options)
|
||||
.filter(filter_fields)
|
||||
.map(df => ({
|
||||
label: df.label,
|
||||
reqd: df.reqd ? 1 : 0,
|
||||
value: df.fieldname,
|
||||
checked: 1,
|
||||
danger: df.reqd
|
||||
})),
|
||||
"columns": 2,
|
||||
"hidden": 1
|
||||
};
|
||||
});
|
||||
|
||||
fields = fields.concat(child_table_fields);
|
||||
|
||||
dialog = new frappe.ui.Dialog({
|
||||
title: __('Download Template'),
|
||||
fields: fields,
|
||||
primary_action: function(values) {
|
||||
var data = values;
|
||||
if (frm.doc.reference_doctype) {
|
||||
var export_params = () => {
|
||||
let columns = {};
|
||||
if(values.select_columns) {
|
||||
columns = get_doctype_checkbox_fields().reduce((columns, field) => {
|
||||
const options = field.get_checked_options();
|
||||
columns[field.df.label] = options;
|
||||
return columns;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return {
|
||||
doctype: frm.doc.reference_doctype,
|
||||
parent_doctype: frm.doc.reference_doctype,
|
||||
select_columns: JSON.stringify(columns),
|
||||
with_data: frm.doc.overwrite && data.with_data,
|
||||
all_doctypes: true,
|
||||
file_type: data.file_type,
|
||||
template: true
|
||||
};
|
||||
};
|
||||
let get_template_url = '/api/method/frappe.core.doctype.data_export.exporter.export_data';
|
||||
open_url_post(get_template_url, export_params());
|
||||
} else {
|
||||
frappe.msgprint(__("Please select the Document Type."));
|
||||
}
|
||||
dialog.hide();
|
||||
},
|
||||
primary_action_label: __('Download')
|
||||
});
|
||||
|
||||
$(dialog.body).find('div[data-fieldname="select_all"], div[data-fieldname="unselect_all"]')
|
||||
.wrapAll('<div class="inline-buttons" />');
|
||||
const button_container = $(dialog.body).find('.inline-buttons');
|
||||
button_container.addClass('flex');
|
||||
$(button_container).find('.frappe-control').map((index, button) => {
|
||||
$(button).css({"margin-right": "1em"});
|
||||
});
|
||||
|
||||
function checkbox_toggle(checked=false) {
|
||||
$(dialog.body).find('[data-fieldtype="MultiCheck"]').map((index, element) => {
|
||||
$(element).find(`:checkbox`).prop("checked", checked).trigger('click');
|
||||
});
|
||||
}
|
||||
|
||||
return dialog;
|
||||
};
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_copy": 1,
|
||||
"creation": "2020-06-11 16:13:23.813709",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_doctype",
|
||||
"action",
|
||||
"insert_new",
|
||||
"overwrite",
|
||||
"only_update",
|
||||
"section_break_4",
|
||||
"import_file",
|
||||
"column_break_4",
|
||||
"error_file",
|
||||
"section_break_6",
|
||||
"skip_errors",
|
||||
"submit_after_import",
|
||||
"ignore_encoding_errors",
|
||||
"no_email",
|
||||
"import_detail",
|
||||
"import_status",
|
||||
"show_only_errors",
|
||||
"import_log",
|
||||
"log_details",
|
||||
"amended_from",
|
||||
"total_rows",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "action",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action",
|
||||
"options": "Insert new records\nUpdate records",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.overwrite",
|
||||
"description": "New data will be inserted.",
|
||||
"fieldname": "insert_new",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Insert new records",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.insert_new",
|
||||
"description": "If you are updating/overwriting already created records.",
|
||||
"fieldname": "overwrite",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Update records",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "overwrite",
|
||||
"description": "If you don't want to create any new records while updating the older records.",
|
||||
"fieldname": "only_update",
|
||||
"fieldtype": "Check",
|
||||
"label": "Don't create new records"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.__islocal)",
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "import_file",
|
||||
"fieldtype": "Attach",
|
||||
"label": "Attach file for Import"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.import_status == \"Partially Successful\"",
|
||||
"description": "This is the template file generated with only the rows having some error. You should use this file for correction and import.",
|
||||
"fieldname": "error_file",
|
||||
"fieldtype": "Attach",
|
||||
"label": "Generated File"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.__islocal)",
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If this is checked, rows with valid data will be imported and invalid rows will be dumped into a new file for you to import later.",
|
||||
"fieldname": "skip_errors",
|
||||
"fieldtype": "Check",
|
||||
"label": "Skip rows with errors"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "submit_after_import",
|
||||
"fieldtype": "Check",
|
||||
"label": "Submit after importing"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_encoding_errors",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore encoding errors"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "no_email",
|
||||
"fieldtype": "Check",
|
||||
"label": "Do not send Emails"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.import_status == \"Failed\"",
|
||||
"depends_on": "import_status",
|
||||
"fieldname": "import_detail",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Import Log"
|
||||
},
|
||||
{
|
||||
"fieldname": "import_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Import Status",
|
||||
"options": "\nSuccessful\nFailed\nIn Progress\nPartially Successful",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "1",
|
||||
"fieldname": "show_only_errors",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show only errors",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "import_status",
|
||||
"fieldname": "import_log",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Import Log"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "log_details",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 1,
|
||||
"label": "Log Details",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Data Import",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_rows",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 1,
|
||||
"label": "Total Rows",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Data Import Legacy",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"max_attachments": 1,
|
||||
"modified": "2020-06-11 16:13:23.813709",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Data Import Legacy",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import os
|
||||
|
||||
import frappe
|
||||
import frappe.modules.import_file
|
||||
from frappe import _
|
||||
from frappe.core.doctype.data_import_legacy.importer import upload
|
||||
from frappe.model.document import Document
|
||||
from frappe.modules.import_file import import_file_by_path as _import_file_by_path
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.data import format_datetime
|
||||
|
||||
|
||||
class DataImportLegacy(Document):
|
||||
def autoname(self):
|
||||
if not self.name:
|
||||
self.name = "Import on " + format_datetime(self.creation)
|
||||
|
||||
def validate(self):
|
||||
if not self.import_file:
|
||||
self.db_set("total_rows", 0)
|
||||
if self.import_status == "In Progress":
|
||||
frappe.throw(_("Can't save the form as data import is in progress."))
|
||||
|
||||
# validate the template just after the upload
|
||||
# if there is total_rows in the doc, it means that the template is already validated and error free
|
||||
if self.import_file and not self.total_rows:
|
||||
upload(data_import_doc=self, from_data_import="Yes", validate_template=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_importable_doctypes():
|
||||
return frappe.cache().hget("can_import", frappe.session.user)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def import_data(data_import):
|
||||
frappe.db.set_value("Data Import Legacy", data_import, "import_status", "In Progress", update_modified=False)
|
||||
frappe.publish_realtime("data_import_progress", {"progress": "0",
|
||||
"data_import": data_import, "reload": True}, user=frappe.session.user)
|
||||
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
||||
|
||||
if data_import not in enqueued_jobs:
|
||||
enqueue(upload, queue='default', timeout=6000, event='data_import', job_name=data_import,
|
||||
data_import_doc=data_import, from_data_import="Yes", user=frappe.session.user)
|
||||
|
||||
|
||||
def import_doc(path, overwrite=False, ignore_links=False, ignore_insert=False,
|
||||
insert=False, submit=False, pre_process=None):
|
||||
if os.path.isdir(path):
|
||||
files = [os.path.join(path, f) for f in os.listdir(path)]
|
||||
else:
|
||||
files = [path]
|
||||
|
||||
for f in files:
|
||||
if f.endswith(".json"):
|
||||
frappe.flags.mute_emails = True
|
||||
_import_file_by_path(f, data_import=True, force=True, pre_process=pre_process, reset_permissions=True)
|
||||
frappe.flags.mute_emails = False
|
||||
frappe.db.commit()
|
||||
elif f.endswith(".csv"):
|
||||
import_file_by_path(f, ignore_links=ignore_links, overwrite=overwrite, submit=submit, pre_process=pre_process)
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False, pre_process=None, no_email=True):
|
||||
from frappe.utils.csvutils import read_csv_content
|
||||
print("Importing " + path)
|
||||
with open(path, "r") as infile:
|
||||
upload(rows=read_csv_content(infile.read()), ignore_links=ignore_links, no_email=no_email, overwrite=overwrite,
|
||||
submit_after_import=submit, pre_process=pre_process)
|
||||
|
||||
|
||||
def export_json(doctype, path, filters=None, or_filters=None, name=None, order_by="creation asc"):
|
||||
def post_process(out):
|
||||
del_keys = ('modified_by', 'creation', 'owner', 'idx')
|
||||
for doc in out:
|
||||
for key in del_keys:
|
||||
if key in doc:
|
||||
del doc[key]
|
||||
for k, v in doc.items():
|
||||
if isinstance(v, list):
|
||||
for child in v:
|
||||
for key in del_keys + ('docstatus', 'doctype', 'modified', 'name'):
|
||||
if key in child:
|
||||
del child[key]
|
||||
|
||||
out = []
|
||||
if name:
|
||||
out.append(frappe.get_doc(doctype, name).as_dict())
|
||||
elif frappe.db.get_value("DocType", doctype, "issingle"):
|
||||
out.append(frappe.get_doc(doctype).as_dict())
|
||||
else:
|
||||
for doc in frappe.get_all(doctype, fields=["name"], filters=filters, or_filters=or_filters, limit_page_length=0, order_by=order_by):
|
||||
out.append(frappe.get_doc(doctype, doc.name).as_dict())
|
||||
post_process(out)
|
||||
|
||||
dirname = os.path.dirname(path)
|
||||
if not os.path.exists(dirname):
|
||||
path = os.path.join('..', path)
|
||||
|
||||
with open(path, "w") as outfile:
|
||||
outfile.write(frappe.as_json(out))
|
||||
|
||||
|
||||
def export_csv(doctype, path):
|
||||
from frappe.core.doctype.data_export.exporter import export_data
|
||||
with open(path, "wb") as csvfile:
|
||||
export_data(doctype=doctype, all_doctypes=True, template=True, with_data=True)
|
||||
csvfile.write(frappe.response.result.encode("utf-8"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def export_fixture(doctype, app):
|
||||
if frappe.session.user != "Administrator":
|
||||
raise frappe.PermissionError
|
||||
|
||||
if not os.path.exists(frappe.get_app_path(app, "fixtures")):
|
||||
os.mkdir(frappe.get_app_path(app, "fixtures"))
|
||||
|
||||
export_json(doctype, frappe.get_app_path(app, "fixtures", frappe.scrub(doctype) + ".json"), order_by="name asc")
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
frappe.listview_settings['Data Import Legacy'] = {
|
||||
add_fields: ["import_status"],
|
||||
has_indicator_for_draft: 1,
|
||||
get_indicator: function(doc) {
|
||||
|
||||
let status = {
|
||||
'Successful': [__("Success"), "green", "import_status,=,Successful"],
|
||||
'Partially Successful': [__("Partial Success"), "blue", "import_status,=,Partially Successful"],
|
||||
'In Progress': [__("In Progress"), "orange", "import_status,=,In Progress"],
|
||||
'Failed': [__("Failed"), "red", "import_status,=,Failed"],
|
||||
'Pending': [__("Pending"), "orange", "import_status,=,"]
|
||||
}
|
||||
|
||||
if (doc.import_status) {
|
||||
return status[doc.import_status];
|
||||
}
|
||||
|
||||
if (doc.docstatus == 0) {
|
||||
return status['Pending'];
|
||||
}
|
||||
|
||||
return status['Pending'];
|
||||
}
|
||||
};
|
||||
|
|
@ -1,538 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
import requests
|
||||
import frappe, json
|
||||
import frappe.permissions
|
||||
|
||||
from frappe import _
|
||||
|
||||
from frappe.utils.csvutils import getlink
|
||||
from frappe.utils.dateutils import parse_date
|
||||
|
||||
from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_absolute_url, duration_to_seconds
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_data_keys():
|
||||
return frappe._dict({
|
||||
"data_separator": _('Start entering data below this line'),
|
||||
"main_table": _("Table") + ":",
|
||||
"parent_table": _("Parent Table") + ":",
|
||||
"columns": _("Column Name") + ":",
|
||||
"doctype": _("DocType") + ":"
|
||||
})
|
||||
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, no_email=True, overwrite=None,
|
||||
update_only = None, ignore_links=False, pre_process=None, via_console=False, from_data_import="No",
|
||||
skip_errors = True, data_import_doc=None, validate_template=False, user=None):
|
||||
"""upload data"""
|
||||
|
||||
# for translations
|
||||
if user:
|
||||
frappe.cache().hdel("lang", user)
|
||||
frappe.set_user_lang(user)
|
||||
|
||||
if data_import_doc and isinstance(data_import_doc, str):
|
||||
data_import_doc = frappe.get_doc("Data Import Legacy", data_import_doc)
|
||||
if data_import_doc and from_data_import == "Yes":
|
||||
no_email = data_import_doc.no_email
|
||||
ignore_encoding_errors = data_import_doc.ignore_encoding_errors
|
||||
update_only = data_import_doc.only_update
|
||||
submit_after_import = data_import_doc.submit_after_import
|
||||
overwrite = data_import_doc.overwrite
|
||||
skip_errors = data_import_doc.skip_errors
|
||||
else:
|
||||
# extra input params
|
||||
params = json.loads(frappe.form_dict.get("params") or '{}')
|
||||
if params.get("submit_after_import"):
|
||||
submit_after_import = True
|
||||
if params.get("ignore_encoding_errors"):
|
||||
ignore_encoding_errors = True
|
||||
if not params.get("no_email"):
|
||||
no_email = False
|
||||
if params.get('update_only'):
|
||||
update_only = True
|
||||
if params.get('from_data_import'):
|
||||
from_data_import = params.get('from_data_import')
|
||||
if not params.get('skip_errors'):
|
||||
skip_errors = params.get('skip_errors')
|
||||
|
||||
frappe.flags.in_import = True
|
||||
frappe.flags.mute_emails = no_email
|
||||
|
||||
def get_data_keys_definition():
|
||||
return get_data_keys()
|
||||
|
||||
def bad_template():
|
||||
frappe.throw(_("Please do not change the rows above {0}").format(get_data_keys_definition().data_separator))
|
||||
|
||||
def check_data_length():
|
||||
if not data:
|
||||
frappe.throw(_("No data found in the file. Please reattach the new file with data."))
|
||||
|
||||
def get_start_row():
|
||||
for i, row in enumerate(rows):
|
||||
if row and row[0]==get_data_keys_definition().data_separator:
|
||||
return i+1
|
||||
bad_template()
|
||||
|
||||
def get_header_row(key):
|
||||
return get_header_row_and_idx(key)[0]
|
||||
|
||||
def get_header_row_and_idx(key):
|
||||
for i, row in enumerate(header):
|
||||
if row and row[0]==key:
|
||||
return row, i
|
||||
return [], -1
|
||||
|
||||
def filter_empty_columns(columns):
|
||||
empty_cols = list(filter(lambda x: x in ("", None), columns))
|
||||
|
||||
if empty_cols:
|
||||
if columns[-1*len(empty_cols):] == empty_cols:
|
||||
# filter empty columns if they exist at the end
|
||||
columns = columns[:-1*len(empty_cols)]
|
||||
else:
|
||||
frappe.msgprint(_("Please make sure that there are no empty columns in the file."),
|
||||
raise_exception=1)
|
||||
|
||||
return columns
|
||||
|
||||
def make_column_map():
|
||||
doctype_row, row_idx = get_header_row_and_idx(get_data_keys_definition().doctype)
|
||||
if row_idx == -1: # old style
|
||||
return
|
||||
|
||||
dt = None
|
||||
for i, d in enumerate(doctype_row[1:]):
|
||||
if d not in ("~", "-"):
|
||||
if d and doctype_row[i] in (None, '' ,'~', '-', _("DocType") + ":"):
|
||||
dt, parentfield = d, None
|
||||
# xls format truncates the row, so it may not have more columns
|
||||
if len(doctype_row) > i+2:
|
||||
parentfield = doctype_row[i+2]
|
||||
doctypes.append((dt, parentfield))
|
||||
column_idx_to_fieldname[(dt, parentfield)] = {}
|
||||
column_idx_to_fieldtype[(dt, parentfield)] = {}
|
||||
if dt:
|
||||
column_idx_to_fieldname[(dt, parentfield)][i+1] = rows[row_idx + 2][i+1]
|
||||
column_idx_to_fieldtype[(dt, parentfield)][i+1] = rows[row_idx + 4][i+1]
|
||||
|
||||
def get_doc(start_idx):
|
||||
if doctypes:
|
||||
doc = {}
|
||||
attachments = []
|
||||
last_error_row_idx = None
|
||||
for idx in range(start_idx, len(rows)):
|
||||
last_error_row_idx = idx # pylint: disable=W0612
|
||||
if (not doc) or main_doc_empty(rows[idx]):
|
||||
for dt, parentfield in doctypes:
|
||||
d = {}
|
||||
for column_idx in column_idx_to_fieldname[(dt, parentfield)]:
|
||||
try:
|
||||
fieldname = column_idx_to_fieldname[(dt, parentfield)][column_idx]
|
||||
fieldtype = column_idx_to_fieldtype[(dt, parentfield)][column_idx]
|
||||
|
||||
if not fieldname or not rows[idx][column_idx]:
|
||||
continue
|
||||
|
||||
d[fieldname] = rows[idx][column_idx]
|
||||
if fieldtype in ("Int", "Check"):
|
||||
d[fieldname] = cint(d[fieldname])
|
||||
elif fieldtype in ("Float", "Currency", "Percent"):
|
||||
d[fieldname] = flt(d[fieldname])
|
||||
elif fieldtype == "Date":
|
||||
if d[fieldname] and isinstance(d[fieldname], str):
|
||||
d[fieldname] = getdate(parse_date(d[fieldname]))
|
||||
elif fieldtype == "Datetime":
|
||||
if d[fieldname]:
|
||||
if " " in d[fieldname]:
|
||||
_date, _time = d[fieldname].split()
|
||||
else:
|
||||
_date, _time = d[fieldname], '00:00:00'
|
||||
_date = parse_date(d[fieldname])
|
||||
d[fieldname] = get_datetime(_date + " " + _time)
|
||||
else:
|
||||
d[fieldname] = None
|
||||
elif fieldtype == "Duration":
|
||||
d[fieldname] = duration_to_seconds(cstr(d[fieldname]))
|
||||
elif fieldtype in ("Image", "Attach Image", "Attach"):
|
||||
# added file to attachments list
|
||||
attachments.append(d[fieldname])
|
||||
|
||||
elif fieldtype in ("Link", "Dynamic Link", "Data") and d[fieldname]:
|
||||
# as fields can be saved in the number format(long type) in data import template
|
||||
d[fieldname] = cstr(d[fieldname])
|
||||
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
# scrub quotes from name and modified
|
||||
if d.get("name") and d["name"].startswith('"'):
|
||||
d["name"] = d["name"][1:-1]
|
||||
|
||||
if sum(0 if not val else 1 for val in d.values()):
|
||||
d['doctype'] = dt
|
||||
if dt == doctype:
|
||||
doc.update(d)
|
||||
else:
|
||||
if not overwrite and doc.get("name"):
|
||||
d['parent'] = doc["name"]
|
||||
d['parenttype'] = doctype
|
||||
d['parentfield'] = parentfield
|
||||
doc.setdefault(d['parentfield'], []).append(d)
|
||||
else:
|
||||
break
|
||||
|
||||
return doc, attachments, last_error_row_idx
|
||||
else:
|
||||
doc = frappe._dict(zip(columns, rows[start_idx][1:]))
|
||||
doc['doctype'] = doctype
|
||||
return doc, [], None
|
||||
|
||||
# used in testing whether a row is empty or parent row or child row
|
||||
# checked only 3 first columns since first two columns can be blank for example the case of
|
||||
# importing the item variant where item code and item name will be blank.
|
||||
def main_doc_empty(row):
|
||||
if row:
|
||||
for i in range(3,0,-1):
|
||||
if len(row) > i and row[i]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_naming(doc):
|
||||
autoname = frappe.get_meta(doctype).autoname
|
||||
if autoname:
|
||||
if autoname[0:5] == 'field':
|
||||
autoname = autoname[6:]
|
||||
elif autoname == 'naming_series:':
|
||||
autoname = 'naming_series'
|
||||
else:
|
||||
return True
|
||||
|
||||
if (autoname not in doc) or (not doc[autoname]):
|
||||
from frappe.model.base_document import get_controller
|
||||
if not hasattr(get_controller(doctype), "autoname"):
|
||||
frappe.throw(_("{0} is a mandatory field").format(autoname))
|
||||
return True
|
||||
|
||||
users = frappe.db.sql_list("select name from tabUser")
|
||||
def prepare_for_insert(doc):
|
||||
# don't block data import if user is not set
|
||||
# migrating from another system
|
||||
if not doc.owner in users:
|
||||
doc.owner = frappe.session.user
|
||||
if not doc.modified_by in users:
|
||||
doc.modified_by = frappe.session.user
|
||||
|
||||
def is_valid_url(url):
|
||||
is_valid = False
|
||||
if url.startswith("/files") or url.startswith("/private/files"):
|
||||
url = get_url(url)
|
||||
|
||||
try:
|
||||
r = requests.get(url)
|
||||
is_valid = True if r.status_code == 200 else False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return is_valid
|
||||
|
||||
def attach_file_to_doc(doctype, docname, file_url):
|
||||
# check if attachment is already available
|
||||
# check if the attachement link is relative or not
|
||||
if not file_url:
|
||||
return
|
||||
if not is_valid_url(file_url):
|
||||
return
|
||||
|
||||
files = frappe.db.sql("""Select name from `tabFile` where attached_to_doctype='{doctype}' and
|
||||
attached_to_name='{docname}' and (file_url='{file_url}' or thumbnail_url='{file_url}')""".format(
|
||||
doctype=doctype,
|
||||
docname=docname,
|
||||
file_url=file_url
|
||||
))
|
||||
|
||||
if files:
|
||||
# file is already attached
|
||||
return
|
||||
|
||||
_file = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_url": file_url,
|
||||
"attached_to_name": docname,
|
||||
"attached_to_doctype": doctype,
|
||||
"attached_to_field": 0,
|
||||
"folder": "Home/Attachments"})
|
||||
_file.save()
|
||||
|
||||
|
||||
# header
|
||||
filename, file_extension = ['','']
|
||||
if not rows:
|
||||
_file = frappe.get_doc("File", {"file_url": data_import_doc.import_file})
|
||||
fcontent = _file.get_content()
|
||||
filename, file_extension = _file.get_extension()
|
||||
|
||||
if file_extension == '.xlsx' and from_data_import == 'Yes':
|
||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
|
||||
rows = read_xlsx_file_from_attached_file(file_url=data_import_doc.import_file)
|
||||
|
||||
elif file_extension == '.csv':
|
||||
from frappe.utils.csvutils import read_csv_content
|
||||
rows = read_csv_content(fcontent, ignore_encoding_errors)
|
||||
|
||||
else:
|
||||
frappe.throw(_("Unsupported File Format"))
|
||||
|
||||
start_row = get_start_row()
|
||||
header = rows[:start_row]
|
||||
data = rows[start_row:]
|
||||
try:
|
||||
doctype = get_header_row(get_data_keys_definition().main_table)[1]
|
||||
columns = filter_empty_columns(get_header_row(get_data_keys_definition().columns)[1:])
|
||||
except:
|
||||
frappe.throw(_("Cannot change header content"))
|
||||
doctypes = []
|
||||
column_idx_to_fieldname = {}
|
||||
column_idx_to_fieldtype = {}
|
||||
|
||||
if skip_errors:
|
||||
data_rows_with_error = header
|
||||
|
||||
if submit_after_import and not cint(frappe.db.get_value("DocType",
|
||||
doctype, "is_submittable")):
|
||||
submit_after_import = False
|
||||
|
||||
parenttype = get_header_row(get_data_keys_definition().parent_table)
|
||||
|
||||
if len(parenttype) > 1:
|
||||
parenttype = parenttype[1]
|
||||
|
||||
# check permissions
|
||||
if not frappe.permissions.can_import(parenttype or doctype):
|
||||
frappe.flags.mute_emails = False
|
||||
return {"messages": [_("Not allowed to Import") + ": " + _(doctype)], "error": True}
|
||||
|
||||
# Throw expception in case of the empty data file
|
||||
check_data_length()
|
||||
make_column_map()
|
||||
total = len(data)
|
||||
|
||||
if validate_template:
|
||||
if total:
|
||||
data_import_doc.total_rows = total
|
||||
return True
|
||||
|
||||
if overwrite==None:
|
||||
overwrite = params.get('overwrite')
|
||||
|
||||
# delete child rows (if parenttype)
|
||||
parentfield = None
|
||||
if parenttype:
|
||||
parentfield = get_parent_field(doctype, parenttype)
|
||||
|
||||
if overwrite:
|
||||
delete_child_rows(data, doctype)
|
||||
|
||||
import_log = []
|
||||
def log(**kwargs):
|
||||
if via_console:
|
||||
print((kwargs.get("title") + kwargs.get("message")).encode('utf-8'))
|
||||
else:
|
||||
import_log.append(kwargs)
|
||||
|
||||
def as_link(doctype, name):
|
||||
if via_console:
|
||||
return "{0}: {1}".format(doctype, name)
|
||||
else:
|
||||
return getlink(doctype, name)
|
||||
|
||||
# publish realtime task update
|
||||
def publish_progress(achieved, reload=False):
|
||||
if data_import_doc:
|
||||
frappe.publish_realtime("data_import_progress", {"progress": str(int(100.0*achieved/total)),
|
||||
"data_import": data_import_doc.name, "reload": reload}, user=frappe.session.user)
|
||||
|
||||
|
||||
error_flag = rollback_flag = False
|
||||
|
||||
batch_size = frappe.conf.data_import_batch_size or 1000
|
||||
|
||||
for batch_start in range(0, total, batch_size):
|
||||
batch = data[batch_start:batch_start + batch_size]
|
||||
|
||||
for i, row in enumerate(batch):
|
||||
# bypass empty rows
|
||||
if main_doc_empty(row):
|
||||
continue
|
||||
|
||||
row_idx = i + start_row
|
||||
doc = None
|
||||
|
||||
publish_progress(i)
|
||||
|
||||
try:
|
||||
doc, attachments, last_error_row_idx = get_doc(row_idx)
|
||||
validate_naming(doc)
|
||||
if pre_process:
|
||||
pre_process(doc)
|
||||
|
||||
original = None
|
||||
if parentfield:
|
||||
parent = frappe.get_doc(parenttype, doc["parent"])
|
||||
doc = parent.append(parentfield, doc)
|
||||
parent.save()
|
||||
else:
|
||||
if overwrite and doc.get("name") and frappe.db.exists(doctype, doc["name"]):
|
||||
original = frappe.get_doc(doctype, doc["name"])
|
||||
original_name = original.name
|
||||
original.update(doc)
|
||||
# preserve original name for case sensitivity
|
||||
original.name = original_name
|
||||
original.flags.ignore_links = ignore_links
|
||||
original.save()
|
||||
doc = original
|
||||
else:
|
||||
if not update_only:
|
||||
doc = frappe.get_doc(doc)
|
||||
prepare_for_insert(doc)
|
||||
doc.flags.ignore_links = ignore_links
|
||||
doc.insert()
|
||||
if attachments:
|
||||
# check file url and create a File document
|
||||
for file_url in attachments:
|
||||
attach_file_to_doc(doc.doctype, doc.name, file_url)
|
||||
if submit_after_import:
|
||||
doc.submit()
|
||||
|
||||
# log errors
|
||||
if parentfield:
|
||||
log(**{"row": doc.idx, "title": 'Inserted row for "%s"' % (as_link(parenttype, doc.parent)),
|
||||
"link": get_absolute_url(parenttype, doc.parent), "message": 'Document successfully saved', "indicator": "green"})
|
||||
elif submit_after_import:
|
||||
log(**{"row": row_idx + 1, "title":'Submitted row for "%s"' % (as_link(doc.doctype, doc.name)),
|
||||
"message": "Document successfully submitted", "link": get_absolute_url(doc.doctype, doc.name), "indicator": "blue"})
|
||||
elif original:
|
||||
log(**{"row": row_idx + 1,"title":'Updated row for "%s"' % (as_link(doc.doctype, doc.name)),
|
||||
"message": "Document successfully updated", "link": get_absolute_url(doc.doctype, doc.name), "indicator": "green"})
|
||||
elif not update_only:
|
||||
log(**{"row": row_idx + 1, "title":'Inserted row for "%s"' % (as_link(doc.doctype, doc.name)),
|
||||
"message": "Document successfully saved", "link": get_absolute_url(doc.doctype, doc.name), "indicator": "green"})
|
||||
else:
|
||||
log(**{"row": row_idx + 1, "title":'Ignored row for %s' % (row[1]), "link": None,
|
||||
"message": "Document updation ignored", "indicator": "orange"})
|
||||
|
||||
except Exception as e:
|
||||
error_flag = True
|
||||
|
||||
# build error message
|
||||
if frappe.local.message_log:
|
||||
err_msg = "\n".join(['<p class="border-bottom small">{}</p>'.format(json.loads(msg).get('message')) for msg in frappe.local.message_log])
|
||||
else:
|
||||
err_msg = '<p class="border-bottom small">{}</p>'.format(cstr(e))
|
||||
|
||||
error_trace = frappe.get_traceback()
|
||||
if error_trace:
|
||||
error_log_doc = frappe.log_error(error_trace)
|
||||
error_link = get_absolute_url("Error Log", error_log_doc.name)
|
||||
else:
|
||||
error_link = None
|
||||
|
||||
log(**{
|
||||
"row": row_idx + 1,
|
||||
"title": 'Error for row %s' % (len(row)>1 and frappe.safe_decode(row[1]) or ""),
|
||||
"message": err_msg,
|
||||
"indicator": "red",
|
||||
"link":error_link
|
||||
})
|
||||
|
||||
# data with error to create a new file
|
||||
# include the errored data in the last row as last_error_row_idx will not be updated for the last row
|
||||
if skip_errors:
|
||||
if last_error_row_idx == len(rows)-1:
|
||||
last_error_row_idx = len(rows)
|
||||
data_rows_with_error += rows[row_idx:last_error_row_idx]
|
||||
else:
|
||||
rollback_flag = True
|
||||
finally:
|
||||
frappe.local.message_log = []
|
||||
|
||||
start_row += batch_size
|
||||
if rollback_flag:
|
||||
frappe.db.rollback()
|
||||
else:
|
||||
frappe.db.commit()
|
||||
|
||||
frappe.flags.mute_emails = False
|
||||
frappe.flags.in_import = False
|
||||
|
||||
log_message = {"messages": import_log, "error": error_flag}
|
||||
if data_import_doc:
|
||||
data_import_doc.log_details = json.dumps(log_message)
|
||||
|
||||
import_status = None
|
||||
if error_flag and data_import_doc.skip_errors and len(data) != len(data_rows_with_error):
|
||||
import_status = "Partially Successful"
|
||||
# write the file with the faulty row
|
||||
file_name = 'error_' + filename + file_extension
|
||||
if file_extension == '.xlsx':
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
xlsx_file = make_xlsx(data_rows_with_error, "Data Import Template")
|
||||
file_data = xlsx_file.getvalue()
|
||||
else:
|
||||
from frappe.utils.csvutils import to_csv
|
||||
file_data = to_csv(data_rows_with_error)
|
||||
_file = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_name": file_name,
|
||||
"attached_to_doctype": "Data Import Legacy",
|
||||
"attached_to_name": data_import_doc.name,
|
||||
"folder": "Home/Attachments",
|
||||
"content": file_data})
|
||||
_file.save()
|
||||
data_import_doc.error_file = _file.file_url
|
||||
|
||||
elif error_flag:
|
||||
import_status = "Failed"
|
||||
else:
|
||||
import_status = "Successful"
|
||||
|
||||
data_import_doc.import_status = import_status
|
||||
data_import_doc.save()
|
||||
if data_import_doc.import_status in ["Successful", "Partially Successful"]:
|
||||
data_import_doc.submit()
|
||||
publish_progress(100, True)
|
||||
else:
|
||||
publish_progress(0, True)
|
||||
frappe.db.commit()
|
||||
else:
|
||||
return log_message
|
||||
|
||||
def get_parent_field(doctype, parenttype):
|
||||
parentfield = None
|
||||
|
||||
# get parentfield
|
||||
if parenttype:
|
||||
for d in frappe.get_meta(parenttype).get_table_fields():
|
||||
if d.options==doctype:
|
||||
parentfield = d.fieldname
|
||||
break
|
||||
|
||||
if not parentfield:
|
||||
frappe.msgprint(_("Did not find {0} for {0} ({1})").format("parentfield", parenttype, doctype))
|
||||
raise Exception
|
||||
|
||||
return parentfield
|
||||
|
||||
def delete_child_rows(rows, doctype):
|
||||
"""delete child rows for all parents"""
|
||||
for p in list(set(r[1] for r in rows)):
|
||||
if p:
|
||||
frappe.db.sql("""delete from `tab{0}` where parent=%s""".format(doctype), p)
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
<div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover log-details-table">
|
||||
<tr>
|
||||
<th style="width:10%"> {{ __("Row No") }} </th>
|
||||
<th style="width:40%"> {{ __("Row Status") }} </th>
|
||||
<th style="width:50%"> {{ __("Message") }} </th>
|
||||
</tr>
|
||||
|
||||
{% for row in data %}
|
||||
{% if (!show_only_errors) || (show_only_errors && row.indicator == "red") %}
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ row.row }} </span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="indicator {{ row.indicator }}"> {{ row.title }} </span>
|
||||
</td>
|
||||
<td>
|
||||
{% if (import_status != "Failed" || (row.indicator == "red")) { %}
|
||||
<div>{{ row.message }}</div>
|
||||
{% if row.link %}
|
||||
<span style="width: 10%; float:right;">
|
||||
<a class="btn-open no-decoration" title="Open Link" href="{{ row.link }}">
|
||||
<i class="octicon octicon-arrow-right"></i>
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% } else { %}
|
||||
<span> {{ __("Document can't saved.") }} </span>
|
||||
{% } %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestDataImportLegacy(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -53,6 +53,7 @@ class User(Document):
|
|||
def after_insert(self):
|
||||
create_notification_settings(self.name)
|
||||
frappe.cache().delete_key('users_for_mentions')
|
||||
frappe.cache().delete_key('enabled_users')
|
||||
|
||||
def validate(self):
|
||||
self.check_demo()
|
||||
|
|
@ -129,6 +130,9 @@ class User(Document):
|
|||
if self.has_value_changed('allow_in_mentions') or self.has_value_changed('user_type'):
|
||||
frappe.cache().delete_key('users_for_mentions')
|
||||
|
||||
if self.has_value_changed('enabled'):
|
||||
frappe.cache().delete_key('enabled_users')
|
||||
|
||||
def has_website_permission(self, ptype, user, verbose=False):
|
||||
"""Returns true if current user is the session user"""
|
||||
return self.name == frappe.session.user
|
||||
|
|
@ -392,6 +396,8 @@ class User(Document):
|
|||
if self.get('allow_in_mentions'):
|
||||
frappe.cache().delete_key('users_for_mentions')
|
||||
|
||||
frappe.cache().delete_key('enabled_users')
|
||||
|
||||
|
||||
def before_rename(self, old_name, new_name, merge=False):
|
||||
self.check_demo()
|
||||
|
|
@ -1230,3 +1236,10 @@ def generate_keys(user):
|
|||
def switch_theme(theme):
|
||||
if theme in ["Dark", "Light"]:
|
||||
frappe.db.set_value("User", frappe.session.user, "desk_theme", theme)
|
||||
|
||||
def get_enabled_users():
|
||||
def _get_enabled_users():
|
||||
enabled_users = frappe.get_all("User", filters={"enabled": "1"}, pluck="name")
|
||||
return enabled_users
|
||||
|
||||
return frappe.cache().get_value("enabled_users", _get_enabled_users)
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ class NotificationLog(Document):
|
|||
frappe.publish_realtime('notification', after_commit=True, user=self.for_user)
|
||||
set_notifications_as_unseen(self.for_user)
|
||||
if is_email_notifications_enabled_for_type(self.for_user, self.type):
|
||||
send_notification_email(self)
|
||||
try:
|
||||
send_notification_email(self)
|
||||
except frappe.OutgoingEmailError:
|
||||
frappe.log_error(message=frappe.get_traceback(), title=_("Failed to send notification email"))
|
||||
|
||||
|
||||
def get_permission_query_conditions(for_user):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
# import frappe
|
||||
import unittest
|
||||
import frappe
|
||||
|
||||
from frappe.desk.reportview import get_stats
|
||||
from frappe.desk.doctype.tag.tag import add_tag
|
||||
|
||||
class TestTag(unittest.TestCase):
|
||||
pass
|
||||
def setUp(self) -> None:
|
||||
frappe.db.sql("DELETE from `tabTag`")
|
||||
frappe.db.sql("UPDATE `tabDocType` set _user_tags=''")
|
||||
|
||||
def test_tag_count_query(self):
|
||||
self.assertDictEqual(get_stats('["_user_tags"]', 'DocType'),
|
||||
{'_user_tags': [['No Tags', frappe.db.count('DocType')]]})
|
||||
add_tag('Standard', 'DocType', 'User')
|
||||
add_tag('Standard', 'DocType', 'ToDo')
|
||||
|
||||
# count with no filter
|
||||
self.assertDictEqual(get_stats('["_user_tags"]', 'DocType'),
|
||||
{'_user_tags': [['Standard', 2], ['No Tags', frappe.db.count('DocType') - 2]]})
|
||||
|
||||
# count with child table field filter
|
||||
self.assertDictEqual(get_stats('["_user_tags"]',
|
||||
'DocType',
|
||||
filters='[["DocField", "fieldname", "like", "%last_name%"], ["DocType", "name", "like", "%use%"]]'),
|
||||
{'_user_tags': [['Standard', 1], ['No Tags', 0]]})
|
||||
|
|
@ -445,24 +445,36 @@ def get_stats(stats, doctype, filters=[]):
|
|||
for tag in tags:
|
||||
if not tag in columns: continue
|
||||
try:
|
||||
tagcount = frappe.get_list(doctype, fields=[tag, "count(*)"],
|
||||
#filters=["ifnull(`%s`,'')!=''" % tag], group_by=tag, as_list=True)
|
||||
filters = filters + ["ifnull(`%s`,'')!=''" % tag], group_by = tag, as_list = True)
|
||||
tag_count = frappe.get_list(doctype,
|
||||
fields=[tag, "count(*)"],
|
||||
filters=filters + [[tag, '!=', '']],
|
||||
group_by=tag,
|
||||
as_list=True,
|
||||
distinct=1,
|
||||
)
|
||||
|
||||
if tag=='_user_tags':
|
||||
stats[tag] = scrub_user_tags(tagcount)
|
||||
stats[tag].append([_("No Tags"), frappe.get_list(doctype,
|
||||
if tag == '_user_tags':
|
||||
stats[tag] = scrub_user_tags(tag_count)
|
||||
no_tag_count = frappe.get_list(doctype,
|
||||
fields=[tag, "count(*)"],
|
||||
filters=filters +["({0} = ',' or {0} = '' or {0} is null)".format(tag)], as_list=True)[0][1]])
|
||||
filters=filters + [[tag, "in", ('', ',')]],
|
||||
as_list=True,
|
||||
group_by=tag,
|
||||
order_by=tag,
|
||||
)
|
||||
|
||||
no_tag_count = no_tag_count[0][1] if no_tag_count else 0
|
||||
|
||||
stats[tag].append([_("No Tags"), no_tag_count])
|
||||
else:
|
||||
stats[tag] = tagcount
|
||||
stats[tag] = tag_count
|
||||
|
||||
except frappe.db.SQLError:
|
||||
# does not work for child tables
|
||||
pass
|
||||
except frappe.db.InternalError:
|
||||
except frappe.db.InternalError as e:
|
||||
# raised when _user_tags column is added on the fly
|
||||
pass
|
||||
|
||||
return stats
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -168,7 +168,18 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
|
|||
strict=False)
|
||||
|
||||
if doctype in UNTRANSLATED_DOCTYPES:
|
||||
values = tuple([v for v in list(values) if re.search(re.escape(txt)+".*", (_(v.name) if as_dict else _(v[0])), re.IGNORECASE)])
|
||||
# Filtering the values array so that query is included in very element
|
||||
values = (
|
||||
v for v in values
|
||||
if re.search(
|
||||
f"{re.escape(txt)}.*", _(v.name if as_dict else v[0]), re.IGNORECASE
|
||||
)
|
||||
)
|
||||
|
||||
# Sorting the values array so that relevant results always come first
|
||||
# This will first bring elements on top in which query is a prefix of element
|
||||
# Then it will bring the rest of the elements and sort them in lexicographical order
|
||||
values = sorted(values, key=lambda x: relevance_sorter(x, txt, as_dict))
|
||||
|
||||
# remove _relevance from results
|
||||
if as_dict:
|
||||
|
|
@ -208,6 +219,13 @@ def scrub_custom_query(query, key, txt):
|
|||
query = query.replace('%s', ((txt or '') + '%'))
|
||||
return query
|
||||
|
||||
def relevance_sorter(key, query, as_dict):
|
||||
value = _(key.name if as_dict else key[0])
|
||||
return (
|
||||
value.lower().startswith(query.lower()) is not True,
|
||||
value
|
||||
)
|
||||
|
||||
@wrapt.decorator
|
||||
def validate_and_sanitize_search_inputs(fn, instance, args, kwargs):
|
||||
kwargs.update(dict(zip(fn.__code__.co_varnames, args)))
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|||
'''
|
||||
transformed_html = '''
|
||||
<h3>Hi John</h3>
|
||||
<p style="margin:5px 0 !important">This is a test email</p>
|
||||
<p style="margin:1em 0 !important">This is a test email</p>
|
||||
'''
|
||||
self.assertTrue(transformed_html in inline_style_in_html(html))
|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@ doc_events = {
|
|||
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions",
|
||||
"frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers"
|
||||
],
|
||||
"on_update_after_submit": [
|
||||
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions"
|
||||
],
|
||||
"on_change": [
|
||||
"frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points",
|
||||
"frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone"
|
||||
|
|
|
|||
|
|
@ -53,13 +53,15 @@ frappe.ui.form.ControlSignature = class ControlSignature extends frappe.ui.form.
|
|||
this.img = $("<img class='img-responsive attach-image-display'>")
|
||||
.appendTo(this.img_wrapper).toggle(false);
|
||||
}
|
||||
refresh_input(e) {
|
||||
refresh_input() {
|
||||
// signature dom is not ready
|
||||
if (!this.body) return;
|
||||
// prevent to load the second time
|
||||
this.make_pad();
|
||||
this.$wrapper.find(".control-input").toggle(false);
|
||||
this.set_editable(this.get_status()=="Write");
|
||||
this.load_pad();
|
||||
if(this.get_status()=="Read") {
|
||||
if (this.get_status() == "Read") {
|
||||
$(this.disp_area).toggle(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,9 +263,6 @@ frappe.ui.form.Layout = class Layout {
|
|||
section.addClass("empty-section");
|
||||
}
|
||||
});
|
||||
|
||||
this.frm && this.frm.dashboard.refresh();
|
||||
|
||||
}
|
||||
|
||||
refresh_fields (fields) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,6 +166,9 @@ select.form-control {
|
|||
.ace_print-margin {
|
||||
background-color: var(--dark-border-color);
|
||||
}
|
||||
.ace_scrollbar {
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.frappe-control[data-fieldtype="Attach"],
|
||||
|
|
|
|||
|
|
@ -179,9 +179,20 @@
|
|||
--text-on-pink: var(--pink-500);
|
||||
--text-on-cyan: var(--cyan-600);
|
||||
|
||||
// Layout Colors
|
||||
--bg-color: var(--gray-50);
|
||||
--fg-color: white;
|
||||
--navbar-bg: white;
|
||||
--fg-hover-color: var(--gray-100);
|
||||
--card-bg: var(--fg-color);
|
||||
--disabled-text-color: var(--gray-700);
|
||||
--disabled-control-bg: var(--gray-50);
|
||||
--control-bg: var(--gray-100);
|
||||
--control-bg-on-gray: var(--gray-200);
|
||||
--awesomebar-focus-bg: var(--fg-color);
|
||||
--modal-bg: white;
|
||||
--toast-bg: var(--modal-bg);
|
||||
--popover-bg: white;
|
||||
|
||||
--awesomplete-hover-bg: var(--control-bg);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,20 +25,7 @@ $input-height: 28px !default;
|
|||
|
||||
--navbar-height: 60px;
|
||||
|
||||
// Layout Colors
|
||||
--bg-color: var(--gray-50);
|
||||
--fg-color: white;
|
||||
--navbar-bg: white;
|
||||
--fg-hover-color: var(--gray-100);
|
||||
--card-bg: var(--fg-color);
|
||||
--disabled-text-color: var(--gray-700);
|
||||
--disabled-control-bg: var(--gray-50);
|
||||
--control-bg: var(--gray-100);
|
||||
--control-bg-on-gray: var(--gray-200);
|
||||
--awesomebar-focus-bg: var(--fg-color);
|
||||
--modal-bg: white;
|
||||
--toast-bg: var(--modal-bg);
|
||||
--popover-bg: white;
|
||||
|
||||
|
||||
--appreciation-color: var(--dark-green-600);
|
||||
--appreciation-bg: var(--dark-green-100);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,13 @@ a {
|
|||
}
|
||||
|
||||
p {
|
||||
margin: 5px 0 !important;
|
||||
margin: 1em 0 !important;
|
||||
}
|
||||
|
||||
.with-container {
|
||||
p {
|
||||
margin: 5px 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
@import './website/index';
|
||||
@import './website/index';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -273,6 +273,31 @@ class TestEnergyPointLog(unittest.TestCase):
|
|||
self.assertEqual(points_after_reverting_todo, points_after_closing_todo - rule_points)
|
||||
self.assertEqual(points_after_saving_todo_again, points_after_reverting_todo + rule_points)
|
||||
|
||||
def test_energy_points_disabled_user(self):
|
||||
frappe.set_user('test@example.com')
|
||||
user = frappe.get_doc('User', 'test@example.com')
|
||||
user.enabled = 0
|
||||
user.save()
|
||||
todo_point_rule = create_energy_point_rule_for_todo()
|
||||
energy_point_of_user = get_points('test@example.com')
|
||||
|
||||
created_todo = create_a_todo()
|
||||
|
||||
created_todo.status = 'Closed'
|
||||
created_todo.save()
|
||||
points_after_closing_todo = get_points('test@example.com')
|
||||
|
||||
# do not update energy points for disabled user
|
||||
self.assertEqual(points_after_closing_todo, energy_point_of_user)
|
||||
|
||||
user.enabled = 1
|
||||
user.save()
|
||||
|
||||
created_todo.save()
|
||||
points_after_re_saving_todo = get_points('test@example.com')
|
||||
self.assertEqual(points_after_re_saving_todo, energy_point_of_user + todo_point_rule.points)
|
||||
|
||||
|
||||
def create_energy_point_rule_for_todo(multiplier_field=None, for_doc_event='Custom', max_points=None,
|
||||
for_assigned_users=0, field_to_check=None, apply_once=False, user_field='owner'):
|
||||
name = 'ToDo Closed'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import frappe
|
||||
from frappe import _
|
||||
import frappe.cache_manager
|
||||
from frappe.core.doctype.user.user import get_enabled_users
|
||||
from frappe.model import log_types
|
||||
from frappe.model.document import Document
|
||||
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
|
||||
|
|
@ -44,7 +45,7 @@ class EnergyPointRule(Document):
|
|||
|
||||
try:
|
||||
for user in users:
|
||||
if not user or user == 'Administrator': continue
|
||||
if not is_eligible_user(user): continue
|
||||
create_energy_points_log(reference_doctype, reference_name, {
|
||||
'points': points,
|
||||
'user': user,
|
||||
|
|
@ -119,3 +120,8 @@ def get_energy_point_doctypes():
|
|||
d.reference_doctype for d in frappe.get_all('Energy Point Rule',
|
||||
['reference_doctype'], {'enabled': 1})
|
||||
]
|
||||
|
||||
def is_eligible_user(user):
|
||||
'''Checks if user is eligible to get energy points'''
|
||||
enabled_users = get_enabled_users()
|
||||
return user and user in enabled_users and user != 'Administrator'
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class TestBoilerPlate(unittest.TestCase):
|
|||
|
||||
def test_create_app(self):
|
||||
title = "Test App"
|
||||
description = "Test app for unit testing"
|
||||
description = "This app's description contains 'single quotes' and \"double quotes\"."
|
||||
publisher = "Test Publisher"
|
||||
email = "example@example.org"
|
||||
icon = "" # empty -> default
|
||||
|
|
|
|||
|
|
@ -426,3 +426,13 @@ class TestCommands(BaseTestCommands):
|
|||
self.assertEqual(self.returncode, 0)
|
||||
self.assertIn("pong", self.stdout)
|
||||
|
||||
def test_version(self):
|
||||
self.execute("bench version")
|
||||
self.assertEqual(self.returncode, 0)
|
||||
|
||||
for output in ["legacy", "plain", "table", "json"]:
|
||||
self.execute(f"bench version -f {output}")
|
||||
self.assertEqual(self.returncode, 0)
|
||||
|
||||
self.execute("bench version -f invalid")
|
||||
self.assertEqual(self.returncode, 2)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
import unittest
|
||||
|
|
@ -6,7 +6,16 @@ import frappe
|
|||
from frappe.desk.search import search_link
|
||||
from frappe.desk.search import search_widget
|
||||
|
||||
|
||||
class TestSearch(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if self._testMethodName == "test_link_field_order":
|
||||
setup_test_link_field_order(self)
|
||||
|
||||
def tearDown(self):
|
||||
if self._testMethodName == "test_link_field_order":
|
||||
teardown_test_link_field_order(self)
|
||||
|
||||
def test_search_field_sanitizer(self):
|
||||
# pass
|
||||
search_link('DocType', 'User', query=None, filters=None, page_length=20, searchfield='name')
|
||||
|
|
@ -38,6 +47,18 @@ class TestSearch(unittest.TestCase):
|
|||
search_link, 'DocType', 'Customer', query=None, filters=None,
|
||||
page_length=20, searchfield=';')
|
||||
|
||||
def test_link_field_order(self):
|
||||
# Making a request to the search_link with the tree doctype
|
||||
search_link(doctype=self.tree_doctype_name, txt='all', query=None,
|
||||
filters=None, page_length=20, searchfield=None)
|
||||
result = frappe.response['results']
|
||||
|
||||
# Check whether the result is sorted or not
|
||||
self.assertEquals(self.parent_doctype_name, result[0]['value'])
|
||||
|
||||
# Check whether searching for parent also list out children
|
||||
self.assertEquals(len(result), len(self.child_doctypes_names) + 1)
|
||||
|
||||
#Search for the word "pay", part of the word "pays" (country) in french.
|
||||
def test_link_search_in_foreign_language(self):
|
||||
try:
|
||||
|
|
@ -80,4 +101,58 @@ class TestSearch(unittest.TestCase):
|
|||
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_data(doctype, txt, searchfield, start, page_len, filters):
|
||||
return [doctype, txt, searchfield, start, page_len, filters]
|
||||
return [doctype, txt, searchfield, start, page_len, filters]
|
||||
|
||||
def setup_test_link_field_order(TestCase):
|
||||
TestCase.tree_doctype_name = 'Test Tree Order'
|
||||
TestCase.child_doctype_list = []
|
||||
TestCase.child_doctypes_names = ['USA', 'India', 'Russia', 'China']
|
||||
TestCase.parent_doctype_name = 'All Territories'
|
||||
|
||||
# Create Tree doctype
|
||||
TestCase.tree_doc = frappe.get_doc({
|
||||
'doctype': 'DocType',
|
||||
'name': TestCase.tree_doctype_name,
|
||||
'module': 'Custom',
|
||||
'custom': 1,
|
||||
'is_tree': 1,
|
||||
'autoname': 'field:random',
|
||||
'fields': [{
|
||||
'fieldname': 'random',
|
||||
'label': 'Random',
|
||||
'fieldtype': 'Data'
|
||||
}]
|
||||
}).insert()
|
||||
TestCase.tree_doc.search_fields = 'parent_test_tree_order'
|
||||
TestCase.tree_doc.save()
|
||||
|
||||
# Create root for the tree doctype
|
||||
frappe.get_doc({
|
||||
"doctype": TestCase.tree_doctype_name,
|
||||
"random": TestCase.parent_doctype_name,
|
||||
"is_group": 1
|
||||
}).insert()
|
||||
|
||||
# Create children for the root
|
||||
for child_name in TestCase.child_doctypes_names:
|
||||
temp = frappe.get_doc({
|
||||
"doctype": TestCase.tree_doctype_name,
|
||||
"random": child_name,
|
||||
"parent_test_tree_order": TestCase.parent_doctype_name
|
||||
}).insert()
|
||||
TestCase.child_doctype_list.append(temp)
|
||||
|
||||
def teardown_test_link_field_order(TestCase):
|
||||
# Deleting all the created doctype
|
||||
for child_doctype in TestCase.child_doctype_list:
|
||||
child_doctype.delete()
|
||||
|
||||
frappe.delete_doc(
|
||||
TestCase.tree_doctype_name,
|
||||
TestCase.parent_doctype_name,
|
||||
ignore_permissions=True,
|
||||
force=True,
|
||||
for_reload=True,
|
||||
)
|
||||
|
||||
TestCase.tree_doc.delete()
|
||||
|
|
|
|||
|
|
@ -66,9 +66,6 @@ def make_boilerplate(dest, app_name):
|
|||
with open(os.path.join(dest, hooks.app_name, ".gitignore"), "w") as f:
|
||||
f.write(frappe.as_unicode(gitignore_template.format(app_name = hooks.app_name)))
|
||||
|
||||
with open(os.path.join(dest, hooks.app_name, "setup.py"), "w") as f:
|
||||
f.write(frappe.as_unicode(setup_template.format(**hooks)))
|
||||
|
||||
with open(os.path.join(dest, hooks.app_name, "requirements.txt"), "w") as f:
|
||||
f.write("# frappe -- https://github.com/frappe/frappe is installed via 'bench init'")
|
||||
|
||||
|
|
@ -82,6 +79,14 @@ def make_boilerplate(dest, app_name):
|
|||
with open(os.path.join(dest, hooks.app_name, hooks.app_name, "modules.txt"), "w") as f:
|
||||
f.write(frappe.as_unicode(hooks.app_title))
|
||||
|
||||
# These values could contain quotes and can break string declarations
|
||||
# So escaping them before setting variables in setup.py and hooks.py
|
||||
for key in ("app_publisher", "app_description", "app_license"):
|
||||
hooks[key] = hooks[key].replace("\\", "\\\\").replace("'", "\\'").replace("\"", "\\\"")
|
||||
|
||||
with open(os.path.join(dest, hooks.app_name, "setup.py"), "w") as f:
|
||||
f.write(frappe.as_unicode(setup_template.format(**hooks)))
|
||||
|
||||
with open(os.path.join(dest, hooks.app_name, hooks.app_name, "hooks.py"), "w") as f:
|
||||
f.write(frappe.as_unicode(hooks_template.format(**hooks)))
|
||||
|
||||
|
|
@ -328,18 +333,18 @@ def get_data():
|
|||
|
||||
setup_template = """from setuptools import setup, find_packages
|
||||
|
||||
with open('requirements.txt') as f:
|
||||
install_requires = f.read().strip().split('\\n')
|
||||
with open("requirements.txt") as f:
|
||||
install_requires = f.read().strip().split("\\n")
|
||||
|
||||
# get version from __version__ variable in {app_name}/__init__.py
|
||||
from {app_name} import __version__ as version
|
||||
|
||||
setup(
|
||||
name='{app_name}',
|
||||
name="{app_name}",
|
||||
version=version,
|
||||
description='{app_description}',
|
||||
author='{app_publisher}',
|
||||
author_email='{app_email}',
|
||||
description="{app_description}",
|
||||
author="{app_publisher}",
|
||||
author_email="{app_email}",
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ def sync_fixtures(app=None):
|
|||
if os.path.exists(frappe.get_app_path(app, "fixtures")):
|
||||
fixture_files = sorted(os.listdir(frappe.get_app_path(app, "fixtures")))
|
||||
for fname in fixture_files:
|
||||
if fname.endswith(".json") or fname.endswith(".csv"):
|
||||
if fname.endswith(".json"):
|
||||
import_doc(frappe.get_app_path(app, "fixtures", fname))
|
||||
|
||||
import_custom_scripts(app)
|
||||
|
|
|
|||
|
|
@ -231,9 +231,6 @@ def update_global_search(doc):
|
|||
if frappe.local.conf.get('disable_global_search'):
|
||||
return
|
||||
|
||||
if frappe.local.conf.get('disable_global_search'):
|
||||
return
|
||||
|
||||
if doc.docstatus > 1 or (doc.meta.has_field("enabled") and not doc.get("enabled")) \
|
||||
or doc.get("disabled"):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ from frappe.website.utils import can_cache
|
|||
HOMEPAGE_PATHS = ('/', '/index', 'index')
|
||||
|
||||
class NotFoundPage(TemplatePage):
|
||||
def __init__(self, path, http_status_code):
|
||||
def __init__(self, path, http_status_code=None):
|
||||
self.request_path = path
|
||||
self.request_url = frappe.local.request.url if hasattr(frappe.local, 'request') else ''
|
||||
path = '404'
|
||||
http_status_code = 404
|
||||
http_status_code = http_status_code or 404
|
||||
super().__init__(path=path, http_status_code=http_status_code)
|
||||
|
||||
def can_render(self):
|
||||
|
|
|
|||
|
|
@ -4163,16 +4163,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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue