Merge branch 'frappe:develop' into wspace-code-cleanup
This commit is contained in:
commit
0dfc5989fd
40 changed files with 328 additions and 167 deletions
|
|
@ -131,3 +131,16 @@ rules:
|
|||
key `$X` is uselessly assigned twice. This could be a potential bug.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-using-db-sql
|
||||
pattern-either:
|
||||
- pattern: frappe.db.sql(...)
|
||||
- pattern: frappe.db.sql_ddl(...)
|
||||
- pattern: frappe.db.sql_list(...)
|
||||
paths:
|
||||
exclude:
|
||||
- "test_*.py"
|
||||
message: |
|
||||
The PR contains a SQL query that may be re-written with frappe.qb (https://frappeframework.com/docs/user/en/api/query-builder) or the Database API (https://frappeframework.com/docs/user/en/api/database)
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
|
|
|||
32
.github/try-on-f-cloud-button.svg
vendored
Normal file
32
.github/try-on-f-cloud-button.svg
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<svg width="201" height="60" viewBox="0 0 201 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_dd)">
|
||||
<rect x="4" y="2" width="193" height="52" rx="6" fill="#2490EF"/>
|
||||
<path d="M28 22.2891H32.8786V35.5H36.2088V22.2891H41.0874V19.5H28V22.2891Z" fill="white"/>
|
||||
<path d="M41.6982 35.5H45.0129V28.7109C45.0129 27.2344 46.0866 26.2188 47.5494 26.2188C48.0085 26.2188 48.6388 26.2969 48.95 26.3984V23.4453C48.6543 23.375 48.2419 23.3281 47.9074 23.3281C46.5691 23.3281 45.472 24.1094 45.0362 25.5938H44.9117V23.5H41.6982V35.5Z" fill="white"/>
|
||||
<path d="M52.8331 40C55.2996 40 56.6068 38.7344 57.2837 36.7969L61.9289 23.5156L58.4197 23.5L55.9221 32.3125H55.7976L53.3233 23.5H49.8374L54.1247 35.8437L53.9302 36.3516C53.4944 37.4766 52.6619 37.5312 51.4947 37.1719L50.7478 39.6562C51.2224 39.8594 51.9927 40 52.8331 40Z" fill="white"/>
|
||||
<path d="M73.6142 35.7344C77.2401 35.7344 79.4966 33.2422 79.4966 29.5469C79.4966 25.8281 77.2401 23.3438 73.6142 23.3438C69.9883 23.3438 67.7319 25.8281 67.7319 29.5469C67.7319 33.2422 69.9883 35.7344 73.6142 35.7344ZM73.6298 33.1562C71.9569 33.1562 71.101 31.6171 71.101 29.5233C71.101 27.4296 71.9569 25.8827 73.6298 25.8827C75.2715 25.8827 76.1274 27.4296 76.1274 29.5233C76.1274 31.6171 75.2715 33.1562 73.6298 33.1562Z" fill="white"/>
|
||||
<path d="M84.7253 28.5625C84.7331 27.0156 85.6512 26.1094 86.9895 26.1094C88.3201 26.1094 89.1215 26.9844 89.1137 28.4531V35.5H92.4284V27.8594C92.4284 25.0625 90.7945 23.3438 88.3046 23.3438C86.5306 23.3438 85.2466 24.2187 84.7097 25.6172H84.5697V23.5H81.4106V35.5H84.7253V28.5625Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.429 19.5H113.429V22.3141H102.429V19.5ZM102.429 35.5V26.6794H112.699V29.4982H105.94V35.5H102.429Z" fill="white"/>
|
||||
<path d="M131.584 24.9625C131.09 21.5057 128.345 19.5 124.785 19.5C120.589 19.5 117.429 22.463 117.429 27.4924C117.429 32.5142 120.55 35.4848 124.785 35.4848C128.604 35.4848 131.137 33.0916 131.584 30.1211L128.651 30.1059C128.282 31.9293 126.745 32.9549 124.824 32.9549C122.22 32.9549 120.354 31.0632 120.354 27.4924C120.354 23.9824 122.204 22.0299 124.832 22.0299C126.784 22.0299 128.314 23.1011 128.651 24.9625H131.584Z" fill="white"/>
|
||||
<path d="M136.409 19.7124H133.571V35.2718H136.409V19.7124Z" fill="white"/>
|
||||
<path d="M144.031 35.5001C147.56 35.5001 149.803 33.0917 149.803 29.483C149.803 25.8667 147.56 23.4507 144.031 23.4507C140.502 23.4507 138.259 25.8667 138.259 29.483C138.259 33.0917 140.502 35.5001 144.031 35.5001ZM144.047 33.2969C142.094 33.2969 141.137 31.6103 141.137 29.4754C141.137 27.3406 142.094 25.6312 144.047 25.6312C145.968 25.6312 146.925 27.3406 146.925 29.4754C146.925 31.6103 145.968 33.2969 144.047 33.2969Z" fill="white"/>
|
||||
<path d="M159.338 30.3641C159.338 32.1419 158.028 33.0232 156.773 33.0232C155.409 33.0232 154.499 32.0887 154.499 30.6072V23.6025H151.66V31.0327C151.66 33.8361 153.307 35.4239 155.675 35.4239C157.479 35.4239 158.749 34.5046 159.298 33.1979H159.424V35.272H162.176V23.6025H159.338V30.3641Z" fill="white"/>
|
||||
<path d="M169.014 35.4769C171.084 35.4769 172.017 34.2841 172.464 33.4332H172.637V35.2718H175.429V19.7124H172.582V25.532H172.464C172.033 24.6887 171.147 23.4503 169.022 23.4503C166.238 23.4503 164.05 25.5624 164.05 29.4522C164.05 33.2965 166.175 35.4769 169.014 35.4769ZM169.806 33.2205C167.931 33.2205 166.943 31.6251 166.943 29.437C166.943 27.2642 167.916 25.7067 169.806 25.7067C171.633 25.7067 172.637 27.173 172.637 29.437C172.637 31.701 171.617 33.2205 169.806 33.2205Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_dd" x="0" y="0" width="201" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="0.25"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.13 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
|
@ -35,6 +35,12 @@
|
|||
|
||||
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com)
|
||||
|
||||
<div align="center">
|
||||
<a href="https://frappecloud.com/deploy?apps=frappe&source=frappe_readme">
|
||||
<img src=".github/try-on-f-cloud-button.svg" height="40">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Table of Contents
|
||||
* [Installation](#installation)
|
||||
* [Contributing](#contributing)
|
||||
|
|
@ -46,6 +52,7 @@ Full-stack web application framework that uses Python and MariaDB on the server
|
|||
* [Install via Docker](https://github.com/frappe/frappe_docker)
|
||||
* [Install via Frappe Bench](https://github.com/frappe/bench)
|
||||
* [Offical Documentation](https://frappeframework.com/docs/user/en/installation)
|
||||
* [Managed Hosting on Frappe Cloud](https://frappecloud.com/deploy?apps=frappe&source=frappe_readme)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ codecov:
|
|||
|
||||
coverage:
|
||||
status:
|
||||
patch: off
|
||||
project:
|
||||
default: false
|
||||
server:
|
||||
|
|
@ -10,11 +11,6 @@ coverage:
|
|||
threshold: 0.5%
|
||||
flags:
|
||||
- server
|
||||
ui-tests:
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
flags:
|
||||
- ui-tests
|
||||
|
||||
comment:
|
||||
layout: "diff, flags"
|
||||
|
|
@ -28,4 +24,4 @@ flags:
|
|||
ui-tests:
|
||||
paths:
|
||||
- ".*\\.js"
|
||||
carryforward: true
|
||||
carryforward: true
|
||||
|
|
|
|||
|
|
@ -1,44 +1,47 @@
|
|||
context('Relative Timeframe', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/website');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
|
||||
});
|
||||
});
|
||||
it('sets relative timespan filter for last week and filters list', () => {
|
||||
cy.visit('/app/List/ToDo/List');
|
||||
cy.clear_filters();
|
||||
cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
cy.add_filter();
|
||||
cy.get('.fieldname-select-area').should('exist');
|
||||
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
cy.get('select.condition.form-control').select("Timespan");
|
||||
cy.get('.filter-field select.input-with-feedback.form-control').select("last week");
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
cy.wait('@list_refresh');
|
||||
cy.get('.list-row-container').its('length').should('eq', 1);
|
||||
cy.get('.list-row-container').should('contain', 'this is second todo');
|
||||
cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
.as('save_user_settings');
|
||||
cy.clear_filters();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
it('sets relative timespan filter for next week and filters list', () => {
|
||||
cy.visit('/app/List/ToDo/List');
|
||||
cy.clear_filters();
|
||||
cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
cy.add_filter();
|
||||
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
cy.get('select.condition.form-control').select("Timespan");
|
||||
cy.get('.filter-field select.input-with-feedback.form-control').select("next week");
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
cy.wait('@list_refresh');
|
||||
cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
.as('save_user_settings');
|
||||
cy.clear_filters();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
});
|
||||
// TODO: Enable this again
|
||||
// currently this is flaky possibly because of different timezone in CI
|
||||
|
||||
// context('Relative Timeframe', () => {
|
||||
// before(() => {
|
||||
// cy.login();
|
||||
// cy.visit('/app/website');
|
||||
// cy.window().its('frappe').then(frappe => {
|
||||
// frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
|
||||
// });
|
||||
// });
|
||||
// it('sets relative timespan filter for last week and filters list', () => {
|
||||
// cy.visit('/app/List/ToDo/List');
|
||||
// cy.clear_filters();
|
||||
// cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
// cy.add_filter();
|
||||
// cy.get('.fieldname-select-area').should('exist');
|
||||
// cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
// cy.get('select.condition.form-control').select("Timespan");
|
||||
// cy.get('.filter-field select.input-with-feedback.form-control').select("last week");
|
||||
// cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
// cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
// cy.wait('@list_refresh');
|
||||
// cy.get('.list-row-container').its('length').should('eq', 1);
|
||||
// cy.get('.list-row-container').should('contain', 'this is second todo');
|
||||
// cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
// .as('save_user_settings');
|
||||
// cy.clear_filters();
|
||||
// cy.wait('@save_user_settings');
|
||||
// });
|
||||
// it('sets relative timespan filter for next week and filters list', () => {
|
||||
// cy.visit('/app/List/ToDo/List');
|
||||
// cy.clear_filters();
|
||||
// cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
// cy.add_filter();
|
||||
// cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
// cy.get('select.condition.form-control').select("Timespan");
|
||||
// cy.get('.filter-field select.input-with-feedback.form-control').select("next week");
|
||||
// cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
// cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
// cy.wait('@list_refresh');
|
||||
// cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
// .as('save_user_settings');
|
||||
// cy.clear_filters();
|
||||
// cy.wait('@save_user_settings');
|
||||
// });
|
||||
// });
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ context('Timeline', () => {
|
|||
cy.click_modal_primary_button('Yes');
|
||||
|
||||
//Deleting the added ToDo
|
||||
cy.get('.menu-btn-group [data-original-title="Menu"]').click();
|
||||
cy.get('.menu-btn-group .dropdown-item').contains('Delete').click();
|
||||
cy.get('[id="page-ToDo"] .menu-btn-group [data-original-title="Menu"]').click();
|
||||
cy.get('[id="page-ToDo"] .menu-btn-group .dropdown-item').contains('Delete').click();
|
||||
cy.findByRole('button', {name: 'Yes'}).click();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ let argv = yargs
|
|||
type: "boolean",
|
||||
description: "Run in watch mode and rebuild on file changes"
|
||||
})
|
||||
.option("live-reload", {
|
||||
type: "boolean",
|
||||
description: `Automatically reload web pages when assets are rebuilt.
|
||||
Can only be used with the --watch flag.`
|
||||
})
|
||||
.option("production", {
|
||||
type: "boolean",
|
||||
description: "Run build in production mode"
|
||||
|
|
@ -478,7 +483,8 @@ async function notify_redis({ error, success }) {
|
|||
}
|
||||
if (success) {
|
||||
payload = {
|
||||
success: true
|
||||
success: true,
|
||||
live_reload: argv["live-reload"]
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1480,7 +1480,10 @@ def get_value(*args, **kwargs):
|
|||
|
||||
def as_json(obj, indent=1):
|
||||
from frappe.utils.response import json_handler
|
||||
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': '))
|
||||
try:
|
||||
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': '))
|
||||
except TypeError:
|
||||
return json.dumps(obj, indent=indent, default=json_handler, separators=(',', ': '))
|
||||
|
||||
def are_emails_muted():
|
||||
from frappe.utils import cint
|
||||
|
|
|
|||
|
|
@ -257,6 +257,13 @@ def watch(apps=None):
|
|||
if apps:
|
||||
command += " --apps {apps}".format(apps=apps)
|
||||
|
||||
live_reload = frappe.utils.cint(
|
||||
os.environ.get("LIVE_RELOAD", frappe.conf.live_reload)
|
||||
)
|
||||
|
||||
if live_reload:
|
||||
command += " --live-reload"
|
||||
|
||||
check_node_executable()
|
||||
frappe_app_path = frappe.get_app_path("frappe", "..")
|
||||
frappe.commands.popen(command, cwd=frappe_app_path, env=get_node_env())
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
def set_delivery_status(self, commit=False):
|
||||
'''Look into the status of Email Queue linked to this Communication and set the Delivery Status of this Communication'''
|
||||
delivery_status = None
|
||||
status_counts = Counter(frappe.db.sql_list('''select status from `tabEmail Queue` where communication=%s''', self.name))
|
||||
status_counts = Counter(frappe.get_all("Email Queue", pluck="status", filters={"communication": self.name}))
|
||||
if self.sent_or_received == "Received":
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -217,17 +217,7 @@ class CommunicationEmailMixin:
|
|||
if not emails:
|
||||
return []
|
||||
|
||||
disabled_users = frappe.db.sql_list("""
|
||||
SELECT
|
||||
email
|
||||
FROM
|
||||
`tabUser`
|
||||
where
|
||||
email in %(emails)s
|
||||
and
|
||||
thread_notify=0
|
||||
""", {'emails': tuple(emails)})
|
||||
return disabled_users
|
||||
return frappe.get_all("User", pluck="email", filters={"email": ["in", emails], "thread_notify": 0})
|
||||
|
||||
@staticmethod
|
||||
def filter_disabled_users(emails):
|
||||
|
|
@ -236,17 +226,7 @@ class CommunicationEmailMixin:
|
|||
if not emails:
|
||||
return []
|
||||
|
||||
disabled_users = frappe.db.sql_list("""
|
||||
SELECT
|
||||
email
|
||||
FROM
|
||||
`tabUser`
|
||||
where
|
||||
email in %(emails)s
|
||||
and
|
||||
enabled=0
|
||||
""", {'emails': tuple(emails)})
|
||||
return disabled_users
|
||||
return frappe.get_all("User", pluck="email", filters={"email": ["in", emails], "enabled": 0})
|
||||
|
||||
def sendmail_input_dict(self, print_html=None, print_format=None,
|
||||
send_me_a_copy=None, print_letterhead=None, is_inbound_mail_communcation=None):
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ class DataExporter:
|
|||
self.writer.writerow([self.data_keys.data_separator])
|
||||
|
||||
def add_data(self):
|
||||
from frappe.query_builder import DocType
|
||||
if self.template and not self.with_data:
|
||||
return
|
||||
|
||||
|
|
@ -305,9 +306,15 @@ class DataExporter:
|
|||
if self.all_doctypes:
|
||||
# add child tables
|
||||
for c in self.child_doctypes:
|
||||
for ci, child in enumerate(frappe.db.sql("""select * from `tab{0}`
|
||||
where parent=%s and parentfield=%s order by idx""".format(c['doctype']),
|
||||
(doc.name, c['parentfield']), as_dict=1)):
|
||||
child_doctype_table = DocType(c["doctype"])
|
||||
data_row = (
|
||||
frappe.qb.from_(child_doctype_table)
|
||||
.select("*")
|
||||
.where(child_doctype_table.parent == doc.name)
|
||||
.where(child_doctype_table.parentfield == c["parentfield"])
|
||||
.orderby(child_doctype_table.idx)
|
||||
)
|
||||
for ci, child in enumerate(data_row.run()):
|
||||
self.add_data_row(rows, c['doctype'], c['parentfield'], child, ci)
|
||||
|
||||
for row in rows:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from frappe.modules.import_file import get_file_path
|
|||
from frappe.model.meta import Meta
|
||||
from frappe.desk.utils import validate_route_conflict
|
||||
from frappe.website.utils import clear_cache
|
||||
from frappe.query_builder.functions import Concat
|
||||
|
||||
class InvalidFieldNameError(frappe.ValidationError): pass
|
||||
class UniqueFieldnameError(frappe.ValidationError): pass
|
||||
|
|
@ -465,7 +466,7 @@ class DocType(Document):
|
|||
return
|
||||
|
||||
# check if atleast 1 record exists
|
||||
if not (frappe.db.table_exists(self.name) and frappe.db.sql("select name from `tab{}` limit 1".format(self.name))):
|
||||
if not (frappe.db.table_exists(self.name) and frappe.get_all(self.name, fields=["name"], limit=1, as_list=True)):
|
||||
return
|
||||
|
||||
existing_property_setter = frappe.db.get_value("Property Setter", {"doc_type": self.name,
|
||||
|
|
@ -571,17 +572,17 @@ class DocType(Document):
|
|||
def make_amendable(self):
|
||||
"""If is_submittable is set, add amended_from docfields."""
|
||||
if self.is_submittable:
|
||||
if not frappe.db.sql("""select name from tabDocField
|
||||
where fieldname = 'amended_from' and parent = %s""", self.name):
|
||||
self.append("fields", {
|
||||
"label": "Amended From",
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "amended_from",
|
||||
"options": self.name,
|
||||
"read_only": 1,
|
||||
"print_hide": 1,
|
||||
"no_copy": 1
|
||||
})
|
||||
docfield_exists = frappe.get_all("DocField", filters={"fieldname": "amended_from", "parent": self.name}, pluck="name", limit=1)
|
||||
if not docfield_exists:
|
||||
self.append("fields", {
|
||||
"label": "Amended From",
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "amended_from",
|
||||
"options": self.name,
|
||||
"read_only": 1,
|
||||
"print_hide": 1,
|
||||
"no_copy": 1
|
||||
})
|
||||
|
||||
def make_repeatable(self):
|
||||
"""If allow_auto_repeat is set, add auto_repeat custom field."""
|
||||
|
|
@ -706,12 +707,13 @@ def validate_series(dt, autoname=None, name=None):
|
|||
and (not autoname.startswith('format:')):
|
||||
|
||||
prefix = autoname.split('.')[0]
|
||||
used_in = frappe.db.sql("""
|
||||
SELECT `name`
|
||||
FROM `tabDocType`
|
||||
WHERE `autoname` LIKE CONCAT(%s, '.%%')
|
||||
AND `name`!=%s
|
||||
""", (prefix, name))
|
||||
doctype = frappe.qb.DocType("DocType")
|
||||
used_in = (frappe.qb
|
||||
.from_(doctype)
|
||||
.select(doctype.name)
|
||||
.where(doctype.autoname.like(Concat(prefix,".%")))
|
||||
.where(doctype.name != name)
|
||||
).run()
|
||||
if used_in:
|
||||
frappe.throw(_("Series {0} already used in {1}").format(prefix, used_in[0][0]))
|
||||
|
||||
|
|
|
|||
|
|
@ -204,10 +204,14 @@ class TestFile(unittest.TestCase):
|
|||
|
||||
|
||||
def delete_test_data(self):
|
||||
for f in frappe.db.sql('''select name, file_name from tabFile where
|
||||
is_home_folder = 0 and is_attachments_folder = 0 order by creation desc'''):
|
||||
frappe.delete_doc("File", f[0])
|
||||
|
||||
test_file_data = frappe.db.get_all(
|
||||
"File",
|
||||
pluck="name",
|
||||
filters={"is_home_folder": 0, "is_attachments_folder": 0},
|
||||
order_by="creation desc",
|
||||
)
|
||||
for f in test_file_data:
|
||||
frappe.delete_doc("File", f)
|
||||
|
||||
def upload_file(self):
|
||||
_file = frappe.get_doc({
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"language_code",
|
||||
"language_name",
|
||||
"flag",
|
||||
|
|
@ -39,15 +40,22 @@
|
|||
"fieldtype": "Link",
|
||||
"label": "Based On",
|
||||
"options": "Language"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-globe",
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-16 22:11:33.066852",
|
||||
"modified": "2021-10-18 14:02:06.818219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Language",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ def has_unseen_error_log(user):
|
|||
'message': _("You have unseen {0}").format('<a href="/app/List/Error%20Log/List"> Error Logs </a>')
|
||||
}
|
||||
|
||||
if frappe.db.sql_list("select name from `tabError Log` where seen = 0 limit 1"):
|
||||
if frappe.get_all("Error Log", filters={"seen": 0}, limit=1):
|
||||
log_settings = frappe.get_cached_doc('Log Settings')
|
||||
|
||||
if log_settings.users_to_notify:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ class NavbarSettings(Document):
|
|||
if not frappe.flags.in_patch and (len(before_save_items) > len(after_save_items)):
|
||||
frappe.throw(_("Please hide the standard navbar items instead of deleting them"))
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_app_logo():
|
||||
app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo', cache=True)
|
||||
if not app_logo:
|
||||
|
|
|
|||
|
|
@ -14,10 +14,9 @@ class TransactionLog(Document):
|
|||
self.row_index = index
|
||||
self.timestamp = now_datetime()
|
||||
if index != 1:
|
||||
prev_hash = frappe.db.sql(
|
||||
"SELECT `chaining_hash` FROM `tabTransaction Log` WHERE `row_index` = '{0}'".format(index - 1))
|
||||
prev_hash = frappe.get_all("Transaction Log", filters={"row_index":str(index-1)}, pluck="chaining_hash", limit=1)
|
||||
if prev_hash:
|
||||
self.previous_hash = prev_hash[0][0]
|
||||
self.previous_hash = prev_hash[0]
|
||||
else:
|
||||
self.previous_hash = "Indexing broken"
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -202,7 +202,8 @@
|
|||
"fieldname": "role_profile_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Profile",
|
||||
"options": "Role Profile"
|
||||
"options": "Role Profile",
|
||||
"permlevel": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "roles_html",
|
||||
|
|
@ -670,7 +671,7 @@
|
|||
}
|
||||
],
|
||||
"max_attachments": 5,
|
||||
"modified": "2021-02-02 16:11:06.037543",
|
||||
"modified": "2021-10-18 16:56:05.578379",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class UserPermission(Document):
|
|||
ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name)
|
||||
frappe.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow))
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def get_user_permissions(user=None):
|
||||
'''Get all users permissions for the user as a dict of doctype'''
|
||||
# if this is called from client-side,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from frappe.desk.form.document_follow import is_document_followed
|
|||
from frappe import _
|
||||
from urllib.parse import quote
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def getdoc(doctype, name, user=None):
|
||||
"""
|
||||
Loads a doclist for a given document. This method is called directly from the client.
|
||||
|
|
@ -52,7 +52,7 @@ def getdoc(doctype, name, user=None):
|
|||
|
||||
frappe.response.docs.append(doc)
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def getdoctype(doctype, with_parent=False, cached_timestamp=None):
|
||||
"""load doctype"""
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# License: MIT. See LICENSE
|
||||
import frappe
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def get_list_settings(doctype):
|
||||
try:
|
||||
return frappe.get_cached_doc("List View Settings", doctype)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from frappe.utils import cstr, format_duration
|
|||
from frappe.model.base_document import get_controller
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get():
|
||||
args = get_form_params()
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ def make_links(columns, data):
|
|||
if col.options and row.get(col.fieldname) and row.get(col.options):
|
||||
row[col.fieldname] = get_link_to_form(row[col.options], row[col.fieldname])
|
||||
elif col.fieldtype == "Currency" and row.get(col.fieldname):
|
||||
doc = frappe.get_doc(col.parent, doc_name) if doc_name else None
|
||||
doc = frappe.get_doc(col.parent, doc_name) if doc_name and col.parent else None
|
||||
# Pass the Document to get the currency based on docfield option
|
||||
row[col.fieldname] = frappe.format_value(row[col.fieldname], col, doc=doc)
|
||||
return columns, data
|
||||
|
|
|
|||
|
|
@ -507,14 +507,13 @@ def convert_archive_content(sql_file_path):
|
|||
sql_file_path = Path(sql_file_path)
|
||||
|
||||
os.rename(sql_file_path, old_sql_file_path)
|
||||
sql_file_path.unlink(missing_ok=True)
|
||||
sql_file_path.touch()
|
||||
|
||||
with open(old_sql_file_path) as r, open(sql_file_path, "a") as w:
|
||||
for line in r:
|
||||
w.write(line.replace("ROW_FORMAT=COMPRESSED", "ROW_FORMAT=DYNAMIC"))
|
||||
|
||||
old_sql_file_path.unlink(missing_ok=True)
|
||||
old_sql_file_path.unlink()
|
||||
|
||||
|
||||
def extract_sql_gzip(sql_gz_path):
|
||||
|
|
|
|||
|
|
@ -336,7 +336,6 @@ def dropbox_auth_finish(return_access_token=False):
|
|||
_("Dropbox access is approved!") + close,
|
||||
indicator_color='green')
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def set_dropbox_access_token(access_token):
|
||||
frappe.db.set_value("Dropbox Settings", None, 'dropbox_access_token', access_token)
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -597,8 +597,8 @@ class DatabaseQuery(object):
|
|||
self.conditions.append(self.get_share_condition())
|
||||
|
||||
else:
|
||||
#if has if_owner permission skip user perm check
|
||||
if role_permissions.get("has_if_owner_enabled") and role_permissions.get("if_owner", {}):
|
||||
# skip user perm check if owner constraint is required
|
||||
if requires_owner_constraint(role_permissions):
|
||||
self.match_conditions.append(
|
||||
f"`tab{self.doctype}`.`owner` = {frappe.db.escape(self.user, percent=False)}"
|
||||
)
|
||||
|
|
@ -895,3 +895,22 @@ def get_date_range(operator, value):
|
|||
timespan = period_map[operator] + ' ' + timespan_map[value] if operator != 'timespan' else value
|
||||
|
||||
return get_timespan_date_range(timespan)
|
||||
|
||||
def requires_owner_constraint(role_permissions):
|
||||
"""Returns True if "select" or "read" isn't available without being creator."""
|
||||
|
||||
if not role_permissions.get("has_if_owner_enabled"):
|
||||
return
|
||||
|
||||
if_owner_perms = role_permissions.get("if_owner")
|
||||
if not if_owner_perms:
|
||||
return
|
||||
|
||||
# has select or read without if owner, no need for constraint
|
||||
for perm_type in ("select", "read"):
|
||||
if role_permissions.get(perm_type) and perm_type not in if_owner_perms:
|
||||
return
|
||||
|
||||
# not checking if either select or read if present in if_owner_perms
|
||||
# because either of those is required to perform a query
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -107,13 +107,9 @@ def get_doc_permissions(doc, user=None, ptype=None):
|
|||
meta = frappe.get_meta(doc.doctype)
|
||||
|
||||
def is_user_owner():
|
||||
doc_owner = doc.get('owner') or ''
|
||||
doc_owner = doc_owner.lower()
|
||||
session_user = frappe.session.user.lower()
|
||||
return doc_owner == session_user
|
||||
return (doc.get("owner") or "").lower() == frappe.session.user.lower()
|
||||
|
||||
|
||||
if has_controller_permissions(doc, ptype, user=user) == False :
|
||||
if has_controller_permissions(doc, ptype, user=user) is False:
|
||||
push_perm_check_log('Not allowed via controller permission check')
|
||||
return {ptype: 0}
|
||||
|
||||
|
|
@ -182,22 +178,23 @@ def get_role_permissions(doctype_meta, user=None, is_owner=None):
|
|||
|
||||
applicable_permissions = list(filter(is_perm_applicable, getattr(doctype_meta, 'permissions', [])))
|
||||
has_if_owner_enabled = any(p.get('if_owner', 0) for p in applicable_permissions)
|
||||
|
||||
perms['has_if_owner_enabled'] = has_if_owner_enabled
|
||||
|
||||
for ptype in rights:
|
||||
pvalue = any(p.get(ptype, 0) for p in applicable_permissions)
|
||||
# check if any perm object allows perm type
|
||||
perms[ptype] = cint(pvalue)
|
||||
if (pvalue
|
||||
and has_if_owner_enabled
|
||||
and not has_permission_without_if_owner_enabled(ptype)
|
||||
and ptype != 'create'):
|
||||
if (
|
||||
pvalue
|
||||
and has_if_owner_enabled
|
||||
and not has_permission_without_if_owner_enabled(ptype)
|
||||
and ptype != 'create'
|
||||
):
|
||||
perms['if_owner'][ptype] = cint(pvalue and is_owner)
|
||||
# has no access if not owner
|
||||
# only provide select or read access so that user is able to at-least access list
|
||||
# (and the documents will be filtered based on owner sin further checks)
|
||||
perms[ptype] = 1 if ptype in ['select', 'read'] else 0
|
||||
perms[ptype] = 1 if ptype in ('select', 'read') else 0
|
||||
|
||||
frappe.local.role_permissions[cache_key] = perms
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ frappe.ui.form.PrintView = class {
|
|||
|
||||
add_sidebar_item(df, is_dynamic) {
|
||||
if (df.fieldtype == 'Select') {
|
||||
df.input_class = 'btn btn-default btn-sm';
|
||||
df.input_class = 'btn btn-default btn-sm text-left';
|
||||
}
|
||||
|
||||
let field = frappe.ui.form.make_control({
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@
|
|||
v-if="is_shown"
|
||||
class="flex justify-between build-success-message align-center"
|
||||
>
|
||||
<div class="mr-4">Compiled successfully</div>
|
||||
<a class="text-white underline" href="/" @click.prevent="reload">
|
||||
Compiled successfully
|
||||
<a
|
||||
v-if="!live_reload"
|
||||
class="ml-4 text-white underline" href="/" @click.prevent="reload"
|
||||
>
|
||||
Refresh
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -14,11 +17,17 @@ export default {
|
|||
name: "BuildSuccess",
|
||||
data() {
|
||||
return {
|
||||
is_shown: false
|
||||
is_shown: false,
|
||||
live_reload: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
show(data) {
|
||||
if (data.live_reload) {
|
||||
this.live_reload = true;
|
||||
this.reload();
|
||||
}
|
||||
|
||||
this.is_shown = true;
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ frappe.realtime.on("build_event", data => {
|
|||
}
|
||||
});
|
||||
|
||||
function show_build_success() {
|
||||
function show_build_success(data) {
|
||||
if (error) {
|
||||
error.hide();
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
let target = $('<div class="build-success-container">')
|
||||
.appendTo($container)
|
||||
|
|
@ -27,7 +28,7 @@ function show_build_success() {
|
|||
});
|
||||
success = vm.$children[0];
|
||||
}
|
||||
success.show();
|
||||
success.show(data);
|
||||
}
|
||||
|
||||
function show_build_error(data) {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export default class BulkOperations {
|
|||
args: {
|
||||
doctype: 'Letter Head',
|
||||
fields: ['name', 'is_default'],
|
||||
limit: 0
|
||||
limit_page_length: 0
|
||||
},
|
||||
async: false,
|
||||
callback (r) {
|
||||
|
|
|
|||
|
|
@ -1049,18 +1049,20 @@ Object.assign(frappe.utils, {
|
|||
return duration;
|
||||
},
|
||||
|
||||
seconds_to_duration(value, duration_options) {
|
||||
let secs = value;
|
||||
let total_duration = {
|
||||
days: Math.floor(secs / (3600 * 24)),
|
||||
hours: Math.floor(secs % (3600 * 24) / 3600),
|
||||
minutes: Math.floor(secs % 3600 / 60),
|
||||
seconds: Math.floor(secs % 60)
|
||||
seconds_to_duration(seconds, duration_options) {
|
||||
const round = seconds > 0 ? Math.floor : Math.ceil;
|
||||
const total_duration = {
|
||||
days: round(seconds / 86400), // 60 * 60 * 24
|
||||
hours: round(seconds % 86400 / 3600),
|
||||
minutes: round(seconds % 3600 / 60),
|
||||
seconds: round(seconds % 60)
|
||||
};
|
||||
|
||||
if (duration_options.hide_days) {
|
||||
total_duration.hours = Math.floor(secs / 3600);
|
||||
total_duration.hours = round(seconds / 3600);
|
||||
total_duration.days = 0;
|
||||
}
|
||||
|
||||
return total_duration;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
if (this.report_name !== frappe.get_route()[1]) {
|
||||
// this.toggle_loading(true);
|
||||
// different report
|
||||
this.load_report();
|
||||
}
|
||||
|
|
@ -556,6 +555,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
refresh() {
|
||||
this.toggle_message(true);
|
||||
this.toggle_report(false);
|
||||
this.show_loading_screen();
|
||||
let filters = this.get_filter_values(true);
|
||||
|
||||
// only one refresh at a time
|
||||
|
|
@ -645,6 +645,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
|
||||
this.show_footer_message();
|
||||
frappe.hide_progress();
|
||||
}).finally(() => {
|
||||
this.hide_loading_screen();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -869,6 +871,24 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
}
|
||||
|
||||
show_loading_screen() {
|
||||
const loading_state = `<div class="msg-box no-border">
|
||||
<div>
|
||||
<img src="/assets/frappe/images/ui-states/list-empty-state.svg" alt="Generic Empty State" class="null-state">
|
||||
</div>
|
||||
<p>${__('Loading')}...</p>
|
||||
</div>`;
|
||||
|
||||
this.$loading.find('div').html(loading_state);
|
||||
this.$report.hide();
|
||||
this.$loading.show();
|
||||
}
|
||||
|
||||
hide_loading_screen() {
|
||||
this.$loading.hide();
|
||||
this.$report.show();
|
||||
}
|
||||
|
||||
get_chart_options(data) {
|
||||
let options = this.report_settings.get_chart_data
|
||||
? this.report_settings.get_chart_data(data.columns, data.result)
|
||||
|
|
@ -1679,6 +1699,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
.hide().appendTo(this.page.main);
|
||||
|
||||
this.$chart = $('<div class="chart-wrapper">').hide().appendTo(this.page.main);
|
||||
|
||||
this.$loading = $(this.message_div('')).hide().appendTo(this.page.main);
|
||||
this.$report = $('<div class="report-wrapper">').appendTo(this.page.main);
|
||||
this.$message = $(this.message_div('')).hide().appendTo(this.page.main);
|
||||
}
|
||||
|
|
@ -1738,11 +1760,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
this.refresh();
|
||||
}
|
||||
|
||||
toggle_loading(flag) {
|
||||
this.toggle_message(flag, __('Loading') + '...');
|
||||
}
|
||||
|
||||
|
||||
toggle_nothing_to_show(flag) {
|
||||
let message = this.prepared_report
|
||||
? __('This is a background report. Please set the appropriate filters and then generate a new one.')
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@
|
|||
|
||||
.list-row-head {
|
||||
@extend .list-row;
|
||||
padding: 15px;
|
||||
cursor: default;
|
||||
|
||||
.list-subject {
|
||||
|
|
@ -214,6 +213,10 @@ input.list-check-all, input.list-row-checkbox {
|
|||
--checkbox-right-margin: calc(var(--checkbox-size) / 2 + #{$level-margin-right});
|
||||
}
|
||||
|
||||
input.list-check-all {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.render-list-checkbox {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,8 +192,8 @@ h5.modal-title {
|
|||
}
|
||||
|
||||
.hidden-xs {
|
||||
@extend .d-none;
|
||||
@extend .d-sm-block;
|
||||
@extend .d-block;
|
||||
@extend .d-sm-none;
|
||||
}
|
||||
|
||||
.visible-xs {
|
||||
|
|
@ -216,6 +216,10 @@ h5.modal-title {
|
|||
float: right;
|
||||
}
|
||||
|
||||
.pull-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.image-with-blur {
|
||||
transition: filter 300ms ease-in-out;
|
||||
filter: blur(1.5rem);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ import redis
|
|||
from urllib.parse import unquote
|
||||
from frappe.cache_manager import clear_user_cache
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def clear(user=None):
|
||||
@frappe.whitelist()
|
||||
def clear():
|
||||
frappe.local.session_obj.update(force=True)
|
||||
frappe.local.db.commit()
|
||||
clear_user_cache(frappe.session.user)
|
||||
|
|
|
|||
|
|
@ -493,6 +493,34 @@ class TestPermissions(unittest.TestCase):
|
|||
frappe.set_user("test2@example.com")
|
||||
self.assertRaises(frappe.PermissionError, getdoc, 'Blog Post', doc.name)
|
||||
|
||||
def test_if_owner_permission_on_get_list(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Blog Post",
|
||||
"blog_category": "-test-blog-category",
|
||||
"blogger": "_Test Blogger 1",
|
||||
"title": "_Test If Owner Permissions on Get List",
|
||||
"content": "_Test Blog Post Content"
|
||||
})
|
||||
|
||||
doc.insert(ignore_if_duplicate=True)
|
||||
|
||||
update('Blog Post', 'Blogger', 0, 'if_owner', 1)
|
||||
update('Blog Post', 'Blogger', 0, 'read', 1)
|
||||
user = frappe.get_doc("User", "test2@example.com")
|
||||
user.add_roles("Website Manager")
|
||||
frappe.clear_cache(doctype="Blog Post")
|
||||
|
||||
frappe.set_user("test2@example.com")
|
||||
self.assertIn(doc.name, frappe.get_list("Blog Post", pluck="name"))
|
||||
|
||||
# Become system manager to remove role
|
||||
frappe.set_user("test1@example.com")
|
||||
user.remove_roles("Website Manager")
|
||||
frappe.clear_cache(doctype="Blog Post")
|
||||
|
||||
frappe.set_user("test2@example.com")
|
||||
self.assertNotIn(doc.name, frappe.get_list("Blog Post", pluck="name"))
|
||||
|
||||
def test_if_owner_permission_on_delete(self):
|
||||
update('Blog Post', 'Blogger', 0, 'if_owner', 1)
|
||||
update('Blog Post', 'Blogger', 0, 'read', 1)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import frappe
|
|||
from frappe.utils import set_request
|
||||
from frappe.website.serve import get_response, get_response_content
|
||||
from frappe.website.utils import (build_response, clear_website_cache, get_home_page)
|
||||
from tenacity import retry, stop_after_attempt, retry_if_exception_type
|
||||
|
||||
|
||||
class TestWebsite(unittest.TestCase):
|
||||
|
|
@ -196,6 +197,11 @@ class TestWebsite(unittest.TestCase):
|
|||
delattr(frappe.hooks, 'page_renderer')
|
||||
frappe.cache().delete_key('app_hooks')
|
||||
|
||||
# TODO: Get rid of this retry logic
|
||||
# Added since test is flaky and we can't figure out why at this point
|
||||
@retry(
|
||||
stop=stop_after_attempt(5), retry=retry_if_exception_type(AssertionError),
|
||||
)
|
||||
def test_printview_page(self):
|
||||
content = get_response_content('/Language/en')
|
||||
self.assertIn('<div class="print-format">', content)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<h4 class="reset-password-heading">{{ _("Reset Password") if frappe.db.get_default('company') else _("Set Password")}}</h4>
|
||||
</div>
|
||||
<form id="reset-password">
|
||||
<div class="form-group" style="display: none;">
|
||||
<div class="form-group">
|
||||
<input id="old_password" type="password"
|
||||
class="form-control" placeholder="{{ _("Old Password") }}">
|
||||
</div>
|
||||
|
|
@ -19,6 +19,12 @@
|
|||
class="form-control" placeholder="{{ _("New Password") }}">
|
||||
<span class="password-strength-indicator indicator"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input id="confirm_password" type="password"
|
||||
class="form-control" placeholder="{{ _("Confirm Password") }}">
|
||||
|
||||
<p class="password-mismatch-message text-muted small hidden mt-2"></p>
|
||||
</div>
|
||||
<p class='password-strength-message text-muted small hidden'></p>
|
||||
<button type="submit" id="update"
|
||||
class="btn btn-primary btn-block btn-update">{{_("Confirm")}}</button>
|
||||
|
|
@ -38,7 +44,7 @@
|
|||
<script>
|
||||
|
||||
frappe.ready(function() {
|
||||
if(!frappe.utils.get_url_arg("key")) {
|
||||
if(frappe.utils.get_url_arg("key")) {
|
||||
$("#old_password").parent().toggle();
|
||||
}
|
||||
|
||||
|
|
@ -61,13 +67,19 @@ frappe.ready(function() {
|
|||
new_password: $("#new_password").val(),
|
||||
logout_all_sessions: 1
|
||||
}
|
||||
|
||||
if(!args.old_password && !args.key) {
|
||||
const confirm_password = $('#confirm_password').val()
|
||||
if (!args.old_password && !args.key) {
|
||||
frappe.msgprint(__("Old Password Required."));
|
||||
}
|
||||
if(!args.new_password) {
|
||||
if (!args.new_password) {
|
||||
frappe.msgprint(__("New Password Required."));
|
||||
}
|
||||
if (args.new_password !== confirm_password) {
|
||||
$('.password-mismatch-message').text(__("Passwords do not match"))
|
||||
.removeClass('hidden text-muted').addClass('text-danger');
|
||||
return false
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
type: "POST",
|
||||
method: "frappe.core.doctype.user.user.update_password",
|
||||
|
|
@ -179,9 +191,7 @@ frappe.ready(function() {
|
|||
message.push("{{ _('Success! You are good to go 👍') }}");
|
||||
}
|
||||
}
|
||||
|
||||
strength_message.html(message.join(' ') || '').removeClass('hidden');
|
||||
// strength_indicator.attr('title', message.join(' ') || '');
|
||||
}
|
||||
|
||||
window.clear_timeout = function() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue