Merge branch 'develop' of https://github.com/frappe/frappe into if_owner_per_check_url
This commit is contained in:
commit
a0fa3d0fd5
115 changed files with 2080 additions and 813 deletions
|
|
@ -42,18 +42,33 @@ context('Form', () => {
|
|||
it('validates behaviour of Data options validations in child table', () => {
|
||||
// test email validations for set_invalid controller
|
||||
let website_input = 'website.in';
|
||||
let valid_email = 'user@email.com';
|
||||
let expectBackgroundColor = 'rgb(255, 245, 245)';
|
||||
|
||||
cy.visit('/app/contact/new');
|
||||
cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
|
||||
cy.get('@table').find('button.grid-add-row').click();
|
||||
cy.get('.grid-body .rows [data-fieldname="email_id"]').click();
|
||||
cy.get('@table').find('input.input-with-feedback.form-control').as('email_input');
|
||||
cy.get('@email_input').type(website_input, { waitForAnimations: false });
|
||||
cy.get('@table').find('button.grid-add-row').click();
|
||||
cy.get('@table').find('[data-idx="1"]').as('row1');
|
||||
cy.get('@table').find('[data-idx="2"]').as('row2');
|
||||
cy.get('@row1').click();
|
||||
cy.get('@row1').find('input.input-with-feedback.form-control').as('email_input1');
|
||||
|
||||
cy.get('@email_input1').type(website_input, { waitForAnimations: false });
|
||||
cy.fill_field('company_name', 'Test Company');
|
||||
cy.get('@email_input').should($div => {
|
||||
|
||||
cy.get('@row2').click();
|
||||
cy.get('@row2').find('input.input-with-feedback.form-control').as('email_input2');
|
||||
cy.get('@email_input2').type(valid_email, { waitForAnimations: false });
|
||||
|
||||
cy.get('@row1').click();
|
||||
cy.get('@email_input1').should($div => {
|
||||
const style = window.getComputedStyle($div[0]);
|
||||
expect(style.backgroundColor).to.equal(expectBackgroundColor);
|
||||
});
|
||||
cy.get('@email_input1').should('have.class', 'invalid');
|
||||
|
||||
cy.get('@row2').click();
|
||||
cy.get('@email_input2').should('not.have.class', 'invalid');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ def make_auto_repeat(**args):
|
|||
return doc
|
||||
|
||||
|
||||
def create_submittable_doctype(doctype):
|
||||
def create_submittable_doctype(doctype, submit_perms=1):
|
||||
if frappe.db.exists('DocType', doctype):
|
||||
return
|
||||
else:
|
||||
|
|
@ -217,9 +217,9 @@ def create_submittable_doctype(doctype):
|
|||
'write': 1,
|
||||
'create': 1,
|
||||
'delete': 1,
|
||||
'submit': 1,
|
||||
'cancel': 1,
|
||||
'amend': 1
|
||||
'submit': submit_perms,
|
||||
'cancel': submit_perms,
|
||||
'amend': submit_perms
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
|
|
|
|||
54
frappe/change_log/v13/v13_0_0.md
Normal file
54
frappe/change_log/v13/v13_0_0.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Version 13.0.0 Release Notes
|
||||
|
||||
## Highlights
|
||||
|
||||
- Re-branded UI 💎 ✨🎊 ([#12277](https://github.com/frappe/frappe/pull/12277))
|
||||
- New Page Builder in Web Page ([#10035](https://github.com/frappe/frappe/pull/10035))
|
||||
- Customizable desk ([#9617](https://github.com/frappe/frappe/pull/9617))
|
||||
- Custom Dashboard for DocTypes ([#9872](https://github.com/frappe/frappe/pull/9872))
|
||||
- Widgets to make dashboards ([#9693](https://github.com/frappe/frappe/pull/9693))
|
||||
- Events Streaming ([#8567](https://github.com/frappe/frappe/pull/8567))
|
||||
- Contextual translation and Translation Tool ([#9636](https://github.com/frappe/frappe/pull/9636))
|
||||
|
||||
### Other Features & Enhancements
|
||||
|
||||
- Added permission to grant only `Select` access ([#12063](https://github.com/frappe/frappe/pull/12063))
|
||||
- Add columns and filters for reports via configuration ([#11287](https://github.com/frappe/frappe/pull/11287))
|
||||
- Configurable Navbar logo and dropdowns ([#11213](https://github.com/frappe/frappe/pull/11213))
|
||||
- Rule based naming of documents ([#11439](https://github.com/frappe/frappe/pull/11439))
|
||||
- New routing style, not using hashes, also /desk -> /app ([#11917](https://github.com/frappe/frappe/pull/11917))
|
||||
- Web Page tracking ([#9959](https://github.com/frappe/frappe/pull/9959))
|
||||
- Introduced "Yesterday" and "Tomorrow" options for Timespan filter ([12179](https://github.com/frappe/frappe/pull/12179))
|
||||
- Child table pagination ([#8786](https://github.com/frappe/frappe/pull/8786))
|
||||
- Introduced Duration Control ([#10248](https://github.com/frappe/frappe/pull/10248))
|
||||
- Form Tour feature ([#10287](https://github.com/frappe/frappe/pull/10287))
|
||||
<details>
|
||||
<summary>More</summary>
|
||||
|
||||
- Introduced Map View ([#11202](https://github.com/frappe/frappe/pull/11202))
|
||||
- Custom JS & CSS support in Web Form ([#9121](https://github.com/frappe/frappe/pull/9121)) ([#9610](https://github.com/frappe/frappe/pull/9610))
|
||||
- Ability to attach photo from webcam ([#12160](https://github.com/frappe/frappe/pull/12160))
|
||||
- Added a System Console to help in debugging ([#11306](https://github.com/frappe/frappe/pull/11306))
|
||||
- Introduced System Settings to automatically delete old Prepared Reports ([#9751](https://github.com/frappe/frappe/pull/9751))
|
||||
- "Mandatory Depends On" and "Read Only Depends On" option for document fields ([#8820](https://github.com/frappe/frappe/pull/8820))
|
||||
- Added 2FA for LDAP users ([#10001](https://github.com/frappe/frappe/pull/10001))
|
||||
- Introduced Help Article Feedback system ([#10260](https://github.com/frappe/frappe/pull/10260))
|
||||
- Introduced Razorpay client ([#11418](https://github.com/frappe/frappe/pull/11418))
|
||||
- Rate Limiting ([#10310](https://github.com/frappe/frappe/pull/10310))
|
||||
- Introduced Log Settings ([#11699](https://github.com/frappe/frappe/pull/11699))
|
||||
- Enhancements in notifications ([#11398](https://github.com/frappe/frappe/pull/11398)) ([#11409](https://github.com/frappe/frappe/pull/11409))
|
||||
- Added a field-level permission check for report data ([12163](https://github.com/frappe/frappe/pull/12163))
|
||||
- Ability to cancel all linked document with a single click ([#8905](https://github.com/frappe/frappe/pull/8905))
|
||||
- Made checkboxes navigable via tab key ([#11030](https://github.com/frappe/frappe/pull/11030))
|
||||
- Renamed "Custom Script" to "Client Script" ([#12324](https://github.com/frappe/frappe/pull/12324))
|
||||
|
||||
</details>
|
||||
|
||||
### Performance
|
||||
|
||||
- Faster application load ([#12364](https://github.com/frappe/frappe/pull/12364)) ([#10229](https://github.com/frappe/frappe/pull/10229)) ([#10147](https://github.com/frappe/frappe/pull/10147)) ([#9930](https://github.com/frappe/frappe/pull/9930))
|
||||
- Theme files will now be compressed to make the website load faster ([#11048](https://github.com/frappe/frappe/pull/11048))
|
||||
- Confirmation emails will be sent instantly ([#10790](https://github.com/frappe/frappe/pull/10790))
|
||||
- Faster scheduled job processing ([#9928](https://github.com/frappe/frappe/pull/9928))
|
||||
- Faster data imports ([#12565](https://github.com/frappe/frappe/pull/12565))
|
||||
- Faster CLI commands ([#12447](https://github.com/frappe/frappe/pull/12447))
|
||||
|
|
@ -21,7 +21,7 @@ Requests via FrappeClient are also handled here.
|
|||
|
||||
@frappe.whitelist()
|
||||
def get_list(doctype, fields=None, filters=None, order_by=None,
|
||||
limit_start=None, limit_page_length=20, parent=None):
|
||||
limit_start=None, limit_page_length=20, parent=None, debug=False, as_dict=True):
|
||||
'''Returns a list of records by filters, fields, ordering and limit
|
||||
|
||||
:param doctype: DocType of the data to be queried
|
||||
|
|
@ -40,10 +40,11 @@ def get_list(doctype, fields=None, filters=None, order_by=None,
|
|||
order_by=order_by,
|
||||
limit_start=limit_start,
|
||||
limit_page_length=limit_page_length,
|
||||
debug=debug,
|
||||
as_list=not as_dict
|
||||
)
|
||||
|
||||
validate_args(args)
|
||||
|
||||
return frappe.get_list(**args)
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -103,14 +104,15 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
|
|||
if frappe.get_meta(doctype).issingle:
|
||||
value = frappe.db.get_values_from_single(fields, filters, doctype, as_dict=as_dict, debug=debug)
|
||||
else:
|
||||
value = get_list(doctype, filters=filters, fields=fields, limit_page_length=1)
|
||||
value = get_list(doctype, filters=filters, fields=fields, debug=debug, limit_page_length=1, parent=parent, as_dict=as_dict)
|
||||
|
||||
if as_dict:
|
||||
value = value[0] if value else {}
|
||||
else:
|
||||
value = value[0][fieldname]
|
||||
return value[0] if value else {}
|
||||
|
||||
return value
|
||||
if not value:
|
||||
return
|
||||
|
||||
return value[0] if len(fields) > 1 else value[0][0]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_single_value(doctype, field):
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments):
|
|||
"""Updates `_comments` property in parent Document with given dict.
|
||||
|
||||
:param _comments: Dict of comments."""
|
||||
if not reference_doctype or not reference_name or frappe.db.get_value("DocType", reference_doctype, "issingle"):
|
||||
if not reference_doctype or not reference_name or frappe.db.get_value("DocType", reference_doctype, "issingle") or frappe.db.get_value("DocType", reference_doctype, "is_virtual"):
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@
|
|||
"fieldname": "communication_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Communication Type",
|
||||
"options": "Communication\nComment\nChat\nBot\nNotification\nFeedback",
|
||||
"options": "Communication\nComment\nChat\nBot\nNotification\nFeedback\nAutomated Message",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
|
|
@ -387,7 +387,7 @@
|
|||
"icon": "fa fa-comment",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2019-12-27 14:44:04.880373",
|
||||
"modified": "2021-03-25 09:44:28.963538",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Communication",
|
||||
|
|
@ -426,13 +426,13 @@
|
|||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export":1,
|
||||
"print":1,
|
||||
"read": 1,
|
||||
"role": "Inbox User"
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Inbox User"
|
||||
},
|
||||
{
|
||||
"delete": 1,
|
||||
|
|
@ -450,4 +450,4 @@
|
|||
"title_field": "subject",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import frappe
|
|||
import json
|
||||
from email.utils import formataddr
|
||||
from frappe.core.utils import get_parent_doc
|
||||
from frappe.utils import (get_url, get_formatted_email, cint,
|
||||
validate_email_address, split_emails, parse_addr, get_datetime)
|
||||
from frappe.utils import (get_url, get_formatted_email, cint, list_to_str,
|
||||
validate_email_address, split_emails, parse_addr, get_datetime)
|
||||
from frappe.email.email_body import get_message_id
|
||||
import frappe.email.smtp
|
||||
import time
|
||||
|
|
@ -20,7 +20,8 @@ from frappe.utils.background_jobs import enqueue
|
|||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
|
||||
sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False,
|
||||
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None,
|
||||
flags=None, read_receipt=None, print_letterhead=True, email_template=None):
|
||||
flags=None, read_receipt=None, print_letterhead=True, email_template=None, communication_type=None,
|
||||
ignore_permissions=False):
|
||||
"""Make a new communication.
|
||||
|
||||
:param doctype: Reference DocType.
|
||||
|
|
@ -42,15 +43,17 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report")
|
||||
send_me_a_copy = cint(send_me_a_copy)
|
||||
|
||||
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'):
|
||||
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
|
||||
doctype=doctype, name=name))
|
||||
if not ignore_permissions:
|
||||
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'):
|
||||
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
|
||||
doctype=doctype, name=name))
|
||||
|
||||
if not sender:
|
||||
sender = get_formatted_email(frappe.session.user)
|
||||
|
||||
if isinstance(recipients, list):
|
||||
recipients = ', '.join(recipients)
|
||||
recipients = list_to_str(recipients) if isinstance(recipients, list) else recipients
|
||||
cc = list_to_str(cc) if isinstance(cc, list) else cc
|
||||
bcc = list_to_str(bcc) if isinstance(bcc, list) else bcc
|
||||
|
||||
comm = frappe.get_doc({
|
||||
"doctype":"Communication",
|
||||
|
|
@ -68,7 +71,8 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
"email_template": email_template,
|
||||
"message_id":get_message_id().strip(" <>"),
|
||||
"read_receipt":read_receipt,
|
||||
"has_attachment": 1 if attachments else 0
|
||||
"has_attachment": 1 if attachments else 0,
|
||||
"communication_type": communication_type
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
comm.save(ignore_permissions=True)
|
||||
|
|
|
|||
|
|
@ -1,293 +1,110 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2015-02-04 04:33:36.330477",
|
||||
"custom": 0,
|
||||
"description": "Internal record of document shares",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"user",
|
||||
"share_doctype",
|
||||
"share_name",
|
||||
"read",
|
||||
"write",
|
||||
"share",
|
||||
"submit",
|
||||
"everyone",
|
||||
"notify_by_email"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "share_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "share_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "share_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "read",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Read",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Read"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "write",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Write",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Write"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "share",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Share",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Share"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "everyone",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Everyone",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Everyone"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "notify_by_email",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Notify by email",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Submit"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-15 15:58:34.126438",
|
||||
"links": [],
|
||||
"modified": "2021-04-04 11:38:50.813312",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocShare",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 0,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 1,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import get_fullname
|
||||
from frappe.utils import get_fullname, cint
|
||||
|
||||
exclude_from_linked_with = True
|
||||
|
||||
|
|
@ -15,12 +15,15 @@ class DocShare(Document):
|
|||
def validate(self):
|
||||
self.validate_user()
|
||||
self.check_share_permission()
|
||||
self.check_is_submittable()
|
||||
self.cascade_permissions_downwards()
|
||||
self.get_doc().run_method("validate_share", self)
|
||||
|
||||
def cascade_permissions_downwards(self):
|
||||
if self.share or self.write:
|
||||
if self.share or self.write or self.submit:
|
||||
self.read = 1
|
||||
if self.submit:
|
||||
self.write = 1
|
||||
|
||||
def get_doc(self):
|
||||
if not getattr(self, "_doc", None):
|
||||
|
|
@ -39,6 +42,11 @@ class DocShare(Document):
|
|||
|
||||
frappe.throw(_('You need to have "Share" permission'), frappe.PermissionError)
|
||||
|
||||
def check_is_submittable(self):
|
||||
if self.submit and not cint(frappe.db.get_value("DocType", self.share_doctype, "is_submittable")):
|
||||
frappe.throw(_("Cannot share {0} with submit permission as the doctype {1} is not submittable").format(
|
||||
frappe.bold(self.share_name), frappe.bold(self.share_doctype)))
|
||||
|
||||
def after_insert(self):
|
||||
doc = self.get_doc()
|
||||
owner = get_fullname(self.owner)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
import frappe.share
|
||||
import unittest
|
||||
from frappe.automation.doctype.auto_repeat.test_auto_repeat import create_submittable_doctype
|
||||
|
||||
class TestDocShare(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
|
@ -91,3 +92,24 @@ class TestDocShare(unittest.TestCase):
|
|||
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", self.user))
|
||||
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", "test1@example.com"))
|
||||
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", "Guest"))
|
||||
|
||||
def test_share_with_submit_perm(self):
|
||||
doctype = "Test DocShare with Submit"
|
||||
create_submittable_doctype(doctype, submit_perms=0)
|
||||
|
||||
submittable_doc = frappe.get_doc(dict(doctype=doctype, test="test docshare with submit")).insert()
|
||||
|
||||
frappe.set_user(self.user)
|
||||
self.assertFalse(frappe.has_permission(doctype, "submit", user=self.user))
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.share.add(doctype, submittable_doc.name, self.user, submit=1)
|
||||
|
||||
frappe.set_user(self.user)
|
||||
self.assertTrue(frappe.has_permission(doctype, "submit", doc=submittable_doc.name, user=self.user))
|
||||
|
||||
# test cascade
|
||||
self.assertTrue(frappe.has_permission(doctype, "read", doc=submittable_doc.name, user=self.user))
|
||||
self.assertTrue(frappe.has_permission(doctype, "write", doc=submittable_doc.name, user=self.user))
|
||||
|
||||
frappe.share.remove(doctype, submittable_doc.name, self.user)
|
||||
|
|
@ -7,4 +7,4 @@ from __future__ import unicode_literals
|
|||
{base_class_import}
|
||||
|
||||
class {classname}({base_class}):
|
||||
pass
|
||||
{custom_controller}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,21 @@
|
|||
|
||||
frappe.ui.form.on('DocType', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query('role', 'permissions', function(doc) {
|
||||
if (doc.custom && frappe.session.user != 'Administrator') {
|
||||
return {
|
||||
query: "frappe.core.doctype.role.role.role_query",
|
||||
filters: [['Role', 'name', '!=', 'All']]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if(frappe.session.user !== "Administrator" || !frappe.boot.developer_mode) {
|
||||
if(frm.is_new()) {
|
||||
frm.set_value("custom", 1);
|
||||
}
|
||||
frm.toggle_enable("custom", 0);
|
||||
frm.toggle_enable("is_virtual", 0);
|
||||
frm.toggle_enable("beta", 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"track_views",
|
||||
"custom",
|
||||
"beta",
|
||||
"is_virtual",
|
||||
"fields_section_break",
|
||||
"fields",
|
||||
"sb1",
|
||||
|
|
@ -528,6 +529,12 @@
|
|||
"fieldname": "index_web_pages_for_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "Index Web Pages for Search"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_virtual",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Virtual"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bolt",
|
||||
|
|
@ -609,7 +616,7 @@
|
|||
"link_fieldname": "reference_doctype"
|
||||
}
|
||||
],
|
||||
"modified": "2021-02-04 15:10:09.227205",
|
||||
"modified": "2021-02-17 20:18:06.212232",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ class DocType(Document):
|
|||
if not frappe.conf.get("developer_mode") and not self.custom:
|
||||
frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError)
|
||||
|
||||
if self.is_virtual and self.custom:
|
||||
frappe.throw(_("Not allowed to create custom Virtual DocType."), CannotCreateStandardDoctypeError)
|
||||
|
||||
|
||||
if frappe.conf.get('developer_mode'):
|
||||
self.owner = 'Administrator'
|
||||
self.modified_by = 'Administrator'
|
||||
|
|
@ -1112,6 +1116,21 @@ def validate_permissions(doctype, for_remove=False, alert=False):
|
|||
if d.get("import") and not isimportable:
|
||||
frappe.throw(_("{0}: Cannot set import as {1} is not importable").format(get_txt(d), doctype))
|
||||
|
||||
def validate_permission_for_all_role(d):
|
||||
if frappe.session.user == 'Administrator':
|
||||
return
|
||||
|
||||
if doctype.custom:
|
||||
if d.role == 'All':
|
||||
frappe.throw(_('Row # {0}: Non administrator user can not set the role {1} to the custom doctype')
|
||||
.format(d.idx, frappe.bold(_('All'))), title=_('Permissions Error'))
|
||||
|
||||
roles = [row.name for row in frappe.get_all('Role', filters={'is_custom': 1})]
|
||||
|
||||
if d.role in roles:
|
||||
frappe.throw(_('Row # {0}: Non administrator user can not set the role {1} to the custom doctype')
|
||||
.format(d.idx, frappe.bold(_(d.role))), title=_('Permissions Error'))
|
||||
|
||||
for d in permissions:
|
||||
if not d.permlevel:
|
||||
d.permlevel=0
|
||||
|
|
@ -1123,6 +1142,7 @@ def validate_permissions(doctype, for_remove=False, alert=False):
|
|||
check_if_importable(d)
|
||||
check_level_zero_is_set(d)
|
||||
remove_rights_for_single(d)
|
||||
validate_permission_for_all_role(d)
|
||||
|
||||
def make_module_and_roles(doc, perm_fieldname="permissions"):
|
||||
"""Make `Module Def` and `Role` records if already not made. Called while installing."""
|
||||
|
|
|
|||
|
|
@ -480,8 +480,19 @@ class TestDocType(unittest.TestCase):
|
|||
'link_doctype': "User",
|
||||
'link_fieldname': "a_field_that_does_not_exists"
|
||||
})
|
||||
|
||||
self.assertRaises(InvalidFieldNameError, validate_links_table_fieldnames, doc)
|
||||
|
||||
def test_create_virtual_doctype(self):
|
||||
"""Test virtual DOcTYpe."""
|
||||
virtual_doc = new_doctype('Test Virtual Doctype')
|
||||
virtual_doc.is_virtual = 1
|
||||
virtual_doc.insert()
|
||||
virtual_doc.save()
|
||||
doc = frappe.get_doc("DocType", "Test Virtual Doctype")
|
||||
|
||||
self.assertEqual(doc.is_virtual, 1)
|
||||
self.assertFalse(frappe.db.table_exists('Test Virtual Doctype'))
|
||||
|
||||
def new_doctype(name, unique=0, depends_on='', fields=None):
|
||||
doc = frappe.get_doc({
|
||||
|
|
|
|||
|
|
@ -970,12 +970,22 @@ def get_files_in_folder(folder, start=0, page_length=20):
|
|||
start = cint(start)
|
||||
page_length = cint(page_length)
|
||||
|
||||
files = frappe.db.get_all('File',
|
||||
attachment_folder = frappe.db.get_value('File',
|
||||
'Home/Attachments',
|
||||
['name', 'file_name', 'file_url', 'is_folder', 'modified'],
|
||||
as_dict=1
|
||||
)
|
||||
|
||||
files = frappe.db.get_list('File',
|
||||
{ 'folder': folder },
|
||||
['name', 'file_name', 'file_url', 'is_folder', 'modified'],
|
||||
start=start,
|
||||
page_length=page_length + 1
|
||||
)
|
||||
|
||||
if folder == 'Home' and attachment_folder not in files:
|
||||
files.insert(0, attachment_folder)
|
||||
|
||||
return {
|
||||
'files': files[:page_length],
|
||||
'has_more': len(files) > page_length
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import frappe
|
|||
import os
|
||||
import unittest
|
||||
from frappe import _
|
||||
from frappe.core.doctype.file.file import move_file
|
||||
from frappe.core.doctype.file.file import move_file, get_files_in_folder
|
||||
from frappe.utils import get_files_path
|
||||
# test_records = frappe.get_test_records('File')
|
||||
|
||||
|
|
@ -412,3 +412,61 @@ class TestAttachment(unittest.TestCase):
|
|||
})
|
||||
|
||||
self.assertTrue(exists)
|
||||
|
||||
|
||||
class TestAttachmentsAccess(unittest.TestCase):
|
||||
|
||||
def test_attachments_access(self):
|
||||
|
||||
frappe.set_user('test4@example.com')
|
||||
self.attached_to_doctype, self.attached_to_docname = make_test_doc()
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_name": 'test_user.txt',
|
||||
"attached_to_doctype": self.attached_to_doctype,
|
||||
"attached_to_name": self.attached_to_docname,
|
||||
"content": 'Testing User'
|
||||
}).insert()
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_name": "test_user_home.txt",
|
||||
"content": 'User Home',
|
||||
}).insert()
|
||||
|
||||
frappe.set_user('test@example.com')
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_name": 'test_system_manager.txt',
|
||||
"attached_to_doctype": self.attached_to_doctype,
|
||||
"attached_to_name": self.attached_to_docname,
|
||||
"content": 'Testing System Manager'
|
||||
}).insert()
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_name": "test_sm_home.txt",
|
||||
"content": 'System Manager Home',
|
||||
}).insert()
|
||||
|
||||
system_manager_files = [file.file_name for file in get_files_in_folder('Home')['files']]
|
||||
system_manager_attachments_files = [file.file_name for file in get_files_in_folder('Home/Attachments')['files']]
|
||||
|
||||
frappe.set_user('test4@example.com')
|
||||
user_files = [file.file_name for file in get_files_in_folder('Home')['files']]
|
||||
user_attachments_files = [file.file_name for file in get_files_in_folder('Home/Attachments')['files']]
|
||||
|
||||
self.assertIn('test_sm_home.txt', system_manager_files)
|
||||
self.assertNotIn('test_sm_home.txt', user_files)
|
||||
self.assertIn('test_user_home.txt', system_manager_files)
|
||||
self.assertIn('test_user_home.txt', user_files)
|
||||
|
||||
self.assertIn('test_system_manager.txt', system_manager_attachments_files)
|
||||
self.assertNotIn('test_system_manager.txt', user_attachments_files)
|
||||
self.assertIn('test_user.txt', system_manager_attachments_files)
|
||||
self.assertIn('test_user.txt', user_attachments_files)
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
frappe.db.rollback()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ frappe.ui.form.on('Report', {
|
|||
}
|
||||
}, "fa fa-table");
|
||||
|
||||
if (doc.is_standard === "Yes") {
|
||||
if (doc.is_standard === "Yes" && frm.perm[0].write) {
|
||||
frm.add_custom_button(doc.disabled ? __("Enable Report") : __("Disable Report"), function() {
|
||||
frm.call('toggle_disable', {
|
||||
disable: doc.disabled ? 0 : 1
|
||||
|
|
|
|||
|
|
@ -307,6 +307,9 @@ class Report(Document):
|
|||
|
||||
@frappe.whitelist()
|
||||
def toggle_disable(self, disable):
|
||||
if not self.has_permission('write'):
|
||||
frappe.throw(_("You are not allowed to edit the report."))
|
||||
|
||||
self.db_set("disabled", cint(disable))
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -201,3 +201,27 @@ result = [
|
|||
|
||||
# check values
|
||||
self.assertTrue('System User' in [d.get('type') for d in data[1]])
|
||||
|
||||
def test_toggle_disabled(self):
|
||||
"""Make sure that authorization is respected.
|
||||
"""
|
||||
# Assuming that there will be reports in the system.
|
||||
reports = frappe.get_all(doctype='Report', limit=1)
|
||||
report_name = reports[0]['name']
|
||||
doc = frappe.get_doc('Report', report_name)
|
||||
status = doc.disabled
|
||||
|
||||
# User has write permission on reports and should pass through
|
||||
frappe.set_user('test@example.com')
|
||||
doc.toggle_disable(not status)
|
||||
doc.reload()
|
||||
self.assertNotEqual(status, doc.disabled)
|
||||
|
||||
# User has no write permission on reports, permission error is expected.
|
||||
frappe.set_user('test1@example.com')
|
||||
doc = frappe.get_doc('Report', report_name)
|
||||
with self.assertRaises(frappe.exceptions.ValidationError):
|
||||
doc.toggle_disable(1)
|
||||
|
||||
# Set user back to administrator
|
||||
frappe.set_user('Administrator')
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
frappe.ui.form.on('Role', {
|
||||
refresh: function(frm) {
|
||||
frm.set_df_property('is_custom', 'read_only', frappe.session.user !== 'Administrator');
|
||||
|
||||
frm.add_custom_button("Role Permissions Manager", function() {
|
||||
frappe.route_options = {"role": frm.doc.name};
|
||||
frappe.set_route("permission-manager");
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@
|
|||
"form_settings_section",
|
||||
"form_sidebar",
|
||||
"timeline",
|
||||
"dashboard"
|
||||
"dashboard",
|
||||
"is_custom"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -141,13 +142,20 @@
|
|||
"fieldname": "notifications",
|
||||
"fieldtype": "Check",
|
||||
"label": "Notifications"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_custom",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Custom"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bookmark",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-03 14:08:38.181035",
|
||||
"modified": "2021-01-27 10:35:37.638350",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Role",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Role(Document):
|
|||
# set if desk_access is not allowed, unset all desk properties
|
||||
if self.name == 'Guest':
|
||||
self.desk_access = 0
|
||||
|
||||
|
||||
if not self.desk_access:
|
||||
for key in desk_properties:
|
||||
self.set(key, 0)
|
||||
|
|
@ -53,7 +53,6 @@ class Role(Document):
|
|||
if user_type != user.user_type:
|
||||
user.save()
|
||||
|
||||
|
||||
def get_info_based_on_role(role, field='email'):
|
||||
''' Get information of all users that have been assigned this role '''
|
||||
users = frappe.get_list("Has Role", filters={"role": role, "parenttype": "User"},
|
||||
|
|
@ -73,3 +72,15 @@ def get_user_info(users, field='email'):
|
|||
def get_users(role):
|
||||
return [d.parent for d in frappe.get_all("Has Role", filters={"role": role, "parenttype": "User"},
|
||||
fields=["parent"])]
|
||||
|
||||
|
||||
# searches for active employees
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def role_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
report_filters = [['Role', 'name', 'like', '%{}%'.format(txt)], ['Role', 'is_custom', '=', 0]]
|
||||
if filters and isinstance(filters, list):
|
||||
report_filters.extend(filters)
|
||||
|
||||
return frappe.get_all('Role', limit_start=start, limit_page_length=page_len,
|
||||
filters=report_filters, as_list=1)
|
||||
0
frappe/core/doctype/test/__init__.py
Normal file
0
frappe/core/doctype/test/__init__.py
Normal file
8
frappe/core/doctype/test/test.js
Normal file
8
frappe/core/doctype/test/test.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2021, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('test', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
42
frappe/core/doctype/test/test.json
Normal file
42
frappe/core/doctype/test/test.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2021-03-31 10:06:57.919697",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"test"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "test",
|
||||
"fieldtype": "Data",
|
||||
"label": "Test"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_virtual": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-31 10:06:57.919697",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "test",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
35
frappe/core/doctype/test/test.py
Normal file
35
frappe/core/doctype/test/test.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
import json
|
||||
|
||||
|
||||
class test(Document):
|
||||
|
||||
def db_insert(self):
|
||||
d = self.get_valid_dict(convert_dates_to_str=True)
|
||||
with open("data_file.json", "w+") as read_file:
|
||||
json.dump(d, read_file)
|
||||
|
||||
def load_from_db(self):
|
||||
with open("data_file.json", "r") as read_file:
|
||||
d = json.load(read_file)
|
||||
super(Document, self).__init__(d)
|
||||
|
||||
def db_update(self):
|
||||
d = self.get_valid_dict(convert_dates_to_str=True)
|
||||
with open("data_file.json", "w+") as read_file:
|
||||
json.dump(d, read_file)
|
||||
|
||||
def get_list(self, args):
|
||||
with open("data_file.json", "r") as read_file:
|
||||
return [json.load(read_file)]
|
||||
|
||||
def get_value(self, fields, filters, **kwargs):
|
||||
# return []
|
||||
with open("data_file.json", "r") as read_file:
|
||||
return [json.load(read_file)]
|
||||
10
frappe/core/doctype/test/test_test.py
Normal file
10
frappe/core/doctype/test/test_test.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class Testtest(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -38,6 +38,13 @@
|
|||
"new_password": "Eastern_43A1W",
|
||||
"enabled": 1
|
||||
},
|
||||
{
|
||||
"doctype": "User",
|
||||
"email": "test4@example.com",
|
||||
"first_name": "_Test4",
|
||||
"new_password": "Eastern_43A1W",
|
||||
"enabled": 1
|
||||
},
|
||||
{
|
||||
"doctype": "User",
|
||||
"email": "testperm@example.com",
|
||||
|
|
|
|||
|
|
@ -247,29 +247,31 @@ class TestUser(unittest.TestCase):
|
|||
self.assertEqual(res1.status_code, 200)
|
||||
self.assertEqual(res2.status_code, 417)
|
||||
|
||||
def test_user_rollback(self):
|
||||
""" """
|
||||
frappe.db.commit()
|
||||
frappe.db.begin()
|
||||
user_id = str(uuid.uuid4())
|
||||
email = f'{user_id}@example.com'
|
||||
try:
|
||||
frappe.flags.in_import = True # disable throttling
|
||||
frappe.get_doc(dict(
|
||||
doctype='User',
|
||||
email=email,
|
||||
first_name=user_id,
|
||||
)).insert()
|
||||
finally:
|
||||
frappe.flags.in_import = False
|
||||
# def test_user_rollback(self):
|
||||
# """
|
||||
# FIXME: This is failing with PR #12693 as Rollback can't happen if notifications sent on user creation.
|
||||
# Make sure that notifications disabled.
|
||||
# """
|
||||
# frappe.db.commit()
|
||||
# frappe.db.begin()
|
||||
# user_id = str(uuid.uuid4())
|
||||
# email = f'{user_id}@example.com'
|
||||
# try:
|
||||
# frappe.flags.in_import = True # disable throttling
|
||||
# frappe.get_doc(dict(
|
||||
# doctype='User',
|
||||
# email=email,
|
||||
# first_name=user_id,
|
||||
# )).insert()
|
||||
# finally:
|
||||
# frappe.flags.in_import = False
|
||||
|
||||
# Check user has been added
|
||||
self.assertIsNotNone(frappe.db.get("User", {"email": email}))
|
||||
|
||||
# Check that rollback works
|
||||
frappe.db.rollback()
|
||||
self.assertIsNone(frappe.db.get("User", {"email": email}))
|
||||
# # Check user has been added
|
||||
# self.assertIsNotNone(frappe.db.get("User", {"email": email}))
|
||||
|
||||
# # Check that rollback works
|
||||
# frappe.db.rollback()
|
||||
# self.assertIsNone(frappe.db.get("User", {"email": email}))
|
||||
|
||||
def delete_contact(user):
|
||||
frappe.db.sql("DELETE FROM `tabContact` WHERE `email_id`= %s", user)
|
||||
|
|
|
|||
|
|
@ -59,15 +59,18 @@ frappe.ui.form.on('User', {
|
|||
onload: function(frm) {
|
||||
frm.can_edit_roles = has_access_to_edit_user();
|
||||
|
||||
if (frm.can_edit_roles && !frm.is_new()) {
|
||||
if (frm.can_edit_roles && !frm.is_new() && in_list(['System User', 'Website User'], frm.doc.user_type)) {
|
||||
if (!frm.roles_editor) {
|
||||
const role_area = $('<div class="role-editor">')
|
||||
.appendTo(frm.fields_dict.roles_html.wrapper);
|
||||
|
||||
frm.roles_editor = new frappe.RoleEditor(role_area, frm, frm.doc.role_profile_name ? 1 : 0);
|
||||
|
||||
var module_area = $('<div>')
|
||||
.appendTo(frm.fields_dict.modules_html.wrapper);
|
||||
frm.module_editor = new frappe.ModuleEditor(frm, module_area);
|
||||
if (frm.doc.user_type == 'System User') {
|
||||
var module_area = $('<div>')
|
||||
.appendTo(frm.fields_dict.modules_html.wrapper);
|
||||
frm.module_editor = new frappe.ModuleEditor(frm, module_area);
|
||||
}
|
||||
} else {
|
||||
frm.roles_editor.show();
|
||||
}
|
||||
|
|
@ -75,7 +78,8 @@ frappe.ui.form.on('User', {
|
|||
},
|
||||
refresh: function(frm) {
|
||||
var doc = frm.doc;
|
||||
if(!frm.is_new() && !frm.roles_editor && frm.can_edit_roles) {
|
||||
if (in_list(['System User', 'Website User'], frm.doc.user_type)
|
||||
&& !frm.is_new() && !frm.roles_editor && frm.can_edit_roles) {
|
||||
frm.reload_doc();
|
||||
return;
|
||||
}
|
||||
|
|
@ -250,15 +254,15 @@ frappe.ui.form.on('User', {
|
|||
}
|
||||
});
|
||||
},
|
||||
generate_keys: function(frm){
|
||||
generate_keys: function(frm) {
|
||||
frappe.call({
|
||||
method: 'frappe.core.doctype.user.user.generate_keys',
|
||||
args: {
|
||||
user: frm.doc.name
|
||||
},
|
||||
callback: function(r){
|
||||
if(r.message){
|
||||
frappe.msgprint(__("Save API Secret: ") + r.message.api_secret);
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frappe.msgprint(__("Save API Secret: {0}", [r.message.api_secret]));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@
|
|||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "enabled",
|
||||
"depends_on": "eval:in_list(['System User', 'Website User'], doc.user_type) && doc.enabled == 1",
|
||||
"fieldname": "sb1",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Roles",
|
||||
|
|
@ -391,6 +391,7 @@
|
|||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "eval:in_list(['System User'], doc.user_type)",
|
||||
"fieldname": "sb_allow_modules",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Allow Modules",
|
||||
|
|
@ -453,18 +454,18 @@
|
|||
"label": "Simultaneous Sessions"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"default": "System User",
|
||||
"description": "If the user has any role checked, then the user becomes a \"System User\". \"System User\" has access to the desktop",
|
||||
"fieldname": "user_type",
|
||||
"fieldtype": "Select",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "User Type",
|
||||
"oldfieldname": "user_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "System User\nWebsite User",
|
||||
"permlevel": 1,
|
||||
"read_only": 1
|
||||
"options": "User Type",
|
||||
"permlevel": 1
|
||||
},
|
||||
{
|
||||
"description": "Allow user to login only after this hour (0-24)",
|
||||
|
|
@ -669,7 +670,7 @@
|
|||
}
|
||||
],
|
||||
"max_attachments": 5,
|
||||
"modified": "2021-02-01 16:11:06.037543",
|
||||
"modified": "2021-02-02 16:11:06.037543",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import frappe.share
|
|||
import frappe.defaults
|
||||
import frappe.permissions
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, flt, has_gravatar, escape_html, format_datetime, now_datetime, get_formatted_email, today
|
||||
from frappe.utils import (cint, flt, has_gravatar, escape_html, format_datetime,
|
||||
now_datetime, get_formatted_email, today)
|
||||
from frappe import throw, msgprint, _
|
||||
from frappe.utils.password import update_password as _update_password, check_password, get_password_reset_limit
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
|
|
@ -19,6 +20,7 @@ from frappe.utils.user import get_system_managers
|
|||
from frappe.website.utils import is_signup_enabled
|
||||
from frappe.rate_limiter import rate_limit
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.core.doctype.user_type.user_type import user_linked_with_permission_on_doctype
|
||||
|
||||
|
||||
STANDARD_USERS = ("Guest", "Administrator")
|
||||
|
|
@ -186,11 +188,36 @@ class User(Document):
|
|||
_update_password(user=self.name, pwd=new_password, logout_all_sessions=self.logout_all_sessions)
|
||||
|
||||
def set_system_user(self):
|
||||
'''Set as System User if any of the given roles has desk_access'''
|
||||
if self.has_desk_access() or self.name == 'Administrator':
|
||||
self.user_type = 'System User'
|
||||
'''For the standard users like admin and guest, the user type is fixed.'''
|
||||
user_type_mapper = {
|
||||
'Administrator': 'System User',
|
||||
'Guest': 'Website User'
|
||||
}
|
||||
|
||||
if self.user_type and not frappe.get_cached_value('User Type', self.user_type, 'is_standard'):
|
||||
if user_type_mapper.get(self.name):
|
||||
self.user_type = user_type_mapper.get(self.name)
|
||||
else:
|
||||
self.set_roles_and_modules_based_on_user_type()
|
||||
else:
|
||||
self.user_type = 'Website User'
|
||||
'''Set as System User if any of the given roles has desk_access'''
|
||||
self.user_type = 'System User' if self.has_desk_access() else 'Website User'
|
||||
|
||||
def set_roles_and_modules_based_on_user_type(self):
|
||||
user_type_doc = frappe.get_cached_doc('User Type', self.user_type)
|
||||
if user_type_doc.role:
|
||||
self.roles = []
|
||||
|
||||
# Check whether User has linked with the 'Apply User Permission On' doctype or not
|
||||
if user_linked_with_permission_on_doctype(user_type_doc, self.name):
|
||||
self.append('roles', {
|
||||
'role': user_type_doc.role
|
||||
})
|
||||
|
||||
frappe.msgprint(_('Role has been set as per the user type {0}')
|
||||
.format(self.user_type), alert=True)
|
||||
|
||||
user_type_doc.update_modules_in_user(self)
|
||||
|
||||
def has_desk_access(self):
|
||||
'''Return true if any of the set roles has desk access'''
|
||||
|
|
@ -877,7 +904,8 @@ def reset_password(user):
|
|||
def user_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
||||
conditions=[]
|
||||
user_type_condition = "and user_type = 'System User'"
|
||||
|
||||
user_type_condition = "and user_type != 'Website User'"
|
||||
if filters and filters.get('ignore_user_type'):
|
||||
user_type_condition = ''
|
||||
filters.pop('ignore_user_type')
|
||||
|
|
|
|||
0
frappe/core/doctype/user_document_type/__init__.py
Normal file
0
frappe/core/doctype/user_document_type/__init__.py
Normal file
109
frappe/core/doctype/user_document_type/user_document_type.json
Normal file
109
frappe/core/doctype/user_document_type/user_document_type.json
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2021-01-13 01:51:40.158521",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"column_break_2",
|
||||
"is_custom",
|
||||
"permissions_section",
|
||||
"read",
|
||||
"write",
|
||||
"create",
|
||||
"column_break_8",
|
||||
"submit",
|
||||
"cancel",
|
||||
"amend",
|
||||
"delete"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "permissions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Role Permissions"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "read",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Read"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "write",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Write"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "create",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Create"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "document_type.custom",
|
||||
"fieldname": "is_custom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Custom",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Submit"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "cancel",
|
||||
"fieldtype": "Check",
|
||||
"label": "Cancel"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "amend",
|
||||
"fieldtype": "Check",
|
||||
"label": "Amend"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "delete",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-16 00:32:24.414313",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User Document Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/user_document_type/user_document_type.py
Normal file
10
frappe/core/doctype/user_document_type/user_document_type.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class UserDocumentType(Document):
|
||||
pass
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2021-01-17 18:28:14.208576",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-17 18:45:44.993190",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User Select Document Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class UserSelectDocumentType(Document):
|
||||
pass
|
||||
0
frappe/core/doctype/user_type/__init__.py
Normal file
0
frappe/core/doctype/user_type/__init__.py
Normal file
10
frappe/core/doctype/user_type/test_user_type.py
Normal file
10
frappe/core/doctype/user_type/test_user_type.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestUserType(unittest.TestCase):
|
||||
pass
|
||||
77
frappe/core/doctype/user_type/user_type.js
Normal file
77
frappe/core/doctype/user_type/user_type.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2021, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('User Type', {
|
||||
refresh: function(frm) {
|
||||
frm.toggle_display('is_standard', frappe.boot.developer_mode);
|
||||
frm.set_df_property('is_standard', 'read_only', !frappe.boot.developer_mode);
|
||||
|
||||
const fields = ['role', 'apply_user_permission_on', 'user_id_field',
|
||||
'user_doctypes', 'user_type_modules'];
|
||||
|
||||
frm.toggle_display(fields, !frm.doc.is_standard);
|
||||
|
||||
frm.set_query('document_type', 'user_doctypes', function() {
|
||||
return {
|
||||
filters: {
|
||||
istable: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('document_type', 'select_doctypes', function() {
|
||||
return {
|
||||
filters: {
|
||||
istable: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('document_type', 'custom_select_doctypes', function() {
|
||||
return {
|
||||
filters: {
|
||||
istable: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('role', function() {
|
||||
return {
|
||||
filters: {
|
||||
is_custom: 1,
|
||||
disabled: 0,
|
||||
desk_access: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('apply_user_permission_on', function() {
|
||||
return {
|
||||
query: "frappe.core.doctype.user_type.user_type.get_user_linked_doctypes"
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.trigger('get_user_id_fields');
|
||||
},
|
||||
|
||||
apply_user_permission_on: function(frm) {
|
||||
frm.set_value('user_id_field', '');
|
||||
frm.trigger('get_user_id_fields');
|
||||
},
|
||||
|
||||
get_user_id_fields: function(frm) {
|
||||
if (frm.doc.apply_user_permission_on) {
|
||||
frappe.call({
|
||||
method: 'frappe.core.doctype.user_type.user_type.get_user_id',
|
||||
args: {
|
||||
parent: frm.doc.apply_user_permission_on
|
||||
},
|
||||
callback: function(r) {
|
||||
set_field_options('user_id_field', [""].concat(r.message));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
141
frappe/core/doctype/user_type/user_type.json
Normal file
141
frappe/core/doctype/user_type/user_type.json
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "Prompt",
|
||||
"creation": "2021-01-13 01:48:02.378548",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"is_standard",
|
||||
"section_break_2",
|
||||
"role",
|
||||
"column_break_4",
|
||||
"apply_user_permission_on",
|
||||
"user_id_field",
|
||||
"section_break_6",
|
||||
"user_doctypes",
|
||||
"custom_select_doctypes",
|
||||
"select_doctypes",
|
||||
"allowed_modules_section",
|
||||
"user_type_modules"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !doc.is_standard",
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Document Types and Permissions"
|
||||
},
|
||||
{
|
||||
"fieldname": "user_doctypes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Document Types",
|
||||
"mandatory_depends_on": "eval: !doc.is_standard",
|
||||
"options": "User Document Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "role",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Role",
|
||||
"mandatory_depends_on": "eval: !doc.is_standard",
|
||||
"options": "Role",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "select_doctypes",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 1,
|
||||
"label": "Document Types (Select Permissions Only)",
|
||||
"options": "User Select Document Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Can only list down the document types which has been linked to the User document type.",
|
||||
"fieldname": "apply_user_permission_on",
|
||||
"fieldtype": "Link",
|
||||
"label": "Apply User Permission On",
|
||||
"mandatory_depends_on": "eval: !doc.is_standard",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !doc.is_standard",
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "apply_user_permission_on",
|
||||
"fieldname": "user_id_field",
|
||||
"fieldtype": "Select",
|
||||
"label": "User Id Field",
|
||||
"mandatory_depends_on": "eval: !doc.is_standard",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !doc.is_standard",
|
||||
"fieldname": "allowed_modules_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Allowed Modules"
|
||||
},
|
||||
{
|
||||
"fieldname": "user_type_modules",
|
||||
"fieldtype": "Table",
|
||||
"no_copy": 1,
|
||||
"options": "User Type Module",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_select_doctypes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Custom Document Types (Select Permission)",
|
||||
"options": "User Select Document Type"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-12 16:25:18.639050",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
271
frappe/core/doctype/user_type/user_type.py
Normal file
271
frappe/core/doctype/user_type/user_type.py
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from six import iteritems
|
||||
from frappe.utils import get_link_to_form
|
||||
from frappe.config import get_modules_from_app
|
||||
from frappe.permissions import add_permission, add_user_permission
|
||||
from frappe.model.document import Document
|
||||
|
||||
class UserType(Document):
|
||||
def validate(self):
|
||||
self.set_modules()
|
||||
self.add_select_perm_doctypes()
|
||||
|
||||
def on_update(self):
|
||||
if self.is_standard:
|
||||
return
|
||||
|
||||
self.validate_document_type_limit()
|
||||
self.validate_role()
|
||||
self.add_role_permissions_for_user_doctypes()
|
||||
self.add_role_permissions_for_select_doctypes()
|
||||
self.add_role_permissions_for_file()
|
||||
self.update_users()
|
||||
get_non_standard_user_type_details()
|
||||
self.remove_permission_for_deleted_doctypes()
|
||||
|
||||
def on_trash(self):
|
||||
if self.is_standard:
|
||||
frappe.throw(_('Standard user type {0} can not be deleted.')
|
||||
.format(frappe.bold(self.name)))
|
||||
|
||||
def set_modules(self):
|
||||
if not self.user_doctypes:
|
||||
return
|
||||
|
||||
modules = frappe.get_all('DocType', fields=['distinct module as module'],
|
||||
filters={'name': ('in', [d.document_type for d in self.user_doctypes])})
|
||||
|
||||
self.set('user_type_modules', [])
|
||||
for row in modules:
|
||||
self.append('user_type_modules', {
|
||||
'module': row.module
|
||||
})
|
||||
|
||||
def validate_document_type_limit(self):
|
||||
limit = frappe.conf.get('user_type_doctype_limit', {}).get(frappe.scrub(self.name))
|
||||
|
||||
if not limit and frappe.session.user != 'Administrator':
|
||||
frappe.throw(_('User does not have permission to create the new {0}')
|
||||
.format(frappe.bold(_('User Type'))), title=_('Permission Error'))
|
||||
|
||||
if not limit:
|
||||
frappe.throw(_('The limit has not set for the user type {0} in the site config file.')
|
||||
.format(frappe.bold(self.name)), title=_('Set Limit'))
|
||||
|
||||
if self.user_doctypes and len(self.user_doctypes) > limit:
|
||||
frappe.throw(_('The total number of user document types limit has been crossed.'),
|
||||
title=_('User Document Types Limit Exceeded'))
|
||||
|
||||
custom_doctypes = [row.document_type for row in self.user_doctypes if row.is_custom]
|
||||
if custom_doctypes and len(custom_doctypes) > 3:
|
||||
frappe.throw(_('You can only set the 3 custom doctypes in the Document Types table.'),
|
||||
title=_('Custom Document Types Limit Exceeded'))
|
||||
|
||||
def validate_role(self):
|
||||
if not self.role:
|
||||
frappe.throw(_("The field {0} is mandatory")
|
||||
.format(frappe.bold(_('Role'))))
|
||||
|
||||
if not frappe.db.get_value('Role', self.role, 'is_custom'):
|
||||
frappe.throw(_("The role {0} should be a custom role.")
|
||||
.format(frappe.bold(get_link_to_form('Role', self.role))))
|
||||
|
||||
def update_users(self):
|
||||
for row in frappe.get_all('User', filters = {'user_type': self.name}):
|
||||
user = frappe.get_cached_doc('User', row.name)
|
||||
self.update_roles_in_user(user)
|
||||
self.update_modules_in_user(user)
|
||||
user.update_children()
|
||||
|
||||
def update_roles_in_user(self, user):
|
||||
user.set('roles', [])
|
||||
user.append('roles', {
|
||||
'role': self.role
|
||||
})
|
||||
|
||||
def update_modules_in_user(self, user):
|
||||
block_modules = frappe.get_all('Module Def', fields = ['name as module'],
|
||||
filters={'name': ['not in', [d.module for d in self.user_type_modules]]})
|
||||
|
||||
if block_modules:
|
||||
user.set('block_modules', block_modules)
|
||||
|
||||
def add_role_permissions_for_user_doctypes(self):
|
||||
perms = ['read', 'write', 'create', 'submit', 'cancel', 'amend', 'delete']
|
||||
for row in self.user_doctypes:
|
||||
docperm = add_role_permissions(row.document_type, self.role)
|
||||
|
||||
values = {perm:row.get(perm) or 0 for perm in perms}
|
||||
for perm in ['print', 'email', 'share']:
|
||||
values[perm] = 1
|
||||
|
||||
frappe.db.set_value('Custom DocPerm', docperm, values)
|
||||
|
||||
def add_select_perm_doctypes(self):
|
||||
if not frappe.flags.in_patch and not frappe.conf.developer_mode:
|
||||
return
|
||||
|
||||
self.select_doctypes = []
|
||||
|
||||
select_doctypes = []
|
||||
user_doctypes = tuple([row.document_type for row in self.user_doctypes])
|
||||
|
||||
for doctype in user_doctypes:
|
||||
doc = frappe.get_meta(doctype)
|
||||
self.prepare_select_perm_doctypes(doc, user_doctypes, select_doctypes)
|
||||
|
||||
for child_table in doc.get_table_fields():
|
||||
if (frappe.db.table_exists(child_table.options)
|
||||
and not frappe.get_cached_value('DocType', child_table.options, 'istable')):
|
||||
child_doc = frappe.get_meta(child_table.options)
|
||||
self.prepare_select_perm_doctypes(child_doc, user_doctypes, select_doctypes)
|
||||
|
||||
if select_doctypes:
|
||||
select_doctypes = set(select_doctypes)
|
||||
for select_doctype in select_doctypes:
|
||||
self.append('select_doctypes', {
|
||||
'document_type': select_doctype
|
||||
})
|
||||
|
||||
def prepare_select_perm_doctypes(self, doc, user_doctypes, select_doctypes):
|
||||
for field in doc.get_link_fields():
|
||||
if field.options not in user_doctypes:
|
||||
select_doctypes.append(field.options)
|
||||
|
||||
def add_role_permissions_for_select_doctypes(self):
|
||||
for doctype in ['select_doctypes', 'custom_select_doctypes']:
|
||||
for row in self.get(doctype):
|
||||
docperm = add_role_permissions(row.document_type, self.role)
|
||||
frappe.db.set_value('Custom DocPerm', docperm,
|
||||
{'select': 1, 'read': 0, 'create': 0, 'write': 0})
|
||||
|
||||
def add_role_permissions_for_file(self):
|
||||
docperm = add_role_permissions('File', self.role)
|
||||
frappe.db.set_value('Custom DocPerm', docperm,
|
||||
{'read': 1, 'create': 1, 'write': 1})
|
||||
|
||||
def remove_permission_for_deleted_doctypes(self):
|
||||
doctypes = [d.document_type for d in self.user_doctypes]
|
||||
|
||||
# Do not remove the doc permission for the file doctype
|
||||
doctypes.append('File')
|
||||
|
||||
for doctype in ['select_doctypes', 'custom_select_doctypes']:
|
||||
for dt in self.get(doctype):
|
||||
doctypes.append(dt.document_type)
|
||||
|
||||
for perm in frappe.get_all('Custom DocPerm',
|
||||
filters = {'role': self.role, 'parent': ['not in', doctypes]}):
|
||||
frappe.delete_doc('Custom DocPerm', perm.name)
|
||||
|
||||
def add_role_permissions(doctype, role):
|
||||
name = frappe.get_value('Custom DocPerm', dict(parent=doctype,
|
||||
role=role, permlevel=0))
|
||||
|
||||
if not name:
|
||||
name = add_permission(doctype, role, 0)
|
||||
|
||||
return name
|
||||
|
||||
def get_non_standard_user_type_details():
|
||||
user_types = frappe.get_all('User Type',
|
||||
fields=['apply_user_permission_on', 'name', 'user_id_field'],
|
||||
filters={'is_standard': 0})
|
||||
|
||||
if user_types:
|
||||
user_type_details = {d.name: [d.apply_user_permission_on, d.user_id_field] for d in user_types}
|
||||
|
||||
frappe.cache().set_value('non_standard_user_types', user_type_details)
|
||||
|
||||
return user_type_details
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_user_linked_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
modules = [d.get('module_name') for d in get_modules_from_app('frappe')]
|
||||
|
||||
filters = [['DocField', 'options', '=', 'User'], ['DocType', 'is_submittable', '=', 0],
|
||||
['DocType', 'issingle', '=', 0], ['DocType', 'module', 'not in', modules],
|
||||
['DocType', 'read_only', '=', 0], ['DocType', 'name', 'like', '%{0}%'.format(txt)]]
|
||||
|
||||
doctypes = frappe.get_all('DocType', fields = ['`tabDocType`.`name`'], filters=filters,
|
||||
order_by = '`tabDocType`.`idx` desc', limit_start=start, limit_page_length=page_len, as_list=1, debug=1)
|
||||
|
||||
custom_dt_filters = [['Custom Field', 'dt', 'like', '%{0}%'.format(txt)],
|
||||
['Custom Field', 'options', '=', 'User'], ['Custom Field', 'fieldtype', '=', 'Link']]
|
||||
|
||||
custom_doctypes = frappe.get_all('Custom Field', fields = ['dt as name'],
|
||||
filters= custom_dt_filters, as_list=1)
|
||||
|
||||
return doctypes + custom_doctypes
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_user_id(parent):
|
||||
data = frappe.get_all('DocField', fields = ['label', 'fieldname as value'],
|
||||
filters= {'options': 'User', 'fieldtype': 'Link', 'parent': parent}) or []
|
||||
|
||||
data.extend(frappe.get_all('Custom Field', fields = ['label', 'fieldname as value'],
|
||||
filters= {'options': 'User', 'fieldtype': 'Link', 'dt': parent}))
|
||||
|
||||
return data
|
||||
|
||||
def user_linked_with_permission_on_doctype(doc, user):
|
||||
if not doc.apply_user_permission_on:
|
||||
return True
|
||||
|
||||
if not doc.user_id_field:
|
||||
frappe.throw(_('User Id Field is mandatory in the user type {0}')
|
||||
.format(frappe.bold(doc.name)))
|
||||
|
||||
if frappe.db.get_value(doc.apply_user_permission_on,
|
||||
{doc.user_id_field: user}, 'name'):
|
||||
return True
|
||||
else:
|
||||
label = frappe.get_meta(doc.apply_user_permission_on).get_field(doc.user_id_field).label
|
||||
|
||||
frappe.msgprint(_("To set the role {0} in the user {1}, kindly set the {2} field as {3} in one of the {4} record.")
|
||||
.format(frappe.bold(doc.role), frappe.bold(user), frappe.bold(label),
|
||||
frappe.bold(user), frappe.bold(doc.apply_user_permission_on)))
|
||||
|
||||
return False
|
||||
|
||||
def apply_permissions_for_non_standard_user_type(doc, method=None):
|
||||
'''Create user permission for the non standard user type'''
|
||||
if not frappe.db.table_exists('User Type'):
|
||||
return
|
||||
|
||||
user_types = frappe.cache().get_value('non_standard_user_types')
|
||||
|
||||
if not user_types:
|
||||
user_types = get_non_standard_user_type_details()
|
||||
|
||||
if not user_types:
|
||||
return
|
||||
|
||||
for user_type, data in iteritems(user_types):
|
||||
if (not doc.get(data[1]) or doc.doctype != data[0]):
|
||||
continue
|
||||
|
||||
if frappe.get_cached_value('User', doc.get(data[1]), 'user_type') != user_type:
|
||||
return
|
||||
|
||||
if (doc.get(data[1]) and (not doc._doc_before_save or doc.get(data[1]) != doc._doc_before_save.get(data[1])
|
||||
or not frappe.db.get_value('User Permission',
|
||||
{'user': doc.get(data[1]), 'allow': data[0], 'for_value': doc.name}, 'name'))):
|
||||
|
||||
perm_data = frappe.db.get_value('User Permission',
|
||||
{'allow': doc.doctype, 'for_value': doc.name}, ['name', 'user'])
|
||||
|
||||
if not perm_data:
|
||||
user_doc = frappe.get_cached_doc('User', doc.get(data[1]))
|
||||
user_doc.set_roles_and_modules_based_on_user_type()
|
||||
user_doc.update_children()
|
||||
add_user_permission(doc.doctype, doc.name, doc.get(data[1]))
|
||||
else:
|
||||
frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1]))
|
||||
13
frappe/core/doctype/user_type/user_type_dashboard.py
Normal file
13
frappe/core/doctype/user_type/user_type_dashboard.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'user_type',
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Reference'),
|
||||
'items': ['User']
|
||||
}
|
||||
]
|
||||
}
|
||||
10
frappe/core/doctype/user_type/user_type_list.js
Normal file
10
frappe/core/doctype/user_type/user_type_list.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
frappe.listview_settings['User Type'] = {
|
||||
add_fields: ["is_standard"],
|
||||
get_indicator: function (doc) {
|
||||
if (doc.is_standard) {
|
||||
return [__("Standard"), "green", "is_standard,=,1"];
|
||||
} else {
|
||||
return [__("Custom"), "blue", "is_standard,=,0"];
|
||||
}
|
||||
}
|
||||
};
|
||||
0
frappe/core/doctype/user_type_module/__init__.py
Normal file
0
frappe/core/doctype/user_type_module/__init__.py
Normal file
33
frappe/core/doctype/user_type_module/user_type_module.json
Normal file
33
frappe/core/doctype/user_type_module/user_type_module.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2021-01-24 03:05:24.634719",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"module"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "module",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Module",
|
||||
"options": "Module Def",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-24 03:07:43.602927",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User Type Module",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/user_type_module/user_type_module.py
Normal file
10
frappe/core/doctype/user_type_module/user_type_module.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class UserTypeModule(Document):
|
||||
pass
|
||||
|
|
@ -30,8 +30,16 @@ def get_roles_and_doctypes():
|
|||
"restrict_to_domain": ("in", active_domains)
|
||||
}, fields=["name"])
|
||||
|
||||
restricted_roles = ['Administrator']
|
||||
if frappe.session.user != 'Administrator':
|
||||
custom_user_type_roles = frappe.get_all('User Type', filters = {'is_standard': 0}, fields=['role'])
|
||||
for row in custom_user_type_roles:
|
||||
restricted_roles.append(row.role)
|
||||
|
||||
restricted_roles.append('All')
|
||||
|
||||
roles = frappe.get_all("Role", filters={
|
||||
"name": ("not in", "Administrator"),
|
||||
"name": ("not in", restricted_roles),
|
||||
"disabled": 0,
|
||||
}, or_filters={
|
||||
"ifnull(restrict_to_domain, '')": "",
|
||||
|
|
@ -54,9 +62,14 @@ def get_permissions(doctype=None, role=None):
|
|||
if doctype:
|
||||
out = [p for p in out if p.parent == doctype]
|
||||
else:
|
||||
out = frappe.get_all('Custom DocPerm', fields='*', filters=dict(parent = doctype), order_by="permlevel")
|
||||
filters=dict(parent = doctype)
|
||||
if frappe.session.user != 'Administrator':
|
||||
custom_roles = frappe.get_all('Role', filters={'is_custom': 1})
|
||||
filters['role'] = ['not in', [row.name for row in custom_roles]]
|
||||
|
||||
out = frappe.get_all('Custom DocPerm', fields='*', filters=filters, order_by="permlevel")
|
||||
if not out:
|
||||
out = frappe.get_all('DocPerm', fields='*', filters=dict(parent = doctype), order_by="permlevel")
|
||||
out = frappe.get_all('DocPerm', fields='*', filters=filters, order_by="permlevel")
|
||||
|
||||
linked_doctypes = {}
|
||||
for d in out:
|
||||
|
|
@ -78,14 +91,14 @@ def add(parent, role, permlevel):
|
|||
@frappe.whitelist()
|
||||
def update(doctype, role, permlevel, ptype, value=None):
|
||||
"""Update role permission params
|
||||
|
||||
|
||||
Args:
|
||||
doctype (str): Name of the DocType to update params for
|
||||
role (str): Role to be updated for, eg "Website Manager".
|
||||
permlevel (int): perm level the provided rule applies to
|
||||
ptype (str): permission type, example "read", "delete", etc.
|
||||
value (None, optional): value for ptype, None indicates False
|
||||
|
||||
|
||||
Returns:
|
||||
str: Refresh flag is permission is updated successfully
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"hide_custom": 0,
|
||||
"icon": "users",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Users",
|
||||
"links": [
|
||||
|
|
@ -135,7 +136,7 @@
|
|||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2020-12-01 13:38:40.085519",
|
||||
"modified": "2021-03-25 23:02:34.582569",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Users",
|
||||
|
|
@ -162,6 +163,12 @@
|
|||
"label": "User Profile",
|
||||
"link_to": "user-profile",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"label": "User Type",
|
||||
"link_to": "User Type",
|
||||
"type": "DocType"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -455,6 +455,7 @@ class Database(object):
|
|||
elif (not ignore) and frappe.db.is_table_missing(e):
|
||||
# table not found, look in singles
|
||||
out = self.get_values_from_single(fields, filters, doctype, as_dict, debug, update)
|
||||
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
|
|
@ -507,6 +508,7 @@ class Database(object):
|
|||
else:
|
||||
return r and [[i[1] for i in r]] or []
|
||||
|
||||
|
||||
def get_singles_dict(self, doctype, debug = False):
|
||||
"""Get Single DocType as dict.
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ class DBTable:
|
|||
self.get_columns_from_docfields()
|
||||
|
||||
def sync(self):
|
||||
if self.meta.get('is_virtual'):
|
||||
# no schema to sync for virtual doctypes
|
||||
return
|
||||
if self.is_new():
|
||||
self.create()
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@ from __future__ import unicode_literals
|
|||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.model.db_query import DatabaseQuery
|
||||
from frappe.permissions import add_permission, reset_perms
|
||||
from frappe.core.doctype.doctype.doctype import clear_permissions_cache
|
||||
|
||||
# test_records = frappe.get_test_records('ToDo')
|
||||
test_user_records = frappe.get_test_records('User')
|
||||
|
||||
class TestToDo(unittest.TestCase):
|
||||
def test_delete(self):
|
||||
|
|
@ -47,6 +51,62 @@ class TestToDo(unittest.TestCase):
|
|||
self.assertEqual(todo.assigned_by_full_name,
|
||||
frappe.db.get_value('User', todo.assigned_by, 'full_name'))
|
||||
|
||||
def test_todo_list_access(self):
|
||||
create_new_todo('Test1', 'testperm@example.com')
|
||||
|
||||
frappe.set_user('test4@example.com')
|
||||
create_new_todo('Test2', 'test4@example.com')
|
||||
test_user_data = DatabaseQuery('ToDo').execute()
|
||||
|
||||
frappe.set_user('testperm@example.com')
|
||||
system_manager_data = DatabaseQuery('ToDo').execute()
|
||||
|
||||
self.assertNotEqual(test_user_data, system_manager_data)
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_doc_read_access(self):
|
||||
#owner and assigned_by is testperm
|
||||
todo1 = create_new_todo('Test1', 'testperm@example.com')
|
||||
test_user = frappe.get_doc('User', 'test4@example.com')
|
||||
|
||||
#owner is testperm, but assigned_by is test4
|
||||
todo2 = create_new_todo('Test2', 'test4@example.com')
|
||||
|
||||
frappe.set_user('test4@example.com')
|
||||
#owner and assigned_by is test4
|
||||
todo3 = create_new_todo('Test3', 'test4@example.com')
|
||||
|
||||
# user without any role to read or write todo document
|
||||
self.assertFalse(todo1.has_permission("read"))
|
||||
self.assertFalse(todo1.has_permission("write"))
|
||||
|
||||
# user without any role but he/she is assigned_by of that todo document
|
||||
self.assertTrue(todo2.has_permission("read"))
|
||||
self.assertTrue(todo2.has_permission("write"))
|
||||
|
||||
# user is the owner and assigned_by of the todo document
|
||||
self.assertTrue(todo3.has_permission("read"))
|
||||
self.assertTrue(todo3.has_permission("write"))
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
test_user.add_roles('Blogger')
|
||||
add_permission('ToDo', 'Blogger')
|
||||
|
||||
frappe.set_user('test4@example.com')
|
||||
|
||||
# user with only read access to todo document, not an owner or assigned_by
|
||||
self.assertTrue(todo1.has_permission("read"))
|
||||
self.assertFalse(todo1.has_permission("write"))
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
test_user.remove_roles('Blogger')
|
||||
reset_perms('ToDo')
|
||||
clear_permissions_cache('ToDo')
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_fetch_if_empty(self):
|
||||
frappe.db.sql('delete from tabToDo')
|
||||
|
||||
|
|
@ -74,3 +134,11 @@ def test_fetch_if_empty(self):
|
|||
|
||||
self.assertEqual(todo.assigned_by_full_name,
|
||||
frappe.db.get_value('User', todo.assigned_by, 'full_name'))
|
||||
|
||||
def create_new_todo(description, assigned_by):
|
||||
todo = {
|
||||
'doctype': 'ToDo',
|
||||
'description': description,
|
||||
'assigned_by': assigned_by
|
||||
}
|
||||
return frappe.get_doc(todo).insert()
|
||||
|
|
|
|||
|
|
@ -85,21 +85,30 @@ class ToDo(Document):
|
|||
else:
|
||||
raise
|
||||
|
||||
# NOTE: todo is viewable if either owner or assigned_to or System Manager in roles
|
||||
# NOTE: todo is viewable if a user is an owner, or set as assigned_to value, or has any role that is allowed to access ToDo doctype.
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("ToDo", ["reference_type", "reference_name"])
|
||||
|
||||
def get_permission_query_conditions(user):
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
if "System Manager" in frappe.get_roles(user):
|
||||
todo_roles = frappe.permissions.get_doctype_roles('ToDo')
|
||||
if 'All' in todo_roles:
|
||||
todo_roles.remove('All')
|
||||
|
||||
if any(check in todo_roles for check in frappe.get_roles(user)):
|
||||
return None
|
||||
else:
|
||||
return """(`tabToDo`.owner = {user} or `tabToDo`.assigned_by = {user})"""\
|
||||
.format(user=frappe.db.escape(user))
|
||||
|
||||
def has_permission(doc, user):
|
||||
if "System Manager" in frappe.get_roles(user):
|
||||
def has_permission(doc, ptype="read", user=None):
|
||||
user = user or frappe.session.user
|
||||
todo_roles = frappe.permissions.get_doctype_roles('ToDo', ptype)
|
||||
if 'All' in todo_roles:
|
||||
todo_roles.remove('All')
|
||||
|
||||
if any(check in todo_roles for check in frappe.get_roles(user)):
|
||||
return True
|
||||
else:
|
||||
return doc.owner==user or doc.assigned_by==user
|
||||
|
|
|
|||
|
|
@ -248,4 +248,4 @@
|
|||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,10 +89,16 @@ def get_docinfo(doc=None, doctype=None, name=None):
|
|||
doc = frappe.get_doc(doctype, name)
|
||||
if not doc.has_permission("read"):
|
||||
raise frappe.PermissionError
|
||||
|
||||
all_communications = _get_communications(doc.doctype, doc.name)
|
||||
automated_messages = filter(lambda x: x['communication_type'] == 'Automated Message', all_communications)
|
||||
communications_except_auto_messages = filter(lambda x: x['communication_type'] != 'Automated Message', all_communications)
|
||||
|
||||
frappe.response["docinfo"] = {
|
||||
"attachments": get_attachments(doc.doctype, doc.name),
|
||||
"attachment_logs": get_comments(doc.doctype, doc.name, 'attachment'),
|
||||
"communications": _get_communications(doc.doctype, doc.name),
|
||||
"communications": communications_except_auto_messages,
|
||||
"automated_messages": automated_messages,
|
||||
'comments': get_comments(doc.doctype, doc.name),
|
||||
'total_comments': len(json.loads(doc.get('_comments') or '[]')),
|
||||
'versions': get_versions(doc),
|
||||
|
|
@ -187,7 +193,7 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
|
|||
C.sender, C.sender_full_name, C.cc, C.bcc,
|
||||
C.creation AS creation, C.subject, C.delivery_status,
|
||||
C._liked_by, C.reference_doctype, C.reference_name,
|
||||
C.read_by_recipient, C.rating
|
||||
C.read_by_recipient, C.rating, C.recipients
|
||||
'''
|
||||
|
||||
conditions = ''
|
||||
|
|
@ -206,7 +212,7 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
|
|||
part1 = '''
|
||||
SELECT {fields}
|
||||
FROM `tabCommunication` as C
|
||||
WHERE C.communication_type IN ('Communication', 'Feedback')
|
||||
WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message')
|
||||
AND (C.reference_doctype = %(doctype)s AND C.reference_name = %(name)s)
|
||||
{conditions}
|
||||
'''.format(fields=fields, conditions=conditions)
|
||||
|
|
@ -216,7 +222,7 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
|
|||
SELECT {fields}
|
||||
FROM `tabCommunication` as C
|
||||
INNER JOIN `tabCommunication Link` ON C.name=`tabCommunication Link`.parent
|
||||
WHERE C.communication_type IN ('Communication', 'Feedback')
|
||||
WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message')
|
||||
AND `tabCommunication Link`.link_doctype = %(doctype)s AND `tabCommunication Link`.link_name = %(name)s
|
||||
{conditions}
|
||||
'''.format(fields=fields, conditions=conditions)
|
||||
|
|
@ -304,4 +310,4 @@ def get_additional_timeline_content(doctype, docname):
|
|||
for method in methods_for_all_doctype + methods_for_current_doctype:
|
||||
contents.extend(frappe.get_attr(method)(doctype, docname) or [])
|
||||
|
||||
return contents
|
||||
return contents
|
||||
|
|
|
|||
|
|
@ -13,13 +13,20 @@ from frappe import _
|
|||
from six import string_types, StringIO
|
||||
from frappe.core.doctype.access_log.access_log import make_access_log
|
||||
from frappe.utils import cstr, format_duration
|
||||
from frappe.model.base_document import get_controller
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.read_only()
|
||||
def get():
|
||||
args = get_form_params()
|
||||
return compress(execute(**args), args=args)
|
||||
# If virtual doctype get data from controller het_list method
|
||||
if frappe.db.get_value("DocType", filters={"name": args.doctype}, fieldname="is_virtual"):
|
||||
controller = get_controller(args.doctype)
|
||||
data = compress(controller(args.doctype).get_list(args))
|
||||
else:
|
||||
data = compress(execute(**args), args=args)
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
|
|
@ -31,7 +38,9 @@ def get_list():
|
|||
@frappe.read_only()
|
||||
def get_count():
|
||||
args = get_form_params()
|
||||
args.fields = ['{distinct}count(name) as total_count'.format(distinct = 'distinct ' if args.distinct=='true' else '')]
|
||||
|
||||
distinct = 'distinct ' if args.distinct=='true' else ''
|
||||
args.fields = [f"count({distinct}`tab{args.doctype}`.name) as total_count"]
|
||||
return execute(**args)[0].get('total_count')
|
||||
|
||||
def execute(doctype, *args, **kwargs):
|
||||
|
|
@ -429,8 +438,9 @@ def get_stats(stats, doctype, filters=[]):
|
|||
|
||||
try:
|
||||
columns = frappe.db.get_table_columns(doctype)
|
||||
except frappe.db.InternalError:
|
||||
except (frappe.db.InternalError, frappe.db.ProgrammingError):
|
||||
# raised when _user_tags column is added on the fly
|
||||
# raised if its a virtual doctype
|
||||
columns = []
|
||||
|
||||
for tag in tags:
|
||||
|
|
@ -541,7 +551,7 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with
|
|||
if isinstance(f[1], string_types) and f[1][0] == '!':
|
||||
flt.append([doctype, f[0], '!=', f[1][1:]])
|
||||
elif isinstance(f[1], (list, tuple)) and \
|
||||
f[1][0] in (">", "<", ">=", "<=", "like", "not like", "in", "not in", "between"):
|
||||
f[1][0] in (">", "<", ">=", "<=", "!=", "like", "not like", "in", "not in", "between"):
|
||||
|
||||
flt.append([doctype, f[0], f[1][0], f[1][1]])
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -36,20 +36,27 @@ def get_all_nodes(doctype, label, parent, tree_method, **filters):
|
|||
return out
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_children(doctype, parent='', **filters):
|
||||
def get_children(doctype, parent=''):
|
||||
return _get_children(doctype, parent)
|
||||
|
||||
def _get_children(doctype, parent='', ignore_permissions=False):
|
||||
parent_field = 'parent_' + doctype.lower().replace(' ', '_')
|
||||
filters=[['ifnull(`{0}`,"")'.format(parent_field), '=', parent],
|
||||
filters = [['ifnull(`{0}`,"")'.format(parent_field), '=', parent],
|
||||
['docstatus', '<' ,'2']]
|
||||
|
||||
doctype_meta = frappe.get_meta(doctype)
|
||||
data = frappe.get_list(doctype, fields=[
|
||||
'name as value',
|
||||
'{0} as title'.format(doctype_meta.get('title_field') or 'name'),
|
||||
'is_group as expandable'],
|
||||
filters=filters,
|
||||
order_by='name')
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
return data
|
||||
return frappe.get_list(
|
||||
doctype,
|
||||
fields=[
|
||||
'name as value',
|
||||
'{0} as title'.format(meta.get('title_field') or 'name'),
|
||||
'is_group as expandable'
|
||||
],
|
||||
filters=filters,
|
||||
order_by='name',
|
||||
ignore_permissions=ignore_permissions
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_node():
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ def get_context(context):
|
|||
|
||||
def send_an_email(self, doc, context):
|
||||
from email.utils import formataddr
|
||||
from frappe.core.doctype.communication.email import make as make_communication
|
||||
subject = self.subject
|
||||
if "{" in subject:
|
||||
subject = frappe.render_template(self.subject, context)
|
||||
|
|
@ -199,6 +200,7 @@ def get_context(context):
|
|||
return
|
||||
|
||||
sender = None
|
||||
message = frappe.render_template(self.message, context)
|
||||
if self.sender and self.sender_email:
|
||||
sender = formataddr((self.sender, self.sender_email))
|
||||
frappe.sendmail(recipients = recipients,
|
||||
|
|
@ -206,7 +208,7 @@ def get_context(context):
|
|||
sender = sender,
|
||||
cc = cc,
|
||||
bcc = bcc,
|
||||
message = frappe.render_template(self.message, context),
|
||||
message = message,
|
||||
reference_doctype = doc.doctype,
|
||||
reference_name = doc.name,
|
||||
attachments = attachments,
|
||||
|
|
@ -214,6 +216,23 @@ def get_context(context):
|
|||
print_letterhead = ((attachments
|
||||
and attachments[0].get('print_letterhead')) or False))
|
||||
|
||||
# Add mail notification to communication list
|
||||
# No need to add if it is already a communication.
|
||||
if doc.doctype != 'Communication':
|
||||
make_communication(doctype=doc.doctype,
|
||||
name=doc.name,
|
||||
content=message,
|
||||
subject=subject,
|
||||
sender=sender,
|
||||
recipients=recipients,
|
||||
communication_medium="Email",
|
||||
send_email=False,
|
||||
attachments=attachments,
|
||||
cc=cc,
|
||||
bcc=bcc,
|
||||
communication_type='Automated Message',
|
||||
ignore_permissions=True)
|
||||
|
||||
def send_a_slack_msg(self, doc, context):
|
||||
send_slack_message(
|
||||
webhook_url=self.slack_webhook_url,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ class TestNotification(unittest.TestCase):
|
|||
frappe.set_user("Administrator")
|
||||
|
||||
def test_new_and_save(self):
|
||||
"""Check creating a new communication triggers a notification.
|
||||
"""
|
||||
communication = frappe.new_doc("Communication")
|
||||
communication.communication_type = 'Comment'
|
||||
communication.subject = "test"
|
||||
|
|
@ -54,6 +56,7 @@ class TestNotification(unittest.TestCase):
|
|||
"reference_name": communication.name, "status":"Not Sent"}))
|
||||
frappe.db.sql("""delete from `tabEmail Queue`""")
|
||||
|
||||
communication.reload()
|
||||
communication.content = "test 2"
|
||||
communication.save()
|
||||
|
||||
|
|
@ -64,6 +67,8 @@ class TestNotification(unittest.TestCase):
|
|||
communication.name, 'subject'), '__testing__')
|
||||
|
||||
def test_condition(self):
|
||||
"""Check notification is triggered based on a condition.
|
||||
"""
|
||||
event = frappe.new_doc("Event")
|
||||
event.subject = "test",
|
||||
event.event_type = "Private"
|
||||
|
|
@ -79,6 +84,11 @@ class TestNotification(unittest.TestCase):
|
|||
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
|
||||
"reference_name": event.name, "status":"Not Sent"}))
|
||||
|
||||
# Make sure that we track the triggered notifications in communication doctype.
|
||||
self.assertTrue(frappe.db.get_value("Communication", {"reference_doctype": "Event",
|
||||
"reference_name": event.name, "communication_type": 'Automated Message'}))
|
||||
|
||||
|
||||
def test_invalid_condition(self):
|
||||
frappe.set_user("Administrator")
|
||||
notification = frappe.new_doc("Notification")
|
||||
|
|
|
|||
|
|
@ -56,12 +56,7 @@ def execute_cmd(cmd, from_async=False):
|
|||
try:
|
||||
method = get_attr(cmd)
|
||||
except Exception as e:
|
||||
if frappe.local.conf.developer_mode:
|
||||
raise e
|
||||
else:
|
||||
frappe.respond_as_web_page(title='Invalid Method', html='Method not found',
|
||||
indicator_color='red', http_status_code=404)
|
||||
return
|
||||
frappe.throw(_('Invalid Method'))
|
||||
|
||||
if from_async:
|
||||
method = method.queue
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ doc_events = {
|
|||
"frappe.core.doctype.file.file.attach_files_to_document",
|
||||
"frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers",
|
||||
"frappe.automation.doctype.assignment_rule.assignment_rule.update_due_date",
|
||||
"frappe.core.doctype.user_type.user_type.apply_permissions_for_non_standard_user_type"
|
||||
],
|
||||
"after_rename": "frappe.desk.notifications.clear_doctype_notifications",
|
||||
"on_cancel": [
|
||||
|
|
|
|||
|
|
@ -555,22 +555,25 @@ class BaseDocument(object):
|
|||
not _df.get('fetch_if_empty')
|
||||
or (_df.get('fetch_if_empty') and not self.get(_df.fieldname))
|
||||
]
|
||||
if not frappe.get_meta(doctype).get('is_virtual'):
|
||||
if not fields_to_fetch:
|
||||
# cache a single value type
|
||||
values = frappe._dict(name=frappe.db.get_value(doctype, docname,
|
||||
'name', cache=True))
|
||||
else:
|
||||
values_to_fetch = ['name'] + [_df.fetch_from.split('.')[-1]
|
||||
for _df in fields_to_fetch]
|
||||
|
||||
if not fields_to_fetch:
|
||||
# cache a single value type
|
||||
values = frappe._dict(name=frappe.db.get_value(doctype, docname,
|
||||
'name', cache=True))
|
||||
else:
|
||||
values_to_fetch = ['name'] + [_df.fetch_from.split('.')[-1]
|
||||
for _df in fields_to_fetch]
|
||||
|
||||
# don't cache if fetching other values too
|
||||
values = frappe.db.get_value(doctype, docname,
|
||||
values_to_fetch, as_dict=True)
|
||||
# don't cache if fetching other values too
|
||||
values = frappe.db.get_value(doctype, docname,
|
||||
values_to_fetch, as_dict=True)
|
||||
|
||||
if frappe.get_meta(doctype).issingle:
|
||||
values.name = doctype
|
||||
|
||||
if frappe.get_meta(doctype).get('is_virtual'):
|
||||
values = frappe.get_doc(doctype, docname)
|
||||
|
||||
if values:
|
||||
setattr(self, df.fieldname, values.name)
|
||||
|
||||
|
|
@ -792,7 +795,7 @@ class BaseDocument(object):
|
|||
|
||||
def _save_passwords(self):
|
||||
"""Save password field values in __Auth table"""
|
||||
from frappe.utils.password import set_encrypted_password
|
||||
from frappe.utils.password import set_encrypted_password, remove_encrypted_password
|
||||
|
||||
if self.flags.ignore_save_passwords is True:
|
||||
return
|
||||
|
|
@ -800,6 +803,10 @@ class BaseDocument(object):
|
|||
for df in self.meta.get('fields', {'fieldtype': ('=', 'Password')}):
|
||||
if self.flags.ignore_save_passwords and df.fieldname in self.flags.ignore_save_passwords: continue
|
||||
new_password = self.get(df.fieldname)
|
||||
|
||||
if not new_password:
|
||||
remove_encrypted_password(self.doctype, self.name, df.fieldname)
|
||||
|
||||
if new_password and not self.is_dummy_password(new_password):
|
||||
# is not a dummy password like '*****'
|
||||
set_encrypted_password(self.doctype, self.name, new_password, df.fieldname)
|
||||
|
|
|
|||
|
|
@ -157,10 +157,10 @@ def update_naming_series(doc):
|
|||
if doc.meta.autoname:
|
||||
if doc.meta.autoname.startswith("naming_series:") \
|
||||
and getattr(doc, "naming_series", None):
|
||||
revert_series_if_last(doc.naming_series, doc.name)
|
||||
revert_series_if_last(doc.naming_series, doc.name, doc)
|
||||
|
||||
elif doc.meta.autoname.split(":")[0] not in ("Prompt", "field", "hash"):
|
||||
revert_series_if_last(doc.meta.autoname, doc.name)
|
||||
revert_series_if_last(doc.meta.autoname, doc.name, doc)
|
||||
|
||||
def delete_from_table(doctype, name, ignore_doctypes, doc):
|
||||
if doctype!="DocType" and doctype==name:
|
||||
|
|
|
|||
|
|
@ -697,7 +697,7 @@ class Document(BaseDocument):
|
|||
`self.check_docstatus_transition`."""
|
||||
conflict = False
|
||||
self._action = "save"
|
||||
if not self.get('__islocal'):
|
||||
if not self.get('__islocal') and not self.meta.get('is_virtual'):
|
||||
if self.meta.issingle:
|
||||
modified = frappe.db.sql("""select value from tabSingles
|
||||
where doctype=%s and field='modified' for update""", self.doctype)
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ def getseries(key, digits):
|
|||
return ('%0'+str(digits)+'d') % current
|
||||
|
||||
|
||||
def revert_series_if_last(key, name):
|
||||
def revert_series_if_last(key, name, doc=None):
|
||||
if ".#" in key:
|
||||
prefix, hashes = key.rsplit(".", 1)
|
||||
if "#" not in hashes:
|
||||
|
|
@ -207,7 +207,7 @@ def revert_series_if_last(key, name):
|
|||
prefix = key
|
||||
|
||||
if '.' in prefix:
|
||||
prefix = parse_naming_series(prefix.split('.'))
|
||||
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,))
|
||||
|
|
|
|||
|
|
@ -247,6 +247,21 @@ def make_boilerplate(template, doc, opts=None):
|
|||
base_class = 'NestedSet'
|
||||
base_class_import = 'from frappe.utils.nestedset import NestedSet'
|
||||
|
||||
custom_controller = 'pass'
|
||||
if doc.get('is_virtual'):
|
||||
custom_controller = """
|
||||
def db_insert(self):
|
||||
pass
|
||||
|
||||
def load_from_db(self):
|
||||
pass
|
||||
|
||||
def db_update(self):
|
||||
pass
|
||||
|
||||
def get_list(self, args):
|
||||
pass"""
|
||||
|
||||
with open(target_file_path, 'w') as target:
|
||||
with open(os.path.join(get_module_path("core"), "doctype", scrub(doc.doctype),
|
||||
"boilerplate", template), 'r') as source:
|
||||
|
|
@ -257,5 +272,6 @@ def make_boilerplate(template, doc, opts=None):
|
|||
classname=doc.name.replace(" ", ""),
|
||||
base_class_import=base_class_import,
|
||||
base_class=base_class,
|
||||
doctype=doc.name, **opts)
|
||||
doctype=doc.name, **opts,
|
||||
custom_controller=custom_controller)
|
||||
))
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ frappe.patches.v12_0.remove_example_email_thread_notify
|
|||
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()
|
||||
frappe.patches.v12_0.set_correct_url_in_files
|
||||
frappe.patches.v13_0.website_theme_custom_scss
|
||||
frappe.patches.v13_0.make_user_type
|
||||
frappe.patches.v13_0.set_existing_dashboard_charts_as_public
|
||||
frappe.patches.v13_0.set_path_for_homepage_in_web_page_view
|
||||
frappe.patches.v13_0.migrate_translation_column_data
|
||||
|
|
|
|||
12
frappe/patches/v13_0/make_user_type.py
Normal file
12
frappe/patches/v13_0/make_user_type.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import frappe
|
||||
from frappe.utils.install import create_user_type
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('core', 'doctype', 'role')
|
||||
frappe.reload_doc('core', 'doctype', 'user_document_type')
|
||||
frappe.reload_doc('core', 'doctype', 'user_type_module')
|
||||
frappe.reload_doc('core', 'doctype', 'user_select_document_type')
|
||||
frappe.reload_doc('core', 'doctype', 'user_type')
|
||||
|
||||
|
||||
create_user_type()
|
||||
|
|
@ -7,6 +7,9 @@ import frappe
|
|||
|
||||
def execute():
|
||||
if frappe.db.table_exists('List View Setting'):
|
||||
if not frappe.db.table_exists('List View Settings'):
|
||||
frappe.reload_doc("desk", "doctype", "List View Settings")
|
||||
|
||||
existing_list_view_settings = frappe.get_all('List View Settings', as_list=True)
|
||||
for list_view_setting in frappe.get_all('List View Setting', fields = ['disable_count', 'disable_sidebar_stats', 'disable_auto_refresh', 'name']):
|
||||
name = list_view_setting.pop('name')
|
||||
|
|
@ -16,5 +19,6 @@ def execute():
|
|||
# setting name here is necessary because autoname is set as prompt
|
||||
list_view_settings.name = name
|
||||
list_view_settings.insert()
|
||||
|
||||
frappe.delete_doc("DocType", "List View Setting", force=True)
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('Website Theme')
|
||||
frappe.reload_doc('website', 'doctype', 'website_theme_ignore_app')
|
||||
frappe.reload_doc('website', 'doctype', 'color')
|
||||
frappe.reload_doctype('Website Theme')
|
||||
|
||||
for theme in frappe.get_all('Website Theme'):
|
||||
doc = frappe.get_doc('Website Theme', theme.name)
|
||||
|
|
|
|||
|
|
@ -78,14 +78,14 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, ra
|
|||
push_perm_check_log(_('User {0} does not have doctype access via role permission for document {1}').format(frappe.bold(user), frappe.bold(doctype)))
|
||||
|
||||
def false_if_not_shared():
|
||||
if ptype in ("read", "write", "share", "email", "print"):
|
||||
if ptype in ("read", "write", "share", "submit", "email", "print"):
|
||||
shared = frappe.share.get_shared(doctype, user,
|
||||
["read" if ptype in ("email", "print") else ptype])
|
||||
|
||||
if doc:
|
||||
doc_name = get_doc_name(doc)
|
||||
if doc_name in shared:
|
||||
if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype):
|
||||
if ptype in ("read", "write", "share", "submit") or meta.permissions[0].get(ptype):
|
||||
return True
|
||||
|
||||
elif shared:
|
||||
|
|
@ -366,6 +366,11 @@ def get_roles(user=None, with_standard=True):
|
|||
|
||||
return roles
|
||||
|
||||
def get_doctype_roles(doctype, access_type="read"):
|
||||
"""Returns a list of roles that are allowed to access passed doctype."""
|
||||
meta = frappe.get_meta(doctype)
|
||||
return [d.role for d in meta.get("permissions") if d.get(access_type)]
|
||||
|
||||
def get_perms_for(roles, perm_doctype='DocPerm'):
|
||||
'''Get perms for given roles'''
|
||||
filters = {
|
||||
|
|
@ -475,7 +480,7 @@ def setup_custom_perms(parent):
|
|||
copy_perms(parent)
|
||||
return True
|
||||
|
||||
def add_permission(doctype, role, permlevel=0):
|
||||
def add_permission(doctype, role, permlevel=0, ptype=None):
|
||||
'''Add a new permission rule to the given doctype
|
||||
for the given Role and Permission Level'''
|
||||
from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype
|
||||
|
|
@ -485,6 +490,9 @@ def add_permission(doctype, role, permlevel=0):
|
|||
permlevel=permlevel, if_owner=0)):
|
||||
return
|
||||
|
||||
if not ptype:
|
||||
ptype = 'read'
|
||||
|
||||
custom_docperm = frappe.get_doc({
|
||||
"doctype":"Custom DocPerm",
|
||||
"__islocal": 1,
|
||||
|
|
@ -492,13 +500,14 @@ def add_permission(doctype, role, permlevel=0):
|
|||
"parenttype": "DocType",
|
||||
"parentfield": "permissions",
|
||||
"role": role,
|
||||
'read': 1,
|
||||
"permlevel": permlevel,
|
||||
ptype: 1,
|
||||
})
|
||||
|
||||
custom_docperm.save()
|
||||
|
||||
validate_permissions_for_doctype(doctype)
|
||||
return custom_docperm.name
|
||||
|
||||
def copy_perms(parent):
|
||||
'''Copy all DocPerm in to Custom DocPerm for the given document'''
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@
|
|||
"public/less/controls.less",
|
||||
"public/less/chat.less",
|
||||
"public/css/fonts/inter/inter.css",
|
||||
"public/scss/desk.scss",
|
||||
"node_modules/frappe-charts/dist/frappe-charts.min.css",
|
||||
"node_modules/plyr/dist/plyr.css"
|
||||
"node_modules/plyr/dist/plyr.css",
|
||||
"public/scss/desk.scss"
|
||||
],
|
||||
"css/frappe-rtl.css": [
|
||||
"public/css/bootstrap-rtl.css",
|
||||
|
|
|
|||
|
|
@ -494,7 +494,7 @@
|
|||
</symbol>
|
||||
<symbol viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" id="icon-heart-active">
|
||||
<path d="M7.606 3.799L8 4.302l.394-.503.106-.14c.048-.065.08-.108.129-.159a3.284 3.284 0 0 1 4.72 0c.424.434.655 1.245.65 2.278-.006 1.578-.685 2.931-1.728 4.159-1.05 1.234-2.439 2.308-3.814 3.328a.763.763 0 0 1-.914 0c-1.375-1.02-2.764-2.094-3.814-3.328C2.686 8.709 2.007 7.357 2 5.778c-.004-1.033.227-1.844.651-2.278a3.284 3.284 0 0 1 4.72 0c.05.05.081.094.129.158.028.038.061.083.106.14z"
|
||||
stroke="none">
|
||||
stroke="var(--icon-stroke)">
|
||||
</path>
|
||||
</symbol>
|
||||
<symbol viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="icon-solid-error">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
|
@ -100,17 +100,11 @@ frappe.db = {
|
|||
|
||||
const fields = [];
|
||||
|
||||
return frappe.call({
|
||||
type: 'GET',
|
||||
method: 'frappe.desk.reportview.get_count',
|
||||
args: {
|
||||
doctype,
|
||||
filters,
|
||||
fields,
|
||||
distinct,
|
||||
}
|
||||
}).then(r => {
|
||||
return r.message.values;
|
||||
return frappe.xcall('frappe.desk.reportview.get_count', {
|
||||
doctype,
|
||||
filters,
|
||||
fields,
|
||||
distinct,
|
||||
});
|
||||
},
|
||||
get_link_options(doctype, txt = '', filters={}) {
|
||||
|
|
|
|||
|
|
@ -83,11 +83,16 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
|
|||
var doctype = this.get_options();
|
||||
var me = this;
|
||||
|
||||
if(!doctype) return;
|
||||
if (!doctype) return;
|
||||
|
||||
let df = this.df;
|
||||
if (this.frm && this.frm.doctype !== this.df.parent) {
|
||||
// incase of grid use common df set in grid
|
||||
df = this.frm.get_docfield(this.doc.parentfield, this.df.fieldname);
|
||||
}
|
||||
// set values to fill in the new document
|
||||
if(this.df.get_route_options_for_new_doc) {
|
||||
frappe.route_options = this.df.get_route_options_for_new_doc(this);
|
||||
if (df && df.get_route_options_for_new_doc) {
|
||||
frappe.route_options = df.get_route_options_for_new_doc(this);
|
||||
} else {
|
||||
frappe.route_options = {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ class FormTimeline extends BaseTimeline {
|
|||
|
||||
prepare_timeline_contents() {
|
||||
this.timeline_items.push(...this.get_communication_timeline_contents());
|
||||
this.timeline_items.push(...this.get_auto_messages_timeline_contents());
|
||||
this.timeline_items.push(...this.get_comment_timeline_contents());
|
||||
if (!this.only_communication) {
|
||||
this.timeline_items.push(...this.get_view_timeline_contents());
|
||||
|
|
@ -181,7 +182,7 @@ class FormTimeline extends BaseTimeline {
|
|||
return communication_timeline_contents;
|
||||
}
|
||||
|
||||
get_communication_timeline_content(doc) {
|
||||
get_communication_timeline_content(doc, allow_reply=true) {
|
||||
doc._url = frappe.utils.get_form_link("Communication", doc.name);
|
||||
this.set_communication_doc_status(doc);
|
||||
if (doc.attachments && typeof doc.attachments === "string") {
|
||||
|
|
@ -189,8 +190,10 @@ class FormTimeline extends BaseTimeline {
|
|||
}
|
||||
doc.owner = doc.sender;
|
||||
doc.user_full_name = doc.sender_full_name;
|
||||
let communication_content = $(frappe.render_template('timeline_message_box', { doc }));
|
||||
this.setup_reply(communication_content, doc);
|
||||
let communication_content = $(frappe.render_template('timeline_message_box', { doc }));
|
||||
if (allow_reply) {
|
||||
this.setup_reply(communication_content, doc);
|
||||
}
|
||||
return communication_content;
|
||||
}
|
||||
|
||||
|
|
@ -209,6 +212,22 @@ class FormTimeline extends BaseTimeline {
|
|||
doc._doc_status_indicator = indicator_color;
|
||||
}
|
||||
|
||||
get_auto_messages_timeline_contents() {
|
||||
let auto_messages_timeline_contents = [];
|
||||
(this.doc_info.automated_messages|| []).forEach(message => {
|
||||
auto_messages_timeline_contents.push({
|
||||
icon: 'notification',
|
||||
icon_size: 'sm',
|
||||
creation: message.creation,
|
||||
is_card: true,
|
||||
content: this.get_communication_timeline_content(message, false),
|
||||
doctype: "Communication",
|
||||
name: message.name
|
||||
});
|
||||
});
|
||||
return auto_messages_timeline_contents;
|
||||
}
|
||||
|
||||
get_comment_timeline_contents() {
|
||||
let comment_timeline_contents = [];
|
||||
(this.doc_info.comments || []).forEach(comment => {
|
||||
|
|
|
|||
|
|
@ -151,19 +151,23 @@ function get_version_comment(version_doc, text) {
|
|||
let version_comment = "";
|
||||
let unlinked_content = "";
|
||||
|
||||
Array.from($(text)).forEach(element => {
|
||||
if ($(element).is('a')) {
|
||||
version_comment += unlinked_content ? frappe.utils.get_form_link('Version', version_doc.name, true, unlinked_content) : "";
|
||||
unlinked_content = "";
|
||||
version_comment += element.outerHTML;
|
||||
} else {
|
||||
unlinked_content += element.outerHTML || element.textContent;
|
||||
try {
|
||||
Array.from($(text)).forEach(element => {
|
||||
if ($(element).is('a')) {
|
||||
version_comment += unlinked_content ? frappe.utils.get_form_link('Version', version_doc.name, true, unlinked_content) : "";
|
||||
unlinked_content = "";
|
||||
version_comment += element.outerHTML;
|
||||
} else {
|
||||
unlinked_content += element.outerHTML || element.textContent;
|
||||
}
|
||||
});
|
||||
if (unlinked_content) {
|
||||
version_comment += frappe.utils.get_form_link('Version', version_doc.name, true, unlinked_content);
|
||||
}
|
||||
});
|
||||
if (unlinked_content) {
|
||||
version_comment += frappe.utils.get_form_link('Version', version_doc.name, true, unlinked_content);
|
||||
return version_comment;
|
||||
} catch (e) {
|
||||
// pass
|
||||
}
|
||||
return version_comment;
|
||||
}
|
||||
return frappe.utils.get_form_link('Version', version_doc.name, true, text);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
return this.script_manager.trigger("onload_post_render");
|
||||
}
|
||||
},
|
||||
() => this.focus_on_first_input(),
|
||||
() => this.is_new() && this.focus_on_first_input(),
|
||||
() => this.run_after_load_hook(),
|
||||
() => this.dashboard.after_refresh()
|
||||
]);
|
||||
|
|
@ -1075,7 +1075,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
}
|
||||
|
||||
refresh_field(fname) {
|
||||
if(this.fields_dict[fname] && this.fields_dict[fname].refresh) {
|
||||
if (this.fields_dict[fname] && this.fields_dict[fname].refresh) {
|
||||
this.fields_dict[fname].refresh();
|
||||
this.layout.refresh_dependency();
|
||||
}
|
||||
|
|
@ -1241,20 +1241,22 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
}
|
||||
}
|
||||
|
||||
set_df_property(fieldname, property, value, docname, table_field) {
|
||||
var df;
|
||||
set_df_property(fieldname, property, value, docname, table_field, table_row_name=null) {
|
||||
let df;
|
||||
if (!docname || !table_field) {
|
||||
df = this.get_docfield(fieldname);
|
||||
} else {
|
||||
var grid = this.fields_dict[fieldname].grid,
|
||||
fname = frappe.utils.filter_dict(grid.docfields, {'fieldname': table_field});
|
||||
if (fname && fname.length)
|
||||
df = frappe.meta.get_docfield(fname[0].parent, table_field, docname);
|
||||
const grid = this.fields_dict[fieldname].grid;
|
||||
const filtered_fields = frappe.utils.filter_dict(grid.docfields, {'fieldname': table_field});
|
||||
if (filtered_fields.length) {
|
||||
df = frappe.meta.get_docfield(filtered_fields[0].parent, table_field, table_row_name);
|
||||
}
|
||||
}
|
||||
if (df && df[property] != value) {
|
||||
df[property] = value;
|
||||
if (!docname || !table_field) {
|
||||
// do not refresh childtable fields since `this.fields_dict` doesn't have child table fields
|
||||
if (table_field && table_row_name) {
|
||||
this.fields_dict[fieldname].grid.grid_rows_by_docname[table_row_name].refresh_field(fieldname);
|
||||
} else {
|
||||
this.refresh_field(fieldname);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ frappe.ui.form.FormViewers = class FormViewers {
|
|||
}
|
||||
|
||||
refresh() {
|
||||
// REDESIGN-TODO: fix this
|
||||
// let users = this.frm.get_docinfo()['viewers'];
|
||||
// let currently_viewing = users.current.filter(user => user != frappe.session.user);
|
||||
// let avatar_group = frappe.avatar_group(currently_viewing, 5, {'align': 'left', 'overlap': true});
|
||||
this.parent.empty(); //.append(avatar_group);
|
||||
let users = this.frm.get_docinfo()['viewers'];
|
||||
let currently_viewing = users.current.filter(user => user != frappe.session.user);
|
||||
let avatar_group = frappe.avatar_group(currently_viewing, 5, {'align': 'left', 'overlap': true});
|
||||
this.parent.empty().append(avatar_group);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ export default class GridRow {
|
|||
constructor(opts) {
|
||||
this.on_grid_fields_dict = {};
|
||||
this.on_grid_fields = [];
|
||||
$.extend(this, opts);
|
||||
if (this.doc && this.parent_df.options) {
|
||||
this.docfields = frappe.meta.get_docfields(this.parent_df.options, this.doc.name);
|
||||
}
|
||||
this.columns = {};
|
||||
this.columns_list = [];
|
||||
$.extend(this, opts);
|
||||
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">';
|
||||
this.make();
|
||||
}
|
||||
|
|
@ -153,7 +156,7 @@ export default class GridRow {
|
|||
this.render_row(true);
|
||||
}
|
||||
|
||||
// refersh form fields
|
||||
// refresh form fields
|
||||
if(this.grid_form) {
|
||||
this.grid_form.layout && this.grid_form.layout.refresh(this.doc);
|
||||
}
|
||||
|
|
@ -249,27 +252,29 @@ export default class GridRow {
|
|||
this.focus_set = false;
|
||||
this.grid.setup_visible_columns();
|
||||
|
||||
for(var ci in this.grid.visible_columns) {
|
||||
var df = this.grid.visible_columns[ci][0],
|
||||
colsize = this.grid.visible_columns[ci][1],
|
||||
txt = this.doc ?
|
||||
frappe.format(this.doc[df.fieldname], df, null, this.doc) :
|
||||
__(df.label);
|
||||
this.grid.visible_columns.forEach((col, ci) => {
|
||||
// to get update df for the row
|
||||
let df = this.docfields.find(field => field.fieldname === col[0].fieldname);
|
||||
|
||||
if(this.doc && df.fieldtype === "Select") {
|
||||
let colsize = col[1];
|
||||
let txt = this.doc ?
|
||||
frappe.format(this.doc[df.fieldname], df, null, this.doc) :
|
||||
__(df.label);
|
||||
|
||||
if (this.doc && df.fieldtype === "Select") {
|
||||
txt = __(txt);
|
||||
}
|
||||
|
||||
if(!this.columns[df.fieldname]) {
|
||||
var column = this.make_column(df, colsize, txt, ci);
|
||||
let column;
|
||||
if (!this.columns[df.fieldname]) {
|
||||
column = this.make_column(df, colsize, txt, ci);
|
||||
} else {
|
||||
var column = this.columns[df.fieldname];
|
||||
column = this.columns[df.fieldname];
|
||||
this.refresh_field(df.fieldname, txt);
|
||||
}
|
||||
|
||||
// background color for cellz
|
||||
if(this.doc) {
|
||||
if(df.reqd && !txt) {
|
||||
// background color for cell
|
||||
if (this.doc) {
|
||||
if (df.reqd && !txt) {
|
||||
column.addClass('error');
|
||||
}
|
||||
if (column.is_invalid) {
|
||||
|
|
@ -278,7 +283,7 @@ export default class GridRow {
|
|||
column.addClass('bold');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
make_column(df, colsize, txt, ci) {
|
||||
|
|
@ -403,9 +408,9 @@ export default class GridRow {
|
|||
|
||||
if (!field.df.onchange_modified) {
|
||||
var field_on_change_function = field.df.onchange;
|
||||
field.df.onchange = function(e) {
|
||||
field.df.onchange = (e) => {
|
||||
field_on_change_function && field_on_change_function(e);
|
||||
me.grid.grid_rows[this.doc.idx - 1].refresh_field(this.df.fieldname);
|
||||
this.refresh_field(field.df.fieldname);
|
||||
};
|
||||
|
||||
field.df.onchange_modified = true;
|
||||
|
|
@ -589,42 +594,37 @@ export default class GridRow {
|
|||
}
|
||||
}
|
||||
refresh_field(fieldname, txt) {
|
||||
var df = this.grid.get_docfield(fieldname) || undefined;
|
||||
let df = this.docfields.find(col => {
|
||||
return col.fieldname === fieldname;
|
||||
});
|
||||
|
||||
// format values if no frm
|
||||
if(!df) {
|
||||
df = this.grid.visible_columns.find((col) => {
|
||||
return col[0].fieldname === fieldname;
|
||||
});
|
||||
if(df && this.doc) {
|
||||
var txt = frappe.format(this.doc[fieldname], df[0],
|
||||
null, this.doc);
|
||||
}
|
||||
if (df && this.doc) {
|
||||
txt = frappe.format(this.doc[fieldname], df, null, this.doc);
|
||||
}
|
||||
|
||||
if(txt===undefined && this.frm) {
|
||||
var txt = frappe.format(this.doc[fieldname], df,
|
||||
null, this.frm.doc);
|
||||
if (!txt && this.frm) {
|
||||
txt = frappe.format(this.doc[fieldname], df, null, this.frm.doc);
|
||||
}
|
||||
|
||||
// reset static value
|
||||
var column = this.columns[fieldname];
|
||||
if(column) {
|
||||
let column = this.columns[fieldname];
|
||||
if (column) {
|
||||
column.static_area.html(txt || "");
|
||||
if(df && df.reqd) {
|
||||
column.toggleClass('error', !!(txt===null || txt===''));
|
||||
if (df && df.reqd) {
|
||||
column.toggleClass('error', !!(txt === null || txt === ''));
|
||||
}
|
||||
}
|
||||
|
||||
let field = this.on_grid_fields_dict[fieldname];
|
||||
// reset field value
|
||||
var field = this.on_grid_fields_dict[fieldname];
|
||||
if(field) {
|
||||
if (field) {
|
||||
field.docname = this.doc.name;
|
||||
field.refresh();
|
||||
}
|
||||
|
||||
// in form
|
||||
if(this.grid_form) {
|
||||
if (this.grid_form) {
|
||||
this.grid_form.refresh_field(fieldname);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
fieldobj.doctype = me.doc.doctype;
|
||||
fieldobj.docname = me.doc.name;
|
||||
fieldobj.df = frappe.meta.get_docfield(me.doc.doctype,
|
||||
fieldobj.df.fieldname, me.frm ? me.frm.doc.name : me.doc.name) || fieldobj.df;
|
||||
fieldobj.df.fieldname, me.doc.name) || fieldobj.df;
|
||||
|
||||
// on form change, permissions can change
|
||||
if (me.frm) {
|
||||
|
|
@ -512,7 +512,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
if (form_obj) {
|
||||
if (this.doc && this.doc.parent) {
|
||||
form_obj.setting_dependency = true;
|
||||
form_obj.set_df_property(this.doc.parentfield, property, value, this.doc.parent, fieldname);
|
||||
form_obj.set_df_property(this.doc.parentfield, property, value, this.doc.parent, fieldname, this.doc.name);
|
||||
form_obj.setting_dependency = false;
|
||||
// refresh child fields
|
||||
this.fields_dict[fieldname] && this.fields_dict[fieldname].refresh();
|
||||
|
|
|
|||
|
|
@ -112,7 +112,6 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
};
|
||||
|
||||
var check_mandatory = function () {
|
||||
var me = this;
|
||||
var has_errors = false;
|
||||
frm.scroll_set = false;
|
||||
|
||||
|
|
@ -124,8 +123,8 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
|
||||
$.each(frappe.meta.docfield_list[doc.doctype] || [], function (i, docfield) {
|
||||
if (docfield.fieldname) {
|
||||
var df = frappe.meta.get_docfield(doc.doctype,
|
||||
docfield.fieldname, frm.doc.name);
|
||||
const df = frappe.meta.get_docfield(doc.doctype,
|
||||
docfield.fieldname, doc.name);
|
||||
|
||||
if (df.fieldtype === "Fold") {
|
||||
folded = frm.layout.folded;
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ frappe.ui.form.Share = Class.extend({
|
|||
user: user,
|
||||
read: $(d.body).find(".add-share-read").prop("checked") ? 1 : 0,
|
||||
write: $(d.body).find(".add-share-write").prop("checked") ? 1 : 0,
|
||||
submit: $(d.body).find(".add-share-submit").prop("checked") ? 1 : 0,
|
||||
share: $(d.body).find(".add-share-share").prop("checked") ? 1 : 0,
|
||||
notify: 1,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
<div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6"><h6>{%= __("User") %}</h6></div>
|
||||
<div class="col-xs-4"><h6>{%= __("User") %}</h6></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><h6>{%= __("Can Read") %}</h6></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><h6>{%= __("Can Write") %}</h6></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><h6>{%= __("Can Submit") %}</h6></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><h6>{%= __("Can Share") %}</h6></div>
|
||||
</div>
|
||||
|
||||
<div class="row shared-user" data-everyone=1>
|
||||
<div class="col-xs-6 share-all"><b>{{ __("Everyone") }}</b></div>
|
||||
<div class="col-xs-4 share-all"><b>{{ __("Everyone") }}</b></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><input type="checkbox" name="read"
|
||||
{% if(cint(everyone.read)) { %}checked{% } %} class="edit-share"></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><input type="checkbox" name="write"
|
||||
|
|
@ -15,6 +16,11 @@
|
|||
{% if(cint(everyone.write)) { %}checked{% } %}
|
||||
{% if (!frm.perm[0].write){ %} disabled="disabled"{% } %}>
|
||||
</div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><input type="checkbox" name="submit"
|
||||
class="edit-share"
|
||||
{% if(cint(everyone.submit)) { %}checked{% } %}
|
||||
{% if (!frm.perm[0].submit){ %} disabled="disabled"{% } %}>
|
||||
</div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><input type="checkbox" name="share"
|
||||
{% if(cint(everyone.share)) { %}checked{% } %} class="edit-share"></div>
|
||||
</div>
|
||||
|
|
@ -23,11 +29,13 @@
|
|||
var s = shared[i]; %}
|
||||
{% if(s && !s.everyone) { %}
|
||||
<div class="row shared-user" data-user="{%= s.user %}" data-name="{%= s.name %}">
|
||||
<div class="col-xs-6">{%= s.user %}</div>
|
||||
<div class="col-xs-4">{%= s.user %}</div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><input type="checkbox" name="read"
|
||||
{% if(cint(s.read)) { %}checked{% } %} class="edit-share"></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><input type="checkbox" name="write"
|
||||
{% if(cint(s.write)) { %}checked{% } %} class="edit-share"{% if (!frm.perm[0].write){ %} disabled="disabled"{% } %}></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><input type="checkbox" name="submit"
|
||||
{% if(cint(s.submit)) { %}checked{% } %} class="edit-share"{% if (!frm.perm[0].submit){ %} disabled="disabled"{% } %}></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><input type="checkbox" name="share"
|
||||
{% if(cint(s.share)) { %}checked{% } %} class="edit-share"></div>
|
||||
</div>
|
||||
|
|
@ -38,22 +46,26 @@
|
|||
<hr>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6"><h6>{%= __("Share this document with") %}</h6></div>
|
||||
<div class="col-xs-4"><h6>{%= __("Share this document with") %}</h6></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><h6>{%= __("Can Read") %}</h6></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><h6>{%= __("Can Write") %}</h6></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><h6>{%= __("Can Submit") %}</h6></div>
|
||||
<div class="col-xs-2 flex justify-center align-center"><h6>{%= __("Can Share") %}</h6></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6 input-wrapper-add-share"></div>
|
||||
<div class="col-xs-4 input-wrapper-add-share"></div>
|
||||
<div class="col-xs-2 flex justify-center align-flex-start mt-2"><input type="checkbox" class="add-share-read" name="read"></div>
|
||||
<div class="col-xs-2 flex justify-center align-flex-start mt-2"><input type="checkbox" class="add-share-write" name="write"
|
||||
{% if (!frm.perm[0].write){ %} disabled="disabled"{% } %}>
|
||||
</div>
|
||||
<div class="col-xs-2 flex justify-center align-flex-start mt-2"><input type="checkbox" class="add-share-submit" name="submit"
|
||||
{% if (!frm.perm[0].submit){ %} disabled="disabled"{% } %}>
|
||||
</div>
|
||||
<div class="col-xs-2 flex justify-center align-flex-start mt-2"><input type="checkbox" class="add-share-share" name="share"></div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-primary btn-sm btn-add-share">{{ __("Add") }}</button>
|
||||
<button class="btn btn-primary btn-sm btn-add-share mt-3">{{ __("Add") }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -1,7 +1,32 @@
|
|||
<div class="timeline-message-box" data-communication-type="{{ doc.communication_type }}">
|
||||
<span class="flex justify-between">
|
||||
<span class="text-color flex">
|
||||
{% if (doc.comment_type && doc.comment_type == "Comment") { %}
|
||||
{% if (doc.communication_type && doc.communication_type == "Automated Message") { %}
|
||||
<span>
|
||||
<!-- Display maximum of 3 users-->
|
||||
{{ __("Notification sent to") }}
|
||||
{% var recipients = (doc.recipients && doc.recipients.split(",")) || [] %}
|
||||
{% var cc = (doc.cc && doc.cc.split(",")) || [] %}
|
||||
{% var bcc = (doc.bcc && doc.bcc.split(",")) || [] %}
|
||||
{% var emails = recipients.concat(cc, bcc) %}
|
||||
{% var display_emails_len = Math.min(emails.length, 3) %}
|
||||
|
||||
{% for (var i=0, len=display_emails_len; i<len; i++) { var email = emails[i]; %}
|
||||
{{ frappe.user_info(email).fullname || email }}
|
||||
{% if (len > i+1) { %}
|
||||
{{ "," }}
|
||||
{% } %}
|
||||
{% } %}
|
||||
|
||||
{% if (emails.length > display_emails_len) { %}
|
||||
{{ "..." }}
|
||||
{% } %}
|
||||
|
||||
<div class="text-muted">
|
||||
{{ comment_when(doc.creation) }}
|
||||
</div>
|
||||
</span>
|
||||
{% } else if (doc.comment_type && doc.comment_type == "Comment") { %}
|
||||
<span>
|
||||
{{ doc.user_full_name || frappe.user.full_name(doc.owner) }} {{ __("commented") }}
|
||||
<span class="text-muted margin-left">
|
||||
|
|
@ -64,4 +89,4 @@
|
|||
{% }); %}
|
||||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -210,7 +210,10 @@ frappe.ui.form.Toolbar = class Toolbar {
|
|||
}
|
||||
|
||||
make_viewers() {
|
||||
if (this.frm.viewers) return;
|
||||
if (this.frm.viewers) {
|
||||
this.frm.viewers.parent.empty();
|
||||
return;
|
||||
}
|
||||
this.frm.viewers = new frappe.ui.form.FormViewers({
|
||||
frm: this.frm,
|
||||
parent: $('<div class="form-viewers d-flex"></div>').prependTo(this.frm.page.page_actions)
|
||||
|
|
|
|||
|
|
@ -256,11 +256,15 @@ $.extend(frappe.model, {
|
|||
},
|
||||
|
||||
can_select: function(doctype) {
|
||||
return frappe.boot.user.can_select.indexOf(doctype)!==-1;
|
||||
if (frappe.boot.user) {
|
||||
return frappe.boot.user.can_select.indexOf(doctype)!==-1;
|
||||
}
|
||||
},
|
||||
|
||||
can_read: function(doctype) {
|
||||
return frappe.boot.user.can_read.indexOf(doctype)!==-1;
|
||||
if (frappe.boot.user) {
|
||||
return frappe.boot.user.can_read.indexOf(doctype)!==-1;
|
||||
}
|
||||
},
|
||||
|
||||
can_write: function(doctype) {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ $.extend(frappe.perm, {
|
|||
if (s.user === user) {
|
||||
perm[0]["read"] = perm[0]["read"] || s.read;
|
||||
perm[0]["write"] = perm[0]["write"] || s.write;
|
||||
perm[0]["submit"] = perm[0]["submit"] || s.submit;
|
||||
perm[0]["share"] = perm[0]["share"] || s.share;
|
||||
|
||||
if (s.read) {
|
||||
|
|
|
|||
|
|
@ -159,9 +159,12 @@ frappe.socketio = {
|
|||
},
|
||||
doc_open: function(doctype, docname) {
|
||||
// notify that the user has opened this doc, if not already notified
|
||||
if(!frappe.socketio.last_doc
|
||||
|| (frappe.socketio.last_doc[0]!=doctype && frappe.socketio.last_doc[1]!=docname)) {
|
||||
if (!frappe.socketio.last_doc
|
||||
|| (frappe.socketio.last_doc[0] != doctype || frappe.socketio.last_doc[1] != docname)) {
|
||||
frappe.socketio.socket.emit('doc_open', doctype, docname);
|
||||
|
||||
frappe.socketio.last_doc &&
|
||||
frappe.socketio.doc_close(frappe.socketio.last_doc[0], frappe.socketio.last_doc[1]);
|
||||
}
|
||||
frappe.socketio.last_doc = [doctype, docname];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -834,7 +834,7 @@ Object.assign(frappe.utils, {
|
|||
get_form_link: function(doctype, name, html = false, display_text = null) {
|
||||
display_text = display_text || name;
|
||||
name = encodeURIComponent(name);
|
||||
const route = `/app/${encodeURIComponent(frappe.router.slug(doctype))}/${name}`;
|
||||
const route = `/app/${encodeURIComponent(doctype.toLowerCase().replace(/ /g, '-'))}/${name}`;
|
||||
if (html) {
|
||||
return `<a href="${route}">${display_text}</a>`;
|
||||
}
|
||||
|
|
@ -938,7 +938,7 @@ Object.assign(frappe.utils, {
|
|||
});
|
||||
},
|
||||
is_rtl(lang=null) {
|
||||
return ["ar", "he", "fa"].includes(lang || frappe.boot.lang);
|
||||
return ["ar", "he", "fa", "ps"].includes(lang || frappe.boot.lang);
|
||||
},
|
||||
bind_actions_with_object($el, object) {
|
||||
// remove previously bound event
|
||||
|
|
|
|||
|
|
@ -603,7 +603,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
},
|
||||
|
||||
delete_saved_draft() {
|
||||
if (this.dialog) {
|
||||
if (this.dialog && this.frm) {
|
||||
localforage.removeItem(this.frm.doctype + this.frm.docname).catch(e => {
|
||||
if (e) {
|
||||
// silently fail
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
--gray-900: #161a1f;
|
||||
|
||||
// Type Colors
|
||||
--text-muted: var(--gray-300);
|
||||
--text-light: var(--gray-400);
|
||||
--text-muted: var(--gray-400);
|
||||
--text-light: var(--gray-300);
|
||||
--text-color: var(--gray-50);
|
||||
--heading-color: var(--gray-50);
|
||||
|
||||
|
|
@ -114,19 +114,21 @@
|
|||
// --criticism-bg: var(--red-600);
|
||||
|
||||
// Frappe Charts Colors
|
||||
--charts-label-color: var(--gray-300);
|
||||
--charts-axis-line-color: var(--gray-500);
|
||||
.chart-container {
|
||||
--charts-label-color: var(--gray-300);
|
||||
--charts-axis-line-color: var(--gray-500);
|
||||
|
||||
--charts-stroke-width: 5px;
|
||||
--charts-dataset-circle-stroke: #ffffff;
|
||||
--charts-dataset-circle-stroke-width: var(--charts-stroke-width);
|
||||
--charts-stroke-width: 5px;
|
||||
--charts-dataset-circle-stroke: #ffffff;
|
||||
--charts-dataset-circle-stroke-width: var(--charts-stroke-width);
|
||||
|
||||
--charts-tooltip-title: var(--charts-label-color);
|
||||
--charts-tooltip-label: var(--charts-label-color);
|
||||
--charts-tooltip-value: white;
|
||||
--charts-tooltip-bg: var(--gray-900);
|
||||
--charts-tooltip-title: var(--charts-label-color);
|
||||
--charts-tooltip-label: var(--charts-label-color);
|
||||
--charts-tooltip-value: white;
|
||||
--charts-tooltip-bg: var(--gray-900);
|
||||
|
||||
--charts-legend-label: var(--charts-label-color);
|
||||
--charts-legend-label: var(--charts-label-color);
|
||||
}
|
||||
|
||||
// find better fix
|
||||
.heatmap-chart {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
@import "notification";
|
||||
@import "global_search";
|
||||
@import "desktop";
|
||||
@import "awesomebar";
|
||||
@import "../common/awesomeplete";
|
||||
@import "sidebar";
|
||||
@import "filters";
|
||||
@import "list";
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
@import "../common/modal";
|
||||
@import "../common/indicator";
|
||||
@import "../common/controls";
|
||||
@import "../common/awesomeplete";
|
||||
@import 'multilevel_dropdown';
|
||||
@import 'website_image';
|
||||
@import 'website_avatar';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,16 @@
|
|||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
.section-head {
|
||||
font-weight: bold;
|
||||
font-size: var(--text-xl);
|
||||
padding: var(--padding-md) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-column {
|
||||
padding: 0 var(--padding-md);
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from frappe.desk.doctype.notification_log.notification_log import enqueue_create
|
|||
from frappe.utils import cint
|
||||
|
||||
@frappe.whitelist()
|
||||
def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=None, notify=0):
|
||||
def add(doctype, name, user=None, read=1, write=0, submit=0, share=0, everyone=0, flags=None, notify=0):
|
||||
"""Share the given document with a user."""
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
|
|
@ -38,6 +38,7 @@ def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=No
|
|||
# always add read, since you are adding!
|
||||
"read": 1,
|
||||
"write": cint(write),
|
||||
"submit": cint(submit),
|
||||
"share": cint(share)
|
||||
})
|
||||
|
||||
|
|
@ -78,11 +79,11 @@ def set_permission(doctype, name, user, permission_to, value=1, everyone=0):
|
|||
if not value:
|
||||
# un-set higher-order permissions too
|
||||
if permission_to=="read":
|
||||
share.read = share.write = share.share = 0
|
||||
share.read = share.write = share.submit = share.share = 0
|
||||
|
||||
share.save()
|
||||
|
||||
if not (share.read or share.write or share.share):
|
||||
if not (share.read or share.write or share.submit or share.share):
|
||||
share.delete()
|
||||
share = {}
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ def set_permission(doctype, name, user, permission_to, value=1, everyone=0):
|
|||
def get_users(doctype, name):
|
||||
"""Get list of users with which this document is shared"""
|
||||
return frappe.db.get_all("DocShare",
|
||||
fields=["`name`", "`user`", "`read`", "`write`", "`share`", "everyone", "owner", "creation"],
|
||||
fields=["`name`", "`user`", "`read`", "`write`", "`submit`", "`share`", "everyone", "owner", "creation"],
|
||||
filters=dict(
|
||||
share_doctype=doctype,
|
||||
share_name=name
|
||||
|
|
|
|||
|
|
@ -110,5 +110,39 @@
|
|||
{%- endblock %}
|
||||
<!-- csrf_token -->
|
||||
{%- block body_include %}{{ body_include or "" }}{% endblock -%}
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
if (frappe.session.user === 'Guest') {
|
||||
frappe.call("frappe.translate.get_all_languages", {
|
||||
with_language_name: true
|
||||
}).then(res => {
|
||||
let language_list = res.message;
|
||||
let language = frappe.get_cookie('preferred_language');
|
||||
let language_codes = [];
|
||||
language_list.forEach(language_doc => {
|
||||
language_codes.push(language_doc.language_code)
|
||||
$("#language-switcher")
|
||||
.append(
|
||||
$("<option></option>")
|
||||
.attr("value", language_doc.language_code)
|
||||
.text(language_doc.language_name)
|
||||
);
|
||||
});
|
||||
$("#language-switcher").removeClass('hide');
|
||||
language = language || (language_codes.includes(navigator.language) ? navigator.language : 'en');
|
||||
$("#language-switcher").val(language);
|
||||
document.documentElement.lang = language;
|
||||
$("#language-switcher").change((e) => {
|
||||
let lang = $("#language-switcher").val();
|
||||
frappe.call("frappe.translate.set_preferred_language_cookie", {
|
||||
"preferred_language": lang
|
||||
}).then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue