Merge branch 'web-form-list-empty-state' of https://github.com/pateljannat/frappe into web-form-list-empty-state
This commit is contained in:
commit
d5a7cd88e4
65 changed files with 912 additions and 557 deletions
35
cypress/integration/list_paging.js
Normal file
35
cypress/integration/list_paging.js
Normal 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');
|
||||
});
|
||||
});
|
||||
22
cypress/integration/number_card.js
Normal file
22
cypress/integration/number_card.js
Normal 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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
||||
|
|
|
|||
|
|
@ -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
16
frappe/commands/site.py
Executable file → Normal 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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, () => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@
|
|||
<tr>
|
||||
<td valign="top">
|
||||
<p>{{ content }}</p>
|
||||
<p class="signature">{{ signature }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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 & 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 haven’t 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,
|
||||
|
|
|
|||
|
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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("<", "<").replace(">", ">") }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
155
yarn.lock
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue