Merge branch 'develop' of https://github.com/frappe/frappe into rebrand-ui
This commit is contained in:
commit
4863d511ab
120 changed files with 1502 additions and 1645 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -188,4 +188,7 @@ typings/
|
|||
|
||||
# cypress
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
cypress/videos
|
||||
|
||||
# JetBrains IDEs
|
||||
.idea/
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ def init(site, sites_path=None, new_site=False):
|
|||
local.site = site
|
||||
local.sites_path = sites_path
|
||||
local.site_path = os.path.join(sites_path, site)
|
||||
local.all_apps = None
|
||||
|
||||
local.request_ip = None
|
||||
local.response = _dict({"docs":[]})
|
||||
|
|
@ -231,8 +232,7 @@ def get_site_config(sites_path=None, site_path=None):
|
|||
if os.path.exists(site_config):
|
||||
config.update(get_file_json(site_config))
|
||||
elif local.site and not local.flags.new_site:
|
||||
print("Site {0} does not exist".format(local.site))
|
||||
sys.exit(1)
|
||||
raise IncorrectSitePath("{0} does not exist".format(local.site))
|
||||
|
||||
return _dict(config)
|
||||
|
||||
|
|
@ -300,7 +300,7 @@ def log(msg):
|
|||
|
||||
debug_log.append(as_unicode(msg))
|
||||
|
||||
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False, primary_action=None, is_minimizable=None):
|
||||
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False, primary_action=None, is_minimizable=None, wide=None):
|
||||
"""Print a message to the user (via HTTP response).
|
||||
Messages are sent in the `__server_messages` property in the
|
||||
response JSON and shown in a pop-up / modal.
|
||||
|
|
@ -310,6 +310,8 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
|
|||
:param raise_exception: [optional] Raise given exception and show message.
|
||||
:param as_table: [optional] If `msg` is a list of lists, render as HTML table.
|
||||
:param primary_action: [optional] Bind a primary server/client side action.
|
||||
:param is_minimizable: [optional] Allow users to minimize the modal
|
||||
:param wide: [optional] Show wide modal
|
||||
"""
|
||||
from frappe.utils import encode
|
||||
|
||||
|
|
@ -367,6 +369,9 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
|
|||
if primary_action:
|
||||
out.primary_action = primary_action
|
||||
|
||||
if wide:
|
||||
out.wide = wide
|
||||
|
||||
message_log.append(json.dumps(out))
|
||||
|
||||
if raise_exception and hasattr(raise_exception, '__name__'):
|
||||
|
|
@ -388,12 +393,12 @@ def clear_last_message():
|
|||
if len(local.message_log) > 0:
|
||||
local.message_log = local.message_log[:-1]
|
||||
|
||||
def throw(msg, exc=ValidationError, title=None, is_minimizable=None):
|
||||
def throw(msg, exc=ValidationError, title=None, is_minimizable=None, wide=None):
|
||||
"""Throw execption and show message (`msgprint`).
|
||||
|
||||
:param msg: Message.
|
||||
:param exc: Exception class. Default `frappe.ValidationError`"""
|
||||
msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable)
|
||||
msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable, wide=wide)
|
||||
|
||||
def emit_js(js, user=False, **kwargs):
|
||||
if user == False:
|
||||
|
|
@ -436,12 +441,8 @@ def get_roles(username=None):
|
|||
"""Returns roles of current user."""
|
||||
if not local.session:
|
||||
return ["Guest"]
|
||||
|
||||
if username:
|
||||
import frappe.permissions
|
||||
return frappe.permissions.get_roles(username)
|
||||
else:
|
||||
return get_user().get_roles()
|
||||
import frappe.permissions
|
||||
return frappe.permissions.get_roles(username or local.session.user)
|
||||
|
||||
def get_request_header(key, default=None):
|
||||
"""Return HTTP request header.
|
||||
|
|
@ -921,10 +922,13 @@ def get_installed_apps(sort=False, frappe_last=False):
|
|||
if not db:
|
||||
connect()
|
||||
|
||||
if not local.all_apps:
|
||||
local.all_apps = get_all_apps(True)
|
||||
|
||||
installed = json.loads(db.get_global("installed_apps") or "[]")
|
||||
|
||||
if sort:
|
||||
installed = [app for app in get_all_apps(True) if app in installed]
|
||||
installed = [app for app in local.all_apps if app in installed]
|
||||
|
||||
if frappe_last:
|
||||
if 'frappe' in installed:
|
||||
|
|
@ -1559,10 +1563,10 @@ def get_doctype_app(doctype):
|
|||
|
||||
loggers = {}
|
||||
log_level = None
|
||||
def logger(module=None, with_more_info=False):
|
||||
def logger(module=None, with_more_info=False, allow_site=True, filter=None, max_size=100_000, file_count=20):
|
||||
'''Returns a python logger that uses StreamHandler'''
|
||||
from frappe.utils.logger import get_logger
|
||||
return get_logger(module=module, with_more_info=with_more_info)
|
||||
return get_logger(module=module, with_more_info=with_more_info, allow_site=allow_site, filter=filter, max_size=max_size, file_count=file_count)
|
||||
|
||||
def log_error(message=None, title=_("Error")):
|
||||
'''Log error to Error Log'''
|
||||
|
|
@ -1707,3 +1711,7 @@ def mock(type, size=1, locale='en'):
|
|||
|
||||
from frappe.chat.util import squashify
|
||||
return squashify(results)
|
||||
|
||||
def validate_and_sanitize_search_inputs(fn):
|
||||
from frappe.desk.search import validate_and_sanitize_search_inputs as func
|
||||
return func(fn)
|
||||
|
|
|
|||
|
|
@ -99,15 +99,16 @@ def application(request):
|
|||
frappe.monitor.stop(response)
|
||||
frappe.recorder.dump()
|
||||
|
||||
frappe.logger("frappe.web").info({
|
||||
"site": get_site_name(request.host),
|
||||
"remote_addr": getattr(request, "remote_addr", "NOTFOUND"),
|
||||
"base_url": getattr(request, "base_url", "NOTFOUND"),
|
||||
"full_path": getattr(request, "full_path", "NOTFOUND"),
|
||||
"method": getattr(request, "method", "NOTFOUND"),
|
||||
"scheme": getattr(request, "scheme", "NOTFOUND"),
|
||||
"http_status_code": getattr(response, "status_code", "NOTFOUND")
|
||||
})
|
||||
if hasattr(frappe.local, 'conf') and frappe.local.conf.enable_frappe_logger:
|
||||
frappe.logger("frappe.web", allow_site=frappe.local.site).info({
|
||||
"site": get_site_name(request.host),
|
||||
"remote_addr": getattr(request, "remote_addr", "NOTFOUND"),
|
||||
"base_url": getattr(request, "base_url", "NOTFOUND"),
|
||||
"full_path": getattr(request, "full_path", "NOTFOUND"),
|
||||
"method": getattr(request, "method", "NOTFOUND"),
|
||||
"scheme": getattr(request, "scheme", "NOTFOUND"),
|
||||
"http_status_code": getattr(response, "status_code", "NOTFOUND")
|
||||
})
|
||||
|
||||
if response and hasattr(frappe.local, 'rate_limiter'):
|
||||
response.headers.extend(frappe.local.rate_limiter.headers())
|
||||
|
|
@ -256,9 +257,11 @@ def serve(port=8000, profile=False, no_reload=False, no_threading=False, site=No
|
|||
'SERVER_NAME': 'localhost:8000'
|
||||
}
|
||||
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.propagate = False
|
||||
|
||||
in_test_env = os.environ.get('CI')
|
||||
if in_test_env:
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR)
|
||||
|
||||
run_simple('0.0.0.0', int(port), application,
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ class CookieManager:
|
|||
self.set_cookie("country", frappe.session.session_country)
|
||||
|
||||
def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Lax"):
|
||||
if not secure:
|
||||
if not secure and hasattr(frappe.local, 'request'):
|
||||
secure = frappe.local.request.scheme == "https"
|
||||
self.cookies[key] = {
|
||||
"value": value,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"hidden": 0,
|
||||
"label": "Tools",
|
||||
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
|
||||
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
|
|
|
|||
|
|
@ -374,6 +374,7 @@ def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = None, e
|
|||
|
||||
# method for reference_doctype filter
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_auto_repeat_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
res = frappe.db.get_all('Property Setter', {
|
||||
'property': 'allow_auto_repeat',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ def get_data():
|
|||
"name": "Stripe Settings",
|
||||
"description": _("Stripe payment gateway settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Paytm Settings",
|
||||
"description": _("Paytm payment gateway settings"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ def delete_contact_and_address(doctype, docname):
|
|||
if len(doc.links)==1:
|
||||
doc.delete()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not txt: txt = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -230,6 +230,8 @@ def get_company_address(company):
|
|||
|
||||
return ret
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def address_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from frappe.desk.reportview import get_match_cond
|
||||
|
||||
|
|
@ -237,16 +239,17 @@ def address_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
link_name = filters.pop('link_name')
|
||||
|
||||
condition = ""
|
||||
for fieldname, value in iteritems(filters):
|
||||
condition += " and {field}={value}".format(
|
||||
field=fieldname,
|
||||
value=value
|
||||
)
|
||||
|
||||
meta = frappe.get_meta("Address")
|
||||
for fieldname, value in iteritems(filters):
|
||||
if meta.get_field(fieldname) or fieldname in frappe.db.DEFAULT_COLUMNS:
|
||||
condition += " and {field}={value}".format(
|
||||
field=fieldname,
|
||||
value=frappe.db.escape(value))
|
||||
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
if searchfield:
|
||||
if searchfield and (meta.get_field(searchfield)\
|
||||
or searchfield in frappe.db.DEFAULT_COLUMNS):
|
||||
searchfields.append(searchfield)
|
||||
|
||||
search_condition = ''
|
||||
|
|
@ -289,4 +292,4 @@ def get_condensed_address(doc):
|
|||
return ", ".join([doc.get(d) for d in fields if doc.get(d)])
|
||||
|
||||
def update_preferred_address(address, field):
|
||||
frappe.db.set_value('Address', address, field, 0)
|
||||
frappe.db.set_value('Address', address, field, 0)
|
||||
|
|
|
|||
|
|
@ -182,19 +182,18 @@ def update_contact(doc, method):
|
|||
contact.flags.ignore_mandatory = True
|
||||
contact.save(ignore_permissions=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def contact_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from frappe.desk.reportview import get_match_cond
|
||||
|
||||
if not frappe.get_meta("Contact").get_field(searchfield)\
|
||||
or searchfield not in frappe.db.DEFAULT_COLUMNS:
|
||||
return []
|
||||
|
||||
link_doctype = filters.pop('link_doctype')
|
||||
link_name = filters.pop('link_name')
|
||||
|
||||
condition = ""
|
||||
for fieldname, value in iteritems(filters):
|
||||
condition += " and {field}={value}".format(
|
||||
field=fieldname,
|
||||
value=value
|
||||
)
|
||||
|
||||
return frappe.db.sql("""select
|
||||
`tabContact`.name, `tabContact`.first_name, `tabContact`.last_name
|
||||
from
|
||||
|
|
@ -209,9 +208,7 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
order by
|
||||
if(locate(%(_txt)s, `tabContact`.name), locate(%(_txt)s, `tabContact`.name), 99999),
|
||||
`tabContact`.idx desc, `tabContact`.name
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
mcond=get_match_cond(doctype),
|
||||
key=searchfield), {
|
||||
limit %(start)s, %(page_len)s """.format(mcond=get_match_cond(doctype), key=searchfield), {
|
||||
'txt': '%' + txt + '%',
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None)
|
|||
|
||||
if doc.sender:
|
||||
# combine for sending to get the format 'Jane <jane@example.com>'
|
||||
doc.sender = formataddr([doc.sender_full_name, doc.sender])
|
||||
doc.sender = get_formatted_email(doc.sender_full_name, mail=doc.sender)
|
||||
|
||||
doc.attachments = []
|
||||
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ frappe.ui.form.on('Data Import', {
|
|||
},
|
||||
|
||||
show_import_warnings(frm, preview_data) {
|
||||
let columns = preview_data.columns;
|
||||
let warnings = JSON.parse(frm.doc.template_warnings || '[]');
|
||||
warnings = warnings.concat(preview_data.warnings || []);
|
||||
|
||||
|
|
@ -367,11 +368,13 @@ frappe.ui.form.on('Data Import', {
|
|||
.map(warning => {
|
||||
let header = '';
|
||||
if (warning.col) {
|
||||
header = __('Column {0}', [warning.col]);
|
||||
let column_number = `<span class="text-uppercase">${__('Column {0}', [warning.col])}</span>`;
|
||||
let column_header = columns[warning.col].header_title;
|
||||
header = `${column_number} (${column_header})`;
|
||||
}
|
||||
return `
|
||||
<div class="warning" data-col="${warning.col}">
|
||||
<h5 class="text-uppercase">${header}</h5>
|
||||
<h5>${header}</h5>
|
||||
<div class="body">${warning.message}</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ frappe.listview_settings['Data Import'] = {
|
|||
get_indicator: function(doc) {
|
||||
var colors = {
|
||||
'Pending': 'orange',
|
||||
'Not Started': 'orange',
|
||||
'Partial Success': 'orange',
|
||||
'Success': 'green',
|
||||
'In Progress': 'orange',
|
||||
|
|
@ -26,6 +27,9 @@ frappe.listview_settings['Data Import'] = {
|
|||
if (imports_in_progress.includes(doc.name)) {
|
||||
status = 'In Progress';
|
||||
}
|
||||
if (status == 'Pending') {
|
||||
status = 'Not Started';
|
||||
}
|
||||
return [__(status), colors[status], 'status,=,' + doc.status];
|
||||
},
|
||||
formatters: {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import io
|
|||
import frappe
|
||||
import timeit
|
||||
import json
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, update_progress_bar, cstr
|
||||
from frappe.utils.csvutils import read_csv_content, get_csv_content_from_google_sheets
|
||||
|
|
@ -233,7 +233,7 @@ class Importer:
|
|||
return updated_doc
|
||||
else:
|
||||
# throw if no changes
|
||||
frappe.throw('No changes to update')
|
||||
frappe.throw("No changes to update")
|
||||
|
||||
def get_eta(self, current, total, processing_time):
|
||||
self.last_eta = getattr(self, "last_eta", 0)
|
||||
|
|
@ -322,7 +322,7 @@ class ImportFile:
|
|||
if isinstance(file, frappe.string_types):
|
||||
if frappe.db.exists("File", {"file_url": file}):
|
||||
self.file_doc = frappe.get_doc("File", {"file_url": file})
|
||||
elif 'docs.google.com/spreadsheets' in file:
|
||||
elif "docs.google.com/spreadsheets" in file:
|
||||
self.google_sheets_url = file
|
||||
elif os.path.exists(file):
|
||||
self.file_path = file
|
||||
|
|
@ -348,7 +348,7 @@ class ImportFile:
|
|||
|
||||
elif self.google_sheets_url:
|
||||
content = get_csv_content_from_google_sheets(self.google_sheets_url)
|
||||
extension = 'csv'
|
||||
extension = "csv"
|
||||
|
||||
if not content:
|
||||
frappe.throw(_("Invalid or corrupted content for import"))
|
||||
|
|
@ -602,12 +602,20 @@ class Row:
|
|||
|
||||
is_table = frappe.get_meta(doctype).istable
|
||||
is_update = self.import_type == UPDATE
|
||||
if is_table and is_update and doc.get("name") in INVALID_VALUES:
|
||||
# for table rows being inserted in update
|
||||
# create a new doc with defaults set
|
||||
new_doc = frappe.new_doc(doctype, as_dict=True)
|
||||
new_doc.update(doc)
|
||||
doc = new_doc
|
||||
if is_table and is_update:
|
||||
# check if the row already exists
|
||||
# if yes, fetch the original doc so that it is not updated
|
||||
# if no, create a new doc
|
||||
id_field = get_id_field(doctype)
|
||||
id_value = doc.get(id_field.fieldname)
|
||||
if id_value and frappe.db.exists(doctype, id_value):
|
||||
doc = frappe.get_doc(doctype, id_value)
|
||||
else:
|
||||
# for table rows being inserted in update
|
||||
# create a new doc with defaults set
|
||||
new_doc = frappe.new_doc(doctype, as_dict=True)
|
||||
new_doc.update(doc)
|
||||
doc = new_doc
|
||||
|
||||
self.check_mandatory_fields(doctype, doc, table_df)
|
||||
return doc
|
||||
|
|
@ -615,16 +623,12 @@ class Row:
|
|||
def validate_value(self, value, col):
|
||||
df = col.df
|
||||
if df.fieldtype == "Select":
|
||||
select_options = [d for d in (df.options or '').split('\n') if d]
|
||||
select_options = get_select_options(df)
|
||||
if select_options and value not in select_options:
|
||||
options_string = ", ".join([frappe.bold(d) for d in select_options])
|
||||
msg = _("Value must be one of {0}").format(options_string)
|
||||
self.warnings.append(
|
||||
{
|
||||
"row": self.row_number,
|
||||
"field": df_as_json(df),
|
||||
"message": msg,
|
||||
}
|
||||
{"row": self.row_number, "field": df_as_json(df), "message": msg,}
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -635,11 +639,7 @@ class Row:
|
|||
frappe.bold(value), frappe.bold(df.options)
|
||||
)
|
||||
self.warnings.append(
|
||||
{
|
||||
"row": self.row_number,
|
||||
"field": df_as_json(df),
|
||||
"message": msg,
|
||||
}
|
||||
{"row": self.row_number, "field": df_as_json(df), "message": msg,}
|
||||
)
|
||||
return
|
||||
elif df.fieldtype in ["Date", "Datetime"]:
|
||||
|
|
@ -668,7 +668,7 @@ class Row:
|
|||
|
||||
def parse_value(self, value, col):
|
||||
df = col.df
|
||||
if isinstance(value, datetime) and df.fieldtype in ["Date", "Datetime"]:
|
||||
if isinstance(value, (datetime, date)) and df.fieldtype in ["Date", "Datetime"]:
|
||||
return value
|
||||
|
||||
value = cstr(value)
|
||||
|
|
@ -689,7 +689,7 @@ class Row:
|
|||
return value
|
||||
|
||||
def get_date(self, value, column):
|
||||
if isinstance(value, datetime):
|
||||
if isinstance(value, (datetime, date)):
|
||||
return value
|
||||
|
||||
date_format = column.date_format
|
||||
|
|
@ -786,9 +786,7 @@ class Header(Row):
|
|||
for j, header in enumerate(row):
|
||||
column_values = [get_item_at_index(r, j) for r in raw_data]
|
||||
map_to_field = column_to_field_map.get(str(j))
|
||||
column = Column(
|
||||
j, header, self.doctype, column_values, map_to_field, self.seen
|
||||
)
|
||||
column = Column(j, header, self.doctype, column_values, map_to_field, self.seen)
|
||||
self.seen.append(header)
|
||||
self.columns.append(column)
|
||||
|
||||
|
|
@ -918,13 +916,20 @@ class Column:
|
|||
self.skip_import = skip_import
|
||||
|
||||
def guess_date_format_for_column(self):
|
||||
""" Guesses date format for a column by parsing all the values in the column,
|
||||
"""Guesses date format for a column by parsing all the values in the column,
|
||||
getting the date format and then returning the one which has the maximum frequency
|
||||
"""
|
||||
|
||||
date_formats = [
|
||||
frappe.utils.guess_date_format(d) for d in self.column_values if isinstance(d, str)
|
||||
]
|
||||
def guess_date_format(d):
|
||||
if isinstance(d, (datetime, date)):
|
||||
if self.df.fieldtype == "Date":
|
||||
return "%Y-%m-%d"
|
||||
if self.df.fieldtype == "Datetime":
|
||||
return "%Y-%m-%d %H:%M:%S"
|
||||
if isinstance(d, str):
|
||||
return frappe.utils.guess_date_format(d)
|
||||
|
||||
date_formats = [guess_date_format(d) for d in self.column_values]
|
||||
date_formats = [d for d in date_formats if d]
|
||||
if not date_formats:
|
||||
return
|
||||
|
|
@ -955,28 +960,61 @@ class Column:
|
|||
if not self.df:
|
||||
return
|
||||
|
||||
if self.df.fieldtype == 'Link':
|
||||
if self.skip_import:
|
||||
return
|
||||
|
||||
if self.df.fieldtype == "Link":
|
||||
# find all values that dont exist
|
||||
values = list(set([cstr(v) for v in self.column_values[1:] if v]))
|
||||
exists = [d.name for d in frappe.db.get_all(self.df.options, filters={'name': ('in', values)})]
|
||||
exists = [
|
||||
d.name for d in frappe.db.get_all(self.df.options, filters={"name": ("in", values)})
|
||||
]
|
||||
not_exists = list(set(values) - set(exists))
|
||||
if not_exists:
|
||||
missing_values = ', '.join(not_exists)
|
||||
self.warnings.append({
|
||||
'col': self.column_number,
|
||||
'message': "The following values do not exist for {}: {}".format(self.df.options, missing_values),
|
||||
'type': 'warning'
|
||||
})
|
||||
missing_values = ", ".join(not_exists)
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": (
|
||||
"The following values do not exist for {}: {}".format(
|
||||
self.df.options, missing_values
|
||||
)
|
||||
),
|
||||
"type": "warning",
|
||||
}
|
||||
)
|
||||
elif self.df.fieldtype in ("Date", "Time", "Datetime"):
|
||||
# guess date format
|
||||
self.date_format = self.guess_date_format_for_column()
|
||||
if not self.date_format:
|
||||
self.date_format = '%Y-%m-%d'
|
||||
self.warnings.append({
|
||||
'col': self.column_number,
|
||||
'message': _("Date format could not determined from the values in this column. Defaulting to yyyy-mm-dd."),
|
||||
'type': 'info'
|
||||
})
|
||||
self.date_format = "%Y-%m-%d"
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": _(
|
||||
"Date format could not be determined from the values in"
|
||||
" this column. Defaulting to yyyy-mm-dd."
|
||||
),
|
||||
"type": "info",
|
||||
}
|
||||
)
|
||||
elif self.df.fieldtype == "Select":
|
||||
options = get_select_options(self.df)
|
||||
if options:
|
||||
values = list(set([cstr(v) for v in self.column_values[1:] if v]))
|
||||
invalid = list(set(values) - set(options))
|
||||
if invalid:
|
||||
valid_values = ", ".join([frappe.bold(o) for o in options])
|
||||
invalid_values = ", ".join([frappe.bold(i) for i in invalid])
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": (
|
||||
"The following values are invalid: {0}. Values must be"
|
||||
" one of {1}".format(invalid_values, valid_values)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
def as_dict(self):
|
||||
d = frappe._dict()
|
||||
|
|
@ -987,7 +1025,7 @@ class Column:
|
|||
d.map_to_field = self.map_to_field
|
||||
d.date_format = self.date_format
|
||||
d.df = self.df
|
||||
if hasattr(self.df, 'is_child_table_field'):
|
||||
if hasattr(self.df, "is_child_table_field"):
|
||||
d.is_child_table_field = self.df.is_child_table_field
|
||||
d.child_table_df = self.df.child_table_df
|
||||
d.skip_import = self.skip_import
|
||||
|
|
@ -1067,7 +1105,7 @@ def build_fields_dict_for_column_matching(parent_doctype):
|
|||
# other fields
|
||||
fields = get_standard_fields(doctype) + frappe.get_meta(doctype).fields
|
||||
for df in fields:
|
||||
label = (df.label or '').strip()
|
||||
label = (df.label or "").strip()
|
||||
fieldtype = df.fieldtype or "Data"
|
||||
parent = df.parent or parent_doctype
|
||||
if fieldtype not in no_value_fields:
|
||||
|
|
@ -1161,12 +1199,17 @@ def get_user_format(date_format):
|
|||
.replace("%d", "dd")
|
||||
)
|
||||
|
||||
|
||||
def df_as_json(df):
|
||||
return {
|
||||
'fieldname': df.fieldname,
|
||||
'fieldtype': df.fieldtype,
|
||||
'label': df.label,
|
||||
'options': df.options,
|
||||
'parent': df.parent,
|
||||
'default': df.default
|
||||
"fieldname": df.fieldname,
|
||||
"fieldtype": df.fieldtype,
|
||||
"label": df.label,
|
||||
"options": df.options,
|
||||
"parent": df.parent,
|
||||
"default": df.default,
|
||||
}
|
||||
|
||||
|
||||
def get_select_options(df):
|
||||
return [d for d in (df.options or "").split("\n") if d]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
frappe.ui.form.on('Report', {
|
||||
refresh: function(frm) {
|
||||
if(!frappe.boot.developer_mode && frappe.session.user !== 'Administrator') {
|
||||
if (frm.doc.is_standard === "Yes" && !frappe.boot.developer_mode) {
|
||||
// make the document read-only
|
||||
frm.set_read_only();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,9 +52,10 @@ class TestServerScript(unittest.TestCase):
|
|||
|
||||
frappe.db.commit()
|
||||
|
||||
# @classmethod
|
||||
# def tearDownClass(cls):
|
||||
# frappe.db.sql('truncate `tabServer Script`')
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql('truncate `tabServer Script`')
|
||||
|
||||
def setUp(self):
|
||||
frappe.cache().delete_value('server_script_map')
|
||||
|
|
|
|||
|
|
@ -812,6 +812,7 @@ def reset_password(user):
|
|||
return 'not found'
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def user_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from frappe.desk.reportview import get_match_cond
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ def user_permission_exists(user, allow, for_value, applicable_for=None):
|
|||
|
||||
return has_same_user_permission
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_applicable_for_doctype_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
linked_doctypes_map = get_linked_doctypes(doctype, True)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Video', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"creation": "2018-10-17 05:47:13.087395",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"provider",
|
||||
"url",
|
||||
"column_break_4",
|
||||
"publish_date",
|
||||
"duration",
|
||||
"section_break_7",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "provider",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Provider",
|
||||
"options": "YouTube\nVimeo",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "url",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "URL",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "publish_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Publish Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "duration",
|
||||
"fieldtype": "Data",
|
||||
"label": "Duration"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-22 12:09:49.057403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Video",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Video(Document):
|
||||
pass
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
.dashboard {
|
||||
margin-top: var(--margin-xs);
|
||||
padding: 1px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.dashboard .grid-col-2 {
|
||||
column-gap: 20px;
|
||||
row-gap: 20px;
|
||||
}
|
||||
|
||||
/* .restricted-button {
|
||||
cursor: default;
|
||||
position: relative;
|
||||
right: -5px;
|
||||
} */
|
||||
|
|
@ -26,13 +26,6 @@ class Dashboard {
|
|||
</div>`).appendTo(this.wrapper.find(".page-content").empty());
|
||||
this.container = this.wrapper.find(".dashboard-graph");
|
||||
this.page = wrapper.page;
|
||||
|
||||
this.page.set_title_sub(
|
||||
$(`<button class="restricted-button">
|
||||
<span class="octicon octicon-lock"></span>
|
||||
<span>${__('Restricted')}</span>
|
||||
</button>`)
|
||||
);
|
||||
}
|
||||
|
||||
show() {
|
||||
|
|
@ -172,19 +165,26 @@ class Dashboard {
|
|||
set_dropdown() {
|
||||
this.page.clear_menu();
|
||||
|
||||
this.page.add_menu_item('Edit...', () => {
|
||||
this.page.add_menu_item(__('Edit'), () => {
|
||||
frappe.set_route('Form', 'Dashboard', frappe.dashboard.dashboard_name);
|
||||
}, 1);
|
||||
});
|
||||
|
||||
this.page.add_menu_item('New...', () => {
|
||||
this.page.add_menu_item(__('New'), () => {
|
||||
frappe.new_doc('Dashboard');
|
||||
}, 1);
|
||||
});
|
||||
|
||||
frappe.db.get_list("Dashboard").then(dashboards => {
|
||||
this.page.add_menu_item(__('Refresh All'), () => {
|
||||
this.chart_group &&
|
||||
this.chart_group.widgets_list.forEach(chart => chart.refresh());
|
||||
this.number_card_group &&
|
||||
this.number_card_group.widgets_list.forEach(card => card.render_card());
|
||||
});
|
||||
|
||||
frappe.db.get_list('Dashboard').then(dashboards => {
|
||||
dashboards.map(dashboard => {
|
||||
let name = dashboard.name;
|
||||
if(name != this.dashboard_name){
|
||||
this.page.add_menu_item(name, () => frappe.set_route("dashboard", name));
|
||||
this.page.add_menu_item(name, () => frappe.set_route("dashboard", name), 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ def get_columns_and_fields(doctype):
|
|||
|
||||
return columns, fields
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def query_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
user = filters.get("user")
|
||||
user_perms = frappe.utils.user.UserPermissions(user)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ def create_plan():
|
|||
'connector_name': 'Local Connector',
|
||||
'connector_type': 'Frappe',
|
||||
# connect to same host.
|
||||
'hostname': frappe.conf.host_name,
|
||||
'hostname': frappe.conf.host_name or frappe.utils.get_site_url(frappe.local.site),
|
||||
'username': 'Administrator',
|
||||
'password': 'admin'
|
||||
'password': frappe.conf.get("admin_password") or 'admin'
|
||||
}).insert(ignore_if_duplicate=True)
|
||||
|
|
|
|||
|
|
@ -221,6 +221,8 @@ class Workspace:
|
|||
incomplete_dependencies = [d for d in item.dependencies if not _doctype_contains_a_record(d)]
|
||||
if len(incomplete_dependencies):
|
||||
item.incomplete_dependencies = incomplete_dependencies
|
||||
else:
|
||||
item.incomplete_dependencies = ""
|
||||
|
||||
if item.onboard:
|
||||
# Mark Spotlights for initial
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ frappe.ui.form.on('Dashboard', {
|
|||
refresh: function(frm) {
|
||||
frm.add_custom_button(__("Show Dashboard"), () => frappe.set_route('dashboard', frm.doc.name));
|
||||
|
||||
if (!frappe.boot.developer_mode) {
|
||||
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
frm.disable_form();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@
|
|||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard"
|
||||
"label": "Is Standard",
|
||||
"read_only_depends_on": "eval: !frappe.boot.developer_mode"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.is_standard",
|
||||
|
|
@ -66,7 +67,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-07-10 17:48:19.468813",
|
||||
"modified": "2020-07-23 11:05:41.890459",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard",
|
||||
|
|
|
|||
|
|
@ -23,43 +23,20 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
frm.chart_filters = null;
|
||||
|
||||
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
frm.set_df_property('chart_options_section', 'hidden', 1);
|
||||
frm.disable_form();
|
||||
}
|
||||
|
||||
frm.add_custom_button('Add Chart to Dashboard', () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Add to Dashboard'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Dashboard'),
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'dashboard',
|
||||
options: 'Dashboard',
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
values.chart_name = frm.doc.chart_name;
|
||||
frappe.xcall(
|
||||
'frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard',
|
||||
{args: values}
|
||||
).then(()=> {
|
||||
let dashboard_route_html =
|
||||
`<a href = "#dashboard/${values.dashboard}">${values.dashboard}</a>`;
|
||||
let message =
|
||||
__(`Dashboard Chart ${values.chart_name} add to Dashboard ` + dashboard_route_html);
|
||||
|
||||
frappe.msgprint(message);
|
||||
});
|
||||
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog(
|
||||
frm.doc.name,
|
||||
'Dashboard Chart',
|
||||
'frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard'
|
||||
);
|
||||
|
||||
if (!frm.doc.chart_name) {
|
||||
frappe.msgprint(__('Please create chart first'));
|
||||
} else {
|
||||
d.show();
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -79,11 +56,6 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
if (frm.doc.report_name) {
|
||||
frm.trigger('set_chart_report_filters');
|
||||
}
|
||||
|
||||
if (!frappe.boot.developer_mode) {
|
||||
frm.set_df_property("custom_options", "hidden", 1);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
is_standard: function(frm) {
|
||||
|
|
@ -147,6 +119,7 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
frm.set_df_property('x_field', 'options', []);
|
||||
frm.set_value('filters_json', '{}');
|
||||
frm.set_value('dynamic_filters_json', '{}');
|
||||
frm.set_value('use_report_chart', 0);
|
||||
frm.trigger('set_chart_report_filters');
|
||||
},
|
||||
|
||||
|
|
@ -175,8 +148,8 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
|
||||
set_chart_field_options: function(frm) {
|
||||
let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null;
|
||||
if (frm.doc.dynamic_filters_json.length > 2) {
|
||||
filters = {...filters, ...JSON.parse(frm.doc.dynamic_filters_json)};
|
||||
if (frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2) {
|
||||
filters = frappe.dashboard_utils.get_all_filters(frm.doc);
|
||||
}
|
||||
frappe.xcall(
|
||||
'frappe.desk.query_report.run',
|
||||
|
|
@ -187,14 +160,11 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
}
|
||||
).then(data => {
|
||||
frm.report_data = data;
|
||||
if (!data.chart) {
|
||||
frm.set_value('is_custom', 0);
|
||||
frm.set_df_property('is_custom', 'hidden', 1);
|
||||
} else {
|
||||
frm.set_df_property('is_custom', 'hidden', 0);
|
||||
}
|
||||
let report_has_chart = Boolean(data.chart);
|
||||
|
||||
if (!frm.doc.is_custom) {
|
||||
frm.set_df_property('use_report_chart', 'hidden', !report_has_chart);
|
||||
|
||||
if (!frm.doc.use_report_chart) {
|
||||
if (data.result.length) {
|
||||
frm.field_options = frappe.report_utils.get_field_options_from_report(data.columns, data);
|
||||
frm.set_df_property('x_field', 'options', frm.field_options.non_numeric_fields);
|
||||
|
|
@ -315,7 +285,7 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
set_filters && frm.set_value('filters_json', JSON.stringify(filters));
|
||||
}
|
||||
|
||||
let fields;
|
||||
let fields = [];
|
||||
if (is_document_type) {
|
||||
fields = [
|
||||
{
|
||||
|
|
@ -340,7 +310,7 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
} else if (frm.chart_filters.length) {
|
||||
fields = frm.chart_filters.filter(f => f.fieldname);
|
||||
|
||||
fields.map( f => {
|
||||
fields.map(f => {
|
||||
if (filters[f.fieldname]) {
|
||||
let condition = '=';
|
||||
const filter_row =
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"chart_name",
|
||||
"chart_type",
|
||||
"report_name",
|
||||
"is_custom",
|
||||
"use_report_chart",
|
||||
"x_field",
|
||||
"y_axis",
|
||||
"source",
|
||||
|
|
@ -194,33 +194,27 @@
|
|||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.chart_type == 'Report' && doc.report_name && !doc.is_custom",
|
||||
"depends_on": "eval:doc.chart_type == 'Report' && doc.report_name && !doc.use_report_chart",
|
||||
"fieldname": "x_field",
|
||||
"fieldtype": "Select",
|
||||
"label": "X Field",
|
||||
"mandatory_depends_on": "eval: doc.report_name && !doc.is_custom"
|
||||
"mandatory_depends_on": "eval: doc.report_name && !doc.use_report_chart"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.chart_type === 'Report'",
|
||||
"fieldname": "report_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Report Name",
|
||||
"mandatory_depends_on": "eval:doc.chart_type === 'Report'",
|
||||
"options": "Report",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.report_name",
|
||||
"fieldname": "is_custom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Custom"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.chart_type == 'Report' && doc.report_name && !doc.is_custom",
|
||||
"depends_on": "eval:doc.chart_type == 'Report' && doc.report_name && !doc.use_report_chart",
|
||||
"fieldname": "y_axis",
|
||||
"fieldtype": "Table",
|
||||
"label": "Y Axis",
|
||||
"mandatory_depends_on": "eval:doc.report_name && !doc.is_custom",
|
||||
"mandatory_depends_on": "eval:doc.report_name && !doc.use_report_chart",
|
||||
"options": "Dashboard Chart Field"
|
||||
},
|
||||
{
|
||||
|
|
@ -247,8 +241,7 @@
|
|||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only_depends_on": "eval: !frappe.boot.developer_mode"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.is_standard",
|
||||
|
|
@ -256,28 +249,29 @@
|
|||
"fieldtype": "Link",
|
||||
"label": "Module",
|
||||
"mandatory_depends_on": "eval: doc.is_standard",
|
||||
"options": "Module Def",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Module Def"
|
||||
},
|
||||
{
|
||||
"fieldname": "dynamic_filters_json",
|
||||
"fieldtype": "Code",
|
||||
"label": "Dynamic Filters JSON",
|
||||
"options": "JSON",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"fieldname": "dynamic_filters_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Dynamic Filters",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Dynamic Filters"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.report_name",
|
||||
"fieldname": "use_report_chart",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Report Chart"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-07-10 16:09:47.102062",
|
||||
"modified": "2020-07-23 11:10:33.509497",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard Chart",
|
||||
|
|
|
|||
|
|
@ -28,15 +28,28 @@ def get_permission_query_conditions(user):
|
|||
if "System Manager" in roles:
|
||||
return None
|
||||
|
||||
allowed_doctypes = ['"%s"' % doctype for doctype in frappe.permissions.get_doctypes_with_read()]
|
||||
allowed_reports = ['"%s"' % key if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()]
|
||||
doctype_condition = False
|
||||
report_condition = False
|
||||
|
||||
allowed_doctypes = [frappe.db.escape(doctype) for doctype in frappe.permissions.get_doctypes_with_read()]
|
||||
allowed_reports = [frappe.db.escape(key) if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()]
|
||||
|
||||
if allowed_doctypes:
|
||||
doctype_condition = '`tabDashboard Chart`.`document_type` in ({allowed_doctypes})'.format(
|
||||
allowed_doctypes=','.join(allowed_doctypes))
|
||||
if allowed_reports:
|
||||
report_condition = '`tabDashboard Chart`.`report_name` in ({allowed_reports})'.format(
|
||||
allowed_reports=','.join(allowed_reports))
|
||||
|
||||
return '''
|
||||
`tabDashboard Chart`.`document_type` in ({allowed_doctypes})
|
||||
or `tabDashboard Chart`.`report_name` in ({allowed_reports})
|
||||
(`tabDashboard Chart`.`chart_type` in ('Count', 'Sum', 'Average')
|
||||
and {doctype_condition})
|
||||
or
|
||||
(`tabDashboard Chart`.`chart_type` = 'Report'
|
||||
and {report_condition})
|
||||
'''.format(
|
||||
allowed_doctypes=','.join(allowed_doctypes),
|
||||
allowed_reports=','.join(allowed_reports)
|
||||
doctype_condition=doctype_condition,
|
||||
report_condition=report_condition
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -128,7 +141,13 @@ def add_chart_to_dashboard(args):
|
|||
|
||||
dashboard = frappe.get_doc('Dashboard', args.dashboard)
|
||||
dashboard_link = frappe.new_doc('Dashboard Chart Link')
|
||||
dashboard_link.chart = args.chart_name
|
||||
dashboard_link.chart = args.chart_name or args.name
|
||||
|
||||
if args.set_standard and dashboard.is_standard:
|
||||
chart = frappe.get_doc('Dashboard Chart', dashboard_link.chart)
|
||||
chart.is_standard = 1
|
||||
chart.module = dashboard.module
|
||||
chart.save()
|
||||
|
||||
dashboard.append('charts', dashboard_link)
|
||||
dashboard.save()
|
||||
|
|
@ -338,6 +357,8 @@ def get_year_ending(date):
|
|||
# last day of this month
|
||||
return add_to_date(date, days=-1)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_charts_for_user(doctype, txt, searchfield, start, page_len, filters):
|
||||
or_filters = {'owner': frappe.session.user, 'is_public': 1}
|
||||
return frappe.db.get_list('Dashboard Chart',
|
||||
|
|
|
|||
|
|
@ -5,14 +5,23 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils.data import validate_json_string
|
||||
from frappe.modules.export_file import export_to_files
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DeskPage(Document):
|
||||
def validate(self):
|
||||
self.validate_cards_json()
|
||||
if (self.is_standard and not frappe.conf.developer_mode and not disable_saving_as_standard()):
|
||||
frappe.throw(_("You need to be in developer mode to edit this document"))
|
||||
|
||||
def validate_cards_json(self):
|
||||
for card in self.cards:
|
||||
try:
|
||||
validate_json_string(card.links)
|
||||
except frappe.ValidationError:
|
||||
frappe.throw(_("Invalid JSON in card links for {0}").format(frappe.bold(card.label)))
|
||||
|
||||
def on_update(self):
|
||||
if disable_saving_as_standard():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -32,40 +32,16 @@ frappe.ui.form.on('Number Card', {
|
|||
|
||||
create_add_to_dashboard_button: function(frm) {
|
||||
frm.add_custom_button('Add Card to Dashboard', () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Add to Dashboard'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Dashboard'),
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'dashboard',
|
||||
options: 'Dashboard',
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
values.name = frm.doc.name;
|
||||
frappe.xcall(
|
||||
'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard',
|
||||
{
|
||||
args: values
|
||||
}
|
||||
).then(()=> {
|
||||
let dashboard_route_html =
|
||||
`<a href = "#dashboard/${values.dashboard}">${values.dashboard}</a>`;
|
||||
let message =
|
||||
__(`Number Card ${values.name} add to Dashboard ` + dashboard_route_html);
|
||||
|
||||
frappe.msgprint(message);
|
||||
});
|
||||
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog(
|
||||
frm.doc.name,
|
||||
'Number Card',
|
||||
'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard'
|
||||
);
|
||||
|
||||
if (!frm.doc.name) {
|
||||
frappe.msgprint(__('Please create Card first'));
|
||||
} else {
|
||||
d.show();
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -140,6 +116,7 @@ frappe.ui.form.on('Number Card', {
|
|||
},
|
||||
|
||||
report_name: function(frm) {
|
||||
frm.filters = [];
|
||||
frm.set_value('filters_json', '{}');
|
||||
frm.set_value('dynamic_filters_json', '{}');
|
||||
frm.set_df_property('report_field', 'options', []);
|
||||
|
|
@ -215,8 +192,8 @@ frappe.ui.form.on('Number Card', {
|
|||
|
||||
set_report_field_options: function(frm) {
|
||||
let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null;
|
||||
if (frm.doc.dynamic_filters_json.length > 2) {
|
||||
filters = {...filters, ...JSON.parse(frm.doc.dynamic_filters_json)};
|
||||
if (frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2) {
|
||||
filters = frappe.dashboard_utils.get_all_filters(frm.doc);
|
||||
}
|
||||
frappe.xcall(
|
||||
'frappe.desk.query_report.run',
|
||||
|
|
@ -271,7 +248,7 @@ frappe.ui.form.on('Number Card', {
|
|||
set_filters && frm.set_value('filters_json', JSON.stringify(filters));
|
||||
}
|
||||
|
||||
let fields;
|
||||
let fields = [];
|
||||
if (is_document_type) {
|
||||
fields = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"allow_workflow": 1,
|
||||
"creation": "2020-04-15 18:06:39.444683",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
|
|
@ -116,7 +115,8 @@
|
|||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard"
|
||||
"label": "Is Standard",
|
||||
"read_only_depends_on": "eval: !frappe.boot.developer_mode"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.is_standard",
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-07-17 18:04:00.814756",
|
||||
"modified": "2020-07-23 11:11:03.391719",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Number Card",
|
||||
|
|
|
|||
|
|
@ -32,13 +32,17 @@ def get_permission_query_conditions(user=None):
|
|||
if "System Manager" in roles:
|
||||
return None
|
||||
|
||||
allowed_doctypes = ['"%s"' % doctype for doctype in frappe.permissions.get_doctypes_with_read()]
|
||||
doctype_condition = False
|
||||
|
||||
allowed_doctypes = [frappe.db.escape(doctype) for doctype in frappe.permissions.get_doctypes_with_read()]
|
||||
|
||||
if allowed_doctypes:
|
||||
doctype_condition = '`tabNumber Card`.`document_type` in ({allowed_doctypes})'.format(
|
||||
allowed_doctypes=','.join(allowed_doctypes))
|
||||
|
||||
return '''
|
||||
`tabNumber Card`.`document_type` in ({allowed_doctypes})
|
||||
'''.format(
|
||||
allowed_doctypes=','.join(allowed_doctypes)
|
||||
)
|
||||
{doctype_condition}
|
||||
'''.format(doctype_condition=doctype_condition)
|
||||
|
||||
def has_permission(doc, ptype, user):
|
||||
roles = frappe.get_roles(user)
|
||||
|
|
@ -124,11 +128,16 @@ def create_number_card(args):
|
|||
doc.insert(ignore_permissions=True)
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters):
|
||||
meta = frappe.get_meta(doctype)
|
||||
searchfields = meta.get_search_fields()
|
||||
search_conditions = []
|
||||
|
||||
if not frappe.db.exists('DocType', doctype):
|
||||
return
|
||||
|
||||
if txt:
|
||||
for field in searchfields:
|
||||
search_conditions.append('`tab{doctype}`.`{field}` like %(txt)s'.format(field=field, doctype=doctype, txt=txt))
|
||||
|
|
@ -172,5 +181,11 @@ def add_card_to_dashboard(args):
|
|||
dashboard_link = frappe.new_doc('Number Card Link')
|
||||
dashboard_link.card = args.name
|
||||
|
||||
if args.set_standard and dashboard.is_standard:
|
||||
card = frappe.get_doc('Number Card', dashboard_link.card)
|
||||
card.is_standard = 1
|
||||
card.module = dashboard.module
|
||||
card.save()
|
||||
|
||||
dashboard.append('cards', dashboard_link)
|
||||
dashboard.save()
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"creation": "2016-05-25 09:43:44.767581",
|
||||
"doctype": "DocType",
|
||||
|
|
@ -46,4 +47,4 @@
|
|||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,8 +67,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None)
|
|||
# Reordered columns
|
||||
columns = json.loads(report.custom_columns)
|
||||
|
||||
if report.report_type == 'Query Report':
|
||||
result = reorder_data_for_custom_columns(columns, query_columns, result)
|
||||
result = reorder_data_for_custom_columns(columns, query_columns, result, report.report_type)
|
||||
|
||||
result = add_data_to_custom_columns(columns, result)
|
||||
|
||||
|
|
@ -216,15 +215,21 @@ def add_data_to_custom_columns(columns, result):
|
|||
|
||||
return data
|
||||
|
||||
def reorder_data_for_custom_columns(custom_columns, columns, result):
|
||||
def reorder_data_for_custom_columns(custom_columns, columns, result, report_type):
|
||||
custom_column_labels = [col["label"] for col in custom_columns]
|
||||
|
||||
if report_type == 'Query Report':
|
||||
original_column_labels = [col.split(":")[0] for col in columns]
|
||||
else:
|
||||
original_column_labels = [col["label"] for col in columns]
|
||||
|
||||
reordered_result = []
|
||||
columns = [col.split(":")[0] for col in columns]
|
||||
|
||||
for res in result:
|
||||
r = []
|
||||
for col in custom_columns:
|
||||
for col_name in custom_column_labels:
|
||||
try:
|
||||
idx = columns.index(col.get("label"))
|
||||
idx = original_column_labels.index(col_name)
|
||||
r.append(res[idx])
|
||||
except ValueError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from frappe.handler import is_whitelisted
|
|||
from frappe import _
|
||||
from six import string_types
|
||||
import re
|
||||
import wrapt
|
||||
|
||||
UNTRANSLATED_DOCTYPES = ["DocType", "Role"]
|
||||
|
||||
|
|
@ -206,3 +207,15 @@ def scrub_custom_query(query, key, txt):
|
|||
if '%s' in query:
|
||||
query = query.replace('%s', ((txt or '') + '%'))
|
||||
return query
|
||||
|
||||
@wrapt.decorator
|
||||
def validate_and_sanitize_search_inputs(fn, instance, args, kwargs):
|
||||
kwargs.update(dict(zip(fn.__code__.co_varnames, args)))
|
||||
sanitize_searchfield(kwargs['searchfield'])
|
||||
kwargs['start'] = cint(kwargs['start'])
|
||||
kwargs['page_len'] = cint(kwargs['page_len'])
|
||||
|
||||
if kwargs['doctype'] and not frappe.db.exists('DocType', kwargs['doctype']):
|
||||
return []
|
||||
|
||||
return fn(**kwargs)
|
||||
|
|
@ -57,6 +57,8 @@ def relink(name, reference_doctype=None, reference_name=None):
|
|||
communication_type = "Communication" and
|
||||
name = %s""", (reference_doctype, reference_name, name))
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_communication_doctype(doctype, txt, searchfield, start, page_len, filters):
|
||||
user_perms = frappe.utils.user.UserPermissions(frappe.session.user)
|
||||
user_perms.build_permissions()
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ frappe.ui.form.on("Email Account", {
|
|||
enable_incoming: function(frm) {
|
||||
frm.doc.no_remaining = null; //perform full sync
|
||||
//frm.set_df_property("append_to", "reqd", frm.doc.enable_incoming);
|
||||
frm.trigger("warn_autoreply_on_incoming");
|
||||
},
|
||||
|
||||
enable_auto_reply: function(frm) {
|
||||
frm.trigger("warn_autoreply_on_incoming");
|
||||
},
|
||||
|
||||
notify_if_unreplied: function(frm) {
|
||||
|
|
@ -184,7 +189,18 @@ frappe.ui.form.on("Email Account", {
|
|||
read as well as unread message from server. This may also cause the duplication\
|
||||
of Communication (emails).");
|
||||
frappe.confirm(msg, null, function() {
|
||||
frm.set_value("email_sync_option", "ALL");
|
||||
frm.set_value("email_sync_option", "UNSEEN");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
warn_autoreply_on_incoming: function(frm) {
|
||||
if (frm.doc.enable_incoming && frm.doc.enable_auto_reply && frm.doc.__islocal) {
|
||||
var msg = __("Enabling auto reply on an incoming email account will send automated replies \
|
||||
to all the synchronized emails. Do you wish to continue?");
|
||||
frappe.confirm(msg, null, function() {
|
||||
frm.set_value("enable_auto_reply", 0);
|
||||
frappe.show_alert({message: __("Disabled Auto Reply"), indicator: "blue"});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ class EmailAccount(Document):
|
|||
email_server = None
|
||||
|
||||
if frappe.local.flags.in_test:
|
||||
incoming_mails = test_mails
|
||||
incoming_mails = test_mails or []
|
||||
else:
|
||||
email_sync_rule = self.build_email_sync_rule()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ from __future__ import unicode_literals
|
|||
import frappe, os
|
||||
import unittest, email
|
||||
|
||||
test_records = frappe.get_test_records('Email Account')
|
||||
from frappe.test_runner import make_test_records
|
||||
|
||||
make_test_records("User")
|
||||
make_test_records("Email Account")
|
||||
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.desk.form.load import get_attachments
|
||||
|
|
|
|||
|
|
@ -1,640 +1,166 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2012-08-02 15:17:28",
|
||||
"custom": 0,
|
||||
"description": "Email Queue records.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"sender",
|
||||
"recipients",
|
||||
"show_as_cc",
|
||||
"message",
|
||||
"status",
|
||||
"error",
|
||||
"message_id",
|
||||
"reference_doctype",
|
||||
"reference_name",
|
||||
"communication",
|
||||
"send_after",
|
||||
"priority",
|
||||
"add_unsubscribe_link",
|
||||
"unsubscribe_param",
|
||||
"unsubscribe_method",
|
||||
"expose_recipients",
|
||||
"attachments",
|
||||
"retry"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sender",
|
||||
"fieldtype": "Data",
|
||||
"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,
|
||||
"ignore_xss_filter": 1,
|
||||
"label": "Sender",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Email",
|
||||
"permlevel": 0,
|
||||
"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": "Email"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "recipients",
|
||||
"fieldtype": "Table",
|
||||
"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": "Recipient",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Email Queue Recipient",
|
||||
"permlevel": 0,
|
||||
"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": "Email Queue Recipient"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "show_as_cc",
|
||||
"fieldtype": "Small Text",
|
||||
"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": "Show as cc",
|
||||
"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
|
||||
"label": "Show as cc"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Code",
|
||||
"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": "Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"label": "Message"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Not Sent",
|
||||
"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": 1,
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nNot Sent\nSending\nSent\nError\nExpired",
|
||||
"permlevel": 0,
|
||||
"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": "\nNot Sent\nSending\nSent\nError\nExpired"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "error",
|
||||
"fieldtype": "Code",
|
||||
"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": "Error",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"label": "Error"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "message_id",
|
||||
"fieldtype": "Data",
|
||||
"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": "Message ID",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"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": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reference Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"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 DocName",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "communication",
|
||||
"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": "Communication",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Communication",
|
||||
"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": 1,
|
||||
"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": "send_after",
|
||||
"fieldtype": "Datetime",
|
||||
"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": "Send After",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "priority",
|
||||
"fieldtype": "Int",
|
||||
"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": "Priority",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "add_unsubscribe_link",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Add Unsubscribe Link",
|
||||
"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
|
||||
"label": "Add Unsubscribe Link"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "unsubscribe_param",
|
||||
"fieldtype": "Data",
|
||||
"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": "Unsubscribe Param",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "unsubscribe_method",
|
||||
"fieldtype": "Data",
|
||||
"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": "Unsubscribe Method",
|
||||
"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
|
||||
"label": "Unsubscribe Method"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "expose_recipients",
|
||||
"fieldtype": "Data",
|
||||
"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": "Expose Recipients",
|
||||
"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
|
||||
"label": "Expose Recipients"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "attachments",
|
||||
"fieldtype": "Code",
|
||||
"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": "Attachments",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "retry",
|
||||
"fieldtype": "Int",
|
||||
"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": "Retry",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-envelope",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-09-05 14:22:27.664645",
|
||||
"links": [],
|
||||
"modified": "2020-07-17 15:58:15.369419",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Queue",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"role": "System Manager"
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -107,6 +107,9 @@ class Newsletter(WebsiteGenerator):
|
|||
if self.get("__islocal"):
|
||||
throw(_("Please save the Newsletter before sending"))
|
||||
|
||||
if not self.recipients:
|
||||
frappe.throw(_("Newsletter should have at least one recipient"))
|
||||
|
||||
def get_context(self, context):
|
||||
newsletters = get_newsletter_list("Newsletter", None, None, 0)
|
||||
if newsletters:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import email.utils
|
|||
from six import iteritems, text_type, string_types
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.header import Header
|
||||
from email import policy
|
||||
|
||||
|
||||
def get_email(recipients, sender='', msg='', subject='[No Subject]',
|
||||
|
|
@ -68,8 +69,8 @@ class EMail:
|
|||
self.subject = subject
|
||||
self.expose_recipients = expose_recipients
|
||||
|
||||
self.msg_root = MIMEMultipart('mixed')
|
||||
self.msg_alternative = MIMEMultipart('alternative')
|
||||
self.msg_root = MIMEMultipart('mixed', policy=policy.SMTPUTF8)
|
||||
self.msg_alternative = MIMEMultipart('alternative', policy=policy.SMTPUTF8)
|
||||
self.msg_root.attach(self.msg_alternative)
|
||||
self.cc = cc or []
|
||||
self.bcc = bcc or []
|
||||
|
|
@ -100,7 +101,7 @@ class EMail:
|
|||
Attach message in the text portion of multipart/alternative
|
||||
"""
|
||||
from email.mime.text import MIMEText
|
||||
part = MIMEText(message, 'plain', 'utf-8')
|
||||
part = MIMEText(message, 'plain', 'utf-8', policy=policy.SMTPUTF8)
|
||||
self.msg_alternative.attach(part)
|
||||
|
||||
def set_part_html(self, message, inline_images):
|
||||
|
|
@ -113,9 +114,9 @@ class EMail:
|
|||
message, _inline_images = replace_filename_with_cid(message)
|
||||
|
||||
# prepare parts
|
||||
msg_related = MIMEMultipart('related')
|
||||
msg_related = MIMEMultipart('related', policy=policy.SMTPUTF8)
|
||||
|
||||
html_part = MIMEText(message, 'html', 'utf-8')
|
||||
html_part = MIMEText(message, 'html', 'utf-8', policy=policy.SMTPUTF8)
|
||||
msg_related.attach(html_part)
|
||||
|
||||
for image in _inline_images:
|
||||
|
|
@ -124,7 +125,7 @@ class EMail:
|
|||
|
||||
self.msg_alternative.attach(msg_related)
|
||||
else:
|
||||
self.msg_alternative.attach(MIMEText(message, 'html', 'utf-8'))
|
||||
self.msg_alternative.attach(MIMEText(message, 'html', 'utf-8', policy=policy.SMTPUTF8))
|
||||
|
||||
def set_html_as_text(self, html):
|
||||
"""Set plain text from HTML"""
|
||||
|
|
@ -135,7 +136,7 @@ class EMail:
|
|||
from email.mime.text import MIMEText
|
||||
|
||||
maintype, subtype = mime_type.split('/')
|
||||
part = MIMEText(message, _subtype = subtype)
|
||||
part = MIMEText(message, _subtype = subtype, policy=policy.SMTPUTF8)
|
||||
|
||||
if as_attachment:
|
||||
part.add_header('Content-Disposition', 'attachment', filename=filename)
|
||||
|
|
@ -222,7 +223,8 @@ class EMail:
|
|||
|
||||
# reset headers as values may be changed.
|
||||
for key, val in iteritems(headers):
|
||||
self.set_header(key, val)
|
||||
if val:
|
||||
self.set_header(key, val)
|
||||
|
||||
# call hook to enable apps to modify msg_root before sending
|
||||
for hook in frappe.get_hooks("make_email_body_message"):
|
||||
|
|
@ -238,7 +240,7 @@ class EMail:
|
|||
"""validate, build message and convert to string"""
|
||||
self.validate()
|
||||
self.make()
|
||||
return self.msg_root.as_string()
|
||||
return self.msg_root.as_string(policy=policy.SMTPUTF8)
|
||||
|
||||
def get_formatted_html(subject, message, footer=None, print_html=None,
|
||||
email_account=None, header=None, unsubscribe_link=None, sender=None):
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ This is the text version of this email
|
|||
subject='Test Subject',
|
||||
content=email_html,
|
||||
text_content=email_text
|
||||
).as_string()
|
||||
).as_string().replace("\r\n", "\n")
|
||||
|
||||
def test_prepare_message_returns_already_encoded_string(self):
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|||
subject='Test Subject',
|
||||
content=email_html,
|
||||
header=['Email Title', 'green']
|
||||
).as_string()
|
||||
).as_string().replace("\r\n", "\n")
|
||||
|
||||
self.assertTrue('''<span class=3D"indicator indicator-green" style=3D"background-color:#98=
|
||||
d85b; border-radius:8px; display:inline-block; height:8px; margin-right:5px=
|
||||
|
|
|
|||
|
|
@ -620,7 +620,12 @@
|
|||
},
|
||||
"Congo, The Democratic Republic of the": {
|
||||
"code": "cd",
|
||||
"number_format": "#,###.##"
|
||||
"number_format": "#,###.##",
|
||||
"currency": "CDF",
|
||||
"currency_name": "Congolese franc",
|
||||
"currency_symbol": "FC",
|
||||
"currency_fraction": "Centime",
|
||||
"currency_fraction_units": 100
|
||||
},
|
||||
"Cook Islands": {
|
||||
"code": "ck",
|
||||
|
|
|
|||
|
|
@ -189,14 +189,17 @@ def upload_system_backup_to_google_drive():
|
|||
if frappe.flags.create_new_backup:
|
||||
set_progress(1, "Backing up Data.")
|
||||
backup = new_backup()
|
||||
fileurl_backup = backup.backup_path_db
|
||||
fileurl_site_config = backup.site_config_backup_path
|
||||
fileurl_public_files = backup.backup_path_files
|
||||
fileurl_private_files = backup.backup_path_private_files
|
||||
else:
|
||||
fileurl_backup, fileurl_site_config, fileurl_public_files, fileurl_private_files = get_latest_backup_file(with_files=True)
|
||||
file_urls = []
|
||||
file_urls.append(backup.backup_path_db)
|
||||
file_urls.append(backup.site_config_backup_path)
|
||||
|
||||
for fileurl in [fileurl_backup, fileurl_site_config, fileurl_public_files, fileurl_private_files]:
|
||||
if account.file_backup:
|
||||
file_urls.append(backup.backup_path_files)
|
||||
file_urls.append(backup.backup_path_private_files)
|
||||
else:
|
||||
file_urls = get_latest_backup_file(with_files=account.file_backup)
|
||||
|
||||
for fileurl in file_urls:
|
||||
if not fileurl:
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Paytm Settings', {
|
||||
refresh: function(frm) {
|
||||
frm.dashboard.set_headline(__("For more information, {0}.", [`<a href='https://erpnext.com/docs/user/manual/en/erpnext_integration/paytm-integration'>${__('Click here')}</a>`]));
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-04-02 00:11:22.846697",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"merchant_id",
|
||||
"merchant_key",
|
||||
"staging",
|
||||
"column_break_4",
|
||||
"industry_type_id",
|
||||
"website"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "merchant_id",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Merchant ID",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "merchant_key",
|
||||
"fieldtype": "Password",
|
||||
"in_list_view": 1,
|
||||
"label": "Merchant Key",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "staging",
|
||||
"fieldtype": "Check",
|
||||
"label": "Staging",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !doc.staging",
|
||||
"fieldname": "website",
|
||||
"fieldtype": "Data",
|
||||
"label": "Website",
|
||||
"mandatory_depends_on": "eval: !doc.staging",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !doc.staging",
|
||||
"fieldname": "industry_type_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Industry Type ID",
|
||||
"mandatory_depends_on": "eval: !doc.staging",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-08 13:36:09.703143",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Paytm Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
159
frappe/integrations/doctype/paytm_settings/paytm_settings.py
Normal file
159
frappe/integrations/doctype/paytm_settings/paytm_settings.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import json
|
||||
import requests
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import get_url, call_hook_method, cint, flt, cstr
|
||||
from frappe.integrations.utils import create_request_log, create_payment_gateway
|
||||
from frappe.utils import get_request_site_address
|
||||
from paytmchecksum import generateSignature, verifySignature
|
||||
from frappe.utils.password import get_decrypted_password
|
||||
|
||||
class PaytmSettings(Document):
|
||||
supported_currencies = ["INR"]
|
||||
|
||||
def validate(self):
|
||||
create_payment_gateway('Paytm')
|
||||
call_hook_method('payment_gateway_enabled', gateway='Paytm')
|
||||
|
||||
def validate_transaction_currency(self, currency):
|
||||
if currency not in self.supported_currencies:
|
||||
frappe.throw(_("Please select another payment method. Paytm does not support transactions in currency '{0}'").format(currency))
|
||||
|
||||
def get_payment_url(self, **kwargs):
|
||||
'''Return payment url with several params'''
|
||||
# create unique order id by making it equal to the integration request
|
||||
integration_request = create_request_log(kwargs, "Host", "Paytm")
|
||||
kwargs.update(dict(order_id=integration_request.name))
|
||||
|
||||
return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs)))
|
||||
|
||||
def get_paytm_config():
|
||||
''' Returns paytm config '''
|
||||
|
||||
paytm_config = frappe.db.get_singles_dict('Paytm Settings')
|
||||
paytm_config.update(dict(merchant_key=get_decrypted_password('Paytm Settings', 'Paytm Settings', 'merchant_key')))
|
||||
|
||||
if cint(paytm_config.staging):
|
||||
paytm_config.update(dict(
|
||||
website="WEBSTAGING",
|
||||
url='https://securegw-stage.paytm.in/order/process',
|
||||
transaction_status_url='https://securegw-stage.paytm.in/order/status',
|
||||
industry_type_id='RETAIL'
|
||||
))
|
||||
else:
|
||||
paytm_config.update(dict(
|
||||
url='https://securegw.paytm.in/order/process',
|
||||
transaction_status_url='https://securegw.paytm.in/order/status',
|
||||
))
|
||||
return paytm_config
|
||||
|
||||
def get_paytm_params(payment_details, order_id, paytm_config):
|
||||
|
||||
# initialize a dictionary
|
||||
paytm_params = dict()
|
||||
|
||||
redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.verify_transaction"
|
||||
|
||||
|
||||
paytm_params.update({
|
||||
"MID" : paytm_config.merchant_id,
|
||||
"WEBSITE" : paytm_config.website,
|
||||
"INDUSTRY_TYPE_ID" : paytm_config.industry_type_id,
|
||||
"CHANNEL_ID" : "WEB",
|
||||
"ORDER_ID" : order_id,
|
||||
"CUST_ID" : payment_details['payer_email'],
|
||||
"EMAIL" : payment_details['payer_email'],
|
||||
"TXN_AMOUNT" : cstr(flt(payment_details['amount'], 2)),
|
||||
"CALLBACK_URL" : redirect_uri,
|
||||
})
|
||||
|
||||
checksum = generateSignature(paytm_params, paytm_config.merchant_key)
|
||||
|
||||
paytm_params.update({
|
||||
"CHECKSUMHASH" : checksum
|
||||
})
|
||||
|
||||
return paytm_params
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def verify_transaction(**paytm_params):
|
||||
'''Verify checksum for received data in the callback and then verify the transaction'''
|
||||
paytm_config = get_paytm_config()
|
||||
is_valid_checksum = False
|
||||
|
||||
paytm_params.pop('cmd', None)
|
||||
paytm_checksum = paytm_params.pop('CHECKSUMHASH', None)
|
||||
|
||||
if paytm_params and paytm_config and paytm_checksum:
|
||||
# Verify checksum
|
||||
is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_checksum)
|
||||
|
||||
if is_valid_checksum and paytm_params.get('RESPCODE') == '01':
|
||||
verify_transaction_status(paytm_config, paytm_params['ORDERID'])
|
||||
else:
|
||||
frappe.respond_as_web_page("Payment Failed",
|
||||
"Transaction failed to complete. In case of any deductions, deducted amount will get refunded to your account.",
|
||||
http_status_code=401, indicator_color='red')
|
||||
frappe.log_error("Order unsuccessful. Failed Response:"+cstr(paytm_params), 'Paytm Payment Failed')
|
||||
|
||||
def verify_transaction_status(paytm_config, order_id):
|
||||
'''Verify transaction completion after checksum has been verified'''
|
||||
paytm_params=dict(
|
||||
MID=paytm_config.merchant_id,
|
||||
ORDERID= order_id
|
||||
)
|
||||
|
||||
checksum = generateSignature(paytm_params, paytm_config.merchant_key)
|
||||
paytm_params["CHECKSUMHASH"] = checksum
|
||||
|
||||
post_data = json.dumps(paytm_params)
|
||||
url = paytm_config.transaction_status_url
|
||||
|
||||
response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json()
|
||||
finalize_request(order_id, response)
|
||||
|
||||
def finalize_request(order_id, transaction_response):
|
||||
request = frappe.get_doc('Integration Request', order_id)
|
||||
transaction_data = frappe._dict(json.loads(request.data))
|
||||
redirect_to = transaction_data.get('redirect_to') or None
|
||||
redirect_message = transaction_data.get('redirect_message') or None
|
||||
|
||||
if transaction_response['STATUS'] == "TXN_SUCCESS":
|
||||
if transaction_data.reference_doctype and transaction_data.reference_docname:
|
||||
custom_redirect_to = None
|
||||
try:
|
||||
custom_redirect_to = frappe.get_doc(transaction_data.reference_doctype,
|
||||
transaction_data.reference_docname).run_method("on_payment_authorized", 'Completed')
|
||||
request.db_set('status', 'Completed')
|
||||
except Exception:
|
||||
request.db_set('status', 'Failed')
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
|
||||
if custom_redirect_to:
|
||||
redirect_to = custom_redirect_to
|
||||
|
||||
redirect_url = '/integrations/payment-success'
|
||||
else:
|
||||
request.db_set('status', 'Failed')
|
||||
redirect_url = '/integrations/payment-failed'
|
||||
|
||||
if redirect_to:
|
||||
redirect_url += '?' + urlencode({'redirect_to': redirect_to})
|
||||
if redirect_message:
|
||||
redirect_url += '&' + urlencode({'redirect_message': redirect_message})
|
||||
|
||||
frappe.local.response['type'] = 'redirect'
|
||||
frappe.local.response['location'] = redirect_url
|
||||
|
||||
def get_gateway_controller(doctype, docname):
|
||||
reference_doc = frappe.get_doc(doctype, docname)
|
||||
gateway_controller = frappe.db.get_value("Payment Gateway", reference_doc.payment_gateway, "gateway_controller")
|
||||
return gateway_controller
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestVideo(unittest.TestCase):
|
||||
class TestPaytmSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -74,11 +74,11 @@
|
|||
},
|
||||
{
|
||||
"default": "us-east-1",
|
||||
"description": "See https://docs.aws.amazon.com/de_de/general/latest/gr/rande.html#s3_region for details.",
|
||||
"description": "See https://docs.aws.amazon.com/general/latest/gr/s3.html for details.",
|
||||
"fieldname": "region",
|
||||
"fieldtype": "Select",
|
||||
"label": "Region",
|
||||
"options": "us-east-1\nus-east-2\nus-west-1\nus-west-2\nap-south-1\nap-southeast-1\nap-southeast-2\nap-northeast-1\nap-northeast-2\nap-northeast-3\nca-central-1\ncn-north-1\ncn-northwest-1\neu-central-1\neu-west-1\neu-west-2\neu-west-3\neu-north-1\nsa-east-1"
|
||||
"options": "us-east-1\nus-east-2\nus-west-1\nus-west-2\naf-south-1\nap-east-1\nap-south-1\nap-southeast-1\nap-southeast-2\nap-northeast-1\nap-northeast-2\nap-northeast-3\nca-central-1\ncn-north-1\ncn-northwest-1\neu-central-1\neu-west-1\neu-west-2\neu-west-3\neu-south-1\neu-north-1\nme-south-1\nsa-east-1"
|
||||
},
|
||||
{
|
||||
"fieldname": "endpoint_url",
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
"hide_toolbar": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-13 20:57:24.432183",
|
||||
"modified": "2020-07-27 17:27:21.400000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "S3 Backup Settings",
|
||||
|
|
@ -172,4 +172,4 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -702,16 +702,13 @@ class BaseDocument(object):
|
|||
df = self.meta.get_field(fieldname)
|
||||
sanitized_value = value
|
||||
|
||||
if df and df.get("fieldtype") in ("Data", "Code", "Small Text", "Text") and df.get("options")=="Email":
|
||||
sanitized_value = sanitize_email(value)
|
||||
if df and (df.get("ignore_xss_filter")
|
||||
or (df.get("fieldtype") in ("Data", "Small Text", "Text") and df.get("options")=="Email")
|
||||
or df.get("fieldtype") in ("Attach", "Attach Image", "Barcode", "Code")
|
||||
|
||||
elif df and (df.get("ignore_xss_filter")
|
||||
or (df.get("fieldtype")=="Code" and df.get("options")!="Email")
|
||||
or df.get("fieldtype") in ("Attach", "Attach Image", "Barcode")
|
||||
|
||||
# cancelled and submit but not update after submit should be ignored
|
||||
or self.docstatus==2
|
||||
or (self.docstatus==1 and not df.get("allow_on_submit"))):
|
||||
# cancelled and submit but not update after submit should be ignored
|
||||
or self.docstatus==2
|
||||
or (self.docstatus==1 and not df.get("allow_on_submit"))):
|
||||
continue
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1307,6 +1307,16 @@ class Document(BaseDocument):
|
|||
users = set([assignment.owner for assignment in assignments])
|
||||
return users
|
||||
|
||||
def add_tag(self, tag):
|
||||
"""Add a Tag to this document"""
|
||||
from frappe.desk.doctype.tag.tag import DocTags
|
||||
DocTags(self.doctype).add(self.name, tag)
|
||||
|
||||
def get_tags(self):
|
||||
"""Return a list of Tags attached to this document"""
|
||||
from frappe.desk.doctype.tag.tag import DocTags
|
||||
return DocTags(self.doctype).get_tags(self.name).split(",")[1:]
|
||||
|
||||
def execute_action(doctype, name, action, **kwargs):
|
||||
"""Execute an action on a document (called by background worker)"""
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
|
|
|
|||
|
|
@ -307,4 +307,4 @@ def set_workflow_state_on_action(doc, workflow_name, action):
|
|||
for state in workflow.states:
|
||||
if state.doc_status == docstatus:
|
||||
doc.set(workflow_state_field, state.state)
|
||||
return
|
||||
return
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import pytz
|
|||
|
||||
from frappe import _
|
||||
from frappe.auth import LoginManager
|
||||
from http import cookies
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
from oauthlib.oauth2.rfc6749.grant_types import AuthorizationCodeGrant, ImplicitGrant, ResourceOwnerPasswordCredentialsGrant, ClientCredentialsGrant, RefreshTokenGrant
|
||||
from oauthlib.oauth2 import RequestValidator
|
||||
|
|
@ -130,15 +131,12 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
oac.scopes = get_url_delimiter().join(request.scopes)
|
||||
oac.redirect_uri_bound_to_authorization_code = request.redirect_uri
|
||||
oac.client = client_id
|
||||
oac.user = unquote(cookie_dict['user_id'])
|
||||
oac.user = unquote(cookie_dict['user_id'].value)
|
||||
oac.authorization_code = code['code']
|
||||
oac.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
def authenticate_client(self, request, *args, **kwargs):
|
||||
|
||||
cookie_dict = get_cookie_dict_from_headers(request)
|
||||
|
||||
#Get ClientID in URL
|
||||
if request.client_id:
|
||||
oc = frappe.get_doc("OAuth Client", request.client_id)
|
||||
|
|
@ -155,7 +153,9 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
except Exception as e:
|
||||
print("Failed body authentication: Application %s does not exist".format(cid=request.client_id))
|
||||
|
||||
return frappe.session.user == unquote(cookie_dict.get('user_id', "Guest"))
|
||||
cookie_dict = get_cookie_dict_from_headers(request)
|
||||
user_id = unquote(cookie_dict['user_id']) if 'user_id' in cookie_dict else "Guest"
|
||||
return frappe.session.user == user_id
|
||||
|
||||
def authenticate_client_id(self, client_id, request, *args, **kwargs):
|
||||
cli_id = frappe.db.get_value('OAuth Client', client_id, 'name')
|
||||
|
|
@ -400,13 +400,10 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
return True
|
||||
|
||||
def get_cookie_dict_from_headers(r):
|
||||
cookie = cookies.BaseCookie()
|
||||
if r.headers.get('Cookie'):
|
||||
cookie = r.headers.get('Cookie')
|
||||
cookie = cookie.split("; ")
|
||||
cookie_dict = {k:v for k,v in (x.split('=') for x in cookie)}
|
||||
return cookie_dict
|
||||
else:
|
||||
return {}
|
||||
cookie.load(r.headers.get('Cookie'))
|
||||
return cookie
|
||||
|
||||
def calculate_at_hash(access_token, hash_alg):
|
||||
"""Helper method for calculating an access token
|
||||
|
|
|
|||
|
|
@ -295,3 +295,5 @@ frappe.patches.v13_0.update_date_filters_in_user_settings
|
|||
frappe.patches.v13_0.update_duration_options
|
||||
frappe.patches.v13_0.replace_old_data_import # 2020-06-24
|
||||
frappe.patches.v13_0.create_custom_dashboards_cards_and_charts
|
||||
frappe.patches.v13_0.rename_is_custom_field_in_dashboard_chart
|
||||
frappe.patches.v13_0.generate_theme_files_in_public_folder
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
themes = frappe.db.get_all(
|
||||
"Website Theme", filters={"theme_url": ("not like", "/files/website_theme/%")}
|
||||
)
|
||||
for theme in themes:
|
||||
doc = frappe.get_doc("Website Theme", theme.name)
|
||||
doc.generate_bootstrap_theme()
|
||||
doc.save()
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import frappe
|
||||
from frappe.model.utils.rename_field import rename_field
|
||||
|
||||
def execute():
|
||||
if not frappe.db.table_exists('Dashboard Chart'):
|
||||
return
|
||||
|
||||
frappe.reload_doc('desk', 'doctype', 'dashboard_chart')
|
||||
|
||||
if frappe.db.has_column('Dashboard Chart', 'is_custom'):
|
||||
rename_field('Dashboard Chart', 'is_custom', 'use_report_chart')
|
||||
|
|
@ -35,13 +35,20 @@ frappe.ui.form.on("Print Format", {
|
|||
else if (frm.doc.custom_format && !frm.doc.raw_printing) {
|
||||
frm.set_df_property("html", "reqd", 1);
|
||||
}
|
||||
frm.add_custom_button(__("Make Default"), function () {
|
||||
frappe.call({
|
||||
method: "frappe.printing.doctype.print_format.print_format.make_default",
|
||||
args: {
|
||||
name: frm.doc.name
|
||||
}
|
||||
})
|
||||
frappe.db.get_value('DocType', frm.doc.doc_type, 'default_print_format', (r) => {
|
||||
if (r.default_print_format != frm.doc.name) {
|
||||
frm.add_custom_button(__("Set as Default"), function () {
|
||||
frappe.call({
|
||||
method: "frappe.printing.doctype.print_format.print_format.make_default",
|
||||
args: {
|
||||
name: frm.doc.name
|
||||
},
|
||||
callback: function() {
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@ body {
|
|||
p {
|
||||
margin: 1em 0 !important;
|
||||
}
|
||||
.ql-editor {
|
||||
white-space: normal;
|
||||
}
|
||||
.ql-editor p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
hr {
|
||||
border-top: 1px solid #d1d8dd;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,23 +274,29 @@ frappe.data_import.DataExporter = class DataExporter {
|
|||
? this.column_map[child_fieldname]
|
||||
: this.column_map[doctype];
|
||||
|
||||
let is_field_mandatory = df => (df.fieldname === 'name' && !child_fieldname)
|
||||
|| (df.reqd && this.exporting_for == 'Insert New Records');
|
||||
let is_field_mandatory = df => {
|
||||
if (df.reqd && this.exporting_for == 'Insert New Records') {
|
||||
return true;
|
||||
}
|
||||
if (autoname_field && df.fieldname == autoname_field.fieldname) {
|
||||
return true;
|
||||
}
|
||||
if (df.fieldname === 'name') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return fields
|
||||
.filter(df => {
|
||||
if (autoname_field && df.fieldname === autoname_field.fieldname) {
|
||||
if (autoname_field && df.fieldname === 'name') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(df => {
|
||||
let label = __(df.label);
|
||||
if (autoname_field && df.fieldname === 'name') {
|
||||
label = label + ` (${__(autoname_field.label)})`;
|
||||
}
|
||||
return {
|
||||
label,
|
||||
label: __(df.label),
|
||||
value: df.fieldname,
|
||||
danger: is_field_mandatory(df),
|
||||
checked: false,
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ frappe.data_import.ImportPreview = class ImportPreview {
|
|||
.replace('%y', 'yy')
|
||||
.replace('%m', 'mm')
|
||||
.replace('%d', 'dd')
|
||||
.replace('%H', 'HH')
|
||||
.replace('%M', 'mm')
|
||||
.replace('%S', 'ss')
|
||||
: null;
|
||||
|
||||
let column_title = `<span class="indicator green">
|
||||
|
|
@ -354,4 +357,4 @@ function get_fields_as_options(doctype, column_map) {
|
|||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({
|
|||
set_description: function() {
|
||||
const { description } = this.df;
|
||||
const { time_zone } = frappe.sys_defaults;
|
||||
if (!frappe.datetime.is_timezone_same()) {
|
||||
if (!this.df.hide_timezone && !frappe.datetime.is_timezone_same()) {
|
||||
if (!description) {
|
||||
this.df.description = time_zone;
|
||||
} else if (!description.includes(time_zone)) {
|
||||
|
|
|
|||
|
|
@ -332,6 +332,10 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
|
|||
let docfield = frappe.meta.get_docfield(doctype, fieldname);
|
||||
let label = docfield ? docfield.label : frappe.model.unscrub(fieldname);
|
||||
|
||||
if (docfield && docfield.fieldtype === 'Check') {
|
||||
filter[3] = filter[3] ? __('Yes'): __('No');
|
||||
}
|
||||
|
||||
if (filter[3] && Array.isArray(filter[3]) && filter[3].length > 5) {
|
||||
filter[3] = filter[3].slice(0, 5);
|
||||
filter[3].push('...');
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({
|
|||
make_input() {
|
||||
let template = `
|
||||
<div class="multiselect-list dropdown">
|
||||
<div class="form-control cursor-pointer dropdown-toggle input-xs" data-toggle="dropdown" tabindex=0>
|
||||
<span class="status-text ellipsis"></span>
|
||||
<div class="form-control cursor-pointer dropdown-toggle input-sm" data-toggle="dropdown" tabindex=0>
|
||||
<div class="status-text ellipsis"></div>
|
||||
</div>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-input-wrapper">
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ Quill.register(CustomColor, true);
|
|||
frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
||||
make_wrapper() {
|
||||
this._super();
|
||||
this.$wrapper.find(".like-disabled-input").addClass('ql-editor');
|
||||
},
|
||||
|
||||
make_input() {
|
||||
|
|
@ -201,6 +200,10 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
let value = this.quill ? this.quill.root.innerHTML : '';
|
||||
// hack to retain space sequence.
|
||||
value = value.replace(/(\s)(\s)/g, ' ');
|
||||
|
||||
if (!$(value).find('.ql-editor').length) {
|
||||
value = `<div class="ql-editor read-mode">${value}</div>`;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1401,7 +1401,13 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
var docperms = frappe.perm.get_perm(this.doc.doctype);
|
||||
for (var i=0, l=docperms.length; i<l; i++) {
|
||||
var p = docperms[i];
|
||||
perm[p.permlevel || 0] = {read:1, print:1, cancel:1, email:1};
|
||||
perm[p.permlevel || 0] = {
|
||||
read: p.read,
|
||||
cancel: p.cancel,
|
||||
share: p.share,
|
||||
print: p.print,
|
||||
email: p.email
|
||||
};
|
||||
}
|
||||
this.perm = perm;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,7 +228,12 @@ frappe.form.formatters = {
|
|||
return frappe.form.formatters.Text(value);
|
||||
},
|
||||
TextEditor: function(value) {
|
||||
return frappe.form.formatters.Text(value);
|
||||
let formatted_value = frappe.form.formatters.Text(value);
|
||||
// to use ql-editor styles
|
||||
if (!$(formatted_value).find('.ql-editor').length) {
|
||||
formatted_value = `<div class="ql-editor read-mode">${formatted_value}</div>`;
|
||||
}
|
||||
return formatted_value;
|
||||
},
|
||||
Code: function(value) {
|
||||
return "<pre>" + (value==null ? "" : $("<div>").text(value).html()) + "</pre>"
|
||||
|
|
|
|||
|
|
@ -393,8 +393,11 @@ export default class GridRow {
|
|||
|
||||
// sync get_query
|
||||
field.get_query = this.grid.get_field(df.fieldname).get_query;
|
||||
field.df.onchange = function() {
|
||||
me.grid.grid_rows[this.doc.idx-1].refresh_field(this.df.fieldname);
|
||||
|
||||
var field_on_change_function = field.df.onchange;
|
||||
field.df.onchange = function(e) {
|
||||
field_on_change_function && field_on_change_function(e);
|
||||
me.grid.grid_rows[this.doc.idx - 1].refresh_field(this.df.fieldname);
|
||||
};
|
||||
field.refresh();
|
||||
if(field.$input) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ frappe.ui.form.on = frappe.ui.form.on_change = function(doctype, fieldname, hand
|
|||
|
||||
let _handler = (...args) => {
|
||||
try {
|
||||
handler(...args);
|
||||
return handler(...args);
|
||||
} catch (error) {
|
||||
console.error(handler);
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ frappe.ui.Filter = class {
|
|||
this.conditions.push([key, __(`{0}`, [filter.label])]);
|
||||
for (let fieldtype of Object.keys(this.invalid_condition_map)) {
|
||||
if (!filter.valid_for_fieldtypes.includes(fieldtype)) {
|
||||
this.invalid_condition_map[fieldtype].push(filter.label);
|
||||
this.invalid_condition_map[fieldtype].push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -243,7 +243,7 @@ frappe.ui.Filter = class {
|
|||
let args = {};
|
||||
if (this.filters_config[condition].depends_on) {
|
||||
const field_name = this.filters_config[condition].depends_on;
|
||||
const filter_value = this.base_list.get_filter_value(field_name);
|
||||
const filter_value = this.filter_list.get_filter_value(fieldname);
|
||||
args[field_name] = filter_value;
|
||||
}
|
||||
frappe
|
||||
|
|
|
|||
|
|
@ -195,13 +195,18 @@ frappe.ui.FilterGroup = class {
|
|||
filter_items: (doctype, fieldname) => {
|
||||
return !this.filter_exists([doctype, fieldname]);
|
||||
},
|
||||
base_list: this.base_list,
|
||||
filter_list: this.base_list || this,
|
||||
};
|
||||
let filter = new frappe.ui.Filter(args);
|
||||
this.filters.push(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
get_filter_value(fieldname) {
|
||||
let filter_obj = this.filters.find(f => f.fieldname == fieldname) || {};
|
||||
return filter_obj.value;
|
||||
}
|
||||
|
||||
filter_exists(filter_value) {
|
||||
// filter_value of form: [doctype, fieldname, condition, value]
|
||||
let exists = false;
|
||||
|
|
|
|||
|
|
@ -212,6 +212,55 @@ frappe.dashboard_utils = {
|
|||
}
|
||||
|
||||
return filters;
|
||||
},
|
||||
|
||||
get_dashboard_link_field() {
|
||||
let field = {
|
||||
label: __('Select Dashboard'),
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'dashboard',
|
||||
options: 'Dashboard',
|
||||
};
|
||||
|
||||
if (!frappe.boot.developer_mode) {
|
||||
field.get_query = () => {
|
||||
return {
|
||||
filters: {
|
||||
is_standard: 0
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return field;
|
||||
},
|
||||
|
||||
get_add_to_dashboard_dialog(docname, doctype, method) {
|
||||
const field = this.get_dashboard_link_field();
|
||||
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('Add to Dashboard'),
|
||||
fields: [field],
|
||||
primary_action: (values) => {
|
||||
values.name = docname;
|
||||
values.set_standard = frappe.boot.developer_mode;
|
||||
frappe.xcall(
|
||||
method,
|
||||
{args: values}
|
||||
).then(()=> {
|
||||
let dashboard_route_html =
|
||||
`<a href = "#dashboard/${values.dashboard}">${values.dashboard}</a>`;
|
||||
let message =
|
||||
__(`${doctype} ${values.name} added to Dashboard ` + dashboard_route_html);
|
||||
|
||||
frappe.msgprint(message);
|
||||
});
|
||||
|
||||
dialog.hide();
|
||||
}
|
||||
});
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -17,7 +17,7 @@ frappe.help.show = function(doctype) {
|
|||
|
||||
frappe.help.show_video = function(youtube_id, title) {
|
||||
if (frappe.utils.is_url(youtube_id)) {
|
||||
const expression = '(?:youtube.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu.be/)([^\"&?/s]{11})';
|
||||
const expression = '(?:youtube.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu.be/)([^"&?\\s]{11})';
|
||||
youtube_id = youtube_id.match(expression)[1];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ frappe.breadcrumbs = {
|
|||
|
||||
preferred: {
|
||||
"File": "",
|
||||
"Video": "",
|
||||
"Dashboard": "Customization",
|
||||
"Dashboard Chart": "Customization",
|
||||
"Dashboard Chart Source": "Customization"
|
||||
|
|
|
|||
|
|
@ -44,26 +44,6 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
}
|
||||
});
|
||||
|
||||
$(document).on("upload_complete", function(event, attachment) {
|
||||
if(me.dialog.display) {
|
||||
var wrapper = $(me.dialog.fields_dict.select_attachments.wrapper);
|
||||
|
||||
// find already checked items
|
||||
var checked_items = wrapper.find('[data-file-name]:checked').map(function() {
|
||||
return $(this).attr("data-file-name");
|
||||
});
|
||||
|
||||
// reset attachment list
|
||||
me.render_attach();
|
||||
|
||||
// check latest added
|
||||
checked_items.push(attachment.name);
|
||||
|
||||
$.each(checked_items, function(i, filename) {
|
||||
wrapper.find('[data-file-name="'+ filename +'"]').prop("checked", true);
|
||||
});
|
||||
}
|
||||
})
|
||||
this.prepare();
|
||||
this.dialog.show();
|
||||
|
||||
|
|
@ -387,77 +367,86 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
folder: 'Home/Attachments',
|
||||
on_success: attachment => {
|
||||
this.attachments.push(attachment);
|
||||
this.render_attach();
|
||||
this.render_attachment_rows(attachment);
|
||||
}
|
||||
};
|
||||
|
||||
if(this.frm) {
|
||||
if (this.frm) {
|
||||
args = {
|
||||
doctype: this.frm.doctype,
|
||||
docname: this.frm.docname,
|
||||
folder: 'Home/Attachments',
|
||||
on_success: attachment => {
|
||||
this.frm.attachments.attachment_uploaded(attachment);
|
||||
this.render_attach();
|
||||
this.render_attachment_rows(attachment);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$("<h6 class='text-muted add-attachment' style='margin-top: 12px; cursor:pointer;'>"
|
||||
+__("Select Attachments")+"</h6><div class='attach-list'></div>\
|
||||
<p class='add-more-attachments'>\
|
||||
<a class='text-muted small'><i class='octicon octicon-plus' style='font-size: 12px'></i> "
|
||||
+__("Add Attachment")+"</a></p>").appendTo(attach.empty())
|
||||
$(`
|
||||
<h6 class='text-muted add-attachment' style='margin-top: 12px; cursor:pointer;'>
|
||||
${__("Select Attachments")}
|
||||
</h6>
|
||||
<div class='attach-list'></div>
|
||||
<p class='add-more-attachments'>
|
||||
<a class='text-muted small'>
|
||||
<i class='octicon octicon-plus' style='font-size: 12px'></i>
|
||||
${__("Add Attachment")}
|
||||
</a>
|
||||
</p>
|
||||
`).appendTo(attach.empty());
|
||||
|
||||
attach
|
||||
.find(".add-more-attachments a")
|
||||
.on('click',() => new frappe.ui.FileUploader(args));
|
||||
this.render_attach();
|
||||
.on('click', () => new frappe.ui.FileUploader(args));
|
||||
this.render_attachment_rows();
|
||||
},
|
||||
render_attach:function(){
|
||||
var fields = this.dialog.fields_dict;
|
||||
var attach = $(fields.select_attachments.wrapper).find(".attach-list").empty();
|
||||
|
||||
var files = [];
|
||||
if (this.attachments && this.attachments.length) {
|
||||
files = files.concat(this.attachments);
|
||||
}
|
||||
if (cur_frm) {
|
||||
files = files.concat(cur_frm.get_files());
|
||||
}
|
||||
render_attachment_rows: function(attachment) {
|
||||
const select_attachments = this.dialog.fields_dict.select_attachments;
|
||||
const attachment_rows = $(select_attachments.wrapper).find(".attach-list");
|
||||
if (attachment) {
|
||||
attachment_rows.append(this.get_attachment_row(attachment, true));
|
||||
} else {
|
||||
let files = [];
|
||||
if (this.attachments && this.attachments.length) {
|
||||
files = files.concat(this.attachments);
|
||||
}
|
||||
if (this.frm) {
|
||||
files = files.concat(this.frm.get_files());
|
||||
}
|
||||
|
||||
if(files.length) {
|
||||
$.each(files, function(i, f) {
|
||||
if (!f.file_name) return;
|
||||
f.file_url = frappe.urllib.get_full_url(f.file_url);
|
||||
|
||||
$(repl('<p class="checkbox">'
|
||||
+ '<label><span><input type="checkbox" data-file-name="%(name)s"></input></span>'
|
||||
+ '<span class="small">%(file_name)s</span>'
|
||||
+ ' <a href="%(file_url)s" target="_blank" class="text-muted small">'
|
||||
+ '<i class="fa fa-share" style="vertical-align: middle; margin-left: 3px;"></i>'
|
||||
+ '</label></p>', f))
|
||||
.appendTo(attach)
|
||||
});
|
||||
}
|
||||
this.select_attachments();
|
||||
},
|
||||
select_attachments:function(){
|
||||
let me = this;
|
||||
if(me.dialog.display) {
|
||||
let wrapper = $(me.dialog.fields_dict.select_attachments.wrapper);
|
||||
|
||||
let unchecked_items = wrapper.find('[data-file-name]:not(:checked)').map(function() {
|
||||
return $(this).attr("data-file-name");
|
||||
});
|
||||
|
||||
$.each(unchecked_items, function(i, filename) {
|
||||
wrapper.find('[data-file-name="'+ filename +'"]').prop("checked", true);
|
||||
});
|
||||
if (files.length) {
|
||||
$.each(files, (i, f) => {
|
||||
if (!f.file_name) return;
|
||||
if (!attachment_rows.find(`[data-file-name="${f.name}"]`).length) {
|
||||
f.file_url = frappe.urllib.get_full_url(f.file_url);
|
||||
attachment_rows.append(this.get_attachment_row(f));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get_attachment_row(attachment, checked) {
|
||||
return $(`<p class="checkbox">
|
||||
<label>
|
||||
<span>
|
||||
<input
|
||||
type="checkbox"
|
||||
data-file-name="${attachment.name}"
|
||||
${checked ? 'checked': ''}>
|
||||
</input>
|
||||
</span>
|
||||
<span class="small">${attachment.file_name}</span>
|
||||
<a href="${attachment.file_url}" target="_blank" class="text-muted small">
|
||||
<i class="fa fa-share" style="vertical-align: middle; margin-left: 3px;"></i>
|
||||
</label>
|
||||
</p>`);
|
||||
},
|
||||
|
||||
setup_email: function() {
|
||||
// email
|
||||
var me = this;
|
||||
var fields = this.dialog.fields_dict;
|
||||
|
||||
if(this.attach_document_print) {
|
||||
|
|
|
|||
|
|
@ -168,6 +168,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
|
||||
add_card_to_dashboard() {
|
||||
let field_options = frappe.report_utils.get_field_options_from_report(this.columns, this.raw_data);
|
||||
const dashboard_field = frappe.dashboard_utils.get_dashboard_link_field();
|
||||
const set_standard = frappe.boot.developer_mode;
|
||||
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('Create Card'),
|
||||
fields: [
|
||||
|
|
@ -192,12 +195,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
label: __('Add to Dashboard'),
|
||||
fieldtype: 'Section Break'
|
||||
},
|
||||
{
|
||||
fieldname: 'dashboard',
|
||||
label: __('Choose Dashboard'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Dashboard',
|
||||
},
|
||||
dashboard_field,
|
||||
{
|
||||
fieldname: 'cb_2',
|
||||
fieldtype: 'Column Break'
|
||||
|
|
@ -213,7 +211,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
if (!values.label) {
|
||||
values.label = `${values.report_function} of ${toTitle(values.report_field)}`;
|
||||
}
|
||||
this.create_number_card(values, values.dashboard, values.label);
|
||||
this.create_number_card(values, values.dashboard, values.label, set_standard);
|
||||
dialog.hide();
|
||||
}
|
||||
});
|
||||
|
|
@ -224,27 +222,26 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
|
||||
add_chart_to_dashboard() {
|
||||
if (this.chart_fields || this.chart_options) {
|
||||
const dashboard_field = frappe.dashboard_utils.get_dashboard_link_field();
|
||||
const set_standard = frappe.boot.developer_mode;
|
||||
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('Create Chart'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'dashboard',
|
||||
label: 'Choose Dashboard',
|
||||
fieldtype: 'Link',
|
||||
options: 'Dashboard',
|
||||
},
|
||||
{
|
||||
fieldname: 'dashboard_chart_name',
|
||||
label: 'Chart Name',
|
||||
fieldtype: 'Data',
|
||||
}
|
||||
},
|
||||
dashboard_field,
|
||||
],
|
||||
primary_action_label: __('Add'),
|
||||
primary_action: (values) => {
|
||||
this.create_dashboard_chart(
|
||||
this.chart_fields || this.chart_options,
|
||||
values.dashboard,
|
||||
values.dashboard_chart_name
|
||||
values.dashboard_chart_name,
|
||||
set_standard
|
||||
);
|
||||
dialog.hide();
|
||||
}
|
||||
|
|
@ -256,12 +253,13 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
}
|
||||
|
||||
create_number_card(values, dashboard_name, card_name) {
|
||||
create_number_card(values, dashboard_name, card_name, set_standard) {
|
||||
let args = {
|
||||
'dashboard': dashboard_name || null,
|
||||
'type': 'Report',
|
||||
'report_name': this.report_name,
|
||||
'filters_json': JSON.stringify(this.get_filter_values()),
|
||||
set_standard: set_standard,
|
||||
};
|
||||
Object.assign(args, values);
|
||||
|
||||
|
|
@ -274,7 +272,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
);
|
||||
}
|
||||
|
||||
create_dashboard_chart(chart_args, dashboard_name, chart_name) {
|
||||
create_dashboard_chart(chart_args, dashboard_name, chart_name, set_standard) {
|
||||
let args = {
|
||||
'dashboard': dashboard_name || null,
|
||||
'chart_type': 'Report',
|
||||
|
|
@ -282,7 +280,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
'type': chart_args.chart_type || frappe.model.unscrub(chart_args.type),
|
||||
'color': chart_args.color,
|
||||
'filters_json': JSON.stringify(this.get_filter_values()),
|
||||
'custom_options': {}
|
||||
'custom_options': {},
|
||||
'set_standard': set_standard,
|
||||
};
|
||||
|
||||
for (let key in chart_args) {
|
||||
|
|
@ -303,7 +302,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
'y_axis': chart_args.y_axis_fields.map(f => {
|
||||
return {'y_field': f.y_field, 'color': f.color};
|
||||
}),
|
||||
'is_custom': 0
|
||||
'use_report_chart': 0
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
|
@ -311,7 +310,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
Object.assign(args,
|
||||
{
|
||||
'chart_name': chart_name,
|
||||
'is_custom': 1
|
||||
'use_report_chart': 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -969,8 +968,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
if (column.isHeader && !data && this.data) {
|
||||
// totalRow doesn't have a data object
|
||||
// proxy it using the first data object
|
||||
// this is needed only for currency formatting
|
||||
data = this.data[0];
|
||||
// applied to Float, Currency fields, needed only for currency formatting.
|
||||
// make first data column have value 'Total'
|
||||
let index = 1;
|
||||
if (this.datatable && this.datatable.options.checkboxColumn) index = 2;
|
||||
|
||||
if (column.colIndex === index && !value) {
|
||||
value = "Total";
|
||||
column.fieldtype = "Data"; // avoid type issues for value if Date column
|
||||
} else if (in_list(["Currency", "Float"], column.fieldtype)) {
|
||||
// proxy for currency and float
|
||||
data = this.data[0];
|
||||
}
|
||||
}
|
||||
return frappe.format(value, column,
|
||||
{for_print: false, always_show_decimals: true}, data);
|
||||
|
|
|
|||
|
|
@ -767,10 +767,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
const index = this.fields.findIndex(f => column.field === f[0]);
|
||||
if (index === -1) return;
|
||||
const field = this.fields[index];
|
||||
if (field[0] === 'name' && this.group_by === null) {
|
||||
|
||||
if (field[0] === 'name') {
|
||||
this.refresh();
|
||||
frappe.throw(__('Cannot remove ID field'));
|
||||
}
|
||||
|
||||
this.fields.splice(index, 1);
|
||||
this.build_fields();
|
||||
this.setup_columns();
|
||||
|
|
@ -849,7 +851,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
columns: 2,
|
||||
options: columns[this.doctype]
|
||||
.filter(df => {
|
||||
return !df.hidden;
|
||||
return !df.hidden && df.fieldname !== 'name';
|
||||
})
|
||||
.map(df => ({
|
||||
label: __(df.label),
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ export default class ChartWidget extends Widget {
|
|||
}
|
||||
|
||||
get_report_chart_data(result) {
|
||||
if (result.chart && this.chart_doc.is_custom) {
|
||||
if (result.chart && this.chart_doc.use_report_chart) {
|
||||
return result.chart.data;
|
||||
} else {
|
||||
let y_fields = [];
|
||||
|
|
@ -638,7 +638,7 @@ export default class ChartWidget extends Widget {
|
|||
|
||||
update_last_synced() {
|
||||
if (!this.chart_doc.last_synced_on) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
let last_synced_text = __("Last synced {0}", [
|
||||
comment_when(this.chart_doc.last_synced_on)
|
||||
|
|
@ -680,14 +680,15 @@ export default class ChartWidget extends Widget {
|
|||
}
|
||||
|
||||
update_default_date_filters(report_filters, chart_filters) {
|
||||
report_filters.map(f => {
|
||||
if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) {
|
||||
if (f.reqd || chart_filters[f.fieldname]) {
|
||||
chart_filters[f.fieldname] = f.default;
|
||||
if (report_filters) {
|
||||
report_filters.map(f => {
|
||||
if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) {
|
||||
if (f.reqd || chart_filters[f.fieldname]) {
|
||||
chart_filters[f.fieldname] = f.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
return chart_filters;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export default class NumberCardWidget extends Widget {
|
|||
|
||||
get_number() {
|
||||
return frappe.xcall(this.settings.method, this.settings.args).then(res => {
|
||||
this.settings.get_number(res);
|
||||
return this.settings.get_number(res);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export default class OnboardingWidget extends Widget {
|
|||
if (step.is_single) {
|
||||
route = `Form/${step.reference_document}`;
|
||||
} else {
|
||||
route = `Form/${step.reference_document}/${__('New')} ${__(step.reference_document)}`;
|
||||
route = `Form/${step.reference_document}/${__('New')} ${__(step.reference_document)} 1`;
|
||||
}
|
||||
|
||||
let current_route = frappe.get_route();
|
||||
|
|
@ -262,7 +262,7 @@ export default class OnboardingWidget extends Widget {
|
|||
frappe.route_hooks.after_save = callback;
|
||||
}
|
||||
|
||||
frappe.set_route(`Form/${step.reference_document}/${__('New')} ${__(step.reference_document)}`);
|
||||
frappe.set_route(`Form/${step.reference_document}/${__('New')} ${__(step.reference_document)} 1`);
|
||||
}
|
||||
|
||||
show_quick_entry(step) {
|
||||
|
|
|
|||
|
|
@ -131,7 +131,8 @@ function shorten_number(number, country) {
|
|||
const number_system = get_number_system(country);
|
||||
let x = Math.abs(Math.round(number));
|
||||
for (const map of number_system) {
|
||||
if (x >= map.divisor) {
|
||||
const condition = map.condition ? map.condition(x) : x >= map.divisor;
|
||||
if (condition) {
|
||||
return Math.round(number/map.divisor) + ' ' + map.symbol;
|
||||
}
|
||||
}
|
||||
|
|
@ -161,9 +162,14 @@ function get_number_system(country) {
|
|||
{
|
||||
divisor: 1.0e+6,
|
||||
symbol: 'M'
|
||||
},
|
||||
{
|
||||
divisor: 1.0e+3,
|
||||
symbol: 'K',
|
||||
condition: (num) => num.toFixed().length > 5
|
||||
}]
|
||||
};
|
||||
return number_system_map[country];
|
||||
}
|
||||
|
||||
export { generate_route, generate_grid, build_summary_item, shorten_number };
|
||||
export { generate_route, generate_grid, build_summary_item, shorten_number };
|
||||
|
|
|
|||
|
|
@ -174,18 +174,12 @@ class ShortcutDialog extends WidgetDialog {
|
|||
onchange: () => {
|
||||
if (this.dialog.get_value("type") == "DocType") {
|
||||
let doctype = this.dialog.get_value("link_to");
|
||||
|
||||
doctype &&
|
||||
frappe.db
|
||||
.get_value("DocType", doctype, "issingle")
|
||||
.then((res) => {
|
||||
if (res.message && res.message.issingle) {
|
||||
this.hide_filters();
|
||||
} else {
|
||||
this.setup_filter(doctype);
|
||||
this.show_filters();
|
||||
}
|
||||
});
|
||||
if (doctype && frappe.boot.single_types.includes(doctype)) {
|
||||
this.hide_filters();
|
||||
} else if (doctype) {
|
||||
this.setup_filter(doctype);
|
||||
this.show_filters();
|
||||
}
|
||||
} else {
|
||||
this.hide_filters();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -719,7 +719,6 @@ li.user-progress {
|
|||
margin-right: 5px;
|
||||
margin-left: 0px;
|
||||
position: relative;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
// Will not be required after commonifying lists with empty state
|
||||
|
|
@ -731,28 +730,11 @@ li.user-progress {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
// mozilla doesn't support
|
||||
// Firefox doesn't support
|
||||
// pseudo elements on checkbox
|
||||
@-moz-document url-prefix() {
|
||||
@supports (-moz-appearance: none) or (-ms-ime-align:auto) {
|
||||
input[type="checkbox"] {
|
||||
visibility: visible;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (-moz-appearance: none) {
|
||||
input[type="checkbox"] {
|
||||
visibility: visible;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// edge doesn't support pseudo elements on checkbox
|
||||
//Microsoft Edge Browser 12+ (All)
|
||||
@supports (-ms-ime-align:auto) {
|
||||
input[type="checkbox"] {
|
||||
visibility: visible;
|
||||
left: 0;
|
||||
height: @checkbox-height !important
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ p {
|
|||
margin: 1em 0 !important;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
white-space: normal;
|
||||
p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid @border-color;
|
||||
}
|
||||
|
|
@ -210,4 +217,4 @@ hr {
|
|||
.report-title {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
/* csslint ignore:end */
|
||||
/* csslint ignore:end */
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ql-editor .mention {
|
||||
.ql-editor:not(.read-mode) .mention {
|
||||
height: auto;
|
||||
width: auto;
|
||||
border-radius: 10px;
|
||||
|
|
@ -132,3 +132,7 @@
|
|||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.ql-editor.read-mode {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
@import "variables";
|
||||
@import "mixins";
|
||||
@import '~quill/dist/quill.core';
|
||||
@import 'frappe/public/css/font-awesome';
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
@import 'multilevel-dropdown';
|
||||
|
|
@ -12,6 +13,20 @@
|
|||
@import 'portal';
|
||||
@import 'doc';
|
||||
|
||||
.ql-editor.read-mode {
|
||||
padding: 0;
|
||||
line-height: 1.6;
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
|
|
@ -323,4 +338,4 @@ h5.modal-title {
|
|||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,11 @@
|
|||
{{ head_include or "" }}
|
||||
{% endblock -%}
|
||||
|
||||
{%- block style %}{%- endblock -%}
|
||||
{%- block style %}
|
||||
{% if colocated_css -%}
|
||||
<style>{{ colocated_css }}</style>
|
||||
{%- endif %}
|
||||
{%- endblock -%}
|
||||
|
||||
<script>
|
||||
window.frappe = {};
|
||||
|
|
@ -86,7 +90,11 @@
|
|||
<script type="text/javascript" src="{{ link | abs_url }}"></script>
|
||||
{%- endfor -%}
|
||||
|
||||
{%- block script %}{%- endblock %}
|
||||
{%- block script %}
|
||||
{% if colocated_js -%}
|
||||
<script>{{ colocated_js }}</script>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
<!-- csrf_token -->
|
||||
{%- block body_include %}{{ body_include or "" }}{% endblock -%}
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@
|
|||
{%- endif -%}
|
||||
</a>
|
||||
{% endmacro %}
|
||||
|
||||
{% if footer_items -%}
|
||||
<div class="footer-links">
|
||||
<div class="row">
|
||||
<div class="footer-col-left col-sm-6">
|
||||
|
|
@ -24,4 +22,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
{%- set res = frappe.utils.get_thumbnail_base64_for_image(src) if src else false -%}
|
||||
{%- if res and res['base64'].startswith('data:') -%}
|
||||
<img src="{{ res['base64'] }}" class="image-with-blur {{ resolve_class(class) }}"
|
||||
data-src="{{ src or '' }}" alt="{{ alt or '' }}"
|
||||
width="{{ res['width'] }}" height="{{ res['height'] }}"
|
||||
style="width: {{ res['width'] }}px; height: {{ res['height'] }}px;" />
|
||||
alt="{{ alt or '' }}" width="{{ res['width'] }}" height="{{ res['height'] }}" data-src="{{ src or '' }}" />
|
||||
{%- else -%}
|
||||
<img src="{{ src or '' }}" class="{{ resolve_class(class) }}" alt="{{ alt or '' }}" />
|
||||
{%- endif -%}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ login.login_handlers = (function() {
|
|||
} else if(data.message == 'Password Reset'){
|
||||
window.location.href = frappe.utils.sanitise_redirect(data.redirect_to);
|
||||
} else if(data.message=="No App") {
|
||||
login.set_indicator("{{ _("Success") }}", 'green');
|
||||
login.set_indicator("{{ _('Success') }}", 'green');
|
||||
if(localStorage) {
|
||||
var last_visited =
|
||||
localStorage.getItem("last_visited")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
{%- if metatags -%}
|
||||
{%- for name in metatags %}
|
||||
<meta {% if name.startswith('og:') %}property="{{ name }}"{% else %}name="{{ name }}"{% endif %} content="{{ metatags[name] | striptags | escape }}">
|
||||
{%- set content = metatags.get(name, '') -%}
|
||||
{%- if content -%}
|
||||
<meta {% if name.startswith('og:') %}property="{{ name }}"{% else %}name="{{ name }}"{% endif %} content="{{ content | striptags | escape }}">
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
|
|
|||
43
frappe/templates/pages/integrations/paytm_checkout.html
Normal file
43
frappe/templates/pages/integrations/paytm_checkout.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %} Payment {% endblock %}
|
||||
|
||||
{%- block header -%}
|
||||
<head>
|
||||
<title>Merchant Checkout Page</title>
|
||||
</head>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script defer type="text/javascript">
|
||||
document.paytm_form.submit();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{%- block page_content -%}
|
||||
<body>
|
||||
<div class="centered">
|
||||
<h2>Please do not refresh this page...</h2>
|
||||
|
||||
<form method="post" action="{{ url }}" name="paytm_form">
|
||||
{% for name, value in payment_details.items() %}
|
||||
<input type="hidden" name="{{ name }}" value="{{ value }}">
|
||||
{% endfor %}
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
||||
{% block style %}
|
||||
<style>
|
||||
.centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.web-footer {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
27
frappe/templates/pages/integrations/paytm_checkout.py
Normal file
27
frappe/templates/pages/integrations/paytm_checkout.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
import json
|
||||
from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
paytm_config = get_paytm_config()
|
||||
|
||||
try:
|
||||
doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id'])
|
||||
|
||||
context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config)
|
||||
|
||||
context.url = paytm_config.url
|
||||
|
||||
except Exception:
|
||||
frappe.log_error()
|
||||
frappe.redirect_to_message(_('Invalid Token'),
|
||||
_('Seems token you are using is invalid!'),
|
||||
http_status_code=400, indicator_color='red')
|
||||
|
||||
frappe.local.flags.redirect_location = frappe.local.response.location
|
||||
raise frappe.Redirect
|
||||
|
|
@ -99,9 +99,7 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
|
|||
{%- if df.fieldtype=="Code" %}
|
||||
<pre class="value">{{ doc.get(df.fieldname) }}</pre>
|
||||
{% else -%}
|
||||
{%- if df.fieldtype=="Text Editor" -%}<div class='ql-editor'>{%- endif -%}
|
||||
{{ doc.get_formatted(df.fieldname, parent_doc or doc, translated=df.translatable) }}
|
||||
{%- if df.fieldtype=="Text Editor" -%}</div>{%- endif -%}
|
||||
{% endif -%}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ class TestNaming(unittest.TestCase):
|
|||
series = 'TEST-'
|
||||
key = 'TEST-'
|
||||
name = 'TEST-00003'
|
||||
frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series)
|
||||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,))
|
||||
revert_series_if_last(key, name)
|
||||
count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0]
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class TestScheduler(TestCase):
|
|||
|
||||
# 2nd job not loaded
|
||||
self.assertFalse(job.enqueue())
|
||||
job.delete()
|
||||
frappe.db.sql('DELETE FROM `tabScheduled Job Log` WHERE `scheduled_job_type`=%s', job.name)
|
||||
|
||||
def test_is_dormant(self):
|
||||
self.assertTrue(is_dormant(check_time= get_datetime('2100-01-01 00:00:00')))
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue