Merge branch 'web-form-list-empty-state' of https://github.com/pateljannat/frappe into web-form-list-empty-state

This commit is contained in:
Jannat Patel 2022-03-17 16:42:49 +05:30
commit d5a7cd88e4
65 changed files with 912 additions and 557 deletions

View file

@ -0,0 +1,35 @@
context('List Paging', () => {
before(() => {
cy.login();
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_multiple_todo_records");
});
});
it('test load more with count selection buttons', () => {
cy.visit('/app/todo/view/report');
cy.get('.list-paging-area .list-count').should('contain.text', '20 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '40 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '60 of');
cy.get('.list-paging-area .btn-group .btn-paging[data-value="100"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '100 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '200 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '300 of');
// check if refresh works after load more
cy.get('.page-head .standard-actions [data-original-title="Refresh"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '300 of');
cy.get('.list-paging-area .btn-group .btn-paging[data-value="500"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '500 of');
});
});

View file

@ -0,0 +1,22 @@
context('Number Card', () => {
before(() => {
cy.login();
cy.visit('/app/website');
});
it('Check filter populate for child table doctype', () => {
cy.visit('/app/number-card/new-number-card-1');
cy.get('[data-fieldname="parent_document_type"]').should('have.css', 'display', 'none');
cy.get_field('document_type', 'Link');
cy.fill_field('document_type', 'Workspace Link', 'Link').focus().blur();
cy.get_field('document_type', 'Link').should('have.value', 'Workspace Link');
cy.fill_field('label', 'Test Number Card', 'Data');
cy.get('[data-fieldname="filters_json"]').click().wait(200);
cy.get('.modal-body .filter-action-buttons .add-filter').click();
cy.get('.modal-body .fieldname-select-area').click();
cy.get('.modal-actions .btn-modal-close').click();
});
});

View file

@ -13,9 +13,6 @@ context('Report View', () => {
'enabled': 0,
'docstatus': 1 // submit document
}, true);
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_multiple_contact_records");
});
});
it('Field with enabled allow_on_submit should be editable.', () => {
@ -43,32 +40,4 @@ context('Report View', () => {
expect(r.message.enabled).to.equals(1);
});
});
it('test load more with count selection buttons', () => {
cy.visit('/app/contact/view/report');
cy.get('.list-paging-area .list-count').should('contain.text', '20 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '40 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '60 of');
cy.get('.list-paging-area .btn-group .btn-paging[data-value="100"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '100 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '200 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '300 of');
// check if refresh works after load more
cy.get('.page-head .standard-actions [data-original-title="Refresh"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '300 of');
cy.get('.list-paging-area .btn-group .btn-paging[data-value="500"]').click();
cy.get('.list-paging-area .list-count').should('contain.text', '500 of');
cy.get('.list-paging-area .btn-more').click();
cy.get('.list-paging-area .list-count').should('contain.text', '1000 of');
});
});

View file

@ -9,7 +9,7 @@ const cliui = require("cliui")();
const chalk = require("chalk");
const html_plugin = require("./frappe-html");
const rtlcss = require('rtlcss');
const postCssPlugin = require("esbuild-plugin-postcss2").default;
const postCssPlugin = require("@frappe/esbuild-plugin-postcss2").default;
const ignore_assets = require("./ignore-assets");
const sass_options = require("./sass_options");
const build_cleanup_plugin = require("./build-cleanup");

View file

@ -20,7 +20,8 @@ module.exports = {
.then(content => {
content = scrub_html_template(content);
return {
contents: `\n\tfrappe.templates['${filename}'] = \`${content}\`;\n`
contents: `\n\tfrappe.templates['${filename}'] = \`${content}\`;\n`,
watchFiles: [filepath]
};
})
.catch(() => {

View file

@ -850,8 +850,7 @@ def set_value(doctype, docname, fieldname, value=None):
return frappe.client.set_value(doctype, docname, fieldname, value)
def get_cached_doc(*args, **kwargs):
if args and len(args) > 1 and isinstance(args[1], str):
key = get_document_cache_key(args[0], args[1])
if key := can_cache_doc(args):
# local cache
doc = local.document_cache.get(key)
if doc:
@ -869,8 +868,24 @@ def get_cached_doc(*args, **kwargs):
return doc
def can_cache_doc(args):
"""
Determine if document should be cached based on get_doc params.
Returns cache key if doc can be cached, None otherwise.
"""
if not args:
return
doctype = args[0]
name = doctype if len(args) == 1 else args[1]
# Only cache if both doctype and name are strings
if isinstance(doctype, str) and isinstance(name, str):
return get_document_cache_key(doctype, name)
def get_document_cache_key(doctype, name):
return '{0}::{1}'.format(doctype, name)
return f'{doctype}::{name}'
def clear_document_cache(doctype, name):
cache().hdel("last_modified", doctype)
@ -911,8 +926,7 @@ def get_doc(*args, **kwargs):
doc = frappe.model.document.get_doc(*args, **kwargs)
# set in cache
if args and len(args) > 1:
key = get_document_cache_key(args[0], args[1])
if key := can_cache_doc(args):
local.document_cache[key] = doc
cache().hset('document_cache', key, doc.as_dict())

View file

@ -325,6 +325,7 @@ def get_desk_settings():
def get_notification_settings():
return frappe.get_cached_doc('Notification Settings', frappe.session.user)
@frappe.whitelist()
def get_link_title_doctypes():
dts = frappe.get_all("DocType", {"show_title_field_in_link": 1})
custom_dts = frappe.get_all(

16
frappe/commands/site.py Executable file → Normal file
View file

@ -1,7 +1,7 @@
# imports - standard imports
import os
import sys
import shutil
import sys
# imports - third party imports
import click
@ -65,11 +65,11 @@ def restore(context, sql_file_path, encryption_key=None, db_root_username=None,
"Restore site database from an sql file"
from frappe.installer import (
_new_site,
extract_sql_from_archive,
extract_files,
extract_sql_from_archive,
is_downgrade,
is_partial,
validate_database_sql
validate_database_sql,
)
from frappe.utils.backups import Backup
if not os.path.exists(sql_file_path):
@ -207,7 +207,7 @@ def restore(context, sql_file_path, encryption_key=None, db_root_username=None,
@click.option('--encryption-key', help='Backup encryption key')
@pass_context
def partial_restore(context, sql_file_path, verbose, encryption_key=None):
from frappe.installer import partial_restore, extract_sql_from_archive
from frappe.installer import extract_sql_from_archive, partial_restore
from frappe.utils.backups import Backup
if not os.path.exists(sql_file_path):
@ -545,7 +545,7 @@ def _use(site, sites_path='.'):
def use(site, sites_path='.'):
if os.path.exists(os.path.join(sites_path, site)):
with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile:
with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile:
sitefile.write(site)
print("Current Site set to {}".format(site))
else:
@ -751,6 +751,7 @@ def set_admin_password(context, admin_password=None, logout_all_sessions=False):
def set_user_password(site, user, password, logout_all_sessions=False):
import getpass
from frappe.utils.password import update_password
try:
@ -881,15 +882,16 @@ def stop_recording(context):
raise SiteNotSpecifiedError
@click.command('ngrok')
@click.option('--bind-tls', is_flag=True, default=False, help='Returns a reference to the https tunnel.')
@pass_context
def start_ngrok(context):
def start_ngrok(context, bind_tls):
from pyngrok import ngrok
site = get_site(context)
frappe.init(site=site)
port = frappe.conf.http_port or frappe.conf.webserver_port
tunnel = ngrok.connect(addr=str(port), host_header=site)
tunnel = ngrok.connect(addr=str(port), host_header=site, bind_tls=bind_tls)
print(f'Public URL: {tunnel.public_url}')
print('Inspect logs at http://localhost:4040')

View file

@ -18,6 +18,7 @@ from urllib.parse import unquote
from frappe.utils.user import is_system_user
from frappe.contacts.doctype.contact.contact import get_contact_name
from frappe.automation.doctype.assignment_rule.assignment_rule import apply as apply_assignment_rule
from parse import compile
exclude_from_linked_with = True
@ -114,6 +115,44 @@ class Communication(Document, CommunicationEmailMixin):
frappe.publish_realtime('new_message', self.as_dict(),
user=self.reference_name, after_commit=True)
def set_signature_in_email_content(self):
"""Set sender's User.email_signature or default outgoing's EmailAccount.signature to the email
"""
if not self.content:
return
quill_parser = compile('<div class="ql-editor read-mode">{}</div>')
email_body = quill_parser.parse(self.content)
if not email_body:
return
email_body = email_body[0]
user_email_signature = frappe.db.get_value(
"User",
self.sender,
"email_signature",
) if self.sender else None
signature = user_email_signature or frappe.db.get_value(
"Email Account",
{"default_outgoing": 1, "add_signature": 1},
"signature",
)
if not signature:
return
_signature = quill_parser.parse(signature)[0] if "ql-editor" in signature else None
if (_signature or signature) not in self.content:
self.content = f'{self.content}</p><br><p class="signature">{signature}'
def before_save(self):
if not self.flags.skip_add_signature:
self.set_signature_in_email_content()
def on_update(self):
# add to _comment property of the doctype, so it shows up in
# comments count for the list view

View file

@ -22,12 +22,30 @@ OUTGOING_EMAIL_ACCOUNT_MISSING = _("""
@frappe.whitelist()
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False,
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None,
flags=None, read_receipt=None, print_letterhead=True, email_template=None, communication_type=None,
ignore_permissions=False) -> Dict[str, str]:
"""Make a new communication.
def make(
doctype=None,
name=None,
content=None,
subject=None,
sent_or_received="Sent",
sender=None,
sender_full_name=None,
recipients=None,
communication_medium="Email",
send_email=False,
print_html=None,
print_format=None,
attachments="[]",
send_me_a_copy=False,
cc=None,
bcc=None,
read_receipt=None,
print_letterhead=True,
email_template=None,
communication_type=None,
**kwargs,
) -> Dict[str, str]:
"""Make a new communication. Checks for email permissions for specified Document.
:param doctype: Reference DocType.
:param name: Reference Document name.
@ -44,17 +62,71 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
:param send_me_a_copy: Send a copy to the sender (default **False**).
:param email_template: Template which is used to compose mail .
"""
is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report")
send_me_a_copy = cint(send_me_a_copy)
if kwargs:
from frappe.utils.commands import warn
warn(
f"Options {kwargs} used in frappe.core.doctype.communication.email.make "
"are deprecated or unsupported",
category=DeprecationWarning
)
if not ignore_permissions:
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'):
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
doctype=doctype, name=name))
if doctype and name and not frappe.has_permission(doctype=doctype, ptype="email", doc=name):
raise frappe.PermissionError(
f"You are not allowed to send emails related to: {doctype} {name}"
)
if not sender:
sender = get_formatted_email(frappe.session.user)
return _make(
doctype=doctype,
name=name,
content=content,
subject=subject,
sent_or_received=sent_or_received,
sender=sender,
sender_full_name=sender_full_name,
recipients=recipients,
communication_medium=communication_medium,
send_email=send_email,
print_html=print_html,
print_format=print_format,
attachments=attachments,
send_me_a_copy=cint(send_me_a_copy),
cc=cc,
bcc=bcc,
read_receipt=read_receipt,
print_letterhead=print_letterhead,
email_template=email_template,
communication_type=communication_type,
add_signature=False,
)
def _make(
doctype=None,
name=None,
content=None,
subject=None,
sent_or_received="Sent",
sender=None,
sender_full_name=None,
recipients=None,
communication_medium="Email",
send_email=False,
print_html=None,
print_format=None,
attachments="[]",
send_me_a_copy=False,
cc=None,
bcc=None,
read_receipt=None,
print_letterhead=True,
email_template=None,
communication_type=None,
add_signature=True,
) -> Dict[str, str]:
"""Internal method to make a new communication that ignores Permission checks.
"""
sender = sender or get_formatted_email(frappe.session.user)
recipients = list_to_str(recipients) if isinstance(recipients, list) else recipients
cc = list_to_str(cc) if isinstance(cc, list) else cc
bcc = list_to_str(bcc) if isinstance(bcc, list) else bcc
@ -77,7 +149,9 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
"read_receipt":read_receipt,
"has_attachment": 1 if attachments else 0,
"communication_type": communication_type,
}).insert(ignore_permissions=True)
})
comm.flags.skip_add_signature = not add_signature
comm.insert(ignore_permissions=True)
# if not committed, delayed task doesn't find the communication
if attachments:
@ -87,17 +161,21 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
if cint(send_email):
if not comm.get_outgoing_email_account():
frappe.throw(msg=OUTGOING_EMAIL_ACCOUNT_MISSING, exc=frappe.OutgoingEmailError)
frappe.throw(
msg=OUTGOING_EMAIL_ACCOUNT_MISSING, exc=frappe.OutgoingEmailError
)
comm.send_email(print_html=print_html, print_format=print_format,
send_me_a_copy=send_me_a_copy, print_letterhead=print_letterhead)
comm.send_email(
print_html=print_html,
print_format=print_format,
send_me_a_copy=send_me_a_copy,
print_letterhead=print_letterhead,
)
emails_not_sent_to = comm.exclude_emails_list(include_sender=send_me_a_copy)
return {
"name": comm.name,
"emails_not_sent_to": ", ".join(emails_not_sent_to)
}
return {"name": comm.name, "emails_not_sent_to": ", ".join(emails_not_sent_to)}
def validate_email(doc: "Communication") -> None:
"""Validate Email Addresses of Recipients and CC"""

View file

@ -732,9 +732,12 @@ class DocType(Document):
frappe.throw(_("DocType's name should not start or end with whitespace"), frappe.NameError)
# a DocType's name should not start with a number or underscore
# and should only contain letters, numbers and underscore
if not re.match(r"^(?![\W])[^\d_\s][\w ]+$", name, **flags):
frappe.throw(_("DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores"), frappe.NameError)
# and should only contain letters, numbers, underscore, and hyphen
if not re.match(r"^(?![\W])[^\d_\s][\w -]+$", name, **flags):
frappe.throw(_(
"A DocType's name should start with a letter and can only "
"consist of letters, numbers, spaces, underscores and hyphens"
), frappe.NameError, title="Invalid Name")
validate_route_conflict(self.doctype, self.name)

View file

@ -24,7 +24,7 @@ class TestDocType(unittest.TestCase):
self.assertRaises(frappe.NameError, new_doctype("8Some DocType").insert)
self.assertRaises(frappe.NameError, new_doctype("Some (DocType)").insert)
self.assertRaises(frappe.NameError, new_doctype("Some Doctype with a name whose length is more than 61 characters").insert)
for name in ("Some DocType", "Some_DocType"):
for name in ("Some DocType", "Some_DocType", "Some-DocType"):
if frappe.db.exists("DocType", name):
frappe.delete_doc("DocType", name)

View file

@ -61,7 +61,7 @@ class Role(Document):
def get_info_based_on_role(role, field='email'):
''' Get information of all users that have been assigned this role '''
users = frappe.get_list("Has Role", filters={"role": role, "parenttype": "User"},
users = frappe.get_list("Has Role", filters={"role": role}, parent_doctype="User",
fields=["parent as user_name"])
return get_user_info(users, field)

View file

@ -668,8 +668,7 @@
"link_fieldname": "user"
}
],
"max_attachments": 5,
"modified": "2022-01-03 11:53:25.250822",
"modified": "2022-03-09 01:47:56.745069",
"modified_by": "Administrator",
"module": "Core",
"name": "User",

View file

@ -181,7 +181,7 @@ class Database(object):
print(e)
raise
if ignore_ddl and (self.is_missing_column(e) or self.is_missing_table(e) or self.cant_drop_field_or_key(e)):
if ignore_ddl and (self.is_missing_column(e) or self.is_table_missing(e) or self.cant_drop_field_or_key(e)):
pass
else:
raise
@ -1028,7 +1028,7 @@ class Database(object):
return []
def is_missing_table_or_column(self, e):
return self.is_missing_column(e) or self.is_missing_table(e)
return self.is_missing_column(e) or self.is_table_missing(e)
def multisql(self, sql_dict, values=(), **kwargs):
current_dialect = frappe.db.db_type or 'mariadb'

View file

@ -154,6 +154,10 @@ class MariaDBDatabase(Database):
def is_table_missing(e):
return e.args[0] == ER.NO_SUCH_TABLE
@staticmethod
def is_missing_table(e):
return MariaDBDatabase.is_table_missing(e)
@staticmethod
def is_missing_column(e):
return e.args[0] == ER.BAD_FIELD_ERROR

View file

@ -99,16 +99,8 @@ class PostgresDatabase(Database):
return db_size[0].get('database_size')
# pylint: disable=W0221
def sql(self, *args, **kwargs):
if args:
# since tuple is immutable
args = list(args)
args[0] = modify_query(args[0])
args = tuple(args)
elif kwargs.get('query'):
kwargs['query'] = modify_query(kwargs.get('query'))
return super(PostgresDatabase, self).sql(*args, **kwargs)
def sql(self, query, *args, **kwargs):
return super(PostgresDatabase, self).sql(modify_query(query), *args, **kwargs)
def get_tables(self, cached=True):
return [d[0] for d in self.sql("""select table_name
@ -153,6 +145,10 @@ class PostgresDatabase(Database):
def is_table_missing(e):
return getattr(e, 'pgcode', None) == '42P01'
@staticmethod
def is_missing_table(e):
return PostgresDatabase.is_table_missing(e)
@staticmethod
def is_missing_column(e):
return getattr(e, 'pgcode', None) == '42703'
@ -335,7 +331,7 @@ def modify_query(query):
query = replace_locate_with_strpos(query)
# select from requires ""
if re.search('from tab', query, flags=re.IGNORECASE):
query = re.sub('from tab([a-zA-Z]*)', r'from "tab\1"', query, flags=re.IGNORECASE)
query = re.sub(r'from tab([\w-]*)', r'from "tab\1"', query, flags=re.IGNORECASE)
return query

View file

@ -28,6 +28,7 @@ frappe.ui.form.on('Number Card', {
frm.trigger('render_filters_table');
}
frm.trigger('create_add_to_dashboard_button');
frm.trigger('set_parent_document_type');
},
create_add_to_dashboard_button: function(frm) {
@ -141,7 +142,9 @@ frappe.ui.form.on('Number Card', {
frm.set_value('filters_json', '[]');
frm.set_value('dynamic_filters_json', '[]');
frm.set_value('aggregate_function_based_on', '');
frm.set_value('parent_document_type', '');
frm.trigger('set_options');
frm.trigger('set_parent_document_type');
},
set_options: function(frm) {
@ -317,6 +320,7 @@ frappe.ui.form.on('Number Card', {
frm.filter_group = new frappe.ui.FilterGroup({
parent: dialog.get_field('filter_area').$wrapper,
doctype: frm.doc.document_type,
parent_doctype: frm.doc.parent_document_type,
on_change: () => {},
});
filters && frm.filter_group.add_filters_to_filter_group(filters);
@ -436,6 +440,36 @@ frappe.ui.form.on('Number Card', {
frm.dynamic_filter_table.find('tbody').html(filter_rows);
}
},
set_parent_document_type: async function(frm) {
let document_type = frm.doc.document_type;
let doc_is_table = document_type &&
(await frappe.db.get_value('DocType', document_type, 'istable')).message.istable;
frm.set_df_property('parent_document_type', 'hidden', !doc_is_table);
if (document_type && doc_is_table) {
let parent = await frappe.db.get_list('DocField', {
filters: {
'fieldtype': 'Table',
'options': document_type
},
fields: ['parent']
});
parent && frm.set_query('parent_document_type', function() {
return {
filters: {
"name": ['in', parent.map(({ parent }) => parent)]
}
};
});
if (parent.length === 1) {
frm.set_value('parent_document_type', parent[0].parent);
}
}
}
});

View file

@ -16,6 +16,7 @@
"aggregate_function_based_on",
"column_break_2",
"document_type",
"parent_document_type",
"report_field",
"report_function",
"is_public",
@ -188,10 +189,17 @@
"label": "Function",
"mandatory_depends_on": "eval: doc.type == 'Report'",
"options": "Sum\nAverage\nMinimum\nMaximum"
},
{
"description": "The document type selected is a child table, so the parent document type is required.",
"fieldname": "parent_document_type",
"fieldtype": "Link",
"label": "Parent Document Type",
"options": "DocType"
}
],
"links": [],
"modified": "2020-07-23 11:11:03.391719",
"modified": "2022-03-10 15:34:38.210910",
"modified_by": "Administrator",
"module": "Desk",
"name": "Number Card",
@ -234,6 +242,7 @@
"search_fields": "label, document_type",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "label",
"track_changes": 1
}

View file

@ -3,6 +3,7 @@
# License: MIT. See LICENSE
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint
from frappe.model.naming import append_number_if_name_exists
@ -17,6 +18,13 @@ class NumberCard(Document):
if frappe.db.exists("Number Card", self.name):
self.name = append_number_if_name_exists('Number Card', self.name)
def validate(self):
if not self.document_type:
frappe.throw(_("Document type is required to create a number card"))
if self.document_type and frappe.get_meta(self.document_type).istable and not self.parent_document_type:
frappe.throw(_("Parent document type is required to create a number card"))
def on_update(self):
if frappe.conf.developer_mode and self.is_standard:
export_to_files(record_list=[['Number Card', self.name]], record_module=self.module)

View file

@ -88,15 +88,16 @@ frappe.ui.form.on('System Console', {
<td>${row.Progress}</td>
</tr>`
}
frm.get_field('processlist').html(`
<p class='text-muted'>Requested on: ${timestamp}</p>
<table class='table-bordered' style='width: 100%'>
<thead><tr>
<th width='10%'>Id</ht>
<th width='5%'>Id</ht>
<th width='10%'>Time</ht>
<th width='10%'>State</ht>
<th width='60%'>Info</ht>
<th width='10%'>Progress</ht>
<th width='15%'>Progress / Wait Event</ht>
</tr></thead>
<tbody>${rows}</thead>`);
});

View file

@ -41,4 +41,14 @@ def execute_code(doc):
@frappe.whitelist()
def show_processlist():
frappe.only_for('System Manager')
return frappe.db.sql('show full processlist', as_dict=1)
return frappe.db.multisql({
"postgres": """
SELECT pid AS "Id",
query_start AS "Time",
state AS "State",
query AS "Info",
wait_event AS "Progress"
FROM pg_stat_activity""",
"mariadb": "show full processlist"
}, as_dict=True)

View file

@ -277,6 +277,7 @@ def sort_page(workspace_pages, pages):
doc = frappe.get_doc('Workspace', page.name)
doc.sequence_id = seq + 1
doc.parent_page = d.get('parent_page') or ""
doc.flags.ignore_links = True
doc.save(ignore_permissions=True)
break

View file

@ -1,9 +1,10 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import json
from collections import defaultdict
import itertools
from typing import List
from typing import Dict, List, Optional
import frappe
import frappe.desk.form.load
@ -367,7 +368,7 @@ def get_exempted_doctypes():
@frappe.whitelist()
def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
def get_linked_docs(doctype: str, name: str, linkinfo: Optional[Dict] = None) -> Dict[str, List]:
if isinstance(linkinfo, str):
# additional fields are added in linkinfo
linkinfo = json.loads(linkinfo)
@ -377,23 +378,21 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
if not linkinfo:
return results
if for_doctype:
links = frappe.get_doc(doctype, name).get_link_filters(for_doctype)
if links:
linkinfo = links
if for_doctype in linkinfo:
# only get linked with for this particular doctype
linkinfo = { for_doctype: linkinfo.get(for_doctype) }
else:
return results
for dt, link in linkinfo.items():
filters = []
link["doctype"] = dt
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
try:
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
except Exception as e:
if isinstance(e, frappe.DoesNotExistError):
if frappe.local.message_log:
frappe.local.message_log.pop()
continue
linkmeta = link_meta_bundle[0]
if not linkmeta.has_permission():
continue
if not linkmeta.get("issingle"):
fields = [d.fieldname for d in linkmeta.get("fields", {
"in_list_view": 1,
@ -456,6 +455,13 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
return results
@frappe.whitelist()
def get(doctype, docname):
linked_doctypes = get_linked_doctypes(doctype=doctype)
return get_linked_docs(doctype=doctype, name=docname, linkinfo=linked_doctypes)
@frappe.whitelist()
def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
"""add list of doctypes this doctype is 'linked' with.
@ -470,6 +476,7 @@ def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
else:
return frappe.cache().hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype))
def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
ret = {}
# find fields where this doctype is linked
@ -499,6 +506,7 @@ def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False)
return ret
def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
filters = [['fieldtype','=', 'Link'], ['options', '=', doctype]]
@ -529,6 +537,7 @@ def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
return ret
def get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
ret = {}

View file

@ -124,7 +124,6 @@ def get_docinfo(doc=None, doctype=None, name=None):
update_user_info(docinfo)
frappe.response["docinfo"] = docinfo
return docinfo
def add_comments(doc, docinfo):
# divide comments into separate lists

View file

@ -352,14 +352,10 @@ def export_query():
)
return
columns = get_columns_dict(data.columns)
from frappe.utils.xlsxutils import make_xlsx
data["result"] = handle_duration_fieldtype_values(
data.get("result"), data.get("columns")
)
xlsx_data, column_widths = build_xlsx_data(columns, data, visible_idx, include_indentation)
format_duration_fields(data)
xlsx_data, column_widths = build_xlsx_data(data, visible_idx, include_indentation)
xlsx_file = make_xlsx(xlsx_data, "Query Report", column_widths=column_widths)
frappe.response["filename"] = report_name + ".xlsx"
@ -367,39 +363,18 @@ def export_query():
frappe.response["type"] = "binary"
def handle_duration_fieldtype_values(result, columns):
for i, col in enumerate(columns):
fieldtype = None
if isinstance(col, str):
col = col.split(":")
if len(col) > 1:
if col[1]:
fieldtype = col[1]
if "/" in fieldtype:
fieldtype, options = fieldtype.split("/")
else:
fieldtype = "Data"
else:
fieldtype = col.get("fieldtype")
def format_duration_fields(data: frappe._dict) -> None:
for i, col in enumerate(data.columns):
if col.get("fieldtype") != "Duration":
continue
if fieldtype == "Duration":
for entry in range(0, len(result)):
row = result[entry]
if isinstance(row, dict):
val_in_seconds = row[col.fieldname]
if val_in_seconds:
duration_val = format_duration(val_in_seconds)
row[col.fieldname] = duration_val
else:
val_in_seconds = row[i]
if val_in_seconds:
duration_val = format_duration(val_in_seconds)
row[i] = duration_val
return result
for row in data.result:
index = col.fieldname if isinstance(row, dict) else i
if row[index]:
row[index] = format_duration(row[index])
def build_xlsx_data(columns, data, visible_idx, include_indentation, ignore_visible_idx=False):
def build_xlsx_data(data, visible_idx, include_indentation, ignore_visible_idx=False):
result = [[]]
column_widths = []

View file

@ -104,7 +104,7 @@ class AutoEmailReport(Document):
report_data['columns'] = columns
report_data['result'] = data
xlsx_data, column_widths = build_xlsx_data(columns, report_data, [], 1, ignore_visible_idx=True)
xlsx_data, column_widths = build_xlsx_data(report_data, [], 1, ignore_visible_idx=True)
xlsx_file = make_xlsx(xlsx_data, "Auto Email Report", column_widths=column_widths)
return xlsx_file.getvalue()
@ -113,7 +113,7 @@ class AutoEmailReport(Document):
report_data['columns'] = columns
report_data['result'] = data
xlsx_data, column_widths = build_xlsx_data(columns, report_data, [], 1, ignore_visible_idx=True)
xlsx_data, column_widths = build_xlsx_data(report_data, [], 1, ignore_visible_idx=True)
return to_csv(xlsx_data)
else:

View file

@ -236,8 +236,7 @@
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
"max_attachments": 3,
"modified": "2021-12-06 20:09:37.963141",
"modified": "2022-03-09 01:48:16.741603",
"modified_by": "Administrator",
"module": "Email",
"name": "Newsletter",

View file

