Merge remote-tracking branch 'upstream/develop' into dev-granular-printing
This commit is contained in:
commit
c4ed8cf0e7
69 changed files with 508 additions and 236 deletions
2
.github/workflows/docker-release.yml
vendored
2
.github/workflows/docker-release.yml
vendored
|
|
@ -12,4 +12,4 @@ jobs:
|
|||
- name: curl
|
||||
run: |
|
||||
apk add curl bash
|
||||
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests
|
||||
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'
|
||||
|
|
|
|||
|
|
@ -2,32 +2,58 @@ context('Query Report', () => {
|
|||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/website');
|
||||
cy.insert_doc('Report', {
|
||||
'report_name': 'Test ToDo Report',
|
||||
'ref_doctype': 'ToDo',
|
||||
'report_type': 'Query Report',
|
||||
'query': 'select * from tabToDo'
|
||||
}, true).as('doc');
|
||||
});
|
||||
|
||||
it('add custom column in report', () => {
|
||||
cy.visit('/app/query-report/Permitted Documents For User');
|
||||
|
||||
cy.get('.page-form.flex', { timeout: 60000 }).should('have.length', 1).then(() => {
|
||||
cy.get('#page-query-report input[data-fieldname="user"]').as('input');
|
||||
cy.get('@input').focus().type('test@erpnext.com', { delay: 100 }).blur();
|
||||
cy.get('#page-query-report input[data-fieldname="user"]').as('input-user');
|
||||
cy.get('@input-user').focus().type('test@erpnext.com', { delay: 100 }).blur();
|
||||
cy.wait(300);
|
||||
cy.get('#page-query-report input[data-fieldname="doctype"]').as('input-test');
|
||||
cy.get('@input-test').focus().type('Role', { delay: 100 }).blur();
|
||||
cy.get('#page-query-report input[data-fieldname="doctype"]').as('input-role');
|
||||
cy.get('@input-role').focus().type('Role', { delay: 100 }).blur();
|
||||
|
||||
cy.get('.datatable').should('exist');
|
||||
cy.get('.menu-btn-group button').click({ force: true });
|
||||
cy.get('.dropdown-menu li').contains('Add Column').click({ force: true });
|
||||
cy.get('.modal-dialog').should('contain', 'Add Column');
|
||||
cy.get('#page-query-report .page-actions .menu-btn-group button').click({ force: true });
|
||||
cy.get('#page-query-report .menu-btn-group .dropdown-menu').contains('Add Column').click({ force: true });
|
||||
cy.get_open_dialog().get('.modal-title').should('contain', 'Add Column');
|
||||
cy.get('select[data-fieldname="doctype"]').select("Role", { force: true });
|
||||
cy.get('select[data-fieldname="field"]').select("Role Name", { force: true });
|
||||
cy.get('select[data-fieldname="insert_after"]').select("Name", { force: true });
|
||||
cy.get('button').contains('Submit').click({ force: true });
|
||||
cy.get('.menu-btn-group button').click({ force: true });
|
||||
cy.get('.dropdown-menu li').contains('Save').click({ force: true });
|
||||
cy.get('.modal-dialog').should('contain', 'Save Report');
|
||||
cy.get_open_dialog().findByRole('button', {name: 'Submit'}).click({ force: true });
|
||||
cy.get('#page-query-report .page-actions .menu-btn-group button').click({ force: true });
|
||||
cy.get('#page-query-report .menu-btn-group .dropdown-menu').contains('Save').click({ timeout: 100, force: true });
|
||||
cy.get_open_dialog().get('.modal-title').should('contain', 'Save Report');
|
||||
|
||||
cy.get('input[data-fieldname="report_name"]').type("Test Report", { delay: 100, force: true });
|
||||
cy.get('button').contains('Submit').click({ timeout: 1000, force: true });
|
||||
cy.get_open_dialog().findByRole('button', {name: 'Submit'}).click({ timeout: 1000, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
let save_report_and_open = (report, update_name) => {
|
||||
cy.get('#page-query-report .page-actions .menu-btn-group button').click({ force: true });
|
||||
cy.get('#page-query-report .menu-btn-group .dropdown-menu').contains('Save').click({ timeout: 100, force: true });
|
||||
cy.get_open_dialog().get('.modal-title').should('contain', 'Save Report');
|
||||
|
||||
cy.get('input[data-fieldname="report_name"]').type(update_name, { delay: 100, force: true });
|
||||
cy.get_open_dialog().findByRole('button', {name: 'Submit'}).click({ timeout: 1000, force: true });
|
||||
|
||||
cy.visit('/app/query-report/'+report);
|
||||
cy.get('.datatable').should('exist');
|
||||
};
|
||||
|
||||
it('test multi level query report', () => {
|
||||
cy.visit('/app/query-report/Test ToDo Report');
|
||||
cy.get('.datatable').should('exist');
|
||||
|
||||
save_report_and_open('Test ToDo Report 1', ' 1');
|
||||
save_report_and_open('Test ToDo Report 11', '1');
|
||||
});
|
||||
});
|
||||
|
|
@ -17,7 +17,7 @@ context('Sidebar', () => {
|
|||
cy.get('.group-by-item > .dropdown-item').should('contain', 'Me');
|
||||
|
||||
//Assigning a doctype to a user
|
||||
cy.click_listview_row_item(0);
|
||||
cy.visit('/app/doctype/ToDo');
|
||||
cy.get('.form-assignments > .flex > .text-muted').click();
|
||||
cy.get_field('assign_to_me', 'Check').click();
|
||||
cy.get('.modal-footer > .standard-actions > .btn-primary').click();
|
||||
|
|
@ -44,8 +44,7 @@ context('Sidebar', () => {
|
|||
cy.clear_filters();
|
||||
|
||||
//To remove the assignment
|
||||
cy.visit('/app/doctype');
|
||||
cy.click_listview_row_item(0);
|
||||
cy.visit('/app/doctype/ToDo');
|
||||
cy.get('.assignments > .avatar-group > .avatar > .avatar-frame').click();
|
||||
cy.get('.remove-btn').click({force: true});
|
||||
cy.hide_dialog();
|
||||
|
|
@ -53,4 +52,4 @@ context('Sidebar', () => {
|
|||
cy.click_sidebar_button("Assigned To");
|
||||
cy.get('.empty-state').should('contain', 'No filters found');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -137,10 +137,10 @@ lang = local("lang")
|
|||
if typing.TYPE_CHECKING:
|
||||
from frappe.database.mariadb.database import MariaDBDatabase
|
||||
from frappe.database.postgres.database import PostgresDatabase
|
||||
from pypika import Query
|
||||
from frappe.query_builder.builder import MariaDB, Postgres
|
||||
|
||||
db: typing.Union[MariaDBDatabase, PostgresDatabase]
|
||||
qb: Query
|
||||
qb: typing.Union[MariaDB, Postgres]
|
||||
|
||||
# end: static analysis hack
|
||||
|
||||
|
|
@ -487,11 +487,11 @@ def get_request_header(key, default=None):
|
|||
:param default: Default value."""
|
||||
return request.headers.get(key, default)
|
||||
|
||||
def sendmail(recipients=[], sender="", subject="No Subject", message="No Message",
|
||||
def sendmail(recipients=None, sender="", subject="No Subject", message="No Message",
|
||||
as_markdown=False, delayed=True, reference_doctype=None, reference_name=None,
|
||||
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, add_unsubscribe_link=1,
|
||||
attachments=None, content=None, doctype=None, name=None, reply_to=None, queue_separately=False,
|
||||
cc=[], bcc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
|
||||
cc=None, bcc=None, message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
|
||||
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False,
|
||||
inline_images=None, template=None, args=None, header=None, print_letterhead=False, with_container=False):
|
||||
"""Send email using user's default **Email Account** or global default **Email Account**.
|
||||
|
|
@ -521,6 +521,14 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
|
|||
:param header: Append header in email
|
||||
:param with_container: Wraps email inside a styled container
|
||||
"""
|
||||
|
||||
if recipients is None:
|
||||
recipients = []
|
||||
if cc is None:
|
||||
cc = []
|
||||
if bcc is None:
|
||||
bcc = []
|
||||
|
||||
text_content = None
|
||||
if template:
|
||||
message, text_content = get_email_from_template(template, args)
|
||||
|
|
@ -718,18 +726,20 @@ def only_has_select_perm(doctype, user=None, ignore_permissions=False):
|
|||
else:
|
||||
return False
|
||||
|
||||
def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=False, throw=False):
|
||||
def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=False, throw=False, parent_doctype=None):
|
||||
"""Raises `frappe.PermissionError` if not permitted.
|
||||
|
||||
:param doctype: DocType for which permission is to be check.
|
||||
:param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`.
|
||||
:param doc: [optional] Checks User permissions for given doc.
|
||||
:param user: [optional] Check for given user. Default: current user."""
|
||||
:param user: [optional] Check for given user. Default: current user.
|
||||
:param parent_doctype: Required when checking permission for a child DocType (unless doc is specified)."""
|
||||
if not doctype and doc:
|
||||
doctype = doc.doctype
|
||||
|
||||
import frappe.permissions
|
||||
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user, raise_exception=throw)
|
||||
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user,
|
||||
raise_exception=throw, parent_doctype=parent_doctype)
|
||||
if throw and not out:
|
||||
if doc:
|
||||
frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name))
|
||||
|
|
|
|||
|
|
@ -659,10 +659,14 @@ def publish_realtime(context, event, message, room, user, doctype, docname, afte
|
|||
|
||||
@click.command('browse')
|
||||
@click.argument('site', required=False)
|
||||
@click.option('--user', required=False, help='Login as user')
|
||||
@pass_context
|
||||
def browse(context, site):
|
||||
def browse(context, site, user=None):
|
||||
'''Opens the site on web browser'''
|
||||
from frappe.auth import LoginManager
|
||||
from frappe.auth import CookieManager
|
||||
import webbrowser
|
||||
|
||||
site = context.sites[0] if context.sites else site
|
||||
|
||||
if not site:
|
||||
|
|
@ -672,7 +676,24 @@ def browse(context, site):
|
|||
site = site.lower()
|
||||
|
||||
if site in frappe.utils.get_sites():
|
||||
webbrowser.open(frappe.utils.get_site_url(site), new=2)
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
||||
sid = ''
|
||||
if user:
|
||||
if frappe.conf.developer_mode or user == "Administrator":
|
||||
frappe.utils.set_request(path="/")
|
||||
frappe.local.cookie_manager = CookieManager()
|
||||
frappe.local.login_manager = LoginManager()
|
||||
frappe.local.login_manager.login_as(user)
|
||||
sid = f'/app?sid={frappe.session.sid}'
|
||||
else:
|
||||
print("Please enable developer mode to login as a user")
|
||||
|
||||
url = f'{frappe.utils.get_site_url(site)}{sid}'
|
||||
if user == "Administrator":
|
||||
print(f'Login URL: {url}')
|
||||
webbrowser.open(url, new=2)
|
||||
else:
|
||||
click.echo("\nSite named \033[1m{}\033[0m doesn't exist\n".format(site))
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ def load_address_and_contact(doc, key=None):
|
|||
["Dynamic Link", "link_name", "=", doc.name],
|
||||
["Dynamic Link", "parenttype", "=", "Address"],
|
||||
]
|
||||
address_list = frappe.get_all("Address", filters=filters, fields=["*"])
|
||||
address_list = frappe.get_list("Address", filters=filters, fields=["*"])
|
||||
|
||||
address_list = [a.update({"display": get_address_display(a)})
|
||||
for a in address_list]
|
||||
|
|
@ -34,16 +34,16 @@ def load_address_and_contact(doc, key=None):
|
|||
["Dynamic Link", "link_name", "=", doc.name],
|
||||
["Dynamic Link", "parenttype", "=", "Contact"],
|
||||
]
|
||||
contact_list = frappe.get_all("Contact", filters=filters, fields=["*"])
|
||||
contact_list = frappe.get_list("Contact", filters=filters, fields=["*"])
|
||||
|
||||
for contact in contact_list:
|
||||
contact["email_ids"] = frappe.get_list("Contact Email", filters={
|
||||
contact["email_ids"] = frappe.get_all("Contact Email", filters={
|
||||
"parenttype": "Contact",
|
||||
"parent": contact.name,
|
||||
"is_primary": 0
|
||||
}, fields=["email_id"])
|
||||
|
||||
contact["phone_nos"] = frappe.get_list("Contact Phone", filters={
|
||||
contact["phone_nos"] = frappe.get_all("Contact Phone", filters={
|
||||
"parenttype": "Contact",
|
||||
"parent": contact.name,
|
||||
"is_primary_phone": 0,
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ def get_contact_with_phone_number(number):
|
|||
return contacts[0].parent if contacts else None
|
||||
|
||||
def get_contact_name(email_id):
|
||||
contact = frappe.get_list("Contact Email", filters={"email_id": email_id}, fields=["parent"], limit=1)
|
||||
contact = frappe.get_all("Contact Email", filters={"email_id": email_id}, fields=["parent"], limit=1)
|
||||
return contact[0].parent if contact else None
|
||||
|
||||
def get_contacts_linking_to(doctype, docname, fields=None):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# License: MIT. See LICENSE
|
||||
import frappe
|
||||
from frappe.utils import cstr
|
||||
from tenacity import retry, retry_if_exception_type, stop_after_attempt
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
|
@ -24,25 +25,21 @@ def make_access_log(
|
|||
page=None,
|
||||
columns=None,
|
||||
):
|
||||
|
||||
user = frappe.session.user
|
||||
in_request = frappe.request and frappe.request.method == "GET"
|
||||
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Access Log",
|
||||
"user": user,
|
||||
"export_from": doctype,
|
||||
"reference_document": document,
|
||||
"file_type": file_type,
|
||||
"report_name": report_name,
|
||||
"page": page,
|
||||
"method": method,
|
||||
"filters": frappe.utils.cstr(filters) if filters else None,
|
||||
"columns": columns,
|
||||
}
|
||||
)
|
||||
doc.insert(ignore_permissions=True)
|
||||
frappe.get_doc({
|
||||
"doctype": "Access Log",
|
||||
"user": user,
|
||||
"export_from": doctype,
|
||||
"reference_document": document,
|
||||
"file_type": file_type,
|
||||
"report_name": report_name,
|
||||
"page": page,
|
||||
"method": method,
|
||||
"filters": cstr(filters) or None,
|
||||
"columns": columns,
|
||||
}).db_insert()
|
||||
|
||||
# `frappe.db.commit` added because insert doesnt `commit` when called in GET requests like `printview`
|
||||
# dont commit in test mode
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@
|
|||
"icon": "fa fa-comment",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-28 11:43:57.504565",
|
||||
"modified": "2021-10-25 11:43:57.504565",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Activity Log",
|
||||
|
|
@ -182,6 +182,5 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "subject",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -406,7 +406,7 @@ def get_contacts(email_strings, auto_create_contact=False):
|
|||
return contacts
|
||||
|
||||
def add_contact_links_to_communication(communication, contact_name):
|
||||
contact_links = frappe.get_list("Dynamic Link", filters={
|
||||
contact_links = frappe.get_all("Dynamic Link", filters={
|
||||
"parenttype": "Contact",
|
||||
"parent": contact_name
|
||||
}, fields=["link_doctype", "link_name"])
|
||||
|
|
|
|||
|
|
@ -763,7 +763,9 @@ class Column:
|
|||
seen = []
|
||||
fields_column_map = {}
|
||||
|
||||
def __init__(self, index, header, doctype, column_values, map_to_field=None, seen=[]):
|
||||
def __init__(self, index, header, doctype, column_values, map_to_field=None, seen=None):
|
||||
if seen is None:
|
||||
seen = []
|
||||
self.index = index
|
||||
self.column_number = index + 1
|
||||
self.doctype = doctype
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@
|
|||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "If enabled, changes to the document are tracked and shown in timeline",
|
||||
"fieldname": "track_changes",
|
||||
|
|
@ -649,7 +649,7 @@
|
|||
"link_fieldname": "reference_doctype"
|
||||
}
|
||||
],
|
||||
"modified": "2021-09-05 15:39:13.233403",
|
||||
"modified": "2021-10-29 11:39:13.233403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-03-14 12:21:44.292471",
|
||||
"modified": "2021-10-25 12:21:44.292471",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Error Log",
|
||||
|
|
@ -144,6 +144,5 @@
|
|||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-12-29 14:40:38.619106",
|
||||
"modified": "2021-10-25 14:40:38.619106",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Error Snapshot",
|
||||
|
|
@ -394,6 +394,5 @@
|
|||
"sort_field": "timestamp",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "evalue",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-01-22 00:00:00.000000",
|
||||
"modified": "2021-10-25 00:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Scheduled Job Log",
|
||||
|
|
@ -59,6 +59,5 @@
|
|||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,16 +69,6 @@ frappe.method_that_doesnt_exist("do some magic")
|
|||
disabled = 1,
|
||||
script = '''
|
||||
frappe.db.commit()
|
||||
'''
|
||||
),
|
||||
dict(
|
||||
name='test_cache_methods',
|
||||
script_type = 'DocType Event',
|
||||
doctype_event = 'Before Save',
|
||||
reference_doctype = 'ToDo',
|
||||
disabled = 1,
|
||||
script = '''
|
||||
frappe.cache().set_value('test_key', doc.name)
|
||||
'''
|
||||
)
|
||||
]
|
||||
|
|
@ -149,14 +139,3 @@ class TestServerScript(unittest.TestCase):
|
|||
|
||||
server_script.disabled = 1
|
||||
server_script.save()
|
||||
|
||||
def test_cache_methods_in_server_script(self):
|
||||
server_script = frappe.get_doc('Server Script', 'test_cache_methods')
|
||||
server_script.disabled = 0
|
||||
server_script.save()
|
||||
|
||||
todo = frappe.get_doc(dict(doctype='ToDo', description='test me')).insert()
|
||||
self.assertEqual(todo.name, frappe.cache().get_value('test_key'))
|
||||
|
||||
server_script.disabled = 1
|
||||
server_script.save()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies and contributors
|
||||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
import hashlib
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.utils import cint, now_datetime
|
||||
import hashlib
|
||||
|
||||
|
||||
class TransactionLog(Document):
|
||||
def before_insert(self):
|
||||
|
|
@ -44,10 +46,14 @@ class TransactionLog(Document):
|
|||
|
||||
|
||||
def get_current_index():
|
||||
current = frappe.db.sql("""SELECT `current`
|
||||
FROM `tabSeries`
|
||||
WHERE `name` = 'TRANSACTLOG'
|
||||
FOR UPDATE""")
|
||||
series = DocType("Series")
|
||||
current = (
|
||||
frappe.qb.from_(series)
|
||||
.where(series.name == "TRANSACTLOG")
|
||||
.for_update()
|
||||
.select("current")
|
||||
).run()
|
||||
|
||||
if current and current[0][0] is not None:
|
||||
current = current[0][0]
|
||||
|
||||
|
|
|
|||
|
|
@ -717,8 +717,10 @@ def ask_pass_update():
|
|||
# update the sys defaults as to awaiting users
|
||||
from frappe.utils import set_default
|
||||
|
||||
users = frappe.db.sql("""SELECT DISTINCT(parent) as user FROM `tabUser Email`
|
||||
WHERE awaiting_password = 1""", as_dict=True)
|
||||
doctype = DocType("User Email")
|
||||
users = frappe.qb.from_(doctype).where(doctype.awaiting_password == 1).select(
|
||||
doctype.parent.as_("user")
|
||||
).distinct().run(as_dict=True)
|
||||
|
||||
password_list = [ user.get("user") for user in users ]
|
||||
set_default("email_user_password", u','.join(password_list))
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-09-05 14:22:27.664645",
|
||||
"modified": "2021-10-25 14:22:27.664645",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "View Log",
|
||||
|
|
@ -158,7 +158,6 @@
|
|||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
import frappe
|
||||
|
|
@ -12,13 +12,17 @@ def execute(filters=None):
|
|||
return columns, data
|
||||
|
||||
def get_data(filters=None):
|
||||
|
||||
logs = frappe.db.sql("SELECT * FROM `tabTransaction Log` order by creation desc ", as_dict=1)
|
||||
result = []
|
||||
logs = frappe.get_all("Transaction Log", fields=["*"], order_by="creation desc")
|
||||
|
||||
for l in logs:
|
||||
row_index = int(l.row_index)
|
||||
if row_index > 1:
|
||||
previous_hash = frappe.db.sql("SELECT chaining_hash FROM `tabTransaction Log` WHERE row_index = {0}".format(row_index - 1))
|
||||
previous_hash = frappe.get_all(
|
||||
"Transaction Log",
|
||||
fields=["chaining_hash"],
|
||||
filters={"row_index": row_index - 1},
|
||||
)
|
||||
if not previous_hash:
|
||||
integrity = False
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -170,6 +170,12 @@ class Database(object):
|
|||
frappe.errprint('Syntax error in query:')
|
||||
frappe.errprint(query)
|
||||
|
||||
elif self.is_deadlocked(e):
|
||||
raise frappe.QueryDeadlockError
|
||||
|
||||
elif self.is_timedout(e):
|
||||
raise frappe.QueryTimeoutError
|
||||
|
||||
if ignore_ddl and (self.is_missing_column(e) or self.is_missing_table(e) or self.cant_drop_field_or_key(e)):
|
||||
pass
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@
|
|||
"hide_toolbar": 1,
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified": "2021-10-25 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Notification Log",
|
||||
|
|
@ -139,6 +139,5 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "subject",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-10-05 13:26:03.106050",
|
||||
"modified": "2021-10-25 13:26:03.106050",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Route History",
|
||||
|
|
@ -121,7 +121,6 @@
|
|||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import unique
|
||||
from frappe.query_builder import DocType
|
||||
|
||||
class Tag(Document):
|
||||
pass
|
||||
|
|
@ -42,10 +43,12 @@ def remove_tag(tag, dt, dn):
|
|||
@frappe.whitelist()
|
||||
def get_tagged_docs(doctype, tag):
|
||||
frappe.has_permission(doctype, throw=True)
|
||||
|
||||
return frappe.db.sql("""SELECT name
|
||||
FROM `tab{0}`
|
||||
WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag))
|
||||
doctype = DocType(doctype)
|
||||
return (
|
||||
frappe.qb.from_(doctype)
|
||||
.where(doctype._user_tags.like(tag))
|
||||
.select(doctype.name)
|
||||
).run()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tags(doctype, txt):
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ def get_submitted_linked_docs(doctype, name, docs=None, visited=None):
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=[]):
|
||||
def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=None):
|
||||
"""
|
||||
Cancel all linked doctype, optionally ignore doctypes specified in a list.
|
||||
|
||||
|
|
@ -85,6 +85,8 @@ def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=[]):
|
|||
docs (json str) - It contains list of dictionaries of a linked documents.
|
||||
ignore_doctypes_on_cancel_all (list) - List of doctypes to ignore while cancelling.
|
||||
"""
|
||||
if ignore_doctypes_on_cancel_all is None:
|
||||
ignore_doctypes_on_cancel_all = []
|
||||
|
||||
docs = json.loads(docs)
|
||||
if isinstance(ignore_doctypes_on_cancel_all, str):
|
||||
|
|
@ -96,7 +98,7 @@ def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=[]):
|
|||
frappe.publish_progress(percent=i/len(docs) * 100, title=_("Cancelling documents"))
|
||||
|
||||
|
||||
def validate_linked_doc(docinfo, ignore_doctypes_on_cancel_all=[]):
|
||||
def validate_linked_doc(docinfo, ignore_doctypes_on_cancel_all=None):
|
||||
"""
|
||||
Validate a document to be submitted and non-exempted from auto-cancel.
|
||||
|
||||
|
|
@ -109,7 +111,7 @@ def validate_linked_doc(docinfo, ignore_doctypes_on_cancel_all=[]):
|
|||
"""
|
||||
|
||||
#ignore doctype to cancel
|
||||
if docinfo.get("doctype") in ignore_doctypes_on_cancel_all:
|
||||
if docinfo.get("doctype") in (ignore_doctypes_on_cancel_all or []):
|
||||
return False
|
||||
|
||||
# skip non-submittable doctypes since they don't need to be cancelled
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ def get_preview_data(doctype, docname):
|
|||
|
||||
for key, val in preview_data.items():
|
||||
if val and meta.has_field(key) and key not in [image_field, title_field, 'name']:
|
||||
formatted_preview_data[meta.get_field(key).label] = frappe.format(val, meta.get_field(key).fieldtype)
|
||||
formatted_preview_data[meta.get_field(key).label] = frappe.format(
|
||||
val,
|
||||
meta.get_field(key).fieldtype,
|
||||
translated=True,
|
||||
)
|
||||
|
||||
return formatted_preview_data
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ def get_filters_for(doctype):
|
|||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_open_count(doctype, name, items=[]):
|
||||
def get_open_count(doctype, name, items=None):
|
||||
'''Get open count for given transactions and filters
|
||||
|
||||
:param doctype: Reference DocType
|
||||
|
|
@ -235,7 +235,8 @@ def get_open_count(doctype, name, items=[]):
|
|||
links = meta.get_dashboard_data()
|
||||
|
||||
# compile all items in a list
|
||||
if not items:
|
||||
if items is None:
|
||||
items = []
|
||||
for group in links.transactions:
|
||||
items.extend(group.get("items"))
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,19 @@ def get_report_doc(report_name):
|
|||
return doc
|
||||
|
||||
|
||||
def get_report_result(report, filters):
|
||||
if report.report_type == "Query Report":
|
||||
res = report.execute_query_report(filters)
|
||||
|
||||
elif report.report_type == "Script Report":
|
||||
res = report.execute_script_report(filters)
|
||||
|
||||
elif report.report_type == "Custom Report":
|
||||
ref_report = get_report_doc(report.report_name)
|
||||
res = get_report_result(ref_report, filters)
|
||||
|
||||
return res
|
||||
|
||||
def generate_report_result(report, filters=None, user=None, custom_columns=None):
|
||||
user = user or frappe.session.user
|
||||
filters = filters or []
|
||||
|
|
@ -66,13 +79,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None)
|
|||
if filters and isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
|
||||
res = []
|
||||
|
||||
if report.report_type == "Query Report":
|
||||
res = report.execute_query_report(filters)
|
||||
|
||||
elif report.report_type == "Script Report":
|
||||
res = report.execute_script_report(filters)
|
||||
res = get_report_result(report, filters) or []
|
||||
|
||||
columns, result, message, chart, report_summary, skip_total_row = ljust_list(res, 6)
|
||||
columns = [get_column_as_dict(col) for col in columns]
|
||||
|
|
|
|||
|
|
@ -180,15 +180,16 @@ def update_wildcard_field_param(data):
|
|||
|
||||
|
||||
def clean_params(data):
|
||||
data.pop('cmd', None)
|
||||
data.pop('data', None)
|
||||
data.pop('ignore_permissions', None)
|
||||
data.pop('view', None)
|
||||
data.pop('user', None)
|
||||
|
||||
if "csrf_token" in data:
|
||||
del data["csrf_token"]
|
||||
|
||||
for param in (
|
||||
"cmd",
|
||||
"data",
|
||||
"ignore_permissions",
|
||||
"view",
|
||||
"user",
|
||||
"csrf_token",
|
||||
"join"
|
||||
):
|
||||
data.pop(param, None)
|
||||
|
||||
def parse_json(data):
|
||||
if isinstance(data.get("filters"), str):
|
||||
|
|
@ -214,11 +215,13 @@ def get_parenttype_and_fieldname(field, data):
|
|||
|
||||
return parenttype, fieldname
|
||||
|
||||
def compress(data, args = {}):
|
||||
def compress(data, args=None):
|
||||
"""separate keys and values"""
|
||||
from frappe.desk.query_report import add_total_row
|
||||
|
||||
if not data: return data
|
||||
if args is None:
|
||||
args = {}
|
||||
values = []
|
||||
keys = list(data[0])
|
||||
for row in data:
|
||||
|
|
@ -423,15 +426,20 @@ def delete_bulk(doctype, items):
|
|||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_sidebar_stats(stats, doctype, filters=[]):
|
||||
def get_sidebar_stats(stats, doctype, filters=None):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
return {"stats": get_stats(stats, doctype, filters)}
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_stats(stats, doctype, filters=[]):
|
||||
def get_stats(stats, doctype, filters=None):
|
||||
"""get tag info"""
|
||||
import json
|
||||
|
||||
if filters is None:
|
||||
filters = []
|
||||
tags = json.loads(stats)
|
||||
if filters:
|
||||
filters = json.loads(filters)
|
||||
|
|
@ -480,12 +488,11 @@ def get_stats(stats, doctype, filters=[]):
|
|||
return stats
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_filter_dashboard_data(stats, doctype, filters=[]):
|
||||
def get_filter_dashboard_data(stats, doctype, filters=None):
|
||||
"""get tags info"""
|
||||
import json
|
||||
tags = json.loads(stats)
|
||||
if filters:
|
||||
filters = json.loads(filters)
|
||||
filters = json.loads(filters or [])
|
||||
stats = {}
|
||||
|
||||
columns = frappe.db.get_table_columns(doctype)
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ from email import policy
|
|||
|
||||
def get_email(recipients, sender='', msg='', subject='[No Subject]',
|
||||
text_content = None, footer=None, print_html=None, formatted=None, attachments=None,
|
||||
content=None, reply_to=None, cc=[], bcc=[], email_account=None, expose_recipients=None,
|
||||
inline_images=[], header=None):
|
||||
content=None, reply_to=None, cc=None, bcc=None, email_account=None, expose_recipients=None,
|
||||
inline_images=None, header=None):
|
||||
""" Prepare an email with the following format:
|
||||
- multipart/mixed
|
||||
- multipart/alternative
|
||||
|
|
@ -25,6 +25,14 @@ def get_email(recipients, sender='', msg='', subject='[No Subject]',
|
|||
- attachment
|
||||
"""
|
||||
content = content or msg
|
||||
|
||||
if cc is None:
|
||||
cc = []
|
||||
if bcc is None:
|
||||
bcc = []
|
||||
if inline_images is None:
|
||||
inline_images = []
|
||||
|
||||
emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, bcc=bcc, email_account=email_account, expose_recipients=expose_recipients)
|
||||
|
||||
if not content.strip().startswith("<"):
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ class EmailServer:
|
|||
|
||||
return error_msg
|
||||
|
||||
def update_flag(self, uid_list={}):
|
||||
def update_flag(self, uid_list=None):
|
||||
""" set all uids mails the flag as seen """
|
||||
|
||||
if not uid_list:
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ class DataTooLongException(ValidationError): pass
|
|||
class FileAlreadyAttachedException(Exception): pass
|
||||
class DocumentAlreadyRestored(ValidationError): pass
|
||||
class AttachmentLimitReached(ValidationError): pass
|
||||
class QueryTimeoutError(Exception): pass
|
||||
class QueryDeadlockError(Exception): pass
|
||||
# OAuth exceptions
|
||||
class InvalidAuthorizationHeader(CSRFTokenError): pass
|
||||
class InvalidAuthorizationPrefix(CSRFTokenError): pass
|
||||
|
|
|
|||
|
|
@ -286,12 +286,16 @@ class FrappeClient(object):
|
|||
doc.modified = frappe.db.get_single_value(doctype, "modified")
|
||||
frappe.get_doc(doc).insert()
|
||||
|
||||
def get_api(self, method, params={}):
|
||||
def get_api(self, method, params=None):
|
||||
if params is None:
|
||||
params = {}
|
||||
res = self.session.get(self.url + "/api/method/" + method + "/",
|
||||
params=params, verify=self.verify, headers=self.headers)
|
||||
return self.post_process(res)
|
||||
|
||||
def post_api(self, method, params={}):
|
||||
def post_api(self, method, params=None):
|
||||
if params is None:
|
||||
params = {}
|
||||
res = self.session.post(self.url + "/api/method/" + method + "/",
|
||||
params=params, verify=self.verify, headers=self.headers)
|
||||
return self.post_process(res)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ class BaseDocument(object):
|
|||
if hasattr(self, "__setup__"):
|
||||
self.__setup__()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.get(key) if hasattr(self, key) else frappe.throw(msg=key, exc=KeyError)
|
||||
|
||||
@property
|
||||
def meta(self):
|
||||
if not getattr(self, "_meta", None):
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ class DatabaseQuery(object):
|
|||
join='left join', distinct=False, start=None, page_length=None, limit=None,
|
||||
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
|
||||
update=None, add_total_row=None, user_settings=None, reference_doctype=None,
|
||||
run=True, strict=True, pluck=None, ignore_ddl=False) -> List:
|
||||
run=True, strict=True, pluck=None, ignore_ddl=False, parent_doctype=None) -> List:
|
||||
if not ignore_permissions and \
|
||||
not frappe.has_permission(self.doctype, "select", user=user) and \
|
||||
not frappe.has_permission(self.doctype, "read", user=user):
|
||||
not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype) and \
|
||||
not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype):
|
||||
|
||||
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
|
||||
raise frappe.PermissionError(self.doctype)
|
||||
|
|
@ -318,7 +318,8 @@ class DatabaseQuery(object):
|
|||
doctype = table_name[4:-1]
|
||||
ptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'
|
||||
|
||||
if not self.flags.ignore_permissions and not frappe.has_permission(doctype, ptype=ptype):
|
||||
if not self.flags.ignore_permissions and \
|
||||
not frappe.has_permission(doctype, ptype=ptype, parent_doctype=self.doctype):
|
||||
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(doctype))
|
||||
raise frappe.PermissionError(doctype)
|
||||
|
||||
|
|
@ -487,9 +488,9 @@ class DatabaseQuery(object):
|
|||
f.value = date_range
|
||||
fallback = "'0001-01-01 00:00:00'"
|
||||
|
||||
if (f.fieldname in ('creation', 'modified')):
|
||||
if f.operator in ('>', '<') and (f.fieldname in ('creation', 'modified')):
|
||||
value = cstr(f.value)
|
||||
fallback = "NULL"
|
||||
fallback = "'0001-01-01 00:00:00'"
|
||||
|
||||
elif f.operator.lower() in ('between') and \
|
||||
(f.fieldname in ('creation', 'modified') or (df and (df.fieldtype=="Date" or df.fieldtype=="Datetime"))):
|
||||
|
|
@ -544,6 +545,7 @@ class DatabaseQuery(object):
|
|||
fallback = 0
|
||||
|
||||
if isinstance(f.value, Column):
|
||||
can_be_null = False # added to avoid the ifnull/coalesce addition
|
||||
quote = '"' if frappe.conf.db_type == 'postgres' else "`"
|
||||
value = f"{tname}.{quote}{f.value.name}{quote}"
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from frappe import _
|
|||
from frappe.utils import now_datetime, cint, cstr
|
||||
import re
|
||||
from frappe.model import log_types
|
||||
from frappe.query_builder import DocType
|
||||
|
||||
|
||||
def set_new_name(doc):
|
||||
|
|
@ -194,7 +195,15 @@ def parse_naming_series(parts, doctype='', doc=''):
|
|||
|
||||
def getseries(key, digits):
|
||||
# series created ?
|
||||
current = frappe.db.sql("SELECT `current` FROM `tabSeries` WHERE `name`=%s FOR UPDATE", (key,))
|
||||
# Using frappe.qb as frappe.get_values does not allow order_by=None
|
||||
series = DocType("Series")
|
||||
current = (
|
||||
frappe.qb.from_(series)
|
||||
.where(series.name == key)
|
||||
.for_update()
|
||||
.select("current")
|
||||
).run()
|
||||
|
||||
if current and current[0][0] is not None:
|
||||
current = current[0][0]
|
||||
# yes, update it
|
||||
|
|
@ -260,7 +269,13 @@ def revert_series_if_last(key, name, doc=None):
|
|||
prefix = parse_naming_series(prefix.split('.'), doc=doc)
|
||||
|
||||
count = cint(name.replace(prefix, ""))
|
||||
current = frappe.db.sql("SELECT `current` FROM `tabSeries` WHERE `name`=%s FOR UPDATE", (prefix,))
|
||||
series = DocType("Series")
|
||||
current = (
|
||||
frappe.qb.from_(series)
|
||||
.where(series.name == prefix)
|
||||
.for_update()
|
||||
.select("current")
|
||||
).run()
|
||||
|
||||
if current and current[0][0]==count:
|
||||
frappe.db.sql("UPDATE `tabSeries` SET `current` = `current` - 1 WHERE `name`=%s", prefix)
|
||||
|
|
|
|||
|
|
@ -238,7 +238,9 @@ class ParallelTestWithOrchestrator(ParallelTestRunner):
|
|||
self.call_orchestrator('test-completed')
|
||||
return super().print_result()
|
||||
|
||||
def call_orchestrator(self, endpoint, data={}):
|
||||
def call_orchestrator(self, endpoint, data=None):
|
||||
if data is None:
|
||||
data = {}
|
||||
# add repo token header
|
||||
# build id in header
|
||||
headers = {
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ frappe.patches.v13_0.rename_list_view_setting_to_list_view_settings
|
|||
frappe.patches.v13_0.remove_twilio_settings
|
||||
frappe.patches.v12_0.rename_uploaded_files_with_proper_name
|
||||
frappe.patches.v13_0.queryreport_columns
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype')
|
||||
frappe.patches.v13_0.jinja_hook
|
||||
frappe.patches.v13_0.update_notification_channel_if_empty
|
||||
frappe.patches.v14_0.drop_data_import_legacy
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ def print_has_permission_check_logs(func):
|
|||
return inner
|
||||
|
||||
@print_has_permission_check_logs
|
||||
def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, raise_exception=True):
|
||||
def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, raise_exception=True, parent_doctype=None):
|
||||
"""Returns True if user has permission `ptype` for given `doctype`.
|
||||
If `doc` is passed, it also checks user, share and owner permissions.
|
||||
|
||||
|
|
@ -47,11 +47,12 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, ra
|
|||
doc = doctype
|
||||
doctype = doc.doctype
|
||||
|
||||
if frappe.is_table(doctype):
|
||||
if user == "Administrator":
|
||||
return True
|
||||
|
||||
if user=="Administrator":
|
||||
return True
|
||||
if frappe.is_table(doctype):
|
||||
return has_child_table_permission(doctype, ptype, doc, verbose,
|
||||
user, raise_exception, parent_doctype)
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
|
|
@ -96,7 +97,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, ra
|
|||
if not perm:
|
||||
perm = false_if_not_shared()
|
||||
|
||||
return perm
|
||||
return bool(perm)
|
||||
|
||||
def get_doc_permissions(doc, user=None, ptype=None):
|
||||
"""Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`"""
|
||||
|
|
@ -560,3 +561,35 @@ def filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=
|
|||
def push_perm_check_log(log):
|
||||
if frappe.flags.get('has_permission_check_logs') == None: return
|
||||
frappe.flags.get('has_permission_check_logs').append(_(log))
|
||||
|
||||
def has_child_table_permission(child_doctype, ptype="read", child_doc=None,
|
||||
verbose=False, user=None, raise_exception=True, parent_doctype=None):
|
||||
parent_doc = None
|
||||
|
||||
if child_doc:
|
||||
parent_doctype = child_doc.get("parenttype")
|
||||
parent_doc = frappe.get_cached_doc({
|
||||
"doctype": parent_doctype,
|
||||
"docname": child_doc.get("parent")
|
||||
})
|
||||
|
||||
if parent_doctype:
|
||||
if not is_parent_valid(child_doctype, parent_doctype):
|
||||
frappe.throw(_("{0} is not a valid parent DocType for {1}").format(
|
||||
frappe.bold(parent_doctype),
|
||||
frappe.bold(child_doctype)
|
||||
), title=_("Invalid Parent DocType"))
|
||||
else:
|
||||
frappe.throw(_("Please specify a valid parent DocType for {0}").format(
|
||||
frappe.bold(child_doctype)
|
||||
), title=_("Parent DocType Required"))
|
||||
|
||||
return has_permission(parent_doctype, ptype=ptype, doc=parent_doc,
|
||||
verbose=verbose, user=user, raise_exception=raise_exception)
|
||||
|
||||
|
||||
def is_parent_valid(child_doctype, parent_doctype):
|
||||
from frappe.core.utils import find
|
||||
parent_meta = frappe.get_meta(parent_doctype)
|
||||
child_table_field_exists = find(parent_meta.get_table_fields(), lambda d: d.options == child_doctype)
|
||||
return not parent_meta.istable and child_table_field_exists
|
||||
|
|
@ -15,11 +15,6 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
|
|||
.addClass("input-with-feedback form-control")
|
||||
.prependTo(this.input_area);
|
||||
|
||||
if (in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'],
|
||||
this.df.fieldtype)) {
|
||||
this.$input.attr("maxlength", this.df.length || 140);
|
||||
}
|
||||
|
||||
this.$input.on('paste', (e) => {
|
||||
let pasted_data = frappe.utils.get_clipboard_data(e);
|
||||
let maxlength = this.$input.attr('maxlength');
|
||||
|
|
@ -199,6 +194,13 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
|
|||
}
|
||||
}
|
||||
set_input_attributes() {
|
||||
if (in_list(
|
||||
['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only'],
|
||||
this.df.fieldtype
|
||||
)) {
|
||||
this.$input.attr("maxlength", this.df.length || 140);
|
||||
}
|
||||
|
||||
this.$input
|
||||
.attr("data-fieldtype", this.df.fieldtype)
|
||||
.attr("data-fieldname", this.df.fieldname)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ Quill.register(FontStyle, true);
|
|||
Quill.register(AlignStyle, true);
|
||||
Quill.register(DirectionStyle, true);
|
||||
|
||||
// direction class
|
||||
const DirectionClass = Quill.import('attributors/class/direction');
|
||||
Quill.register(DirectionClass, true);
|
||||
|
||||
// replace font tag with span
|
||||
const Inline = Quill.import('blots/inline');
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ frappe.ui.form.Footer = class FormFooter {
|
|||
fieldname: 'comment'
|
||||
},
|
||||
on_submit: (comment) => {
|
||||
if (strip_html(comment).trim() != "") {
|
||||
if (strip_html(comment).trim() != "" || comment.includes('img')) {
|
||||
this.frm.comment_box.disable();
|
||||
frappe.xcall("frappe.desk.form.utils.add_comment", {
|
||||
reference_doctype: this.frm.doctype,
|
||||
|
|
|
|||
|
|
@ -206,6 +206,25 @@ frappe.request.call = function(opts) {
|
|||
}
|
||||
};
|
||||
|
||||
var exception_handlers = {
|
||||
'QueryTimeoutError': function() {
|
||||
frappe.utils.play_sound("error");
|
||||
frappe.msgprint({
|
||||
title: __('Request Timeout'),
|
||||
indicator: 'red',
|
||||
message: __("Server was too busy to process this request. Please try again.")
|
||||
});
|
||||
},
|
||||
'QueryDeadlockError': function() {
|
||||
frappe.utils.play_sound("error");
|
||||
frappe.msgprint({
|
||||
title: __('Deadlock Occurred'),
|
||||
indicator: 'red',
|
||||
message: __("Server was too busy to process this request. Please try again.")
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var ajax_args = {
|
||||
url: opts.url || frappe.request.url,
|
||||
data: opts.args,
|
||||
|
|
@ -272,13 +291,25 @@ frappe.request.call = function(opts) {
|
|||
})
|
||||
.fail(function(xhr, textStatus) {
|
||||
try {
|
||||
if (xhr.responseText) {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
if (data.exception) {
|
||||
// frappe.exceptions.CustomError -> CustomError
|
||||
var exception = data.exception.split('.').at(-1);
|
||||
var exception_handler = exception_handlers[exception];
|
||||
if (exception_handler) {
|
||||
exception_handler(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var status_code_handler = statusCode[xhr.statusCode().status];
|
||||
if (status_code_handler) {
|
||||
status_code_handler(xhr);
|
||||
} else {
|
||||
// if not handled by error handler!
|
||||
opts.error_callback && opts.error_callback(xhr);
|
||||
return;
|
||||
}
|
||||
// if not handled by error handler!
|
||||
opts.error_callback && opts.error_callback(xhr);
|
||||
} catch(e) {
|
||||
console.log("Unable to handle failed response"); // eslint-disable-line
|
||||
console.trace(e); // eslint-disable-line
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ frappe.search.SearchDialog = class {
|
|||
get_link(result) {
|
||||
let link = "";
|
||||
if (result.route) {
|
||||
link = `href="#${result.route.join("/")}"`;
|
||||
link = `href="/app/${result.route.join("/")}"`;
|
||||
} else if (result.data_path) {
|
||||
link = `data-path=${result.data_path}"`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ body {
|
|||
}
|
||||
|
||||
.social-logins {
|
||||
margin: var(--margin-md) 0;
|
||||
margin-top: var(--margin-md);
|
||||
font-size: var(--text-md);
|
||||
|
||||
.social-login-buttons {
|
||||
|
|
@ -147,7 +147,11 @@ body {
|
|||
}
|
||||
min-width: 50%;
|
||||
padding: 0 4px;
|
||||
margin-bottom: var(--margin-sm);
|
||||
margin-bottom: var(--margin-md);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
@import './website/index';
|
||||
@import './website/index';
|
||||
|
|
@ -13,6 +13,10 @@
|
|||
.navbar-light {
|
||||
border-bottom: 1px solid $border-color;
|
||||
background: $navbar-bg;
|
||||
|
||||
.navbar-toggler .icon {
|
||||
stroke: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-primary {
|
||||
|
|
@ -25,6 +29,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.navbar-search {
|
||||
background-color: var(--blue-400);
|
||||
width: 300px;
|
||||
|
|
@ -36,6 +44,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.navbar-toggler {
|
||||
border-color: rgba(255,255,255, 0.1);
|
||||
|
||||
.icon {
|
||||
stroke: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
svg use {
|
||||
--icon-stroke: white;
|
||||
|
|
|
|||
|
|
@ -145,11 +145,6 @@
|
|||
|
||||
.section-with-cards .card {
|
||||
@include transition();
|
||||
border: none;
|
||||
|
||||
.card-body {
|
||||
padding: 0 1.5rem 2rem 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $gray-500;
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@
|
|||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-06 17:25:40.477044",
|
||||
"modified": "2021-10-25 17:25:40.477044",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Social",
|
||||
"name": "Energy Point Log",
|
||||
|
|
@ -131,6 +131,5 @@
|
|||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "user",
|
||||
"track_changes": 1
|
||||
}
|
||||
"title_field": "user"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,11 @@
|
|||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
<span>
|
||||
<svg class="icon icon-lg">
|
||||
<use href="#icon-menu"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@
|
|||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
<span>
|
||||
<svg class="icon icon-lg">
|
||||
<use href="#icon-menu"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
|
|
|
|||
|
|
@ -60,7 +60,10 @@ def main(app=None, module=None, doctype=None, verbose=False, tests=(),
|
|||
|
||||
# workaround! since there is no separate test db
|
||||
frappe.clear_cache()
|
||||
frappe.utils.scheduler.disable_scheduler()
|
||||
scheduler_disabled_by_user = frappe.utils.scheduler.is_scheduler_disabled()
|
||||
if not scheduler_disabled_by_user:
|
||||
frappe.utils.scheduler.disable_scheduler()
|
||||
|
||||
set_test_email_config()
|
||||
frappe.conf.update({'bench_id': 'test_bench', 'use_rq_auth': False})
|
||||
|
||||
|
|
@ -77,6 +80,9 @@ def main(app=None, module=None, doctype=None, verbose=False, tests=(),
|
|||
else:
|
||||
ret = run_all_tests(app, verbose, profile, ui_tests, failfast=failfast, junit_xml_output=junit_xml_output)
|
||||
|
||||
if not scheduler_disabled_by_user:
|
||||
frappe.utils.scheduler.enable_scheduler()
|
||||
|
||||
if frappe.db: frappe.db.commit()
|
||||
|
||||
# workaround! since there is no separate test db
|
||||
|
|
|
|||
|
|
@ -142,6 +142,12 @@ class TestReportview(unittest.TestCase):
|
|||
self.assertTrue({ "name": event1.name } not in data)
|
||||
self.assertTrue({ "name": event2.name } not in data)
|
||||
|
||||
# test between is formatted for creation column
|
||||
data = DatabaseQuery("Event").execute(
|
||||
filters={"creation": ["between", ["2016-07-06", "2016-07-07"]]},
|
||||
fields=["name"])
|
||||
|
||||
|
||||
def test_ignore_permissions_for_get_filters_cond(self):
|
||||
frappe.set_user('test2@example.com')
|
||||
self.assertRaises(frappe.PermissionError, get_filters_cond, 'DocType', dict(istable=1), [])
|
||||
|
|
|
|||
|
|
@ -593,3 +593,13 @@ class TestPermissions(unittest.TestCase):
|
|||
|
||||
# reset the user
|
||||
frappe.set_user(current_user)
|
||||
|
||||
def test_child_table_permissions(self):
|
||||
frappe.set_user("test@example.com")
|
||||
self.assertIsInstance(frappe.get_list("Has Role", parent_doctype="User", limit=1), list)
|
||||
self.assertRaisesRegex(frappe.exceptions.ValidationError,
|
||||
".* is not a valid parent DocType for .*", frappe.get_list, doctype="Has Role", parent_doctype="ToDo")
|
||||
self.assertRaisesRegex(frappe.exceptions.ValidationError,
|
||||
"Please specify a valid parent DocType for .*", frappe.get_list, "Has Role")
|
||||
self.assertRaisesRegex(frappe.exceptions.ValidationError,
|
||||
".* is not a valid parent DocType for .*", frappe.get_list, doctype="Has Role", parent_doctype="Has Role")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class TestWebsite(unittest.TestCase):
|
|||
frappe.set_user('Guest')
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.value_cache = {}
|
||||
frappe.db.delete('Access Log')
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
def test_home_page(self):
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from typing import List, Union, Tuple
|
|||
import frappe
|
||||
from frappe.model.utils import InvalidIncludePath, render_include
|
||||
from frappe.utils import get_bench_path, is_html, strip, strip_html_tags
|
||||
from frappe.query_builder import Field
|
||||
from frappe.query_builder import Field, DocType
|
||||
from pypika.terms import PseudoColumn
|
||||
|
||||
|
||||
|
|
@ -334,14 +334,15 @@ def clear_cache():
|
|||
def get_messages_for_app(app, deduplicate=True):
|
||||
"""Returns all messages (list) for a specified `app`"""
|
||||
messages = []
|
||||
modules = ", ".join('"{}"'.format(m.title().replace("_", " ")) \
|
||||
for m in frappe.local.app_modules[app])
|
||||
modules = [frappe.unscrub(m) for m in frappe.local.app_modules[app]]
|
||||
|
||||
# doctypes
|
||||
if modules:
|
||||
if isinstance(modules, str):
|
||||
modules = [modules]
|
||||
filtered_doctypes = frappe.qb.from_("DocType").where(
|
||||
Field("module").isin(modules)
|
||||
).select("name").run()
|
||||
).select("name").run(pluck=True)
|
||||
for name in filtered_doctypes:
|
||||
messages.extend(get_messages_from_doctype(name))
|
||||
|
||||
|
|
@ -355,9 +356,14 @@ def get_messages_for_app(app, deduplicate=True):
|
|||
|
||||
|
||||
# reports
|
||||
for name in frappe.db.sql_list("""select tabReport.name from tabDocType, tabReport
|
||||
where tabReport.ref_doctype = tabDocType.name
|
||||
and tabDocType.module in ({})""".format(modules)):
|
||||
report = DocType("Report")
|
||||
doctype = DocType("DocType")
|
||||
names = (
|
||||
frappe.qb.from_(doctype)
|
||||
.from_(report)
|
||||
.where((report.ref_doctype == doctype.name) & doctype.module.isin(modules))
|
||||
.select(report.name).run(pluck=True))
|
||||
for name in names:
|
||||
messages.append((None, name))
|
||||
messages.extend(get_messages_from_report(name))
|
||||
for i in messages:
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ from frappe.utils import get_url, get_datetime, time_diff_in_seconds, cint
|
|||
|
||||
class ExpiredLoginException(Exception): pass
|
||||
|
||||
def toggle_two_factor_auth(state, roles=[]):
|
||||
def toggle_two_factor_auth(state, roles=None):
|
||||
'''Enable or disable 2FA in site_config and roles'''
|
||||
for role in roles:
|
||||
for role in roles or []:
|
||||
role = frappe.get_doc('Role', {'role_name': role})
|
||||
role.two_factor_auth = cint(state)
|
||||
role.save(ignore_permissions=True)
|
||||
|
|
@ -417,4 +417,4 @@ def reset_otp_secret(user):
|
|||
enqueue(method=frappe.sendmail, queue='short', timeout=300, event=None, is_async=True, job_name=None, now=False, **email_args)
|
||||
return frappe.msgprint(_("OTP Secret has been reset. Re-registration will be required on next login."))
|
||||
else:
|
||||
return frappe.throw(_("OTP secret can only be reset by the Administrator."))
|
||||
return frappe.throw(_("OTP secret can only be reset by the Administrator."))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from typing import Optional
|
|||
import frappe
|
||||
import operator
|
||||
import json
|
||||
import base64
|
||||
import re, datetime, math, time
|
||||
from code import compile_command
|
||||
from urllib.parse import quote, urljoin
|
||||
|
|
@ -1013,7 +1014,6 @@ def get_thumbnail_base64_for_image(src):
|
|||
return cache().hget('thumbnail_base64', src, generator=_get_base64)
|
||||
|
||||
def image_to_base64(image, extn):
|
||||
import base64
|
||||
from io import BytesIO
|
||||
|
||||
buffered = BytesIO()
|
||||
|
|
@ -1023,6 +1023,20 @@ def image_to_base64(image, extn):
|
|||
img_str = base64.b64encode(buffered.getvalue())
|
||||
return img_str
|
||||
|
||||
def pdf_to_base64(filename):
|
||||
from frappe.utils.file_manager import get_file_path
|
||||
|
||||
if '../' in filename or filename.rsplit('.')[-1] not in ['pdf', 'PDF']:
|
||||
return
|
||||
|
||||
file_path = get_file_path(filename)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
with open(file_path, 'rb') as pdf_file:
|
||||
base64_string = base64.b64encode(pdf_file.read())
|
||||
|
||||
return base64_string
|
||||
|
||||
# from Jinja2 code
|
||||
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
||||
|
|
|
|||
|
|
@ -302,6 +302,9 @@ def get_file(fname):
|
|||
|
||||
def get_file_path(file_name):
|
||||
"""Returns file path from given file name"""
|
||||
if '../' in file_name:
|
||||
return
|
||||
|
||||
f = frappe.db.sql("""select file_url from `tabFile`
|
||||
where name=%s or file_name=%s""", (file_name, file_name))
|
||||
if f:
|
||||
|
|
|
|||
|
|
@ -68,6 +68,13 @@ def web_blocks(blocks):
|
|||
return html
|
||||
|
||||
|
||||
def get_dom_id(seed=None):
|
||||
from frappe import generate_hash
|
||||
if not seed:
|
||||
seed = 'DOM'
|
||||
return 'id-' + generate_hash(seed, 12)
|
||||
|
||||
|
||||
def include_script(path):
|
||||
path = bundled_asset(path)
|
||||
return f'<script type="text/javascript" src="{path}"></script>'
|
||||
|
|
@ -94,4 +101,4 @@ def is_rtl(rtl=None):
|
|||
from frappe import local
|
||||
if rtl is None:
|
||||
return local.lang in ["ar", "he", "fa", "ps"]
|
||||
return rtl
|
||||
return rtl
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import frappe
|
|||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import now
|
||||
from frappe.query_builder import DocType, Order
|
||||
|
||||
class NestedSetRecursionError(frappe.ValidationError): pass
|
||||
class NestedSetMultipleRootsError(frappe.ValidationError): pass
|
||||
|
|
@ -146,10 +147,21 @@ def rebuild_tree(doctype, parent_field):
|
|||
frappe.only_for('System Manager')
|
||||
|
||||
# get all roots
|
||||
right = 1
|
||||
table = DocType(doctype)
|
||||
column = getattr(table, parent_field)
|
||||
|
||||
result = (
|
||||
frappe.qb.from_(table)
|
||||
.where(
|
||||
(column == "") | (column.isnull())
|
||||
)
|
||||
.orderby(table.name, order=Order.asc)
|
||||
.select(table.name)
|
||||
).run()
|
||||
|
||||
frappe.db.auto_commit_on_many_writes = 1
|
||||
|
||||
right = 1
|
||||
result = frappe.db.sql("SELECT name FROM `tab%s` WHERE `%s`='' or `%s` IS NULL ORDER BY name ASC" % (doctype, parent_field, parent_field))
|
||||
for r in result:
|
||||
right = rebuild_node(doctype, r[0], right, parent_field)
|
||||
|
||||
|
|
@ -159,22 +171,23 @@ def rebuild_node(doctype, parent, left, parent_field):
|
|||
"""
|
||||
reset lft, rgt and recursive call for all children
|
||||
"""
|
||||
from frappe.utils import now
|
||||
n = now()
|
||||
|
||||
# the right value of this node is the left value + 1
|
||||
right = left+1
|
||||
|
||||
# get all children of this node
|
||||
result = frappe.db.sql("SELECT name FROM `tab{0}` WHERE `{1}`=%s"
|
||||
.format(doctype, parent_field), (parent))
|
||||
table = DocType(doctype)
|
||||
column = getattr(table, parent_field)
|
||||
|
||||
result = (
|
||||
frappe.qb.from_(table).where(column == parent).select(table.name)
|
||||
).run()
|
||||
|
||||
for r in result:
|
||||
right = rebuild_node(doctype, r[0], right, parent_field)
|
||||
|
||||
# we've got the left value, and now that we've processed
|
||||
# the children of this node we also know the right value
|
||||
frappe.db.sql("""UPDATE `tab{0}` SET lft=%s, rgt=%s, modified=%s
|
||||
WHERE name=%s""".format(doctype), (left,right,n,parent))
|
||||
frappe.db.set_value(doctype, parent, {"lft": left, "rgt": right}, for_update=False)
|
||||
|
||||
#return the right value of this node + 1
|
||||
return right+1
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ def report_error(status_code):
|
|||
allow_traceback = cint(frappe.db.get_system_setting('allow_error_traceback')) if frappe.db else True
|
||||
if (allow_traceback and (status_code!=404 or frappe.conf.logging)
|
||||
and not frappe.local.flags.disable_traceback):
|
||||
frappe.errprint(frappe.utils.get_traceback())
|
||||
traceback = frappe.utils.get_traceback()
|
||||
if traceback:
|
||||
frappe.errprint(traceback)
|
||||
frappe.local.response.exception = traceback.splitlines()[-1]
|
||||
|
||||
response = build_response("json")
|
||||
response.status_code = status_code
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
import copy
|
||||
import inspect
|
||||
import json
|
||||
import mimetypes
|
||||
|
|
@ -129,7 +130,7 @@ def get_safe_globals():
|
|||
make_get_request=frappe.integrations.utils.make_get_request,
|
||||
make_post_request=frappe.integrations.utils.make_post_request,
|
||||
socketio_port=frappe.conf.socketio_port,
|
||||
get_hooks=frappe.get_hooks,
|
||||
get_hooks=get_hooks,
|
||||
sanitize_html=frappe.utils.sanitize_html,
|
||||
log_error=frappe.log_error
|
||||
),
|
||||
|
|
@ -173,8 +174,6 @@ def get_safe_globals():
|
|||
rollback=frappe.db.rollback,
|
||||
)
|
||||
|
||||
out.frappe.cache = cache
|
||||
|
||||
if frappe.response:
|
||||
out.frappe.response = frappe.response
|
||||
|
||||
|
|
@ -192,13 +191,9 @@ def get_safe_globals():
|
|||
|
||||
return out
|
||||
|
||||
def cache():
|
||||
return NamespaceDict(
|
||||
get_value = frappe.cache().get_value,
|
||||
set_value = frappe.cache().set_value,
|
||||
hset = frappe.cache().hset,
|
||||
hget = frappe.cache().hget
|
||||
)
|
||||
def get_hooks(hook=None, default=None, app_name=None):
|
||||
hooks = frappe.get_hooks(hook=hook, default=default, app_name=app_name)
|
||||
return copy.deepcopy(hooks)
|
||||
|
||||
def read_sql(query, *args, **kwargs):
|
||||
'''a wrapper for frappe.db.sql to allow reads'''
|
||||
|
|
@ -327,6 +322,7 @@ VALID_UTILS = (
|
|||
"is_image",
|
||||
"get_thumbnail_base64_for_image",
|
||||
"image_to_base64",
|
||||
"pdf_to_base64",
|
||||
"strip_html",
|
||||
"escape_html",
|
||||
"pretty_date",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See LICENSE
|
||||
|
||||
import click
|
||||
|
||||
import frappe
|
||||
from weasyprint import HTML, CSS
|
||||
|
||||
try:
|
||||
from weasyprint import HTML, CSS
|
||||
except OSError:
|
||||
click.secho(
|
||||
"\n".join(["WeasyPrint depdends on additional system dependencies.",
|
||||
"Follow instructions specific to your operating system:",
|
||||
"https://doc.courtbouillon.org/weasyprint/stable/first_steps.html"]),
|
||||
fg="yellow"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-05 14:11:24.718770",
|
||||
"modified": "2021-10-25 14:11:24.718770",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Page View",
|
||||
|
|
@ -82,6 +82,5 @@
|
|||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "path",
|
||||
"track_changes": 1
|
||||
}
|
||||
"title_field": "path"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@
|
|||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
<span>
|
||||
<svg class="icon icon-lg">
|
||||
<use href="#icon-menu"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@ def get_list_data(doctype, txt=None, limit_start=0, fields=None, cmd=None, limit
|
|||
"""Returns processed HTML page for a standard listing."""
|
||||
limit_start = cint(limit_start)
|
||||
|
||||
if frappe.is_table(doctype):
|
||||
frappe.throw(_("Child DocTypes are not allowed"), title=_("Invalid DocType"))
|
||||
|
||||
if not txt and frappe.form_dict.search:
|
||||
txt = frappe.form_dict.search
|
||||
del frappe.form_dict['search']
|
||||
|
|
@ -183,8 +186,7 @@ def get_list_context(context, doctype, web_form_name=None):
|
|||
|
||||
return list_context
|
||||
|
||||
def get_list(doctype, txt, filters, limit_start, limit_page_length=20, ignore_permissions=False,
|
||||
fields=None, order_by=None):
|
||||
def get_list(doctype, txt, filters, limit_start, limit_page_length=20, ignore_permissions=False, fields=None, order_by=None):
|
||||
meta = frappe.get_meta(doctype)
|
||||
if not filters:
|
||||
filters = []
|
||||
|
|
|
|||
|
|
@ -79,7 +79,11 @@
|
|||
<form class="form-signin form-login" role="form">
|
||||
{%- if social_login -%}
|
||||
<div class="page-card-body">
|
||||
<form class="form-signin form-login" role="form">
|
||||
{{ email_login_body() }}
|
||||
</form>
|
||||
<div class="social-logins text-center">
|
||||
<p class="text-muted login-divider">{{ _("or") }}</p>
|
||||
<div class="social-login-buttons">
|
||||
{% for provider in provider_logins %}
|
||||
<div class="login-button-wrapper">
|
||||
|
|
@ -91,12 +95,8 @@
|
|||
{{ _("Login With {0}").format(provider.provider_name) }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<p class="text-muted login-divider">{{ _("or") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#email"
|
||||
class="btn btn-block btn-default btn-sm btn-login-option">
|
||||
{{ _("Login With Email") }}</a>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ email_login_body() }}
|
||||
|
|
|
|||
13
yarn.lock
13
yarn.lock
|
|
@ -554,9 +554,9 @@ caniuse-api@^3.0.0:
|
|||
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:
|
||||
version "1.0.30001228"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa"
|
||||
integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==
|
||||
version "1.0.30001272"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001272.tgz"
|
||||
integrity sha512-DV1j9Oot5dydyH1v28g25KoVm7l8MTxazwuiH3utWiAS6iL/9Nh//TGwqFEeqqN8nnWYQ8HHhUq+o4QPt9kvYw==
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
|
|
@ -4934,13 +4934,6 @@ vuedraggable@^2.24.3:
|
|||
dependencies:
|
||||
sortablejs "1.10.2"
|
||||
|
||||
wcwidth@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
||||
integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
|
||||
dependencies:
|
||||
defaults "^1.0.3"
|
||||
|
||||
which-boxed-primitive@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue