Merge develop into ip-rest

This commit is contained in:
Himanshu Warekar 2019-09-25 23:58:29 +05:30
commit 315b91ea5d
40 changed files with 479 additions and 188 deletions

View file

@ -23,7 +23,7 @@ if sys.version[0] == '2':
reload(sys)
sys.setdefaultencoding("utf-8")
__version__ = '12.0.13'
__version__ = '12.0.15'
__title__ = "Frappe Framework"
local = Local()
@ -1039,7 +1039,13 @@ def get_newargs(fn, kwargs):
if hasattr(fn, 'fnargs'):
fnargs = fn.fnargs
else:
fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
try:
fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
except ValueError:
fnargs = inspect.getfullargspec(fn).args
varargs = inspect.getfullargspec(fn).varargs
varkw = inspect.getfullargspec(fn).varkw
defaults = inspect.getfullargspec(fn).defaults
newargs = {}
for a in kwargs:
@ -1409,8 +1415,9 @@ def publish_progress(*args, **kwargs):
:param percent: Percent progress
:param title: Title
:param doctype: Optional, for DocType
:param name: Optional, for Document name
:param doctype: Optional, for document type
:param docname: Optional, for document name
:param description: Optional description
"""
import frappe.realtime
return frappe.realtime.publish_progress(*args, **kwargs)

View file

@ -21,6 +21,7 @@
"designation",
"gender",
"phone",
"mobile_no",
"company_name",
"image",
"sb_00",
@ -192,9 +193,15 @@
{
"fieldname": "phone_nos",
"fieldtype": "Table",
"label": "Phone Nos",
"label": "Numbers",
"options": "Contact Phone"
},
{
"fieldname": "mobile_no",
"fieldtype": "Data",
"label": "Mobile No",
"read_only": 1
},
{
"default": "0",
"fieldname": "pulled_from_google_contacts",
@ -238,8 +245,8 @@
"icon": "fa fa-user",
"idx": 1,
"image_field": "image",
"modified": "2019-09-13 15:50:38.999884",
"modified_by": "himanshu@erpnext.com",
"modified": "2019-09-24 17:48:26.790985",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Contact",
"name_case": "Title Case",

View file

@ -29,11 +29,10 @@ class Contact(Document):
break
def validate(self):
self.set_primary("email_id", "email_ids")
self.set_primary("phone", "phone_nos")
if self.email_id:
self.email_id = self.email_id.strip()
self.set_primary_email()
self.set_primary("phone")
self.set_primary("mobile_no")
self.check_if_primary_phone_and_mobile_no_same()
self.set_user()
@ -79,24 +78,51 @@ class Contact(Document):
if autosave:
self.save(ignore_permissions=True)
def add_phone(self, phone, is_primary=0, autosave=False):
def add_phone(self, phone, is_primary_phone=0, is_primary_mobile_no=0, autosave=False):
self.append("phone_nos", {
"phone": phone,
"is_primary": is_primary
"is_primary_phone": is_primary_phone,
"is_primary_mobile_no": is_primary_mobile_no
})
if autosave:
self.save(ignore_permissions=True)
def set_primary(self, fieldname, child_table):
if len(self.get(child_table)) == 1:
self.get(child_table)[0].is_primary = 1
setattr(self, fieldname, self.get(child_table)[0].get(fieldname))
else:
for d in self.get(child_table):
if d.is_primary == 1:
setattr(self, fieldname, d.get(fieldname))
break
def set_primary_email(self):
if not self.email_ids:
self.email_id = ""
return
if len([email.email_id for email in self.email_ids if email.is_primary]) > 1:
frappe.throw(_("Only one {0} can be set as primary.").format(frappe.bold("Email ID")))
for d in self.email_ids:
if d.is_primary == 1:
self.email_id = d.email_id.strip()
break
def set_primary(self, fieldname):
# Used to set primary mobile and phone no.
if len(self.phone_nos) == 0:
setattr(self, fieldname, "")
return
field_name = "is_primary_" + fieldname
is_primary = [phone.phone for phone in self.phone_nos if phone.get(field_name)]
if len(is_primary) > 1:
frappe.throw(_("Only one {0} can be set as primary.").format(frappe.bold(frappe.unscrub(fieldname))))
for d in self.phone_nos:
if d.get(field_name) == 1:
setattr(self, fieldname, d.phone)
break
def check_if_primary_phone_and_mobile_no_same(self):
if self.phone and self.mobile_no and self.phone == self.mobile_no:
number = frappe.bold(self.phone)
frappe.throw(_("Number {0} cannot be set as primary for Phone as well as Mobile No.").format(number))
def get_default_contact(doctype, name):
'''Returns default contact for the given doctype, name'''

View file

@ -5,8 +5,82 @@ from __future__ import unicode_literals
import frappe
import unittest
test_records = frappe.get_test_records('Contact')
from frappe.exceptions import ValidationError
class TestContact(unittest.TestCase):
pass
def test_check_default_email(self):
emails = [
{"email": "test1@example.com", "is_primary": 0},
{"email": "test2@example.com", "is_primary": 0},
{"email": "test3@example.com", "is_primary": 0},
{"email": "test4@example.com", "is_primary": 1},
{"email": "test5@example.com", "is_primary": 0},
]
contact = create_contact("Email", "Mr", emails=emails)
self.assertEqual(contact.email_id, "test4@example.com")
def test_check_default_phone_and_mobile(self):
phones = [
{"phone": "+91 0000000000", "is_primary_phone": 0, "is_primary_mobile_no": 0},
{"phone": "+91 0000000001", "is_primary_phone": 0, "is_primary_mobile_no": 0},
{"phone": "+91 0000000002", "is_primary_phone": 1, "is_primary_mobile_no": 0},
{"phone": "+91 0000000003", "is_primary_phone": 0, "is_primary_mobile_no": 1},
]
contact = create_contact("Phone", "Mr", phones=phones)
self.assertEqual(contact.phone, "+91 0000000002")
self.assertEqual(contact.mobile_no, "+91 0000000003")
def test_same_phone_and_mobile(self):
phones = [
{"phone": "+91 0000000000", "is_primary_phone": 1, "is_primary_mobile_no": 1},
]
contact = create_contact("Phone", "Mr", phones=phones, save=False)
self.assertRaises(ValidationError, contact.save)
def test_no_primary_set(self):
emails = [
{"email": "test1@example.com", "is_primary": 0},
{"email": "test2@example.com", "is_primary": 0},
{"email": "test3@example.com", "is_primary": 0},
{"email": "test4@example.com", "is_primary": 0},
{"email": "test5@example.com", "is_primary": 0},
]
phones = [
{"phone": "+91 0000000000", "is_primary_phone": 0, "is_primary_mobile_no": 0},
{"phone": "+91 0000000001", "is_primary_phone": 0, "is_primary_mobile_no": 0},
{"phone": "+91 0000000002", "is_primary_phone": 1, "is_primary_mobile_no": 1},
{"phone": "+91 0000000003", "is_primary_phone": 0, "is_primary_mobile_no": 0},
]
contact_email = create_contact("Default", "Mr", emails=emails, phones=phones, save=False)
contact_phone = create_contact("Default", "Mr", emails=emails, phones=phones, save=False)
# No default set for emails if many emails are passed as params
self.assertRaises(ValidationError, contact_email.save)
# No default set for phones if many phones are passed as params
self.assertRaises(ValidationError, contact_phone.save)
def create_contact(name, salutation, emails=None, phones=None, save=True):
doc = frappe.get_doc({
"doctype": "Contact",
"first_name": name,
"status": "Open",
"salutation": salutation
})
if emails:
for d in emails:
doc.add_email(d.get("email"), d.get("is_primary"))
if phones:
for d in phones:
doc.add_phone(d.get("phone"), d.get("is_primary_phone"), d.get("is_primary_mobile_no"))
if save:
doc.insert()
return doc

View file

@ -1,19 +1,39 @@
[
{
"doctype": "Contact",
"salutation": "Mr",
"email_id": "test_contact@example.com",
"first_name": "_Test Contact For _Test Customer",
"is_primary_contact": 1,
"phone": "+91 0000000000",
"status": "Open"
},
{
"doctype": "Contact",
"email_id": "test_contact@example.com",
"first_name": "_Test Contact For _Test Supplier",
"is_primary_contact": 1,
"phone": "+91 0000000000",
"status": "Open"
}
{
"doctype": "Contact",
"salutation": "Mr",
"first_name": "_Test Contact For _Test Customer",
"is_primary_contact": 1,
"status": "Open",
"email_ids": [
{
"email_id": "test_contact@example.com",
"is_primary": 1
}
],
"phone_nos": [
{
"phone": "+91 0000000000",
"is_primary_phone": 1
}
]
},
{
"doctype": "Contact",
"first_name": "_Test Contact For _Test Supplier",
"is_primary_contact": 1,
"status": "Open",
"email_ids": [
{
"email_id": "test_contact@example.com",
"is_primary": 1
}
],
"phone_nos": [
{
"phone": "+91 0000000000",
"is_primary_phone": 1
}
]
}
]

View file

@ -13,9 +13,11 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Email ID",
"options": "Email"
"options": "Email",
"reqd": 1
},
{
"columns": 2,
"default": "0",
"fieldname": "is_primary",
"fieldtype": "Check",
@ -24,7 +26,7 @@
}
],
"istable": 1,
"modified": "2019-08-02 13:14:22.193463",
"modified": "2019-09-24 17:47:30.565805",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Contact Email",

View file

@ -5,25 +5,36 @@
"engine": "InnoDB",
"field_order": [
"phone",
"is_primary"
"is_primary_phone",
"is_primary_mobile_no"
],
"fields": [
{
"default": "0",
"fieldname": "is_primary",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Primary"
},
{
"fieldname": "phone",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Phone"
"label": "Number",
"reqd": 1
},
{
"columns": 2,
"default": "0",
"fieldname": "is_primary_phone",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Primary Phone"
},
{
"columns": 2,
"default": "0",
"fieldname": "is_primary_mobile_no",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Primary Mobile"
}
],
"istable": 1,
"modified": "2019-08-05 11:40:59.104224",
"modified": "2019-09-24 17:47:50.375326",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Contact Phone",

View file

@ -7,7 +7,7 @@ import frappe
from frappe import _
field_map = {
"Contact": ["first_name", "last_name", "address", "phone", "email_id", "is_primary_contact"],
"Contact": ["first_name", "last_name", "address", "phone", "mobile_no", "email_id", "is_primary_contact"],
"Address": ["address_line1", "address_line2", "city", "state", "pincode", "country", "is_primary_address"]
}

View file

@ -82,8 +82,8 @@ def create_linked_contact(link_list, address):
"address": address,
"status": "Open"
})
contact.add_email("test_contact@example.com")
contact.add_phone("+91 0000000000")
contact.add_email("test_contact@example.com", is_primary=True)
contact.add_phone("+91 0000000000", is_primary_phone=True)
for name in link_list:
contact.append("links",{
@ -103,7 +103,7 @@ class TestAddressesAndContacts(unittest.TestCase):
create_linked_contact(links_list, d)
report_data = get_data({"reference_doctype": "Test Custom Doctype"})
for idx, link in enumerate(links_list):
test_item = [link, 'test address line 1', 'test address line 2', 'Milan', None, None, 'Italy', 0, '_Test First Name', '_Test Last Name', '_Test Address-Billing', '+91 0000000000', 'test_contact@example.com', 1]
test_item = [link, 'test address line 1', 'test address line 2', 'Milan', None, None, 'Italy', 0, '_Test First Name', '_Test Last Name', '_Test Address-Billing', '+91 0000000000', None, 'test_contact@example.com', 1]
self.assertListEqual(test_item, report_data[idx])
def tearDown(self):

View file

@ -10,20 +10,16 @@ test_records = frappe.get_test_records('Role')
class TestUser(unittest.TestCase):
def test_disable_role(self):
frappe.get_doc("User", "test@example.com").add_roles("_Test Role 3")
role = frappe.get_doc("Role", "_Test Role 3")
role.disabled = 1
role.save()
self.assertTrue("_Test Role 3" not in frappe.get_roles("test@example.com"))
frappe.get_doc("User", "test@example.com").add_roles("_Test Role 3")
self.assertTrue("_Test Role 3" not in frappe.get_roles("test@example.com"))
role = frappe.get_doc("Role", "_Test Role 3")
role.disabled = 0
role.save()
frappe.get_doc("User", "test@example.com").add_roles("_Test Role 3")
self.assertTrue("_Test Role 3" in frappe.get_roles("test@example.com"))

View file

@ -38,7 +38,6 @@
"mute_sounds",
"change_password",
"new_password",
"send_password_update_notification",
"logout_all_sessions",
"reset_password_key",
"last_password_reset_date",
@ -299,13 +298,6 @@
"label": "Set New Password",
"no_copy": 1
},
{
"default": "0",
"depends_on": "eval:!doc.__islocal",
"fieldname": "send_password_update_notification",
"fieldtype": "Check",
"label": "Send Password Update Notification"
},
{
"default": "0",
"fieldname": "logout_all_sessions",
@ -593,7 +585,7 @@
"idx": 413,
"image_field": "user_image",
"max_attachments": 5,
"modified": "2019-08-09 10:34:56.912283",
"modified": "2019-09-18 14:14:01.233124",
"modified_by": "Administrator",
"module": "Core",
"name": "User",

View file

@ -153,10 +153,6 @@ class User(Document):
if new_password and not self.flags.in_insert:
_update_password(user=self.name, pwd=new_password, logout_all_sessions=self.logout_all_sessions)
if self.send_password_update_notification and self.enabled:
self.password_update_mail(new_password)
frappe.msgprint(_("New password emailed"))
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':
@ -250,10 +246,6 @@ class User(Document):
self.send_login_mail(_("Password Reset"),
"password_reset", {"link": link}, now=True)
def password_update_mail(self, password):
self.send_login_mail(_("Password Update"),
"password_update", {"new_password": password}, now=True)
def send_welcome_mail_to_user(self):
from frappe.utils import get_url
link = self.reset_password()
@ -1032,9 +1024,10 @@ def update_roles(role_profile):
user.add_roles(*roles)
def create_contact(user, ignore_links=False, ignore_mandatory=False):
from frappe.contacts.doctype.contact.contact import get_contact_name
if user.name in ["Administrator", "Guest"]: return
if not frappe.db.get_value("Contact", {"email_id": user.email}):
if not get_contact_name(user.email):
contact = frappe.get_doc({
"doctype": "Contact",
"first_name": user.first_name,
@ -1044,7 +1037,7 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False):
})
if user.email:
contact.add_email(user.email)
contact.add_email(user.email, is_primary=True)
if user.phone:
contact.add_phone(user.phone)
@ -1053,7 +1046,6 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False):
contact.add_phone(user.mobile_no)
contact.insert(ignore_permissions=True, ignore_links=ignore_links, ignore_mandatory=ignore_mandatory)
@frappe.whitelist()
def generate_keys(user):
"""