@ -186,7 +186,7 @@ def get_context(context):
def send_an_email(self, doc, context):
from email.utils import formataddr
from frappe.core.doctype.communication.email import make as make_communication
from frappe.core.doctype.communication.email import _make as make_communication
subject = self.subject
if "{" in subject:
subject = frappe.render_template(self.subject, context)
@ -216,7 +216,8 @@ def get_context(context):
# Add mail notification to communication list
# No need to add if it is already a communication.
if doc.doctype != 'Communication':
make_communication(doctype=doc.doctype,
make_communication(
doctype=doc.doctype,
name=doc.name,
content=message,
subject=subject,
@ -228,7 +229,7 @@ def get_context(context):
cc=cc,
bcc=bcc,
communication_type='Automated Message',
ignore_permissions=True)
)
def send_a_slack_msg(self, doc, context):
send_slack_message(

View file

@ -259,17 +259,12 @@ def get_formatted_html(subject, message, footer=None, print_html=None,
email_account = email_account or EmailAccount.find_outgoing(match_by_email=sender)
signature = None
if "<!-- signature-included -->" not in message:
signature = get_signature(email_account)
rendered_email = frappe.get_template("templates/emails/standard.html").render({
"brand_logo": get_brand_logo(email_account) if with_container or header else None,
"with_container": with_container,
"site_url": get_url(),
"header": get_header(header),
"content": message,
"signature": signature,
"footer": get_footer(email_account, footer),
"title": subject,
"print_html": print_html,
@ -281,8 +276,7 @@ def get_formatted_html(subject, message, footer=None, print_html=None,
if unsubscribe_link:
html = html.replace("<!--unsubscribe link here-->", unsubscribe_link.html)
html = inline_style_in_html(html)
return html
return inline_style_in_html(html)
@frappe.whitelist()
def get_email_html(template, args, subject, header=None, with_container=False):

View file

@ -43,8 +43,8 @@ def update_document_title(
title_field = doc.meta.get_title_field()
title_updated = (title_field != "name") and (updated_title != doc.get(title_field))
name_updated = updated_name != doc.name
title_updated = updated_title and (title_field != "name") and (updated_title != doc.get(title_field))
name_updated = updated_name and (updated_name != doc.name)
if name_updated:
docname = rename_doc(doctype=doctype, old=docname, new=updated_name, merge=merge)

View file

@ -11,7 +11,7 @@ from frappe.query_builder import DocType
from frappe.utils import get_datetime, now
def caclulate_hash(path: str) -> str:
def calculate_hash(path: str) -> str:
"""Calculate md5 hash of the file in binary mode
Args:
@ -99,7 +99,7 @@ def import_file_by_path(path: str,force: bool = False,data_import: bool = False,
print(f"{path} missing")
return
calculated_hash = caclulate_hash(path)
calculated_hash = calculate_hash(path)
if docs:
if not isinstance(docs, list):

View file

@ -24,7 +24,7 @@ ul.tree-children {
}
.tree-link .node-parent,
.tree-link .node-leaf {
margin-right: 5px;
margin-right: 8px;
}
.tree-link.active i {
color: #5e64ff;

View file

@ -166,6 +166,9 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui
}
parse_options(options) {
if (typeof options === 'string' && options[0] === '[') {
options = frappe.utils.parse_json(options);
}
if (typeof options === 'string') {
options = options.split('\n');
}

View file

@ -501,9 +501,9 @@ export default class Grid {
}
set_column_disp(fieldname, show) {
if ($.isArray(fieldname)) {
if (Array.isArray(fieldname)) {
for (let field of fieldname) {
this.update_docfield_property(field, "hidden", show);
this.update_docfield_property(field, "hidden", show ? 0 : 1);
this.set_editable_grid_column_disp(field, show);
}
} else {

View file

@ -1,9 +1,8 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See LICENSE
frappe.ui.form.LinkedWith = class LinkedWith {
constructor(opts) {
$.extend(this, opts);
}
@ -21,29 +20,23 @@ frappe.ui.form.LinkedWith = class LinkedWith {
}
make_dialog() {
this.dialog = new frappe.ui.Dialog({
title: __("Linked With")
});
this.dialog.on_page_show = () => {
// execute ajax calls sequentially
// 1. get linked doctypes
// 2. load all doctypes
// 3. load linked docs
this.get_linked_doctypes()
.then(() => this.load_doctypes())
.then(() => this.links_not_permitted_or_missing())
.then(() => this.get_linked_docs())
.then(() => this.make_html());
frappe.xcall(
"frappe.desk.form.linked_with.get",
{"doctype": cur_frm.doctype, "docname": cur_frm.docname},
).then(r => {
this.frm.__linked_docs = r;
}).then(() => this.make_html());
};
}
make_html() {
const linked_docs = this.frm.__linked_docs;
let html = '';
const linked_docs = this.frm.__linked_docs;
const linked_doctypes = Object.keys(linked_docs);
if (linked_doctypes.length === 0) {
@ -63,88 +56,6 @@ frappe.ui.form.LinkedWith = class LinkedWith {
$(this.dialog.body).html(html);
}
load_doctypes() {
const already_loaded = Object.keys(locals.DocType);
let doctypes_to_load = [];
if (this.frm.__linked_doctypes) {
doctypes_to_load =
Object.keys(this.frm.__linked_doctypes)
.filter(doctype => !already_loaded.includes(doctype));
}
// load all doctypes asynchronously using with_doctype
const promises = doctypes_to_load.map(dt => {
return frappe.model.with_doctype(dt, () => {
if(frappe.listview_settings[dt]) {
// add additional fields to __linked_doctypes
this.frm.__linked_doctypes[dt].add_fields =
frappe.listview_settings[dt].add_fields;
}
});
});
return Promise.all(promises);
}
links_not_permitted_or_missing() {
let links = null;
if (this.frm.__linked_doctypes) {
links =
Object.keys(this.frm.__linked_doctypes)
.filter(frappe.model.can_get_report);
}
let flag;
if(!links) {
$(this.dialog.body).html(`${this.frm.__linked_doctypes
? __("Not enough permission to see links")
: __("Not Linked to any record")}`);
flag = true;
}
flag = false;
// reject Promise if not_permitted or missing
return new Promise(
(resolve, reject) => flag ? reject() : resolve()
);
}
get_linked_doctypes() {
return new Promise((resolve) => {
if (this.frm.__linked_doctypes) {
resolve();
}
frappe.call({
method: "frappe.desk.form.linked_with.get_linked_doctypes",
args: {
doctype: this.frm.doctype
},
callback: (r) => {
this.frm.__linked_doctypes = r.message;
resolve();
}
});
});
}
get_linked_docs() {
return frappe.call({
method: "frappe.desk.form.linked_with.get_linked_docs",
args: {
doctype: this.frm.doctype,
name: this.frm.docname,
linkinfo: this.frm.__linked_doctypes,
for_doctype: this.for_doctype
},
callback: (r) => {
this.frm.__linked_docs = r.message || {};
}
});
}
make_doc_head(heading) {
return `
<header class="level list-row list-row-head text-muted small">

View file

@ -44,8 +44,17 @@ frappe.ui.form.Attachments = class Attachments {
// add attachment objects
var attachments = this.get_attachments();
if(attachments.length) {
attachments.forEach(function(attachment) {
me.add_attachment(attachment)
let exists = {};
let unique_attachments = attachments.filter(attachment => {
return Object.prototype.hasOwnProperty.call(
exists,
attachment.file_name
)
? false
: (exists[attachment.file_name] = true);
});
unique_attachments.forEach(attachment => {
me.add_attachment(attachment);
});
} else {
this.attachments_label.removeClass("has-attachments");
@ -75,7 +84,19 @@ frappe.ui.form.Attachments = class Attachments {
remove_action = function(target_id) {
frappe.confirm(__("Are you sure you want to delete the attachment?"),
function() {
me.remove_attachment(target_id);
let target_attachment = me
.get_attachments()
.find(attachment => attachment.name === target_id);
let to_be_removed = me
.get_attachments()
.filter(
attachment =>
attachment.file_name ===
target_attachment.file_name
);
to_be_removed.forEach(attachment =>
me.remove_attachment(attachment.name)
);
}
);
return false;

View file

@ -760,6 +760,10 @@ class FilterArea {
const doctype_fields = this.list_view.meta.fields;
const title_field = this.list_view.meta.title_field;
const has_existing_filters = (
this.list_view.filters
&& this.list_view.filters.length > 0
);
fields = fields.concat(
doctype_fields
@ -794,13 +798,17 @@ class FilterArea {
options = options.join("\n");
}
}
let default_value =
fieldtype === "Link"
? frappe.defaults.get_user_default(options)
: null;
let default_value;
if (fieldtype === "Link" && !has_existing_filters) {
default_value = frappe.defaults.get_user_default(options);
}
if (["__default", "__global"].includes(default_value)) {
default_value = null;
}
return {
fieldtype: fieldtype,
label: __(df.label),

View file

@ -6,8 +6,8 @@ frappe.provide('frappe.views.list_view');
window.cur_list = null;
frappe.views.ListFactory = class ListFactory extends frappe.views.Factory {
make (route) {
var me = this;
var doctype = route[1];
const me = this;
const doctype = route[1];
// List / Gantt / Kanban / etc
// File is a special view
@ -21,60 +21,58 @@ frappe.views.ListFactory = class ListFactory extends frappe.views.Factory {
}
frappe.provide('frappe.views.list_view.' + doctype);
const page_name = frappe.get_route_str();
if (!frappe.views.list_view[page_name]) {
frappe.views.list_view[page_name] = new view_class({
doctype: doctype,
parent: me.make_page(true, page_name)
});
} else {
frappe.container.change_to(page_name);
}
frappe.views.list_view[me.page_name] = new view_class({
doctype: doctype,
parent: me.make_page(true, me.page_name)
});
me.set_cur_list();
}
show() {
before_show() {
if (this.re_route_to_view()) {
return;
return false;
}
this.set_module_breadcrumb();
super.show();
}
on_show() {
this.set_cur_list();
cur_list && cur_list.show();
if (cur_list) cur_list.show();
}
re_route_to_view() {
var route = frappe.get_route();
var doctype = route[1];
var last_route = frappe.route_history.slice(-2)[0];
if (route[0] === 'List' && route.length === 2 && frappe.views.list_view[doctype]) {
if(last_route && last_route[0]==='List' && last_route[1]===doctype) {
// last route same as this route, so going back.
// this happens because /app/List/Item will redirect to /app/List/Item/List
// while coming from back button, the last 2 routes will be same, so
// we know user is coming in the reverse direction (via back button)
const doctype = this.route[1];
const last_route = frappe.route_history.slice(-2)[0];
if (
this.route[0] === 'List' &&
this.route.length === 2 &&
frappe.views.list_view[doctype] &&
last_route &&
last_route[0] === 'List' &&
last_route[1] === doctype
) {
// last route same as this route, so going back.
// this happens because /app/List/Item will redirect to /app/List/Item/List
// while coming from back button, the last 2 routes will be same, so
// we know user is coming in the reverse direction (via back button)
// example:
// Step 1: /app/List/Item redirects to /app/List/Item/List
// Step 2: User hits "back" comes back to /app/List/Item
// Step 3: Now we cannot send the user back to /app/List/Item/List so go back one more step
window.history.go(-1);
return true;
} else {
return false;
}
// example:
// Step 1: /app/List/Item redirects to /app/List/Item/List
// Step 2: User hits "back" comes back to /app/List/Item
// Step 3: Now we cannot send the user back to /app/List/Item/List so go back one more step
window.history.go(-1);
return true;
}
}
set_module_breadcrumb() {
if (frappe.route_history.length > 1) {
var prev_route = frappe.route_history[frappe.route_history.length - 2];
const prev_route = frappe.route_history[frappe.route_history.length - 2];
if (prev_route[0] === 'modules') {
var doctype = frappe.get_route()[1],
module = prev_route[1];
const doctype = this.route[1], module = prev_route[1];
if (frappe.module_links[module] && frappe.module_links[module].includes(doctype)) {
// save the last page from the breadcrumb was accessed
frappe.breadcrumbs.set_doctype_module(doctype, module);
@ -84,10 +82,8 @@ frappe.views.ListFactory = class ListFactory extends frappe.views.Factory {
}
set_cur_list() {
var route = frappe.get_route();
var page_name = frappe.get_route_str();
cur_list = frappe.views.list_view[page_name];
if (cur_list && cur_list.doctype !== route[1]) {
cur_list = frappe.views.list_view[this.page_name];
if (cur_list && cur_list.doctype !== this.route[1]) {
// changing...
window.cur_list = null;
}

View file

@ -83,32 +83,15 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.sort_by = this.view_user_settings.sort_by || "modified";
this.sort_order = this.view_user_settings.sort_order || "desc";
// set filters from user_settings or list_settings
if (
this.view_user_settings.filters &&
this.view_user_settings.filters.length
) {
// Priority 1: user_settings
const saved_filters = this.view_user_settings.filters;
this.filters = this.validate_filters(saved_filters);
} else {
// Priority 2: filters in listview_settings
this.filters = (this.settings.filters || []).map((f) => {
if (f.length === 3) {
f = [this.doctype, f[0], f[1], f[2]];
}
return f;
});
}
// build menu items
this.menu_items = this.menu_items.concat(this.get_menu_items());
// set filters from view_user_settings or list_settings
if (
this.view_user_settings.filters &&
this.view_user_settings.filters.length
) {
// Priority 1: saved filters
// Priority 1: view_user_settings
const saved_filters = this.view_user_settings.filters;
this.filters = this.validate_filters(saved_filters);
} else {
@ -1757,8 +1740,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const docnames = this.get_checked_items(true).map(
(docname) => docname.toString()
);
let message = __("Delete {0} item permanently?", [docnames.length], "Title of confirmation dialog");
if (docnames.length > 1) {
message = __("Delete {0} items permanently?", [docnames.length], "Title of confirmation dialog");
}
frappe.confirm(
__("Delete {0} items permanently?", [docnames.length], "Title of confirmation dialog"),
message,
() => {
this.disable_list_update = true;
bulk_operations.delete(docnames, () => {

View file

@ -138,6 +138,7 @@ frappe.render_tree = function(opts) {
opts.base_url = frappe.urllib.get_base_url();
opts.landscape = false;
opts.print_css = frappe.boot.print_css;
opts.print_format_css_path = frappe.assets.bundled_asset('print_format.bundle.css');
var tree = frappe.render_template("print_tree", opts);
var w = window.open();

View file

@ -577,13 +577,15 @@ $.extend(frappe.model, {
},
delete_doc: function(doctype, docname, callback) {
var title = docname;
var title_field = frappe.get_meta(doctype).title_field;
let title = docname;
const title_field = frappe.get_meta(doctype).title_field;
if (frappe.get_meta(doctype).autoname == "hash" && title_field) {
var title = frappe.model.get_value(doctype, docname, title_field);
title += " (" + docname + ")";
const value = frappe.model.get_value(doctype, docname, title_field);
if (value) {
title = `${value} (${docname})`;
}
}
frappe.confirm(__("Permanently delete {0}?", [title]), function() {
frappe.confirm(__("Permanently delete {0}?", [title.bold()]), function() {
return frappe.call({
method: 'frappe.client.delete',
args: {

View file

@ -134,7 +134,17 @@ frappe.msgprint = function(msg, title, is_minimizable) {
}
if(data.message instanceof Array) {
data.message.forEach(function(m) {
let messages = data.message;
const exceptions = messages
.map(m => JSON.parse(m))
.filter(m => m.raise_exception);
// only show exceptions if any exceptions exist
if (exceptions.length) {
messages = exceptions;
}
messages.forEach(function(m) {
frappe.msgprint(m);
});
return;

View file

@ -196,6 +196,15 @@ Object.assign(frappe.utils, {
}
return true;
},
parse_json: function(str) {
let parsed_json = '';
try {
parsed_json = JSON.parse(str);
} catch (e) {
return str;
}
return parsed_json;
},
strip_whitespace: function(html) {
return (html || "").replace(/<p>\s*<\/p>/g, "").replace(/<br>(\s*<br>\s*)+/g, "<br><br>");
},

View file

@ -10,20 +10,21 @@ frappe.views.Factory = class Factory {
}
show() {
var page_name = frappe.get_route_str(),
me = this;
this.route = frappe.get_route();
this.page_name = frappe.get_route_str();
if (frappe.pages[page_name]) {
frappe.container.change_to(page_name);
if(me.on_show) {
me.on_show();
if (this.before_show && this.before_show() === false) return;
if (frappe.pages[this.page_name]) {
frappe.container.change_to(this.page_name);
if (this.on_show) {
this.on_show();
}
} else {
var route = frappe.get_route();
if(route[1]) {
me.make(route);
if (this.route[1]) {
this.make(this.route);
} else {
frappe.show_not_found(route);
frappe.show_not_found(this.route);
}
}
}
@ -34,15 +35,17 @@ frappe.views.Factory = class Factory {
}
frappe.make_page = function(double_column, page_name) {
if(!page_name) {
var page_name = frappe.get_route_str();
if (!page_name) {
page_name = frappe.get_route_str();
}
var page = frappe.container.add_page(page_name);
const page = frappe.container.add_page(page_name);
frappe.ui.make_app_page({
parent: page,
single_column: !double_column
});
frappe.container.change_to(page_name);
return page;
}

View file

@ -1,91 +1,106 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{{ title }}</title>
<link href="{{ base_url }}/assets/frappe/css/bootstrap.css" rel="stylesheet">
<link type="text/css" rel="stylesheet"
href="{{ base_url }}/assets/frappe/css/font-awesome.css">
<link rel="stylesheet" type="text/css" href="{{ base_url }}/assets/frappe/css/tree.css">
<style>
{{ print_css }}
</style>
<style>
.tree.opened::before,
.tree-node.opened::before,
.tree:last-child::after,
.tree-node:last-child::after {
z-index: 1;
border-left: 1px solid #d1d8dd;
background: none;
}
.tree a,
.tree-link {
text-decoration: none;
cursor: default;
}
.tree.opened > .tree-children > .tree-node > .tree-link::before,
.tree-node.opened > .tree-children > .tree-node > .tree-link::before {
border-top: 1px solid #d1d8dd;
z-index: 1;
background: none;
}
i.fa.fa-fw.fa-folder {
z-index: 2;
position: relative;
}
.tree:last-child::after, .tree-node:last-child::after {
display: none;
}
.tree-node-toolbar {
display: none;
}
i.octicon.octicon-primitive-dot.text-extra-muted {
width: 7px;
height: 7px;
border-radius: 50%;
background: #d1d8dd;
display: inline-block;
position: relative;
z-index: 2;
}
@media (max-width: 767px) {
ul.tree-children {
padding-left: 20px;
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{{ title }}</title>
<link href="{{ base_url }}/assets/frappe/css/bootstrap.css" rel="stylesheet">
<link type="text/css" rel="stylesheet"
href="{{ base_url }}/assets/frappe/css/font-awesome.css">
<link rel="stylesheet" type="text/css" href="{{ base_url }}/assets/frappe/css/tree.css">
<link rel="stylesheet" type="text/css" href="{{ base_url }}{{ print_format_css_path }}">
<style>
{{ print_css }}
</style>
<style>
.tree.opened::before,
.tree-node.opened::before,
.tree:last-child::after,
.tree-node:last-child::after {
z-index: 1;
border-left: 1px solid #d1d8dd;
background: none;
}
.tree a,
.tree-link {
text-decoration: none;
cursor: default;
}
.tree.opened > .tree-children > .tree-node > .tree-link::before,
.tree-node.opened > .tree-children > .tree-node > .tree-link::before {
border-top: 1px solid #d1d8dd;
z-index: 1;
background: none;
}
i.fa.fa-fw.fa-folder {
z-index: 2;
position: relative;
}
.tree:last-child::after, .tree-node:last-child::after {
display: none;
}
.tree-node-toolbar {
display: none;
}
i.octicon.octicon-primitive-dot.text-extra-muted {
width: 7px;
height: 7px;
border-radius: 50%;
background: #d1d8dd;
display: inline-block;
position: relative;
z-index: 2;
}
}
</style>
</head>
<body>
<div class="print-format-gutter">
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if print_settings.letter_head && print_settings.letter_head.footer %}
<div class="letter-head-footer">
{{ print_settings.letter_head.footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ __("Page {0} of {1}", [`<span class="page"></span>`, `<span class="topage"></span>`]) }}
</p>
</div>
{% endif %}
<div class="print-format {% if landscape %} landscape {% endif %}">
{% if print_settings.letter_head %}
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
<div class="letter-head">{{ print_settings.letter_head.header }}</div>
</div>
{% endif %}
<div class="tree opened">
{{ tree }}
@media (max-width: 767px) {
ul.tree-children {
padding-left: 20px;
}
}
</style>
</head>
<body>
<svg id="frappe-symbols" aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" class="d-block" xmlns="http://www.w3.org/2000/svg">
<symbol viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" id="icon-primitive-dot">
<path d="M9.5 6a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0z"></path>
</symbol>
<symbol viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" id="icon-folder-open">
<path d="M8.024 6.5H3a.5.5 0 0 0-.5.5v8a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V9.5A.5.5 0 0 0 17 9h-6.783a.5.5 0 0 1-.417-.224L8.441 6.724a.5.5 0 0 0-.417-.224z" stroke="var(--icon-stroke)" stroke-miterlimit="10" stroke-linecap="square"></path>
<path d="M3.88 4.5v-1a.5.5 0 0 1 .5-.5h11.24a.5.5 0 0 1 .5.5V7" stroke="var(--icon-stroke)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
</symbol>
<symbol viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" id="icon-folder-normal">
<path d="M2.5 4v10a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V6.5a1 1 0 0 0-1-1h-6.283a.5.5 0 0 1-.417-.224L8.441 3.224A.5.5 0 0 0 8.024 3H3.5a1 1 0 0 0-1 1z" stroke="var(--icon-stroke)" stroke-miterlimit="10" stroke-linecap="square"></path>
</symbol>
</svg>
<div class="print-format-gutter">
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if print_settings.letter_head && print_settings.letter_head.footer %}
<div class="letter-head-footer">
{{ print_settings.letter_head.footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ __("Page {0} of {1}", [`<span class="page"></span>`, `<span class="topage"></span>`]) }}
</p>
</div>
</div>
</div>
</body>
{% endif %}
<div class="print-format {% if landscape %} landscape {% endif %}">
{% if print_settings.letter_head %}
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
<div class="letter-head">{{ print_settings.letter_head.header }}</div>
</div>
{% endif %}
<div class="tree opened">
{{ tree }}
</div>
</div>
</div>
</body>
</html>

View file

@ -125,11 +125,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
}
after_render() {
if (this.report_doc) {
this.set_dirty_state_for_custom_report();
} else {
if (!this.report_doc) {
this.save_report_settings();
} else if (!$.isEmptyObject(this.report_doc.json)) {
this.set_dirty_state_for_custom_report();
}
if (!this.group_by) {
this.init_chart();
}

View file

@ -1,5 +1,5 @@
from pypika.functions import *
from pypika.terms import Function
from pypika.terms import Function, CustomFunction, ArithmeticExpression, Arithmetic
from frappe.query_builder.utils import ImportMapper, db_type_is
from frappe.query_builder.custom import GROUP_CONCAT, STRING_AGG, MATCH, TO_TSVECTOR
from frappe.database.query import Query
@ -25,6 +25,24 @@ Match = ImportMapper(
}
)
class _PostgresTimestamp(ArithmeticExpression):
def __init__(self, datepart, timepart, alias=None):
if isinstance(datepart, str):
datepart = Cast(datepart, "date")
if isinstance(timepart, str):
timepart = Cast(timepart, "time")
super().__init__(operator=Arithmetic.add,
left=datepart, right=timepart, alias=alias)
CombineDatetime = ImportMapper(
{
db_type_is.MARIADB: CustomFunction("TIMESTAMP", ["date", "time"]),
db_type_is.POSTGRES: _PostgresTimestamp,
}
)
def _aggregate(function, dt, fieldname, filters, **kwargs):
return (
@ -46,4 +64,4 @@ def _avg(dt, fieldname, filters=None, **kwargs):
return _aggregate(Avg, dt, fieldname, filters, **kwargs)
def _sum(dt, fieldname, filters=None, **kwargs):
return _aggregate(Sum, dt, fieldname, filters, **kwargs)
return _aggregate(Sum, dt, fieldname, filters, **kwargs)

View file

@ -37,7 +37,6 @@
<tr>
<td valign="top">
<p>{{ content }}</p>
<p class="signature">{{ signature }}</p>
</td>
</tr>
</table>

View file

@ -168,8 +168,8 @@ class TestFormLoad(unittest.TestCase):
"reference_name": note.name,
}).insert()
docinfo = get_docinfo(note)
get_docinfo(note)
docinfo = frappe.response["docinfo"]
self.assertEqual(len(docinfo.comments), 1)
self.assertIn("test", docinfo.comments[0].content)

View file

@ -3,7 +3,7 @@ from typing import Callable
import frappe
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Coalesce, GroupConcat, Match
from frappe.query_builder.functions import Coalesce, GroupConcat, Match, CombineDatetime
from frappe.query_builder.utils import db_type_is
from frappe.query_builder import Case
@ -32,6 +32,27 @@ class TestCustomFunctionsMariaDB(unittest.TestCase):
query.get_sql(), "SELECT `name`,'John' `User` FROM `tabDocType`"
)
def test_timestamp(self):
note = frappe.qb.DocType("Note")
self.assertEqual("TIMESTAMP(posting_date,posting_time)", CombineDatetime(note.posting_date, note.posting_time).get_sql())
self.assertEqual("TIMESTAMP('2021-01-01','00:00:21')", CombineDatetime("2021-01-01", "00:00:21").get_sql())
todo = frappe.qb.DocType("ToDo")
select_query = (frappe.qb
.from_(note)
.join(todo).on(todo.refernce_name == note.name)
.select(CombineDatetime(note.posting_date, note.posting_time)))
self.assertIn("select timestamp(`tabnote`.`posting_date`,`tabnote`.`posting_time`)", str(select_query).lower())
select_query = select_query.orderby(CombineDatetime(note.posting_date, note.posting_time))
self.assertIn("order by timestamp(`tabnote`.`posting_date`,`tabnote`.`posting_time`)", str(select_query).lower())
select_query = select_query.where(CombineDatetime(note.posting_date, note.posting_time) >= CombineDatetime("2021-01-01", "00:00:01"))
self.assertIn("timestamp(`tabnote`.`posting_date`,`tabnote`.`posting_time`)>=timestamp('2021-01-01','00:00:01')", str(select_query).lower())
select_query = select_query.select(CombineDatetime(note.posting_date, note.posting_time, alias="timestamp"))
self.assertIn("timestamp(`tabnote`.`posting_date`,`tabnote`.`posting_time`) `timestamp`", str(select_query).lower())
@run_only_if(db_type_is.POSTGRES)
class TestCustomFunctionsPostgres(unittest.TestCase):
@ -52,6 +73,30 @@ class TestCustomFunctionsPostgres(unittest.TestCase):
query.get_sql(), 'SELECT "name",\'John\' "User" FROM "tabDocType"'
)
def test_timestamp(self):
note = frappe.qb.DocType("Note")
self.assertEqual("posting_date+posting_time", CombineDatetime(note.posting_date, note.posting_time).get_sql())
self.assertEqual("CAST('2021-01-01' AS DATE)+CAST('00:00:21' AS TIME)", CombineDatetime("2021-01-01", "00:00:21").get_sql())
todo = frappe.qb.DocType("ToDo")
select_query = (frappe.qb
.from_(note)
.join(todo).on(todo.refernce_name == note.name)
.select(CombineDatetime(note.posting_date, note.posting_time)))
self.assertIn('select "tabnote"."posting_date"+"tabnote"."posting_time"', str(select_query).lower())
select_query = select_query.orderby(CombineDatetime(note.posting_date, note.posting_time))
self.assertIn('order by "tabnote"."posting_date"+"tabnote"."posting_time"', str(select_query).lower())
select_query = select_query.where(
CombineDatetime(note.posting_date, note.posting_time) >= CombineDatetime('2021-01-01', '00:00:01')
)
self.assertIn("""where "tabnote"."posting_date"+"tabnote"."posting_time">=cast('2021-01-01' as date)+cast('00:00:01' as time)""",
str(select_query).lower())
select_query = select_query.select(CombineDatetime(note.posting_date, note.posting_time, alias="timestamp"))
self.assertIn('"tabnote"."posting_date"+"tabnote"."posting_time" "timestamp"', str(select_query).lower())
class TestBuilderBase(object):
def test_adding_tabs(self):

View file

@ -12,37 +12,30 @@ class TestQueryReport(unittest.TestCase):
def test_xlsx_data_with_multiple_datatypes(self):
"""Test exporting report using rows with multiple datatypes (list, dict)"""
# Describe the columns
columns = {
0: {"label": "Column A", "fieldname": "column_a"},
1: {"label": "Column B", "fieldname": "column_b"},
2: {"label": "Column C", "fieldname": "column_c"}
}
# Create mock data
data = frappe._dict()
data.columns = [
{"label": "Column A", "fieldname": "column_a"},
{"label": "Column B", "fieldname": "column_b", "width": 150},
{"label": "Column C", "fieldname": "column_c", "width": 100}
{"label": "Column A", "fieldname": "column_a", "fieldtype": "Float"},
{"label": "Column B", "fieldname": "column_b", "width": 100, "fieldtype": "Float"},
{"label": "Column C", "fieldname": "column_c", "width": 150, "fieldtype": "Duration"},
]
data.result = [
[1.0, 3.0, 5.5],
{"column_a": 22.1, "column_b": 21.8, "column_c": 30.2},
{"column_b": 5.1, "column_c": 9.5, "column_a": 11.1},
[3.0, 1.5, 7.5],
[1.0, 3.0, 600],
{"column_a": 22.1, "column_b": 21.8, "column_c": 86412},
{"column_b": 5.1, "column_c": 53234, "column_a": 11.1},
[3.0, 1.5, 333],
]
# Define the visible rows
visible_idx = [0, 2, 3]
# Build the result
xlsx_data, column_widths = build_xlsx_data(columns, data, visible_idx, include_indentation=0)
xlsx_data, column_widths = build_xlsx_data(data, visible_idx, include_indentation=0)
self.assertEqual(type(xlsx_data), list)
self.assertEqual(len(xlsx_data), 4) # columns + data
# column widths are divided by 10 to match the scale that is supported by openpyxl
self.assertListEqual(column_widths, [0, 15, 10])
self.assertListEqual(column_widths, [0, 10, 15])
for row in xlsx_data:
self.assertEqual(type(row), list)

View file

@ -135,11 +135,14 @@ def create_contact_records():
insert_contact('Test Form Contact 3', '12345')
@frappe.whitelist()
def create_multiple_contact_records():
if frappe.db.get_all('Contact', {'first_name': 'Multiple Contact 1'}):
def create_multiple_todo_records():
if frappe.db.get_all('ToDo', {'description': 'Multiple ToDo 1'}):
return
for index in range(1001):
insert_contact('Multiple Contact {}'.format(index+1), '12345{}'.format(index+1))
for index in range(501):
frappe.get_doc({
'doctype': 'ToDo',
'description': 'Multiple ToDo {}'.format(index+1)
}).insert()
def insert_contact(first_name, phone_number):
doc = frappe.get_doc({

View file

@ -146,7 +146,7 @@ Monthly,Monatlich,
More,Weiter,
More Information,Mehr Informationen,
More...,Mehr...,
Move,Bewegen,
Move,Verschieben,
My Account,Mein Konto,
My Profile,Mein Profil,
My Settings,Meine Einstellungen,
@ -175,7 +175,7 @@ Payment Gateway,Zahlungs-Gateways,
Payment Gateway Name,Name des Zahlungsgateways,
Payments,Zahlungen,
Period,Periode,
Pincode,Postleitzahl (PLZ),
Pincode,Postleitzahl,
Plan Name,Planname,
Please enable pop-ups,Bitte Pop-ups aktivieren,
Please select Company,Bitte Unternehmen auswählen,
@ -1486,7 +1486,7 @@ Linked,Verknüpft,
Linked With,Verknüpft mit,
Linked with {0},Verknüpft mit {0},
Links,Verknüpfungen,
List,Listenansicht,
List,Liste,
List Filter,Listenfilter,
List View,Listenansicht,
List View Setting,Einstellungen zu Listenansicht,
@ -2427,7 +2427,7 @@ Sum,Summe,
Sum of {0},Summe von {0},
Support Email Address Not Specified,Support-E-Mail-Adresse nicht angegeben,
Suspend Sending,Senden unterbrechen,
Switch To Desk,Switch To Desk,
Switch To Desk,Zum Desk wechseln,
Symbol,Symbol,
Sync,Synchronisieren,
Sync on Migrate,Sync auf Migrate,
@ -2870,8 +2870,8 @@ bullhorn,Megafon,
ca-central-1,ca-central-1,
camera,Kamera,
cancelled this document,brach die Arbeit an diesem Dokument ab,
changed value of {0},Wert von {0} geändert,
changed values for {0},Werte von {0} geändert,
changed value of {0},hat den Wert von {0} geändert,
changed values for {0},hat die Werte von {0} geändert,
chevron-down,Winkel nach unten,
chevron-left,Winkel nach links,
chevron-right,Winkel nach rechts,
@ -3431,7 +3431,7 @@ Mandatory Depends On,Obligatorisch Hängt von ab,
Map Columns,Spalten zuordnen,
Map columns from {0} to fields in {1},Ordnen Sie Spalten von {0} Feldern in {1} zu.,
Mapping column {0} to field {1},Spalte {0} dem Feld {1} zuordnen,
Mark all as Read,Markiere alle als gelesen,
Mark all as Read,Alle als gelesen markieren,
Maximum Points,Maximale Punkte,
Maximum points allowed after multiplying points with the multiplier value\n(Note: For no limit leave this field empty or set 0),Maximal zulässige Punkte nach Multiplikation der Punkte mit dem Multiplikatorwert (Hinweis: Für unbegrenzte Anzahl lassen Sie dieses Feld leer oder setzen Sie 0),
Me,Mir,
@ -3485,7 +3485,7 @@ Page Shortcuts,Seitenkürzel,
Parent Field (Tree),Elternfeld (Baum),
Parent Field must be a valid fieldname,Das übergeordnete Feld muss ein gültiger Feldname sein,
Pin Globally,Global anheften,
Places,Setzt,
Places,Orte,
Please check the filter values set for Dashboard Chart: {},Bitte überprüfen Sie die für das Dashboard-Diagramm festgelegten Filterwerte: {},
Please enable pop-ups in your browser,Bitte aktivieren Sie Popups in Ihrem Browser,
Please find attached {0}: {1},Im Anhang finden Sie {0}: {1},
@ -3541,7 +3541,7 @@ Select Filters,Wählen Sie Filter,
Select Google Calendar to which event should be synced.,"Wählen Sie Google Kalender aus, mit dem das Ereignis synchronisiert werden soll.",
Select Google Contacts to which contact should be synced.,"Wählen Sie Google-Kontakte aus, mit denen der Kontakt synchronisiert werden soll.",
Select Group By...,Wählen Sie Gruppieren nach ...,
Select Mandatory,Wählen Pflicht,
Select Mandatory,Verpflichtende auswählen,
Select atleast 2 actions,Wählen Sie mindestens 2 Aktionen aus,
Select list item,Listenelement auswählen,
Select multiple list items,Wählen Sie mehrere Listenelemente aus,
@ -3664,8 +3664,8 @@ You need to install pycups to use this feature!,"Sie müssen Pycups installieren
Your Target,Dein Ziel,
"browse,","Durchsuche,",
cancelled this document {0},stornierte dieses Dokument {0},
changed value of {0} {1},geänderter Wert von {0} {1},
changed values for {0} {1},geänderte Werte für {0} {1},
changed value of {0} {1},hat den Wert von {0} {1} geändert,
changed values for {0} {1},hat die Werte von {0} {1} geändert,
choose an,wähle ein,
empty,leeren,
of,von,
@ -3789,14 +3789,14 @@ Reset,Zurücksetzen,
Review,Rezension,
Room,Zimmer,
Room Type,Zimmertyp,
Save,speichern,
Save,Speichern,
Search results for,Suchergebnisse für,
Select All,Alles auswählen,
Send,Absenden,
Sending,Versand,
Server Error,Serverfehler,
Set,Menge,
Setup,Einstellungen,
Setup,Einrichtung,
Setup Wizard,Setup-Assistent,
Size,Größe,
Sr,Pos,
@ -3819,7 +3819,7 @@ Warehouse,Lager,
Welcome to {0},Willkommen auf {0},
Year,Jahr,
Yearly,Jährlich,
You,Benutzer,
You,Sie,
You can also copy-paste this link in your browser,Sie können diese Verknüpfung in Ihren Browser kopieren,
and,und,
{0} Name,{0} Name,
@ -3953,7 +3953,7 @@ lock,sperren,
logged in,Angemeldet,
message,Mitteilung,
module,Modul,
move,Bewegung,
move,verschieben,
music,Musik,
new,Neu,
now,jetzt,
@ -4135,9 +4135,9 @@ Using this console may allow attackers to impersonate you and steal your informa
yesterday,gestern,
{0} years ago,Vor {0} Jahren,
New Chart,Neues Diagramm,
New Shortcut,Neue Verknüpfung,
New Shortcut,Neuer Schnellzugriff,
Edit Chart,Diagramm bearbeiten,
Edit Shortcut,Verknüpfung bearbeiten,
Edit Shortcut,Schnellzugriff bearbeiten,
Couldn't Load Desk,Schreibtisch konnte nicht geladen werden,
"Something went wrong while loading Desk. <b>Please relaod the page</b>. If the problem persists, contact the Administrator","Beim Laden von Desk ist ein Fehler aufgetreten. <b>Bitte überarbeiten Sie die Seite</b> . Wenn das Problem weiterhin besteht, wenden Sie sich an den Administrator",
Customize Workspace,Arbeitsbereich anpassen,
@ -4228,7 +4228,7 @@ since last month,seit letztem Monat,
since last year,seit letztem Jahr,
Show,Show,
New Number Card,Neue Zahlenkarte,
Your Shortcuts,Ihre Verknüpfungen,
Your Shortcuts,Ihre Schnellzugriffe,
You haven't added any Dashboard Charts or Number Cards yet.,Sie haben noch keine Dashboard-Diagramme oder Zahlenkarten hinzugefügt.,
Click On Customize to add your first widget,"Klicken Sie auf Anpassen, um Ihr erstes Widget hinzuzufügen",
Are you sure you want to reset all customizations?,Möchten Sie wirklich alle Anpassungen zurücksetzen?,
@ -4650,7 +4650,7 @@ Not permitted to view {0},{0} darf nicht angezeigt werden,
Camera,Kamera,
Invalid filter: {0},Ungültiger Filter: {0},
Let's Get Started,Lass uns anfangen,
Reports & Masters,Berichte &amp; Meister,
Reports & Masters,Berichte & Stammdaten,
New {0} {1} added to Dashboard {2},Neues {0} {1} zum Dashboard hinzugefügt {2},
New {0} {1} created,Neue {0} {1} erstellt,
New {0} Created,Neu {0} erstellt,
@ -4715,3 +4715,67 @@ Reset sorting,Sortierung zurücksetzen,
Sort Ascending,Aufsteigend sortieren,
Sort Descending,Absteigend sortieren,
Remove column,Spalte entfernen,
Set all public,Alle als öffentlich setzen,
Set all private,Alle als privat setzen,
Library,Bibliothek,
My Device,Mein Gerät,
Drag and drop files here or upload from,Ziehen Sie Dateien hierher oder laden Sie sie von,
days,Tage,
seconds,Sekunden,
minutes,Minuten,
Copy,Kopieren,
{} Assigned,{} Zugewiesen,
Hide Saved,Gespeicherte ausblenden,
Show Saved,Gespeicherte anzeigen,
{0} created this {1},{0} erstellte dies {1},
{0} edited this {1},{0} bearbeitete dies {1},
Toggle Full Width,Toggle Volle Breite,
Documentation,Dokumentation,
About,Über,
Search or type a command (Ctrl + G),Suchen oder Befehl eingeben (Strg + G),
{} Pending,{} Ausstehend,
{} Available,{} Verfügbar,
{} Open,{} Offen,
Password set,Passwort gesetzt,
Your new password has been set successfully.,Ihr Passwort wurde erfolgreich aktualisiert.,
You hit the rate limit because of too many requests. Please try after sometime.,Sie haben die maximale Anzahl an Anfragen erreicht. Bitte versuchen Sie es später noch einmal.,
"You need {0} permission to fetch values from {1} {2}","Sie benötigen eine {0}-Berechtigung, um die Werte von {1} {2} abzurufen",
Cannot Fetch Values,Werte können nicht abgerufen werden,
You do not have Read or Select Permissions for {},Sie haben keine Lese- oder Auswahlberechtigung für {},
Or,Oder,
{0} changed values for {1},{0} hat die Werte von {1} geändert,
{0} changed values for {1} {2},{0} hat die Werte von {1} {2} geändert,
{0} cancelled this document,{0} dieses Dokument storniert,
{0} cancelled this document {1},{0} dieses Dokument storniert {1},
{0} submitted this document,{0} hat dieses Dokument eingereicht,
{0} submitted this document {1},{0} hat das Dokument {1} eingereicht,
Customizations Discarded,Anpassungen verworfen,
No filters selected,Keine Filter ausgewählt,
You haven't created a {0} yet,Sie haben noch kein(en) {0} erstellt,
No Data...,Keine Daten...,
Don't have an account?,Sie haben noch kein Benutzerkonto?,
{0} changed value of {1},{0} hat den Wert von {1} geändert,
Basic Info,Grundlegende Informationen,
No.,Nr.,number
No.,Nein.,opposite of yes
There are no upcoming events for you.,Es sind keine Termine für Sie geplant.,
No Upcoming Events,Keine bevorstehenden Termine,
"Looks like you havent received any notifications.","Sieht aus, als hätten Sie keine Benachrichtigungen erhalten.",
No New notifications,Keine neuen Benachrichtigungen,
Overview,Übersicht,
Connections,Verknüpfungen,
Save Customizations,Anpassungen speichern,
Apply Filters,Filter anwenden,
Add a Filter,Filter hinzufügen,
Reset Customizations,Anpassungen zurücksetzen,
{} wants to access the following details from your account,{} möchte Zugriff auf die folgenden Angaben von Ihrem Account,
{0} is not a field of doctype {1},{0} ist kein Feld in Doctype {1},
{0} from {1} to {2} in row #{3},{0} von {1} zu/bis {2} in Zeile #{3},
{0} from {1} to {2},{0} von {1} zu/bis {2},
{0} changed {1} to {2},{0} wurde von {1} zu {2} geändert,
{0} Map,{0} Karte,
Use HTML,HTML verwenden,
Submit on Creation,Nach Erstellung buchen,
Show Absolute Values,Absolutwerte anzeigen,
Row #{0}: Could not find field {1} in {2} DocType,Zeile #{0}: Feld {1} existiert nicht in DocType {2},
Repeat on Days,An Tagen wiederholen,

1 A4 A4
146 More Weiter
147 More Information Mehr Informationen
148 More... Mehr...
149 Move Bewegen Verschieben
150 My Account Mein Konto
151 My Profile Mein Profil
152 My Settings Meine Einstellungen
175 Payment Gateway Name Name des Zahlungsgateways
176 Payments Zahlungen
177 Period Periode
178 Pincode Postleitzahl (PLZ) Postleitzahl
179 Plan Name Planname
180 Please enable pop-ups Bitte Pop-ups aktivieren
181 Please select Company Bitte Unternehmen auswählen
1486 Linked With Verknüpft mit
1487 Linked with {0} Verknüpft mit {0}
1488 Links Verknüpfungen
1489 List Listenansicht Liste
1490 List Filter Listenfilter
1491 List View Listenansicht
1492 List View Setting Einstellungen zu Listenansicht
2427 Sum of {0} Summe von {0}
2428 Support Email Address Not Specified Support-E-Mail-Adresse nicht angegeben
2429 Suspend Sending Senden unterbrechen
2430 Switch To Desk Switch To Desk Zum Desk wechseln
2431 Symbol Symbol
2432 Sync Synchronisieren
2433 Sync on Migrate Sync auf Migrate
2870 ca-central-1 ca-central-1
2871 camera Kamera
2872 cancelled this document brach die Arbeit an diesem Dokument ab
2873 changed value of {0} Wert von {0} geändert hat den Wert von {0} geändert
2874 changed values for {0} Werte von {0} geändert hat die Werte von {0} geändert
2875 chevron-down Winkel nach unten
2876 chevron-left Winkel nach links
2877 chevron-right Winkel nach rechts
3431 Map Columns Spalten zuordnen
3432 Map columns from {0} to fields in {1} Ordnen Sie Spalten von {0} Feldern in {1} zu.
3433 Mapping column {0} to field {1} Spalte {0} dem Feld {1} zuordnen
3434 Mark all as Read Markiere alle als gelesen Alle als gelesen markieren
3435 Maximum Points Maximale Punkte
3436 Maximum points allowed after multiplying points with the multiplier value\n(Note: For no limit leave this field empty or set 0) Maximal zulässige Punkte nach Multiplikation der Punkte mit dem Multiplikatorwert (Hinweis: Für unbegrenzte Anzahl lassen Sie dieses Feld leer oder setzen Sie 0)
3437 Me Mir
3485 Parent Field (Tree) Elternfeld (Baum)
3486 Parent Field must be a valid fieldname Das übergeordnete Feld muss ein gültiger Feldname sein
3487 Pin Globally Global anheften
3488 Places Setzt Orte
3489 Please check the filter values set for Dashboard Chart: {} Bitte überprüfen Sie die für das Dashboard-Diagramm festgelegten Filterwerte: {}
3490 Please enable pop-ups in your browser Bitte aktivieren Sie Popups in Ihrem Browser
3491 Please find attached {0}: {1} Im Anhang finden Sie {0}: {1}
3541 Select Google Calendar to which event should be synced. Wählen Sie Google Kalender aus, mit dem das Ereignis synchronisiert werden soll.
3542 Select Google Contacts to which contact should be synced. Wählen Sie Google-Kontakte aus, mit denen der Kontakt synchronisiert werden soll.
3543 Select Group By... Wählen Sie Gruppieren nach ...
3544 Select Mandatory Wählen Pflicht Verpflichtende auswählen
3545 Select atleast 2 actions Wählen Sie mindestens 2 Aktionen aus
3546 Select list item Listenelement auswählen
3547 Select multiple list items Wählen Sie mehrere Listenelemente aus
3664 Your Target Dein Ziel
3665 browse, Durchsuche,
3666 cancelled this document {0} stornierte dieses Dokument {0}
3667 changed value of {0} {1} geänderter Wert von {0} {1} hat den Wert von {0} {1} geändert
3668 changed values for {0} {1} geänderte Werte für {0} {1} hat die Werte von {0} {1} geändert
3669 choose an wähle ein
3670 empty leeren
3671 of von
3789 Review Rezension
3790 Room Zimmer
3791 Room Type Zimmertyp
3792 Save speichern Speichern
3793 Search results for Suchergebnisse für
3794 Select All Alles auswählen
3795 Send Absenden
3796 Sending Versand
3797 Server Error Serverfehler
3798 Set Menge
3799 Setup Einstellungen Einrichtung
3800 Setup Wizard Setup-Assistent
3801 Size Größe
3802 Sr Pos
3819 Welcome to {0} Willkommen auf {0}
3820 Year Jahr
3821 Yearly Jährlich
3822 You Benutzer Sie
3823 You can also copy-paste this link in your browser Sie können diese Verknüpfung in Ihren Browser kopieren
3824 and und
3825 {0} Name {0} Name
3953 logged in Angemeldet
3954 message Mitteilung
3955 module Modul
3956 move Bewegung verschieben
3957 music Musik
3958 new Neu
3959 now jetzt
4135 yesterday gestern
4136 {0} years ago Vor {0} Jahren
4137 New Chart Neues Diagramm
4138 New Shortcut Neue Verknüpfung Neuer Schnellzugriff
4139 Edit Chart Diagramm bearbeiten
4140 Edit Shortcut Verknüpfung bearbeiten Schnellzugriff bearbeiten
4141 Couldn't Load Desk Schreibtisch konnte nicht geladen werden
4142 Something went wrong while loading Desk. <b>Please relaod the page</b>. If the problem persists, contact the Administrator Beim Laden von Desk ist ein Fehler aufgetreten. <b>Bitte überarbeiten Sie die Seite</b> . Wenn das Problem weiterhin besteht, wenden Sie sich an den Administrator
4143 Customize Workspace Arbeitsbereich anpassen
4228 since last year seit letztem Jahr
4229 Show Show
4230 New Number Card Neue Zahlenkarte
4231 Your Shortcuts Ihre Verknüpfungen Ihre Schnellzugriffe
4232 You haven't added any Dashboard Charts or Number Cards yet. Sie haben noch keine Dashboard-Diagramme oder Zahlenkarten hinzugefügt.
4233 Click On Customize to add your first widget Klicken Sie auf Anpassen, um Ihr erstes Widget hinzuzufügen
4234 Are you sure you want to reset all customizations? Möchten Sie wirklich alle Anpassungen zurücksetzen?
4650 Camera Kamera
4651 Invalid filter: {0} Ungültiger Filter: {0}
4652 Let's Get Started Lass uns anfangen
4653 Reports & Masters Berichte &amp; Meister Berichte & Stammdaten
4654 New {0} {1} added to Dashboard {2} Neues {0} {1} zum Dashboard hinzugefügt {2}
4655 New {0} {1} created Neue {0} {1} erstellt
4656 New {0} Created Neu {0} erstellt
4715 Sort Ascending Aufsteigend sortieren
4716 Sort Descending Absteigend sortieren
4717 Remove column Spalte entfernen
4718 Set all public Alle als öffentlich setzen
4719 Set all private Alle als privat setzen
4720 Library Bibliothek
4721 My Device Mein Gerät
4722 Drag and drop files here or upload from Ziehen Sie Dateien hierher oder laden Sie sie von
4723 days Tage
4724 seconds Sekunden
4725 minutes Minuten
4726 Copy Kopieren
4727 {} Assigned {} Zugewiesen
4728 Hide Saved Gespeicherte ausblenden
4729 Show Saved Gespeicherte anzeigen
4730 {0} created this {1} {0} erstellte dies {1}
4731 {0} edited this {1} {0} bearbeitete dies {1}
4732 Toggle Full Width Toggle Volle Breite
4733 Documentation Dokumentation
4734 About Über
4735 Search or type a command (Ctrl + G) Suchen oder Befehl eingeben (Strg + G)
4736 {} Pending {} Ausstehend
4737 {} Available {} Verfügbar
4738 {} Open {} Offen
4739 Password set Passwort gesetzt
4740 Your new password has been set successfully. Ihr Passwort wurde erfolgreich aktualisiert.
4741 You hit the rate limit because of too many requests. Please try after sometime. Sie haben die maximale Anzahl an Anfragen erreicht. Bitte versuchen Sie es später noch einmal.
4742 You need {0} permission to fetch values from {1} {2} Sie benötigen eine {0}-Berechtigung, um die Werte von {1} {2} abzurufen
4743 Cannot Fetch Values Werte können nicht abgerufen werden
4744 You do not have Read or Select Permissions for {} Sie haben keine Lese- oder Auswahlberechtigung für {}
4745 Or Oder
4746 {0} changed values for {1} {0} hat die Werte von {1} geändert
4747 {0} changed values for {1} {2} {0} hat die Werte von {1} {2} geändert
4748 {0} cancelled this document {0} dieses Dokument storniert
4749 {0} cancelled this document {1} {0} dieses Dokument storniert {1}
4750 {0} submitted this document {0} hat dieses Dokument eingereicht
4751 {0} submitted this document {1} {0} hat das Dokument {1} eingereicht
4752 Customizations Discarded Anpassungen verworfen
4753 No filters selected Keine Filter ausgewählt
4754 You haven't created a {0} yet Sie haben noch kein(en) {0} erstellt
4755 No Data... Keine Daten...
4756 Don't have an account? Sie haben noch kein Benutzerkonto?
4757 {0} changed value of {1} {0} hat den Wert von {1} geändert
4758 Basic Info Grundlegende Informationen
4759 No. Nr. number
4760 No. Nein. opposite of yes
4761 There are no upcoming events for you. Es sind keine Termine für Sie geplant.
4762 No Upcoming Events Keine bevorstehenden Termine
4763 Looks like you haven’t received any notifications. Sieht aus, als hätten Sie keine Benachrichtigungen erhalten.
4764 No New notifications Keine neuen Benachrichtigungen
4765 Overview Übersicht
4766 Connections Verknüpfungen
4767 Save Customizations Anpassungen speichern
4768 Apply Filters Filter anwenden
4769 Add a Filter Filter hinzufügen
4770 Reset Customizations Anpassungen zurücksetzen
4771 {} wants to access the following details from your account {} möchte Zugriff auf die folgenden Angaben von Ihrem Account
4772 {0} is not a field of doctype {1} {0} ist kein Feld in Doctype {1}
4773 {0} from {1} to {2} in row #{3} {0} von {1} zu/bis {2} in Zeile #{3}
4774 {0} from {1} to {2} {0} von {1} zu/bis {2}
4775 {0} changed {1} to {2} {0} wurde von {1} zu {2} geändert
4776 {0} Map {0} Karte
4777 Use HTML HTML verwenden
4778 Submit on Creation Nach Erstellung buchen
4779 Show Absolute Values Absolutwerte anzeigen
4780 Row #{0}: Could not find field {1} in {2} DocType Zeile #{0}: Feld {1} existiert nicht in DocType {2}
4781 Repeat on Days An Tagen wiederholen

View file

@ -57,7 +57,11 @@ def enqueue(method, queue='default', timeout=None, event=None,
# To handle older implementations
is_async = kwargs.pop('async', is_async)
if now or frappe.flags.in_migrate:
if not is_async and not frappe.flags.in_test:
print(_("Using enqueue with is_async=False outside of tests is not recommended, use now=True instead."))
call_directly = now or frappe.flags.in_migrate or (not is_async and not frappe.flags.in_test)
if call_directly:
return frappe.call(method, **kwargs)
q = get_queue(queue, is_async=is_async)

View file

@ -183,8 +183,6 @@ class BackupGenerator:
False,
)
self.todays_date = now_datetime().strftime("%Y%m%d_%H%M%S")
if not (
self.backup_path_conf
and self.backup_path_db
@ -212,7 +210,7 @@ class BackupGenerator:
partial = "-partial" if self.partial else ""
ext = "tgz" if self.compress_files else "tar"
enc = "-enc" if frappe.get_system_settings("encrypt_backup") else ""
self.todays_date = now_datetime().strftime("%Y%m%d_%H%M%S")
for_conf = f"{self.todays_date}-{self.site_slug}-site_config_backup{enc}.json"
for_db = f"{self.todays_date}-{self.site_slug}{partial}-database{enc}.sql.gz"

View file

@ -213,8 +213,7 @@
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
"max_attachments": 5,
"modified": "2021-11-23 10:42:01.759723",
"modified": "2022-03-09 01:48:25.227295",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",

View file

@ -82,7 +82,8 @@ frappe.boot = {
time_zone: {
system: "{{ frappe.utils.get_time_zone() }}",
user: "{{ frappe.db.get_value('User', frappe.session.user, 'time_zone') or frappe.utils.get_time_zone() }}"
}
},
link_title_doctypes: `{{ frappe.call('frappe.boot.get_link_title_doctypes') }}`
};
// for backward compatibility of some libs
frappe.sys_defaults = frappe.boot.sysdefaults;

View file

@ -598,13 +598,24 @@ def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=Fals
break
if doctype_validated:
link_options = []
if limited_to_user:
link_options = "\n".join([doc.name for doc in frappe.get_all(doctype, filters = {"owner":frappe.session.user})])
else:
link_options = "\n".join([doc.name for doc in frappe.get_all(doctype)])
link_options, filters = [], {}
return link_options
if limited_to_user:
filters = {"owner":frappe.session.user}
fields = ['name as value']
title_field = frappe.db.get_value('DocType', doctype, 'title_field', cache=1)
show_title_field_in_link = frappe.db.get_value('DocType', doctype, 'show_title_field_in_link', cache=1) == 1
if title_field and show_title_field_in_link:
fields.append(f'{title_field} as label')
link_options = frappe.get_all(doctype, filters, fields)
if title_field and show_title_field_in_link:
return json.dumps(link_options, default=str)
else:
return "\n".join([doc.value for doc in link_options])
else:
raise frappe.PermissionError('Not Allowed, {0}'.format(doctype))

View file

@ -338,8 +338,7 @@
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
"max_attachments": 20,
"modified": "2022-01-03 13:01:48.182645",
"modified": "2022-03-09 01:45:28.548671",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",

View file

@ -420,8 +420,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"max_attachments": 10,
"modified": "2022-02-24 15:37:22.360138",
"modified": "2022-03-09 01:47:31.094462",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Settings",

View file

@ -8,5 +8,5 @@ def get_context(context):
if frappe.flags.in_migrate: return
context.http_status_code = 500
print(frappe.get_traceback().encode("utf-8"))
print(frappe.get_traceback())
return {"error": frappe.get_traceback().replace("<", "&lt;").replace(">", "&gt;") }

View file

@ -24,7 +24,6 @@
"@editorjs/editorjs": "2.20.0",
"ace-builds": "^1.4.8",
"air-datepicker": "github:frappe/air-datepicker",
"autoprefixer": "^9.8.6",
"awesomplete": "^1.1.5",
"bootstrap": "4.5.0",
"cliui": "^7.0.4",
@ -66,14 +65,17 @@
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@frappe/esbuild-plugin-postcss2": "^0.1.3",
"autoprefixer": "10",
"chalk": "^2.3.2",
"esbuild": "^0.11.21",
"esbuild-plugin-postcss2": "^0.0.9",
"esbuild-vue": "^0.2.0",
"fast-glob": "^3.2.5",
"launch-editor": "^2.2.1",
"md5": "^2.3.0",
"postcss": "8",
"rtlcss": "^3.2.1",
"sass": "^1.49.9",
"yargs": "^16.2.0"
},
"snyk": true,

View file

@ -29,6 +29,7 @@ maxminddb-geolite2==2018.703
num2words~=0.5.10
oauthlib~=3.1.0
openpyxl~=3.0.7
parse~=1.19.0
passlib~=1.7.4
paytmchecksum~=1.7.0
pdfkit~=0.6.1

155
yarn.lock
View file

@ -36,6 +36,20 @@
codex-notifier "^1.1.2"
codex-tooltip "^1.0.1"
"@frappe/esbuild-plugin-postcss2@^0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@frappe/esbuild-plugin-postcss2/-/esbuild-plugin-postcss2-0.1.3.tgz#523a5cc32788f184bb78c7b946c9f132ef386508"
integrity sha512-/kPz/NJki2GFFtcgTnvdkkjgPEU1uHmaN7/OI2Ysc2tEZ7dcL7FYEEV72a5Fv8cniJbmH8UUjItZmHixFCT1Dg==
dependencies:
autoprefixer "^10.2.5"
fs-extra "^9.1.0"
less "^4.x"
postcss-modules "^4.0.0"
resolve-file "^0.3.0"
sass "^1.x"
stylus "^0.x"
tmp "^0.2.1"
"@nodelib/fs.scandir@2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69"
@ -344,6 +358,18 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autoprefixer@10:
version "10.4.2"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b"
integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==
dependencies:
browserslist "^4.19.1"
caniuse-lite "^1.0.30001297"
fraction.js "^4.1.2"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
autoprefixer@^10.2.5:
version "10.2.5"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.5.tgz#096a0337dbc96c0873526d7fef5de4428d05382d"
@ -356,19 +382,6 @@ autoprefixer@^10.2.5:
normalize-range "^0.1.2"
postcss-value-parser "^4.1.0"
autoprefixer@^9.8.6:
version "9.8.6"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f"
integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==
dependencies:
browserslist "^4.12.0"
caniuse-lite "^1.0.30001109"
colorette "^1.2.1"
normalize-range "^0.1.2"
num2fraction "^1.2.2"
postcss "^7.0.32"
postcss-value-parser "^4.1.0"
available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
@ -512,7 +525,7 @@ braces@^3.0.1, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.0, browserslist@^4.16.3:
browserslist@^4.0.0, browserslist@^4.16.0, browserslist@^4.16.3:
version "4.16.6"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
@ -523,6 +536,17 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.0, browserslist@^4
escalade "^3.1.1"
node-releases "^1.1.71"
browserslist@^4.19.1:
version "4.20.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.0.tgz#35951e3541078c125d36df76056e94738a52ebe9"
integrity sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==
dependencies:
caniuse-lite "^1.0.30001313"
electron-to-chromium "^1.4.76"
escalade "^3.1.1"
node-releases "^2.0.2"
picocolors "^1.0.0"
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
@ -570,11 +594,16 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001196, caniuse-lite@^1.0.30001219:
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001196, caniuse-lite@^1.0.30001219:
version "1.0.30001296"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001296.tgz"
integrity sha512-WfrtPEoNSoeATDlf4y3QvkwiELl9GyPLISV5GejTbbQRtQx4LhsXmc9IQ6XCL2d7UxCyEzToEZNMeqR79OUw8Q==
caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001313:
version "1.0.30001316"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001316.tgz#b44a1f419f82d2e119aa0bbdab5ec15471796358"
integrity sha512-JgUdNoZKxPZFzbzJwy4hDSyGuH/gXz2rN51QmoR8cBQsVo58llD3A0vlRKKRt8FGf5u69P9eQyIH8/z9vN/S0Q==
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@ -736,7 +765,7 @@ colord@^2.0.0:
resolved "https://registry.yarnpkg.com/colord/-/colord-2.0.0.tgz#f8c19f2526b7dc5b22d6e57ef102f03a2a43a3d8"
integrity sha512-WMDFJfoY3wqPZNpKUFdse3HhD5BHCbE9JCdxRzoVH+ywRITGOeWAHNkGEmyxLlErEpN9OLMWgdM9dWQtDk5dog==
colorette@^1.2.1, colorette@^1.2.2:
colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
@ -1220,6 +1249,11 @@ electron-to-chromium@^1.3.723:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.736.tgz#f632d900a1f788dab22fec9c62ec5c9c8f0c4052"
integrity sha512-DY8dA7gR51MSo66DqitEQoUMQ0Z+A2DSXFi7tK304bdTVqczCAfUuyQw6Wdg8hIoo5zIxkU1L24RQtUce1Ioig==
electron-to-chromium@^1.4.76:
version "1.4.84"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.84.tgz#2700befbcb49c42c4ee162e137ff392c07658249"
integrity sha512-b+DdcyOiZtLXHdgEG8lncYJdxbdJWJvclPNMg0eLUDcSOSO876WA/pYjdSblUTd7eJdIs4YdIxHWGazx7UPSJw==
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@ -1348,21 +1382,6 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
esbuild-plugin-postcss2@^0.0.9:
version "0.0.9"
resolved "https://registry.yarnpkg.com/esbuild-plugin-postcss2/-/esbuild-plugin-postcss2-0.0.9.tgz#b889af46f703990988d47885632108901948673e"
integrity sha512-iDKxWohm9aD2s+++ihb6GJVcddebsxOaC+Oz8TV0xJnKy0yHz/xazX96HyP45cS6+SFvZwr+SzG+QHbMOuXfMg==
dependencies:
autoprefixer "^10.2.5"
fs-extra "^9.1.0"
less "^4.x"
postcss "8.x"
postcss-modules "^4.0.0"
resolve-file "^0.3.0"
sass "^1.x"
stylus "^0.x"
tmp "^0.2.1"
esbuild-vue@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/esbuild-vue/-/esbuild-vue-0.2.0.tgz#8a3fde404bda57fe32b80e24917d14036e242bd3"
@ -1630,6 +1649,11 @@ fraction.js@^4.0.13:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe"
integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA==
fraction.js@^4.1.2:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
frappe-charts@^2.0.0-rc13:
version "2.0.0-rc13"
resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-2.0.0-rc13.tgz#fdb251d7ae311c41e38f90a3ae108070ec6b9072"
@ -2082,6 +2106,11 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
immutable@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@ -2873,11 +2902,16 @@ nan@^2.13.2:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nanoid@^3.1.22, nanoid@^3.1.23:
nanoid@^3.1.23:
version "3.2.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
nanoid@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
native-request@^1.0.5:
version "1.0.8"
resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.8.tgz#8f66bf606e0f7ea27c0e5995eb2f5d03e33ae6fb"
@ -2962,6 +2996,11 @@ node-releases@^1.1.71:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
node-releases@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
node-sass@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-7.0.0.tgz#33ee7c2df299d51f682f13d79f3d2a562225788e"
@ -3059,11 +3098,6 @@ nth-check@^2.0.0:
dependencies:
boolbase "^1.0.0"
num2fraction@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
@ -3280,6 +3314,11 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4:
version "2.2.3"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d"
@ -3627,14 +3666,19 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss@8.x:
version "8.2.10"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.10.tgz#ca7a042aa8aff494b334d0ff3e9e77079f6f702b"
integrity sha512-b/h7CPV7QEdrqIxtAf2j31U5ef05uBDuvoXv6L51Q4rcS1jdlXAVKJv+atCFdUXYl9dyTHGyoMzIepwowRJjFw==
postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@8:
version "8.4.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.22"
source-map "^0.6.1"
nanoid "^3.3.1"
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^5.2.5:
version "5.2.18"
@ -3664,15 +3708,6 @@ postcss@^7.0.14:
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@^7.0.32:
version "7.0.32"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@^8.2.4:
version "8.3.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709"
@ -4242,6 +4277,15 @@ sass@^1.18.0, sass@^1.x:
dependencies:
chokidar ">=3.0.0 <4.0.0"
sass@^1.49.9:
version "1.49.9"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9"
integrity sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@ -4422,6 +4466,11 @@ sortablejs@^1.7.0:
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.8.3.tgz#5ae908ef96300966e95440a143340f5dd565a0df"
integrity sha512-AftvD4hdKcR5QlGi7L/JST506zGNGrysE8/QohDpwKXJarHWqCt+TUlrtoMk/wkECB607Q019/OZlJViyWiD6A==
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map-js@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"