View file

@ -607,7 +607,7 @@ class Database(object):
"""Update multiple values. Alias for `set_value`."""
return self.set_value(*args, **kwargs)
def set_value(self, dt, dn, field, val, modified=None, modified_by=None,
def set_value(self, dt, dn, field, val=None, modified=None, modified_by=None,
update_modified=True, debug=False):
"""Set a single value in the database, do not call the ORM triggers
but update the modified timestamp (unless specified not to).

View file

@ -52,7 +52,7 @@ class Event(Document):
["Communication Link", "link_doctype", "=", participant.reference_doctype],
["Communication Link", "link_name", "=", participant.reference_docname]
]
comms = frappe.get_list("Communication", filters=filters, fields=["name"])
comms = frappe.get_all("Communication", filters=filters, fields=["name"])
if comms:
for comm in comms:

View file

@ -31,7 +31,14 @@ def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None):
except ValueError:
args = args
fnargs, varargs, varkw, defaults = inspect.getargspec(getattr(doc, method))
try:
fnargs, varargs, varkw, defaults = inspect.getargspec(getattr(doc, method))
except ValueError:
fnargs = inspect.getfullargspec(getattr(doc, method)).args
varargs = inspect.getfullargspec(getattr(doc, method)).varargs
varkw = inspect.getfullargspec(getattr(doc, method)).varkw
defaults = inspect.getfullargspec(getattr(doc, method)).defaults
if not fnargs or (len(fnargs)==1 and fnargs[0]=="self"):
r = doc.run_method(method)

View file

@ -562,7 +562,7 @@ def get_linked_doctypes(columns, data):
for idx, col in enumerate(columns):
df = columns_dict[idx]
if df.get("fieldtype")=="Link":
if isinstance(col, string_types):
if data and isinstance(data[0], (list, tuple)):
linked_doctypes[df["options"]] = idx
else:
# dict

View file

@ -270,7 +270,7 @@ user_privacy_documents = [
{
'doctype': 'Contact',
'match_field': 'email_id',
'personal_fields': ['first_name', 'last_name', 'phone'],
'personal_fields': ['first_name', 'last_name', 'phone', 'mobile_no'],
},
{
'doctype': 'Contact Email',

View file

@ -187,7 +187,7 @@ def sync_contacts_from_google_contacts(g_contact):
contact.add_email(email_id=email.get("value"), is_primary=1 if email.get("metadata").get("primary") else 0)
for phone in connection.get("phoneNumbers", []):
contact.add_phone(phone=phone.get("value"), is_primary=1 if phone.get("metadata").get("primary") else 0)
contact.add_phone(phone=phone.get("value"), is_primary_phone=1 if phone.get("metadata").get("primary") else 0)
contact.insert(ignore_permissions=True)

View file

@ -454,6 +454,18 @@ class BaseDocument(object):
doctype = df.options
if not doctype:
frappe.throw(_("Options not set for link field {0}").format(df.fieldname))
meta = frappe.get_meta(doctype)
if meta.has_field('disabled'):
if not (
frappe.flags.in_import
or frappe.flags.in_migrate
or frappe.flags.in_install
or frappe.flags.in_patch
):
disabled = frappe.get_value(doctype, self.get(df.fieldname), 'disabled')
if disabled:
frappe.throw(_("{0} is disabled").format(frappe.bold(self.get(df.fieldname))))
else:
doctype = self.get(df.options)
if not doctype:

View file

@ -458,18 +458,6 @@ class DatabaseQuery(object):
value = get_between_date_filter(f.value, df)
fallback = "'0001-01-01 00:00:00'"
elif df and df.fieldtype=="Date":
value = frappe.db.format_date(f.value)
fallback = "'0001-01-01'"
elif (df and df.fieldtype=="Datetime") or isinstance(f.value, datetime):
value = frappe.db.format_datetime(f.value)
fallback = "'0001-01-01 00:00:00'"
elif df and df.fieldtype=="Time":
value = get_time(f.value).strftime("%H:%M:%S.%f")
fallback = "'00:00:00'"
elif f.operator.lower() == "is":
if f.value == 'set':
f.operator = '!='
@ -483,6 +471,18 @@ class DatabaseQuery(object):
if 'ifnull' not in column_name:
column_name = 'ifnull({}, {})'.format(column_name, fallback)
elif df and df.fieldtype=="Date":
value = frappe.db.format_date(f.value)
fallback = "'0001-01-01'"
elif (df and df.fieldtype=="Datetime") or isinstance(f.value, datetime):
value = frappe.db.format_datetime(f.value)
fallback = "'0001-01-01 00:00:00'"
elif df and df.fieldtype=="Time":
value = get_time(f.value).strftime("%H:%M:%S.%f")
fallback = "'00:00:00'"
elif f.operator.lower() in ("like", "not like") or (isinstance(f.value, string_types) and
(not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])):
value = "" if f.value==None else f.value

View file

@ -75,7 +75,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa
delete_from_table(doctype, name, ignore_doctypes, None)
if not (frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_test):
if not (for_reload or frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_test):
try:
delete_controllers(name, doc.module)
except (FileNotFoundError, OSError):

View file

@ -1232,6 +1232,18 @@ class Document(BaseDocument):
frappe.bold(self.meta.get_label(from_date_field)),
), frappe.exceptions.InvalidDates)
def get_assigned_users(self):
assignments = frappe.get_all('ToDo',
fields=['owner'],
filters={
'reference_type': self.doctype,
'reference_name': self.name,
'status': ('!=', 'Cancelled'),
})
users = set([assignment.owner for assignment in assignments])
return users
def execute_action(doctype, name, action, **kwargs):
'''Execute an action on a document (called by background worker)'''
doc = frappe.get_doc(doctype, name)

View file

@ -119,7 +119,7 @@ def sync_customizations_for_doctype(data, folder):
custom_doctype, doctype_fieldname), doc_type)
for d in data[key]:
_insert(data)
_insert(d)
else:
for d in data[key]:
@ -242,7 +242,7 @@ def make_boilerplate(template, doc, opts=None):
base_class = 'Document'
base_class_import = 'from frappe.model.document import Document'
if doc.is_tree:
if doc.get('is_tree'):
base_class = 'NestedSet'
base_class_import = 'from frappe.utils.nestedset import NestedSet'

View file

@ -117,18 +117,20 @@ frappe.ui.form.Form = class FrappeForm {
add_nav_keyboard_shortcuts() {
frappe.ui.keys.add_shortcut({
shortcut: 'shift+>',
shortcut: 'shift+ctrl+>',
action: () => this.navigate_records(0),
page: this.page,
description: __('Go to next record'),
ignore_inputs: true,
condition: () => !this.is_new()
});
frappe.ui.keys.add_shortcut({
shortcut: 'shift+<',
shortcut: 'shift+ctrl+<',
action: () => this.navigate_records(1),
page: this.page,
description: __('Go to previous record'),
ignore_inputs: true,
condition: () => !this.is_new()
});
}
@ -837,10 +839,16 @@ frappe.ui.form.Form = class FrappeForm {
frappe.call('frappe.desk.form.utils.get_next', args).then(r => {
if (r.message) {
frappe.set_route('Form', this.doctype, r.message);
this.focus_on_first_input();
}
});
}
focus_on_first_input() {
let $first_input_el = $(frappe.container.page).find('.frappe-control:visible').eq(0);
$first_input_el.find('input, select, textarea').focus();
}
rename_doc() {
frappe.model.rename_doc(this.doctype, this.docname, () => this.refresh_header());
}

View file

@ -186,7 +186,9 @@ frappe.ui.form.QuickEntryForm = Class.extend({
}
},
error: function() {
me.open_doc();
if (!me.skip_redirect_on_error) {
me.open_doc();
}
},
always: function() {
me.dialog.working = false;

View file

@ -8,12 +8,26 @@ frappe.ui.FilterGroup = class {
make() {
this.wrapper.append(this.get_container_template());
this.toggle_clear_filter();
this.set_events();
}
toggle_clear_filter() {
let clear_filter_button = this.wrapper.find('.remove-filters');
if (this.filters.length == 0) {
clear_filter_button.hide();
} else {
clear_filter_button.show();
}
}
set_events() {
this.wrapper.find('.add-filter').on('click', () => {
this.add_filter(this.doctype, 'name');
this.add_filter(this.doctype, 'name')
.then(this.toggle_clear_filter());
});
this.wrapper.find('.remove-filters').on('click', () => {
this.clear_filters();
});
}
@ -34,7 +48,6 @@ frappe.ui.FilterGroup = class {
// {}: Add in page filter by fieldname if exists ('=' => 'like')
if(!this.validate_args(doctype, fieldname)) return false;
const is_new_filter = arguments.length < 2;
if (is_new_filter && this.wrapper.find(".new-filter:visible").length) {
// only allow 1 new filter at a time!
@ -123,6 +136,7 @@ frappe.ui.FilterGroup = class {
update_filters() {
this.filters = this.filters.filter(f => f.field); // remove hidden filters
this.toggle_clear_filter();
}
clear_filters() {
@ -140,9 +154,12 @@ frappe.ui.FilterGroup = class {
get_container_template() {
return $(`<div class="tag-filters-area">
<div class="active-tag-filters">
<button class="btn btn-default btn-xs add-filter text-muted">
<button class="btn btn-default btn-xs filter-button text-muted add-filter">
${__("Add Filter")}
</button>
<button class="btn btn-default btn-xs filter-button text-muted remove-filters">
${__("Clear Filters")}
</button>
</div>
</div>
<div class="filter-edit-area"></div>`);

View file

@ -41,7 +41,7 @@ export default {
methods: {
update_current_module() {
let route = frappe.get_route()
if (route[0] === 'modules' || !route[0]) {
if (route[0] === 'modules') {
this.route = route
let module = this.modules_list.filter(m => m.module_name == route[1])[0]
let module_name = module && (module.label || module.module_name)

View file

@ -1,9 +1,17 @@
@import 'common';
.active-tag-filters {
.add-filter, .filter-tag {
display: flex;
overflow: scroll;
.filter-button, .filter-tag {
margin: 0 10px 10px 0;
}
.add-filter {
align-self: flex-start;
}
.remove-filters {
margin-left: auto;
}
}
.toggle-filter {
@ -47,7 +55,6 @@
.filter-box .row > div[class*="col-sm-"] {
padding-right: 0px;
}
.filter-field {
width: 65% !important;

View file

@ -7,6 +7,7 @@ import frappe
import unittest
from .energy_point_log import get_energy_points as _get_energy_points, create_review_points_log, review
from frappe.utils.testutils import add_custom_field, clear_custom_fields
from frappe.desk.form.assign_to import add as assign_to
class TestEnergyPointLog(unittest.TestCase):
def tearDown(self):
@ -185,16 +186,53 @@ class TestEnergyPointLog(unittest.TestCase):
self.assertEquals(points_after_todo_creation,
points_before_todo_creation + todo_point_rule.points)
def create_energy_point_rule_for_todo(multiplier_field=None, for_doc_event='Custom', max_points=None):
name = 'ToDo Closed'
point_rule = frappe.db.get_all(
'Energy Point Rule',
{'name': name},
['*'],
limit=1
)
def test_point_allocation_for_assigned_users(self):
todo = create_a_todo()
if point_rule: return point_rule[0]
assign_users_to_todo(todo.name, ['test@example.com', 'test2@example.com'])
test_user_before_points = get_points('test@example.com')
test2_user_before_points = get_points('test2@example.com')
rule = create_energy_point_rule_for_todo(for_assigned_users=1)
todo.status = 'Closed'
todo.save()
test_user_after_points = get_points('test@example.com')
test2_user_after_points = get_points('test2@example.com')
self.assertEquals(test_user_after_points,
test_user_before_points + rule.points)
self.assertEquals(test2_user_after_points,
test2_user_before_points + rule.points)
def test_points_on_field_value_change(self):
rule = create_energy_point_rule_for_todo(for_doc_event='Value Change',
field_to_check='description')
frappe.set_user('test@example.com')
points_before_todo_creation = get_points('test@example.com')
todo = create_a_todo()
todo.status = 'Closed'
todo.save()
points_after_closing_todo = get_points('test@example.com')
self.assertEquals(points_after_closing_todo,
points_before_todo_creation)
todo.description = 'This is new todo'
todo.save()
points_after_changing_todo_description = get_points('test@example.com')
self.assertEquals(points_after_changing_todo_description,
points_before_todo_creation + rule.points)
def create_energy_point_rule_for_todo(multiplier_field=None, for_doc_event='Custom',
max_points=None, for_assigned_users=0, field_to_check=None):
name = 'ToDo Closed'
point_rule_exists = frappe.db.exists('Energy Point Rule', name)
if point_rule_exists: return frappe.get_doc('Energy Point Rule', name)
return frappe.get_doc({
'doctype': 'Energy Point Rule',
@ -204,8 +242,10 @@ def create_energy_point_rule_for_todo(multiplier_field=None, for_doc_event='Cust
'condition': 'doc.status == "Closed"',
'for_doc_event': for_doc_event,
'user_field': 'owner',
'for_assigned_users': for_assigned_users,
'multiplier_field': multiplier_field,
'max_points': max_points
'max_points': max_points,
'field_to_check': field_to_check
}).insert(ignore_permissions=1)
def create_a_todo():
@ -216,4 +256,12 @@ def create_a_todo():
def get_points(user, point_type='energy_points'):
return _get_energy_points(user).get(point_type) or 0
return _get_energy_points(user).get(point_type) or 0
def assign_users_to_todo(todo_name, users):
for user in users:
assign_to({
'assign_to': user,
'doctype': 'ToDo',
'name': todo_name
})

View file

@ -2,19 +2,34 @@
// For license information, please see license.txt
frappe.ui.form.on('Energy Point Rule', {
refresh: function(frm) {
frm.events.set_user_and_multiplier_field_options(frm);
validate(frm) {
frm.set_df_property('user_field', 'reqd', !frm.doc.for_assigned_users);
frm.set_df_property('condition', 'reqd', frm.doc.for_doc_event==='Custom');
},
refresh(frm) {
frm.events.set_field_options(frm);
},
for_doc_event(frm) {
if (frm.doc.for_assigned_users) {
frm.set_value('for_assigned_users', !frm.doc.for_doc_event==='New');
}
},
reference_doctype(frm) {
frm.events.set_user_and_multiplier_field_options(frm);
frm.events.set_field_options(frm);
},
set_user_and_multiplier_field_options(frm) {
set_field_options(frm) {
// sets options for field_to_check, user_field and multiplier fields
// based on reference doctype
const reference_doctype = frm.doc.reference_doctype;
if (!reference_doctype) return;
frappe.model.with_doctype(reference_doctype, () => {
const map_for_options = df => ({ label: df.label, value: df.fieldname });
const fields = frappe.meta.get_docfields(frm.doc.reference_doctype);
const fields = frappe.meta.get_docfields(frm.doc.reference_doctype)
.filter(frappe.model.is_value_type);
const fields_to_check = fields.map(map_for_options);
const user_fields = fields.filter(df => (df.fieldtype === 'Link' && df.options === 'User')
|| df.fieldtype === 'Data')
.map(map_for_options)
@ -29,6 +44,7 @@ frappe.ui.form.on('Energy Point Rule', {
// blank option for the ability to unset the multiplier field
multiplier_fields.unshift(null);
frm.set_df_property('field_to_check', 'options', fields_to_check);
frm.set_df_property('user_field', 'options', user_fields);
frm.set_df_property('multiplier_field', 'options', multiplier_fields);
});

View file

@ -10,11 +10,14 @@
"rule_name",
"reference_doctype",
"for_doc_event",
"condition",
"field_to_check",
"points",
"for_assigned_users",
"user_field",
"multiplier_field",
"max_points"
"max_points",
"column_break_12",
"condition"
],
"fields": [
{
@ -42,7 +45,7 @@
"reqd": 1
},
{
"depends_on": "eval:doc.for_doc_event === 'Custom'",
"depends_on": "eval:['Custom', 'Value Change'].includes(doc.for_doc_event)",
"description": "If the condition is satisfied user will be rewarded with the points. eg. doc.status == 'Closed'\n",
"fieldname": "condition",
"fieldtype": "Code",
@ -56,11 +59,11 @@
"reqd": 1
},
{
"depends_on": "eval:!doc.for_assigned_users || doc.for_doc_event==='New'",
"description": "The user from this field will be rewarded points",
"fieldname": "user_field",
"fieldtype": "Select",
"label": "User Field",
"reqd": 1
"label": "User Field"
},
{
"fieldname": "multiplier_field",
@ -69,7 +72,7 @@
},
{
"depends_on": "eval:doc.multiplier_field",
"description": "Maximum points allowed after multiplying points with the multiplier value\n(Note: For no limit set value as 0)",
"description": "Maximum points allowed after multiplying points with the multiplier value\n(Note: For no limit leave this field empty or set 0)",
"fieldname": "max_points",
"fieldtype": "Int",
"label": "Maximum Points"
@ -83,10 +86,28 @@
"fieldname": "for_doc_event",
"fieldtype": "Select",
"label": "For Document Event",
"options": "New\nSubmit\nCancel\nCustom"
"options": "New\nSubmit\nCancel\nValue Change\nCustom"
},
{
"default": "0",
"depends_on": "eval:doc.for_doc_event !=='New'",
"description": "Users assigned to the reference document will get points.",
"fieldname": "for_assigned_users",
"fieldtype": "Check",
"label": "Allot Points To Assigned Users"
},
{
"depends_on": "eval:doc.for_doc_event=='Value Change'",
"fieldname": "field_to_check",
"fieldtype": "Select",
"label": "Field To Check"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
}
],
"modified": "2019-09-05 14:22:27.664645",
"modified": "2019-09-24 15:20:25.383536",
"modified_by": "Administrator",
"module": "Social",
"name": "Energy Point Rule",

View file

@ -31,21 +31,24 @@ class EnergyPointRule(Document):
reference_doctype = doc.doctype
reference_name = doc.name
user = doc.get(self.user_field)
users = []
if self.for_assigned_users:
users = doc.get_assigned_users()
else:
users = [doc.get(self.user_field)]
rule = self.name
# incase of zero as result after roundoff
if not points: return
# if user_field has no value
if not user or user == 'Administrator': return
try:
create_energy_points_log(reference_doctype, reference_name, {
'points': points,
'user': user,
'rule': rule
})
for user in users:
if not user or user == 'Administrator': continue
create_energy_points_log(reference_doctype, reference_name, {
'points': points,
'user': user,
'rule': rule
})
except Exception as e:
frappe.log_error(frappe.get_traceback(), 'apply_energy_point')
@ -57,10 +60,25 @@ class EnergyPointRule(Document):
return doc.docstatus == 1
if self.for_doc_event == 'Cancel':
return doc.docstatus == 2
if self.for_doc_event == 'Value Change':
field_to_check = self.field_to_check
if not field_to_check: return False
doc_before_save = doc.get_doc_before_save()
# check if the field has been changed
# if condition is set check if it is satisfied
return doc_before_save \
and doc_before_save.get(field_to_check) != doc.get(field_to_check) \
and (not self.condition or self.eval_condition(doc))
if self.for_doc_event == 'Custom' and self.condition:
return frappe.safe_eval(self.condition, None, {'doc': doc.as_dict()})
return self.eval_condition(doc)
return False
def eval_condition(self, doc):
return self.condition and frappe.safe_eval(self.condition, None, {
'doc': doc.as_dict()
})
def process_energy_points(doc, state):
if (frappe.flags.in_patch
or frappe.flags.in_install

View file

@ -21,17 +21,21 @@ def allocate_review_points():
settings.point_allocation_periodicity):
return
user_point_map = {}
for level in settings.review_levels:
create_review_points(level)
users = get_users_with_role(level.role)
for user in users:
user_point_map.setdefault(user, 0)
# to avoid duplicate point allocation
user_point_map[user] = max([user_point_map[user], level.review_points])
for user, points in user_point_map.items():
create_review_points_log(user, points)
settings.last_point_allocation_date = today()
settings.save(ignore_permissions=True)
def create_review_points(level):
users = get_users_with_role(level.role)
for user in users:
create_review_points_log(user, level.review_points)
def can_allocate_today(last_date, periodicity):
if not last_date:
return True

View file

@ -1,4 +0,0 @@
<p>{{_("Dear")}} {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},</p>
<p>{{_("Your password has been updated. Here is your new password")}}: <b>{{ new_password }}</b></p>
<p>{{_("Thank you")}},<br>
{{ user_fullname }}</p>

View file

@ -6,10 +6,10 @@ import unittest, frappe, pyotp
from frappe.auth import HTTPRequest
from frappe.utils import cint
from frappe.tests import set_request
from frappe.twofactor import (should_run_2fa, authenticate_for_2factor, get_cached_user_pass,
two_factor_is_enabled_for_, confirm_otp_token, get_otpsecret_for_, get_verification_obj,
render_string_template)
from frappe.auth import validate_ip_address
from frappe.twofactor import (should_run_2fa, authenticate_for_2factor, get_cached_user_pass,
two_factor_is_enabled_for_, confirm_otp_token, get_otpsecret_for_, get_verification_obj)
import time
class TestTwoFactor(unittest.TestCase):
@ -123,7 +123,7 @@ class TestTwoFactor(unittest.TestCase):
'''String template renders as expected with variables.'''
args = {'issuer_name':'Frappe Technologies'}
_str = 'Verification Code from {{issuer_name}}'
_str = render_string_template(_str,args)
_str = frappe.render_template(_str,args)
self.assertEqual(_str,'Verification Code from Frappe Technologies')
def test_bypass_restict_ip(self):

View file

@ -7,7 +7,6 @@ import frappe
from frappe import _
import pyotp, os
from frappe.utils.background_jobs import enqueue
from jinja2 import Template
from pyqrcode import create as qrcreate
from six import BytesIO
from base64 import b64encode, b32encode
@ -223,33 +222,27 @@ def process_2fa_for_email(user, token, otp_secret, otp_issuer, method='Email'):
def get_email_subject_for_2fa(kwargs_dict):
'''Get email subject for 2fa.'''
subject_template = _('Login Verification Code from {}').format(frappe.db.get_value('System Settings', 'System Settings', 'otp_issuer_name'))
subject = render_string_template(subject_template, kwargs_dict)
subject = frappe.render_template(subject_template, kwargs_dict)
return subject
def get_email_body_for_2fa(kwargs_dict):
'''Get email body for 2fa.'''
body_template = 'Enter this code to complete your login:<br><br> <b>{{otp}}</b>'
body = render_string_template(body_template, kwargs_dict)
body = frappe.render_template(body_template, kwargs_dict)
return body
def get_email_subject_for_qr_code(kwargs_dict):
'''Get QRCode email subject.'''
subject_template = _('One Time Password (OTP) Registration Code from {}').format(frappe.db.get_value('System Settings', 'System Settings', 'otp_issuer_name'))
subject = render_string_template(subject_template, kwargs_dict)
subject = frappe.render_template(subject_template, kwargs_dict)
return subject
def get_email_body_for_qr_code(kwargs_dict):
'''Get QRCode email body.'''
body_template = 'Please click on the following link and follow the instructions on the page.<br><br> {{qrcode_link}}'
body = render_string_template(body_template, kwargs_dict)
body = frappe.render_template(body_template, kwargs_dict)
return body
def render_string_template(_str, kwargs_dict):
'''Render string with jinja.'''
s = Template(_str)
s = s.render(**kwargs_dict)
return s
def get_link_for_qrcode(user, totp_uri):
'''Get link to temporary page showing QRCode.'''
key = frappe.generate_hash(length=20)

View file

@ -59,7 +59,7 @@ def get_email_address(user=None):
if not user:
user = frappe.session.user
return frappe.db.get_value("User", user, ["email"], as_dict=True).get("email")
return frappe.db.get_value("User", user, "email")
def get_formatted_email(user):
"""get Email Address of user formatted as: `John Doe <johndoe@example.com>`"""

View file

@ -7,15 +7,19 @@ import frappe
import unittest
import json
from frappe.website.doctype.personal_data_download_request.personal_data_download_request import get_user_data
from frappe.contacts.doctype.contact.contact import get_contact_name
class TestRequestPersonalData(unittest.TestCase):
def setUp(self):
create_user_if_not_exists(email='test_privacy@example.com')
def tearDown(self):
frappe.db.sql("""DELETE FROM `tabPersonal Data Download Request`""")
def test_user_data_creation(self):
user_data = json.loads(get_user_data('test_privacy@example.com'))
expected_data = {'Contact': frappe.get_all('Contact', {'email_id':'test_privacy@example.com'}, ["*"])}
contact_name = get_contact_name('test_privacy@example.com')
expected_data = {'Contact': frappe.get_all('Contact', {"name": contact_name}, ["*"])}
expected_data = json.loads(json.dumps(expected_data, default=str))
self.assertEqual({'Contact': user_data['Contact']}, expected_data)
@ -45,8 +49,7 @@ class TestRequestPersonalData(unittest.TestCase):
frappe.db.sql("delete from `tabEmail Queue`")
def create_user_if_not_exists(email, first_name = None):
if frappe.db.exists("User", email):
return
frappe.delete_doc_if_exists("User", email)
frappe.get_doc({
"doctype": "User",

View file

@ -13,12 +13,12 @@
<div class="login-content page-card" style="margin-top: 30px;">
<form class="form-signin form-login" role="form">
<div class="page-card-head">
<span class="indicator blue" data-text="{{ _("Login") }}"></span>
<span class="indicator blue" data-text="{{ _('Login') }}"></span>
</div>
<input type="text" id="login_email"
class="form-control"
placeholder="{% if login_name_placeholder %}{{ login_name_placeholder }}{% else %}{{ _("Email address") }}{% endif %}"
placeholder="{% if login_name_placeholder %}{{ login_name_placeholder }}{% else %}{{ _('Email Address') }}{% endif %}"
required autofocus>
<div class="password-field" style="position: relative;">
@ -66,12 +66,12 @@
<div class="login-content page-card" style="margin-top: 20px;">
<form class="form-signin form-signup hide" role="form">
<div class="page-card-head">
<span class="indicator blue" data-text="{{ _("Sign Up") }}"></span>
<span class="indicator blue" data-text="{{ _('Sign Up') }}"></span>
</div>
<input type="text" id="signup_fullname"
class="form-control" placeholder="{{ _('Full Name') }}" required autofocus>
<input type="email" id="signup_email"
class="form-control" placeholder="{{ _('Email address') }}" required>
class="form-control" placeholder="{{ _('Email Address') }}" required>
<button class="btn btn-sm btn-primary btn-block btn-signup" type="submit">{{ _("Sign up") }}</button>
</form>
</div>
@ -84,9 +84,9 @@
<div class="login-content page-card" style="margin-top: 20px;">
<form class="form-signin form-forgot hide" role="form">
<div class="page-card-head">
<span class="indicator blue" data-text="{{ _("Forgot Password") }}"></span></div>
<span class="indicator blue" data-text="{{ _('Forgot Password') }}"></span></div>
<input type="email" id="forgot_email"
class="form-control" placeholder="{{ _('Email address') }}" required autofocus>
class="form-control" placeholder="{{ _('Email Address') }}" required autofocus>
<button class="btn btn-sm btn-primary btn-block btn-forgot" type="submit">{{ _("Reset Password") }}</button>
</form>
</div>

View file

@ -41,7 +41,7 @@ def get_context(context):
ldap_settings = LDAPSettings.get_ldap_client_settings()
context["ldap_settings"] = ldap_settings
login_name_placeholder = [_("Email address")]
login_name_placeholder = [_("Email Address")]
if frappe.utils.cint(frappe.get_system_settings("allow_login_using_mobile_number")):
login_name_placeholder.append(_("Mobile number"))