@@ -8,13 +8,14 @@ import unittest | |||
# test_records = frappe.get_test_records('DocType') | |||
class TestDocType(unittest.TestCase): | |||
def new_doctype(self, name): | |||
def new_doctype(self, name, unique=0): | |||
return frappe.get_doc({ | |||
"doctype": "DocType", | |||
"module": "Core", | |||
"custom": 1, | |||
"fields": [{"label": "Some Field", "fieldname": "some_fieldname", "fieldtype": "Data"}], | |||
"fields": [{"label": "Some Field", "fieldname": "some_fieldname", "fieldtype": "Data", "unique": unique}], | |||
"permissions": [{"role": "System Manager", "read": 1}], | |||
"name": name | |||
}) | |||
@@ -28,4 +29,28 @@ class TestDocType(unittest.TestCase): | |||
frappe.delete_doc("DocType", name) | |||
doc = self.new_doctype(name).insert() | |||
doc.delete() | |||
doc.delete() | |||
def test_doctype_unique_constraint_dropped(self): | |||
if frappe.db.exists("DocType", "With_Unique"): | |||
frappe.delete_doc("DocType", "With_Unique") | |||
dt = self.new_doctype("With_Unique", unique=1) | |||
dt.insert() | |||
doc1 = frappe.new_doc("With_Unique") | |||
doc2 = frappe.new_doc("With_Unique") | |||
doc1.some_fieldname = "Something" | |||
doc1.name = "one" | |||
doc2.some_fieldname = "Something" | |||
doc2.name = "two" | |||
doc1.insert() | |||
self.assertRaises(frappe.UniqueValidationError, doc2.insert) | |||
dt.fields[0].unique = 0 | |||
dt.save() | |||
doc2.insert() | |||
doc1.delete() | |||
doc2.delete() |
@@ -1,17 +1,24 @@ | |||
{ | |||
"accept_payment": 0, | |||
"allow_comments": 0, | |||
"allow_delete": 0, | |||
"allow_edit": 1, | |||
"allow_incomplete": 0, | |||
"allow_multiple": 0, | |||
"allow_print": 0, | |||
"amount": 0.0, | |||
"amount_based_on_field": 0, | |||
"breadcrumbs": "[{\"title\": _(\"My Account\"), \"route\": \"me\"}]", | |||
"creation": "2016-09-19 05:16:59.242754", | |||
"doc_type": "User", | |||
"docstatus": 0, | |||
"doctype": "Web Form", | |||
"idx": 0, | |||
"introduction_text": "", | |||
"is_standard": 1, | |||
"login_required": 1, | |||
"modified": "2016-09-24 04:31:41.920694", | |||
"max_attachment_size": 0, | |||
"modified": "2017-07-24 12:14:04.039284", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "edit-profile", | |||
@@ -29,6 +36,8 @@ | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "First Name", | |||
"max_length": 0, | |||
"max_value": 0, | |||
"read_only": 0, | |||
"reqd": 1 | |||
}, | |||
@@ -37,6 +46,8 @@ | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "Middle Name (Optional)", | |||
"max_length": 0, | |||
"max_value": 0, | |||
"read_only": 0, | |||
"reqd": 0 | |||
}, | |||
@@ -45,6 +56,8 @@ | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "Last Name", | |||
"max_length": 0, | |||
"max_value": 0, | |||
"read_only": 0, | |||
"reqd": 0 | |||
}, | |||
@@ -54,6 +67,8 @@ | |||
"fieldtype": "Attach", | |||
"hidden": 0, | |||
"label": "User Image", | |||
"max_length": 0, | |||
"max_value": 0, | |||
"read_only": 0, | |||
"reqd": 0 | |||
}, | |||
@@ -61,6 +76,8 @@ | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"label": "More Information", | |||
"max_length": 0, | |||
"max_value": 0, | |||
"read_only": 0, | |||
"reqd": 0 | |||
}, | |||
@@ -69,6 +86,18 @@ | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "Phone", | |||
"max_length": 0, | |||
"max_value": 0, | |||
"read_only": 0, | |||
"reqd": 0 | |||
}, | |||
{ | |||
"fieldname": "mobile_no", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "Mobile Number", | |||
"max_length": 0, | |||
"max_value": 0, | |||
"read_only": 0, | |||
"reqd": 0 | |||
}, | |||
@@ -78,6 +107,8 @@ | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"label": "Language", | |||
"max_length": 0, | |||
"max_value": 0, | |||
"options": "Language", | |||
"read_only": 0, | |||
"reqd": 0 | |||
@@ -2,25 +2,6 @@ | |||
margin-top: 30px; | |||
} | |||
.setup-wizard-brand { | |||
margin: 30px; | |||
text-align: center; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center | |||
} | |||
.setup-wizard-brand .brand-icon { | |||
width: 36px; | |||
height: 36px; | |||
} | |||
.setup-wizard-brand .brand-name { | |||
font-size: 20px; | |||
margin-left: 8px; | |||
color: #36414C; | |||
} | |||
.setup-wizard-slide { | |||
padding-left: 0px; | |||
padding-right: 0px; | |||
@@ -59,14 +40,6 @@ | |||
font-weight: 500; | |||
} | |||
.setup-wizard-slide .has-error .control-label { | |||
color: #ffa00a; | |||
} | |||
.setup-wizard-slide .has-error .form-control{ | |||
border-color: #ffa00a; | |||
} | |||
.setup-wizard-slide .form-control.bold { | |||
background-color: #fff; | |||
} | |||
@@ -113,8 +86,7 @@ | |||
.setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] { | |||
width: 140px; | |||
height: 180px; /*depends on presence of heading*/ | |||
text-align: center; | |||
margin-left: calc((100% - 140px)/2); | |||
margin-top: 20px; | |||
} | |||
.setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] .form-group, | |||
@@ -78,7 +78,6 @@ frappe.setup.Wizard = Class.extend({ | |||
</div>', {html:html})) | |||
}, | |||
show_working: function() { | |||
$('header').find('.setup-wizard-brand').hide(); | |||
this.hide_current_slide(); | |||
frappe.set_route(this.page_name); | |||
this.current_slide = {"$wrapper": this.get_message(this.working_html()).appendTo(this.parent)}; | |||
@@ -506,7 +505,7 @@ var frappe_slides = [ | |||
icon: "fa fa-user", | |||
fields: [ | |||
{ "fieldtype":"Attach Image", "fieldname":"attach_user_image", | |||
label: __("Attach Your Picture"), is_private: 0}, | |||
label: __("Attach Your Picture"), is_private: 0, align: 'center'}, | |||
{ "fieldname": "full_name", "label": __("Full Name"), "fieldtype": "Data", | |||
reqd:1}, | |||
{ "fieldname": "email", "label": __("Email Address") + ' (' + __("Will be your login ID") + ')', | |||
@@ -721,12 +720,4 @@ var utils = { | |||
frappe.setup.on("before_load", function() { | |||
// load slides | |||
frappe_slides.map(frappe.setup.add_slide); | |||
// set header image | |||
let $icon = $('header .setup-wizard-brand'); | |||
if($icon.length === 0) { | |||
$('header').append(`<div class="setup-wizard-brand""> | |||
<img src="/assets/frappe/images/frappe-bird-grey.svg" | |||
class="brand-icon frappe-icon" style="width:36px;"></div>`); | |||
} | |||
}); |
@@ -47,6 +47,13 @@ def remove_tag(tag, dt, dn): | |||
"removes tag from the record" | |||
DocTags(dt).remove(dn, tag) | |||
@frappe.whitelist() | |||
def get_tagged_docs(doctype, tag): | |||
frappe.has_permission(doctype, throw=True) | |||
return frappe.db.sql("""SELECT name | |||
FROM `tab{0}` | |||
WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag)) | |||
@frappe.whitelist() | |||
def get_tags(doctype, txt, cat_tags): | |||
@@ -69,15 +69,15 @@ def add_subscribers(name, email_list): | |||
count = 0 | |||
for email in email_list: | |||
email = email.strip() | |||
valid = validate_email_add(email, False) | |||
parsed_email = validate_email_add(email, False) | |||
if valid: | |||
if parsed_email: | |||
if not frappe.db.get_value("Email Group Member", | |||
{"email_group": name, "email": email}): | |||
{"email_group": name, "email": parsed_email}): | |||
frappe.get_doc({ | |||
"doctype": "Email Group Member", | |||
"email_group": name, | |||
"email": email | |||
"email": parsed_email | |||
}).insert(ignore_permissions = frappe.flags.ignore_permissions) | |||
count += 1 | |||
@@ -86,7 +86,7 @@ def backup_to_dropbox(): | |||
access_token = generate_oauth2_access_token_from_oauth1_token(dropbox_settings) | |||
if not access_token.get('oauth2_token'): | |||
return | |||
return 'Failed backup upload', 'No Access Token exists! Please generate the access token for Dropbox.' | |||
dropbox_settings['access_token'] = access_token['oauth2_token'] | |||
set_dropbox_access_token(access_token['oauth2_token']) | |||
@@ -0,0 +1,16 @@ | |||
[ | |||
{ | |||
"app_name": "_Test OAuth Client", | |||
"client_id": "test_client_id", | |||
"client_secret": "test_client_secret", | |||
"default_redirect_uri": "http://localhost", | |||
"docstatus": 0, | |||
"doctype": "OAuth Client", | |||
"grant_type": "Authorization Code", | |||
"name": "test_client_id", | |||
"redirect_uris": "http://localhost", | |||
"response_type": "Code", | |||
"scopes": "all openid", | |||
"skip_authorization": 0 | |||
} | |||
] |
@@ -314,7 +314,7 @@ class DbTable: | |||
# if index key exists | |||
if frappe.db.sql("""show index from `{0}` | |||
where key_name=%s | |||
and Non_unique=%s""".format(self.name), (col.fieldname, 0 if col.unique else 1)): | |||
and Non_unique=%s""".format(self.name), (col.fieldname, col.unique)): | |||
query.append("drop index `{}`".format(col.fieldname)) | |||
for col in self.set_default: | |||
@@ -187,4 +187,5 @@ frappe.patches.v8_0.update_gender_and_salutation | |||
execute:frappe.db.sql('update tabReport set module="Desk" where name="ToDo"') | |||
frappe.patches.v8_1.enable_allow_error_traceback_in_system_settings | |||
frappe.patches.v8_1.update_format_options_in_auto_email_report | |||
frappe.patches.v8_1.delete_custom_docperm_if_doctype_not_exists | |||
frappe.patches.v8_1.delete_custom_docperm_if_doctype_not_exists | |||
frappe.patches.v8_5.delete_email_group_member_with_invalid_emails |
@@ -0,0 +1,20 @@ | |||
# Copyright (c) 2017, Frappe and Contributors | |||
# License: GNU General Public License v3. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.utils import validate_email_add | |||
def execute(): | |||
''' update/delete the email group member with the wrong email ''' | |||
email_group_members = frappe.get_all("Email Group Member", fields=["name", "email"]) | |||
for member in email_group_members: | |||
validated_email = validate_email_add(member.email) | |||
if (validated_email==member.email): | |||
pass | |||
else: | |||
try: | |||
frappe.db.set_value("Email Group Member", member.name, "email", validated_email) | |||
except Exception: | |||
frappe.delete_doc(doctype="Email Group Member", name=member.name, force=1, ignore_permissions=True) |
@@ -287,6 +287,8 @@ h6.uppercase, | |||
border-radius: 3px; | |||
margin-left: -7px; | |||
position: relative; | |||
max-width: calc(100% - 50px); | |||
padding-right: 0px; | |||
overflow: visible; | |||
} | |||
.timeline-item.user-content .avatar-medium { | |||
@@ -294,6 +296,11 @@ h6.uppercase, | |||
height: 45px; | |||
width: 45px; | |||
} | |||
.timeline-item.user-content .action-btns { | |||
position: absolute; | |||
right: 0; | |||
padding: 5px 15px 2px 5px; | |||
} | |||
.timeline-item.user-content .comment-header { | |||
background-color: #fafbfc; | |||
padding: 10px 15px 10px 13px; | |||
@@ -301,12 +308,19 @@ h6.uppercase, | |||
color: #8D99A6; | |||
border-bottom: 1px solid #EBEFF2; | |||
} | |||
.timeline-item.user-content .comment-header.links-active { | |||
padding-right: 60px; | |||
} | |||
.timeline-item.user-content .comment-header .commented-on-small { | |||
display: none; | |||
} | |||
.timeline-item.user-content .comment-header .octicon-heart { | |||
color: #ff5858; | |||
cursor: pointer; | |||
} | |||
.timeline-item.user-content .reply { | |||
padding: 15px; | |||
overflow: auto; | |||
} | |||
.timeline-item.user-content .reply > div > p:first-child { | |||
margin-top: 0px; | |||
@@ -317,11 +331,13 @@ h6.uppercase, | |||
.timeline-item.user-content .reply hr { | |||
margin: 10px 0px; | |||
} | |||
.timeline-item.user-content .close-btn-container { | |||
padding: 4px 10px 2px 5px; | |||
.timeline-item.user-content .close-btn-container .close { | |||
color: inherit; | |||
opacity: 1; | |||
padding: 0 0 0 10px; | |||
} | |||
.timeline-item.user-content .edit-btn-container { | |||
padding: 4px 5px; | |||
padding: 0; | |||
} | |||
.timeline-item.user-content .edit-btn-container .edit { | |||
color: inherit; | |||
@@ -515,6 +531,14 @@ h6.uppercase, | |||
padding: 0px; | |||
margin: 0px; | |||
} | |||
.flex-justify-center { | |||
display: flex; | |||
justify-content: center; | |||
} | |||
.flex-justify-end { | |||
display: flex; | |||
justify-content: flex-end; | |||
} | |||
.hide-control { | |||
display: none !important; | |||
} | |||
@@ -192,6 +192,9 @@ body { | |||
} | |||
} | |||
@media (max-width: 767px) { | |||
.toggle-sidebar { | |||
margin-right: 0; | |||
} | |||
body[data-route^="Form"] .page-title .title-text { | |||
font-size: 16px; | |||
width: calc(100% - 30px); | |||
@@ -331,4 +334,64 @@ body { | |||
body[data-route^="Form"] .page-head .sub-heading { | |||
right: 90px; | |||
} | |||
.timeline::before { | |||
content: none; | |||
} | |||
.timeline .timeline-new-email { | |||
margin: 20px 0; | |||
padding-left: 15px; | |||
} | |||
.timeline .timeline-new-email::before { | |||
content: none; | |||
} | |||
.timeline .timeline-item.user-content { | |||
margin: 20px 15px; | |||
} | |||
.timeline .timeline-item.user-content .media-body { | |||
margin-left: 0; | |||
max-width: 100%; | |||
overflow: hidden; | |||
} | |||
.timeline .timeline-item.user-content .media-body:before { | |||
content: none; | |||
} | |||
.timeline .timeline-item.user-content .action-btns { | |||
padding: 5px 10px 2px 5px; | |||
} | |||
.timeline .timeline-item.user-content .comment-header { | |||
padding: 7px 10px; | |||
} | |||
.timeline .timeline-item.user-content .comment-header .links-active { | |||
padding-right: 10px; | |||
} | |||
.timeline .timeline-item.user-content .avatar-medium { | |||
margin-right: 10px; | |||
} | |||
.timeline .timeline-item.user-content .reply { | |||
padding: 10px; | |||
} | |||
.timeline .timeline-item.user-content .commented-on-small { | |||
display: inline-block; | |||
} | |||
.timeline .timeline-item.user-content .commented-on-small { | |||
display: inline-block; | |||
} | |||
.timeline .timeline-item.notification-content { | |||
padding-left: 15px; | |||
margin: 20px 0; | |||
} | |||
.timeline .timeline-item.notification-content::before { | |||
content: none; | |||
} | |||
.timeline .timeline-item.notification-content .small { | |||
padding-left: 0; | |||
} | |||
.timeline .timeline-item .delivery-status-indicator { | |||
float: left; | |||
margin: 0 5px 0 0; | |||
} | |||
.timeline .asset-details { | |||
line-height: 24px; | |||
/*Height of avtar image -36px to align text center vertically*/ | |||
} | |||
} |
@@ -318,7 +318,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||
} else { | |||
$(me.input_area).toggle(false); | |||
if (me.disp_area) { | |||
me.set_disp_area(); | |||
me.set_disp_area(me.value); | |||
$(me.disp_area).toggle(true); | |||
} | |||
} | |||
@@ -332,8 +332,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||
} | |||
}, | |||
set_disp_area: function() { | |||
let value = this.get_input_value(); | |||
set_disp_area: function(value) { | |||
if(in_list(["Currency", "Int", "Float"], this.df.fieldtype) | |||
&& (this.value === 0 || value === 0)) { | |||
// to set the 0 value in readonly for currency, int, float field | |||
@@ -449,7 +448,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ | |||
this.last_value = this.value; | |||
this.value = value; | |||
this.set_formatted_input(value); | |||
this.set_disp_area(); | |||
this.set_disp_area(value); | |||
this.set_mandatory && this.set_mandatory(value); | |||
}, | |||
set_formatted_input: function(value) { | |||
@@ -752,29 +751,38 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ | |||
if(!$.fn.datepicker.language[lang]) { | |||
lang = 'en'; | |||
} | |||
this.today_text = __("Today"); | |||
this.datepicker_options = { | |||
language: lang, | |||
autoClose: true, | |||
todayButton: new Date(), | |||
todayButton: frappe.datetime.now_date(true), | |||
dateFormat: (frappe.boot.sysdefaults.date_format || 'yyyy-mm-dd'), | |||
onSelect: function(dateStr) { | |||
me.$input.trigger('change'); | |||
startDate: frappe.datetime.now_date(true), | |||
onSelect: () => { | |||
this.$input.trigger('change'); | |||
}, | |||
onShow: function() { | |||
$('.datepicker--button:visible').text(__('Today')); | |||
if(!me.frm) return; | |||
var window_height = $(window).height(); | |||
var window_scroll_top = $(window).scrollTop(); | |||
var el_offset_top = me.$input.offset().top + 280; | |||
var position = 'top left'; | |||
if(window_height + window_scroll_top >= el_offset_top) { | |||
position = 'bottom left'; | |||
} | |||
me.datepicker.update('position', position); | |||
onShow: () => { | |||
this.datepicker.$datepicker | |||
.find('.datepicker--button:visible') | |||
.text(me.today_text); | |||
this.update_datepicker_position(); | |||
} | |||
}; | |||
}, | |||
update_datepicker_position: function() { | |||
if(!this.frm) return; | |||
// show datepicker above or below the input | |||
// based on scroll position | |||
var window_height = $(window).height(); | |||
var window_scroll_top = $(window).scrollTop(); | |||
var el_offset_top = this.$input.offset().top + 280; | |||
var position = 'top left'; | |||
if(window_height + window_scroll_top >= el_offset_top) { | |||
position = 'bottom left'; | |||
} | |||
this.datepicker.update('position', position); | |||
}, | |||
set_datepicker: function() { | |||
this.$input.datepicker(this.datepicker_options); | |||
this.datepicker = this.$input.data('datepicker'); | |||
@@ -814,6 +822,30 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ | |||
} | |||
}); | |||
frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ | |||
set_date_options: function() { | |||
this._super(); | |||
this.today_text = __("Now"); | |||
$.extend(this.datepicker_options, { | |||
timepicker: true, | |||
timeFormat: "hh:ii:ss", | |||
todayButton: frappe.datetime.now_datetime(true) | |||
}); | |||
}, | |||
set_description: function() { | |||
const { description } = this.df; | |||
const { time_zone } = frappe.sys_defaults; | |||
if (!frappe.datetime.is_timezone_same()) { | |||
if (!description) { | |||
this.df.description = time_zone; | |||
} else if (!description.includes(time_zone)) { | |||
this.df.description += '<br>' + time_zone; | |||
} | |||
} | |||
this._super(); | |||
} | |||
}); | |||
frappe.ui.form.ControlTime = frappe.ui.form.ControlData.extend({ | |||
make_input: function() { | |||
var me = this; | |||
@@ -823,13 +855,14 @@ frappe.ui.form.ControlTime = frappe.ui.form.ControlData.extend({ | |||
timepicker: true, | |||
onlyTimepicker: true, | |||
timeFormat: "hh:ii:ss", | |||
startDate: frappe.datetime.now_time(true), | |||
onSelect: function() { | |||
me.$input.trigger('change'); | |||
}, | |||
onShow: function() { | |||
$('.datepicker--button:visible').text(__('Now')); | |||
}, | |||
todayButton: new Date() | |||
todayButton: frappe.datetime.now_time(true) | |||
}); | |||
this.datepicker = this.$input.data('datepicker'); | |||
this.refresh(); | |||
@@ -840,33 +873,21 @@ frappe.ui.form.ControlTime = frappe.ui.form.ControlData.extend({ | |||
&& ((this.last_value && this.last_value !== this.value) | |||
|| (!this.datepicker.selectedDates.length))) { | |||
this.datepicker.selectDate(moment(value, 'hh:mm:ss')._d); | |||
var date_obj = frappe.datetime.moment_to_date_obj(moment(value, 'hh:mm:ss')); | |||
this.datepicker.selectDate(date_obj); | |||
} | |||
}, | |||
}); | |||
frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ | |||
set_date_options: function() { | |||
this._super(); | |||
this.datepicker_options.timepicker = true; | |||
this.datepicker_options.timeFormat = "hh:ii:ss"; | |||
this.datepicker_options.onShow = function() { | |||
$('.datepicker--button:visible').text(__('Now')); | |||
}; | |||
}, | |||
parse: function(value) { | |||
if(value) { | |||
// parse and convert | |||
value = frappe.datetime.convert_to_system_tz(frappe.datetime.user_to_str(value)); | |||
} | |||
return value; | |||
}, | |||
format_for_input: function(value) { | |||
if(value) { | |||
// convert and format | |||
value = frappe.datetime.str_to_user(frappe.datetime.convert_to_user_tz(value)); | |||
set_description: function() { | |||
const { description } = this.df; | |||
const { time_zone } = frappe.sys_defaults; | |||
if (!frappe.datetime.is_timezone_same()) { | |||
if (!description) { | |||
this.df.description = time_zone; | |||
} else if (!description.includes(time_zone)) { | |||
this.df.description += '<br>' + time_zone; | |||
} | |||
} | |||
return value || ""; | |||
this._super(); | |||
} | |||
}); | |||
@@ -903,11 +924,12 @@ frappe.ui.form.ControlDateRange = frappe.ui.form.ControlData.extend({ | |||
this.value = value; | |||
} | |||
if (this.value) { | |||
this.$input && this.$input.val(this.format_for_input(this.value[0], this.value[1])); | |||
let formatted = this.format_for_input(this.value[0], this.value[1]); | |||
this.$input && this.$input.val(formatted); | |||
} else { | |||
this.$input && this.$input.val(""); | |||
} | |||
this.set_disp_area(); | |||
this.set_disp_area(value || ''); | |||
this.set_mandatory && this.set_mandatory(value); | |||
}, | |||
parse: function(value) { | |||
@@ -984,7 +1006,7 @@ frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({ | |||
} | |||
this.last_value = value; | |||
this.set_mandatory(value); | |||
this.set_disp_area(); | |||
this.set_disp_area(value); | |||
} | |||
}); | |||
@@ -1219,6 +1241,16 @@ frappe.ui.form.ControlAttachImage = frappe.ui.form.ControlAttach.extend({ | |||
make: function() { | |||
var me = this; | |||
this._super(); | |||
this.container = $('<div class="control-container">').insertAfter($(this.wrapper)); | |||
$(this.wrapper).detach(); | |||
this.container.attr('data-fieldtype', this.df.fieldtype).append(this.wrapper); | |||
if(this.df.align === 'center') { | |||
this.container.addClass("flex-justify-center"); | |||
} else if (this.df.align === 'right') { | |||
this.container.addClass("flex-justify-end"); | |||
} | |||
this.img_wrapper = $('<div style="width: 100%; height: calc(100% - 40px); position: relative;">\ | |||
<div class="missing-image attach-missing-image"><i class="octicon octicon-device-camera"></i></div></div>') | |||
.appendTo(this.wrapper); | |||
@@ -248,14 +248,14 @@ frappe.ui.form.Timeline = Class.extend({ | |||
c["edit"] = ""; | |||
if(c.communication_type=="Comment" && (c.comment_type || "Comment") === "Comment") { | |||
if(frappe.model.can_delete("Communication")) { | |||
c["delete"] = '<a class="close" href="#"><i class="octicon octicon-trashcan"></i></a>'; | |||
c["delete"] = '<a class="close" title="Delete" href="#"><i class="octicon octicon-x"></i></a>'; | |||
} | |||
if(frappe.user.name == c.sender || (frappe.user.name == 'Administrator')) { | |||
c["edit"] = '<a class="edit" href="#"><i class="octicon octicon-pencil"></i></a>'; | |||
c["edit"] = '<a class="edit" title="Edit" href="#"><i class="octicon octicon-pencil"></i></a>'; | |||
} | |||
} | |||
c.comment_on_small = comment_when(c.creation, true); | |||
c.comment_on = comment_when(c.creation); | |||
if(!c.fullname) { | |||
c.fullname = c.sender_full_name || frappe.user.full_name(c.sender); | |||
@@ -360,7 +360,8 @@ frappe.ui.form.Timeline = Class.extend({ | |||
"Unshared": "octicon octicon-circle-slash", | |||
"Like": "octicon octicon-heart", | |||
"Edit": "octicon octicon-pencil", | |||
"Relinked": "octicon octicon-check" | |||
"Relinked": "octicon octicon-check", | |||
"Reply": "octicon octicon-mail-reply" | |||
}[c.comment_type || c.communication_medium] | |||
c.color = { | |||
@@ -378,7 +379,8 @@ frappe.ui.form.Timeline = Class.extend({ | |||
"Label": "#2c3e50", | |||
"Attachment": "#7f8c8d", | |||
"Attachment Removed": "#eee", | |||
"Relinked": "#16a085" | |||
"Relinked": "#16a085", | |||
"Reply": "#8d99a6" | |||
}[c.comment_type || c.communication_medium]; | |||
c.icon_fg = { | |||
@@ -1,6 +1,6 @@ | |||
<div class="media timeline-item {% if (data.user_content) { %} user-content {% } else { %} notification-content {% } %}" data-doctype="{{ data.doctype }}" data-name="{%= data.name %}"> | |||
{% if (data.user_content) { %} | |||
<span class="pull-left avatar avatar-medium" style="margin-top: 1px"> | |||
<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px"> | |||
{% if(data.user_info.image) { %} | |||
<div class="avatar-frame" style="background-image: url({%= data.user_info.image %})"></div> | |||
{% } else { %} | |||
@@ -10,88 +10,108 @@ | |||
</span> | |||
{% } %} | |||
<div class="pull-left media-body" style="max-width: calc(100% - 50px); padding-right: 0px;"> | |||
<div class="pull-left media-body"> | |||
<div class="media-content-wrapper"> | |||
<div class="pull-right close-btn-container"> | |||
<span class="small text-muted"> | |||
{%= data.delete %} | |||
</span> | |||
</div> | |||
<div class="pull-right edit-btn-container"> | |||
<span class="small text-muted"> | |||
{%= data.edit %} | |||
</span> | |||
<div class="action-btns"> | |||
{% if(data.delete) { %} | |||
<div class="pull-right hidden-xs close-btn-container"> | |||
<span class="small text-muted"> | |||
{%= data.delete %} | |||
</span> | |||
</div> | |||
{% } %} | |||
{% if(data.edit) { %} | |||
<div class="pull-right edit-btn-container"> | |||
<span class="small text-muted"> | |||
{%= data.edit %} | |||
</span> | |||
</div> | |||
{% } %} | |||
</div> | |||
{% if(data.communication_type==="Communication" | |||
|| data.communication_type==="Feedback" | |||
|| (data.communication_type==="Comment" | |||
&& data.comment_type==="Comment")) { %} | |||
<div class="comment-header small"> | |||
<i class="{%= data.icon %} fa-fw"></i> | |||
<span title="{%= data.comment_by %}">{%= data.fullname %}</span> | |||
<span> | |||
{% if (data.timeline_doctype===data.frm.doc.doctype | |||
&& data.timeline_name===data.frm.doc.name) { %} | |||
– | |||
<a href="#Form/{%= data.reference_doctype %}/{%= data.reference_name %}" class="text-muted"> | |||
<strong>{{ __(data.reference_doctype) }}</strong> | |||
{{ data.reference_name }} | |||
</a> | |||
<div class="comment-header clearfix small {% if (data.edit || data.delete) { %} links-active {% } %}"> | |||
<span class="pull-left avatar avatar-small visible-xs"> | |||
{% if(data.user_info.image) { %} | |||
<div class="avatar-frame" style="background-image: url({%= data.user_info.image %})"></div> | |||
{% } else { %} | |||
<div class="standard-image" style="background-color: {{ data.user_info.color }}"> | |||
{{ data.user_info.abbr }}</div> | |||
{% } %} | |||
</span> | |||
<span class="text-muted" style="font-weight: normal;"> | |||
– {%= data.comment_on %}</span> | |||
{% if(in_list(["Communication", "Feedback"], data.communication_type)) { %} | |||
{% if (frappe.model.can_read(\'Communication\')) { %} | |||
<a href="#Form/{%= data.doctype %}/{%= data.name %}" | |||
class="text-muted"> | |||
<div class="asset-details"> | |||
<span class="author-wrap"> | |||
<i class="{%= data.icon %} hidden-xs fa-fw"></i> | |||
<span title="{%= data.comment_by %}">{%= data.fullname %}</span> | |||
</span> | |||
<span> | |||
{% if (data.timeline_doctype===data.frm.doc.doctype | |||
&& data.timeline_name===data.frm.doc.name) { %} | |||
– | |||
<a href="#Form/{%= data.reference_doctype %}/{%= data.reference_name %}" class="text-muted"> | |||
<strong>{{ __(data.reference_doctype) }}</strong> | |||
{{ data.reference_name }} | |||
</a> | |||
{% } %} | |||
</span> | |||
{% if(in_list(["Communication", "Feedback"], data.communication_type)) { %} | |||
{% if (frappe.model.can_read(\'Communication\')) { %} | |||
<a href="#Form/{%= data.doctype %}/{%= data.name %}" | |||
class="text-muted"> | |||
{% } %} | |||
{% if (data.delivery_status) { | |||
if (in_list(["Sent", "Opened", "Clicked"], data.delivery_status)) { | |||
var indicator_class = "green"; | |||
} else if (data.delivery_status === "Sending") { | |||
var indicator_class = "orange"; | |||
} else { | |||
var indicator_class = "red"; | |||
} | |||
%} | |||
<span class="text-muted">–</span> | |||
<span class="indicator-right {%= indicator_class %} | |||
delivery-status-indicator" | |||
title="{%= data.delivery_status %}"> | |||
{%= data.delivery_status %}</span> | |||
{% if (data.delivery_status) { | |||
if (in_list(["Sent", "Opened", "Clicked"], data.delivery_status)) { | |||
var indicator_class = "green"; | |||
} else if (data.delivery_status === "Sending") { | |||
var indicator_class = "orange"; | |||
} else { | |||
var indicator_class = "red"; | |||
} | |||
%} | |||
<span class="text-muted hidden-xs">–</span> | |||
<span class="indicator-right {%= indicator_class %} | |||
delivery-status-indicator" | |||
title="{%= data.delivery_status %}"><span class="hidden-xs"> | |||
{%= data.delivery_status %}</span></span> | |||
{% } else { %} | |||
{% if (frappe.model.can_read(\'Communication\')) { %} | |||
<span class="text-muted">–</span> | |||
{%= __("Details") %} | |||
{% } else { %} | |||
{% if (frappe.model.can_read(\'Communication\')) { %} | |||
<span class="text-muted n-dash">–</span> | |||
{%= __("Details") %} | |||
{% } %} | |||
{% } %} | |||
{% } %} | |||
{% if (frappe.model.can_read(\'Communication\')) { %} | |||
</a> | |||
{% } %} | |||
{% if (frappe.model.can_read(\'Communication\')) { %} | |||
</a> | |||
{% } %} | |||
{% if (data.communication_medium === "Email" | |||
&& data.sender !== frappe.session.user_email) { %} | |||
<a class="text-muted reply-link pull-right timeline-content-show" | |||
data-name="{%= data.name %}">{%= __("Reply") %}</a> | |||
{% if (data.communication_medium === "Email" | |||
&& data.sender !== frappe.session.user_email) { %} | |||
<a class="text-muted reply-link pull-right timeline-content-show" | |||
data-name="{%= data.name %}" title="{%= __("Reply") %}"><i class="octicon octicon-mail-reply"></i></a> | |||
{% } %} | |||
{% } %} | |||
{% } %} | |||
<span class="comment-likes" | |||
data-liked-by=\'{{ JSON.stringify(data._liked_by) }}\'> | |||
<i class="octicon octicon-heart like-action | |||
{% if (!data.liked_by_user) { %} | |||
text-extra-muted not-liked | |||
{% } %} fa-fw" | |||
data-doctype="{%= data.doctype %}" | |||
data-name="{%= data.name %}"></i> | |||
<span class="likes-count text-muted"> | |||
{{ (data._liked_by || []).length }}</span> | |||
</span> | |||
<span class="text-muted commented-on hidden-xs"> | |||
– {%= data.comment_on %}</span> | |||
<span class="text-muted commented-on-small"> | |||
– {%= data.comment_on_small %}</span> | |||
<span class="comment-likes hidden-xs" | |||
data-liked-by=\'{{ JSON.stringify(data._liked_by) }}\'> | |||
<i class="octicon octicon-heart like-action | |||
{% if (!data.liked_by_user) { %} | |||
text-extra-muted not-liked | |||
{% } %} fa-fw" | |||
data-doctype="{%= data.doctype %}" | |||
data-name="{%= data.name %}"></i> | |||
<span class="likes-count text-muted"> | |||
{{ (data._liked_by || []).length }}</span> | |||
</span> | |||
</div> | |||
</div> | |||
<div class="reply timeline-content-show" style="overflow-x: auto"> | |||
<div class="reply timeline-content-show"> | |||
<div class="timeline-item-content"> | |||
{% if data.show_subject %} | |||
<p class="text-muted small"> | |||
@@ -143,7 +163,7 @@ | |||
{% if(data.link_doctype && data.link_name) { %} | |||
</a> | |||
{% } %} | |||
<span class="text-muted" style="font-weight: normal;"> | |||
<span class="text-muted commented-on" style="font-weight: normal;"> | |||
– {%= data.comment_on %}</span> | |||
</div> | |||
{% } else { %} | |||
@@ -172,7 +192,7 @@ | |||
</a> | |||
{% } %} | |||
{% } %} | |||
<span class="text-muted" style="font-weight: normal;"> | |||
<span class="text-muted commented-on" style="font-weight: normal;"> | |||
– {%= data.comment_on %}</span> | |||
</div> | |||
{% } %} | |||
@@ -3,8 +3,11 @@ | |||
frappe.provide('frappe.datetime'); | |||
moment.defaultFormat = "YYYY-MM-DD"; | |||
moment.defaultDatetimeFormat = "YYYY-MM-DD HH:mm:ss" | |||
moment.defaultDateFormat = "YYYY-MM-DD"; | |||
moment.defaultTimeFormat = "HH:mm:ss"; | |||
moment.defaultDatetimeFormat = moment.defaultDateFormat + " " + moment.defaultTimeFormat; | |||
moment.defaultFormat = moment.defaultDateFormat; | |||
frappe.provide("frappe.datetime"); | |||
$.extend(frappe.datetime, { | |||
@@ -91,8 +94,14 @@ $.extend(frappe.datetime, { | |||
return frappe.sys_defaults.date_format || "yyyy-mm-dd"; | |||
}, | |||
str_to_user: function(val, no_time_str) { | |||
str_to_user: function(val, only_time = false) { | |||
if(!val) return ""; | |||
if(only_time) { | |||
return moment(val, moment.defaultTimeFormat) | |||
.format(moment.defaultTimeFormat); | |||
} | |||
var user_fmt = frappe.datetime.get_user_fmt().toUpperCase(); | |||
if(typeof val !== "string" || val.indexOf(" ")===-1) { | |||
return moment(val).format(user_fmt); | |||
@@ -101,15 +110,17 @@ $.extend(frappe.datetime, { | |||
} | |||
}, | |||
now_datetime: function() { | |||
return moment().format("YYYY-MM-DD HH:mm:ss"); | |||
}, | |||
get_datetime_as_string: function(d) { | |||
return moment(d).format("YYYY-MM-DD HH:mm:ss"); | |||
}, | |||
user_to_str: function(val, no_time_str) { | |||
user_to_str: function(val, only_time = false) { | |||
if(only_time) { | |||
return moment(val, moment.defaultTimeFormat) | |||
.format(moment.defaultTimeFormat); | |||
} | |||
var user_fmt = frappe.datetime.get_user_fmt().toUpperCase(); | |||
var system_fmt = "YYYY-MM-DD"; | |||
@@ -136,21 +147,60 @@ $.extend(frappe.datetime, { | |||
} | |||
}, | |||
get_today: function() { | |||
return moment().locale("en").format(); | |||
now_date: function(as_obj = false) { | |||
return frappe.datetime._date(moment.defaultDateFormat, as_obj); | |||
}, | |||
now_time: function(as_obj = false) { | |||
return frappe.datetime._date(moment.defaultTimeFormat, as_obj); | |||
}, | |||
now_datetime: function(as_obj = false) { | |||
return frappe.datetime._date(moment.defaultDatetimeFormat, as_obj); | |||
}, | |||
_date: function(format, as_obj = false) { | |||
const { time_zone } = frappe.sys_defaults; | |||
let date; | |||
if (time_zone) { | |||
date = moment.tz(time_zone); | |||
} else { | |||
date = moment(); | |||
} | |||
if (as_obj) { | |||
return frappe.datetime.moment_to_date_obj(date); | |||
} else { | |||
return date.format(format); | |||
} | |||
}, | |||
moment_to_date_obj: function(moment) { | |||
const date_obj = new Date(); | |||
const date_array = moment.toArray(); | |||
date_obj.setFullYear(date_array[0]); | |||
date_obj.setMonth(date_array[1]); | |||
date_obj.setDate(date_array[2]); | |||
date_obj.setHours(date_array[3]); | |||
date_obj.setMinutes(date_array[4]); | |||
date_obj.setSeconds(date_array[5]); | |||
date_obj.setMilliseconds(date_array[6]); | |||
return date_obj; | |||
}, | |||
nowdate: function() { | |||
return frappe.datetime.get_today(); | |||
return frappe.datetime.now_date(); | |||
}, | |||
now_time: function() { | |||
return frappe.datetime.convert_to_system_tz(moment(), false) | |||
.locale("en").format("HH:mm:ss"); | |||
get_today: function() { | |||
return frappe.datetime.now_date(); | |||
}, | |||
validate: function(d) { | |||
return moment(d).isValid(); | |||
return moment(d, [ | |||
moment.defaultDateFormat, | |||
moment.defaultDatetimeFormat, | |||
moment.defaultTimeFormat | |||
], true).isValid(); | |||
}, | |||
}); | |||
@@ -142,10 +142,10 @@ frappe.ui.Graph = class Graph { | |||
show_specific_values() { | |||
this.specific_values.map(d => { | |||
this.specific_y_lines.add(this.snap.g( | |||
this.snap.line(0, 0, this.width - 50, 0).attr({ | |||
this.snap.line(0, 0, this.width - 70, 0).attr({ | |||
class: d.line_type === "dashed" ? "dashed": "" | |||
}), | |||
this.snap.text(this.width - 100, 0, d.name.toUpperCase()).attr({ | |||
this.snap.text(this.width - 95, 0, d.name.toUpperCase()).attr({ | |||
dy: ".32em", | |||
class: "specific-value", | |||
}) | |||
@@ -67,11 +67,11 @@ frappe.ui.notifications = { | |||
add_notification: function(name, value, doc_dt, target = false) { | |||
let label = this.config[name] ? this.config[name].label : name; | |||
let $list_item = !target | |||
? $(`<li><a class="badge-hover" data-doctype="${name}">${label} | |||
? $(`<li><a class="badge-hover" data-doctype="${name}">${__(label)} | |||
<span class="badge pull-right">${value}</span> | |||
</a></li>`) | |||
: $(`<li><a class="progress-small" data-doctype="${doc_dt}" | |||
data-doc="${name}"><span class="dropdown-item-label">${label}<span> | |||
data-doc="${name}"><span class="dropdown-item-label">${__(label)}<span> | |||
<div class="progress-chart"><div class="progress"> | |||
<div class="progress-bar" style="width: ${value}%"></div> | |||
</div></div> | |||
@@ -376,7 +376,8 @@ h6.uppercase, .h6.uppercase { | |||
border-radius: 3px; | |||
margin-left: -7px; | |||
position: relative; | |||
max-width: calc(~"100% - 50px"); | |||
padding-right: 0px; | |||
// to display the triangle beside the box | |||
overflow: visible; | |||
} | |||
@@ -387,12 +388,24 @@ h6.uppercase, .h6.uppercase { | |||
width: 45px; | |||
} | |||
.action-btns { | |||
position: absolute; | |||
right: 0; | |||
padding: 5px 15px 2px 5px; | |||
} | |||
.comment-header { | |||
background-color: @light-bg; | |||
padding: 10px 15px 10px 13px; | |||
margin: 0px; | |||
color: @text-muted; | |||
border-bottom: 1px solid @light-border-color; | |||
&.links-active { | |||
padding-right: 60px; | |||
} | |||
.commented-on-small { | |||
display: none; | |||
} | |||
.octicon-heart { | |||
color: @heart-color; | |||
@@ -402,6 +415,7 @@ h6.uppercase, .h6.uppercase { | |||
.reply { | |||
padding: 15px; | |||
overflow: auto; | |||
& > div > p:first-child { | |||
margin-top: 0px; | |||
@@ -417,11 +431,15 @@ h6.uppercase, .h6.uppercase { | |||
} | |||
.close-btn-container { | |||
padding: 4px 10px 2px 5px; | |||
.close { | |||
color: inherit; | |||
opacity: 1; | |||
padding: 0 0 0 10px; | |||
} | |||
} | |||
.edit-btn-container { | |||
padding: 4px 5px; | |||
padding: 0; | |||
.edit { | |||
color: inherit; | |||
@@ -650,6 +668,16 @@ h6.uppercase, .h6.uppercase { | |||
} | |||
} | |||
.flex-justify-center { | |||
display: flex; | |||
justify-content: center; | |||
} | |||
.flex-justify-end { | |||
display: flex; | |||
justify-content: flex-end; | |||
} | |||
.hide-control { | |||
display: none !important; | |||
} | |||
@@ -223,6 +223,9 @@ body { | |||
} | |||
@media(max-width: 767px) { | |||
.toggle-sidebar { | |||
margin-right: 0; | |||
} | |||
body[data-route^="Form"]{ | |||
.page-title { | |||
.title-text { | |||
@@ -406,4 +409,67 @@ body { | |||
right: 90px; | |||
} | |||
} | |||
.timeline { | |||
&::before { | |||
content: none; | |||
} | |||
.timeline-new-email { | |||
margin: 20px 0; | |||
padding-left: 15px; | |||
&::before { | |||
content: none; | |||
} | |||
} | |||
.timeline-item { | |||
&.user-content { | |||
margin: 20px 15px; | |||
.media-body { | |||
margin-left: 0; | |||
max-width: 100%; | |||
overflow: hidden; | |||
&:before { | |||
content: none; | |||
} | |||
} | |||
.action-btns { | |||
padding: 5px 10px 2px 5px; | |||
} | |||
.comment-header{ | |||
padding: 7px 10px; | |||
.links-active { | |||
padding-right: 10px; | |||
} | |||
} | |||
.avatar-medium { | |||
margin-right: 10px; | |||
} | |||
.reply { | |||
padding: 10px; | |||
} | |||
.commented-on-small{ | |||
display: inline-block; | |||
} | |||
.commented-on-small{ | |||
display: inline-block; | |||
} | |||
} | |||
&.notification-content { | |||
padding-left: 15px; | |||
margin: 20px 0; | |||
&::before { | |||
content: none; | |||
} | |||
.small { | |||
padding-left: 0; | |||
} | |||
} | |||
.delivery-status-indicator { | |||
float: left; | |||
margin: 0 5px 0 0; | |||
} | |||
} | |||
.asset-details { | |||
line-height: 24px; /*Height of avtar image -36px to align text center vertically*/ | |||
} | |||
} | |||
} |
@@ -15,7 +15,7 @@ | |||
{% endif %} | |||
</table> | |||
{% if data %} | |||
<table class="table table-bordered" cellpadding="0" cellspacing="0" border="0" width="100%"> | |||
<table class="table table-bordered text-medium" cellpadding="0" cellspacing="0" border="0" width="100%"> | |||
<thead> | |||
<tr> | |||
{% for col in columns %} | |||
@@ -26,8 +26,15 @@ | |||
background-color: #7575ff; | |||
} | |||
.for-login { | |||
display: none; | |||
} | |||
.for-forgot { | |||
display: none; | |||
} | |||
section { | |||
.for-signup { | |||
display: none; | |||
} | |||
@@ -77,7 +77,9 @@ login.route = function() { | |||
login.reset_sections = function(hide) { | |||
if(hide || hide===undefined) { | |||
$("section").toggle(false); | |||
$("section.for-login").toggle(false); | |||
$("section.for-forgot").toggle(false); | |||
$("section.for-signup").toggle(false); | |||
} | |||
$('section .indicator').each(function() { | |||
$(this).removeClass().addClass('indicator').addClass('blue') | |||
@@ -0,0 +1,31 @@ | |||
QUnit.module('views'); | |||
QUnit.test("Test: Setting column colour [Kanban view]", function(assert) { | |||
assert.expect(3); | |||
let done = assert.async(); | |||
function get_column(name, colour) { | |||
return ('.kanban-column:contains('+name+')>div>div>ul>li>div.'+colour); | |||
} | |||
frappe.run_serially([ | |||
() => frappe.set_route("List", "ToDo", "Kanban", "Kanban test"), | |||
() => frappe.timeout(1), | |||
() => assert.deepEqual(["List", "ToDo", "Kanban", "Kanban test"], frappe.get_route(), | |||
"Kanban view opened successfully."), | |||
() => { | |||
// set colour for columns | |||
$(get_column('High', "red")).click(); | |||
$(get_column('Medium', "green")).click(); | |||
$(get_column('Low', "yellow")).click(); | |||
}, | |||
() => frappe.timeout(1), | |||
() => { | |||
//check if different colours are set | |||
assert.equal($('.red > span')[0].innerText, 'High', | |||
"Colour is set for kanban column."); | |||
assert.equal($('.green > span')[0].innerText, 'Medium', | |||
"Different colour is set for other column."); | |||
}, | |||
() => done() | |||
]); | |||
}); |
@@ -0,0 +1,30 @@ | |||
QUnit.module('views'); | |||
QUnit.test("Test: Creation [Kanban view]", function(assert) { | |||
assert.expect(2); | |||
let done = assert.async(); | |||
frappe.run_serially([ | |||
() => frappe.set_route("List", "ToDo", "List"), | |||
// click kanban in side bar | |||
() => frappe.click_link('Kanban'), | |||
() => frappe.click_link('New Kanban Board'), | |||
() => frappe.timeout(0.5), | |||
// create new kanban | |||
() => { | |||
assert.equal(cur_dialog.title, 'New Kanban Board', | |||
"Dialog for new kanban opened."); | |||
cur_dialog.set_value('board_name', 'Kanban test'); | |||
cur_dialog.set_value('field_name', 'Priority'); | |||
}, | |||
() => frappe.timeout(0.5), | |||
() => cur_dialog.get_primary_btn().click(), | |||
() => frappe.timeout(1), | |||
() => frappe.set_route("List", "Kanban Board", "List"), | |||
() => frappe.timeout(0.5), | |||
// check in kanban list if new kanban is created | |||
() => assert.equal(cur_list.data[0].name, 'Kanban test', | |||
"Added kanban is visible in kanban list."), | |||
() => done() | |||
]); | |||
}); |
@@ -0,0 +1,27 @@ | |||
QUnit.module('views'); | |||
QUnit.test("Test: Filters [Kanban view]", function(assert) { | |||
assert.expect(3); | |||
let done = assert.async(); | |||
frappe.run_serially([ | |||
() => frappe.set_route("List", "ToDo", "Kanban", "Kanban test"), | |||
() => frappe.timeout(1), | |||
() => { | |||
assert.deepEqual(["List", "ToDo", "Kanban", "Kanban test"], frappe.get_route(), | |||
"Kanban view opened successfully."); | |||
// set filter values | |||
return frappe.set_control('priority', 'Low'); | |||
}, | |||
() => frappe.timeout(1), | |||
() => cur_list.page.btn_secondary.click(), | |||
() => frappe.timeout(1), | |||
() => { | |||
assert.equal(cur_list.data[0].priority, 'Low', | |||
'visible element has low priority'); | |||
let non_low_items = cur_list.data.filter(d => d.priority != 'Low'); | |||
assert.equal(non_low_items.length, 0, 'No item without low priority'); | |||
}, | |||
() => done() | |||
]); | |||
}); |
@@ -0,0 +1,26 @@ | |||
QUnit.module('views'); | |||
QUnit.test("Test: Kanban view", function(assert) { | |||
assert.expect(3); | |||
let done = assert.async(); | |||
let total_elements; | |||
frappe.run_serially([ | |||
() => frappe.set_route("List", "ToDo", "List"), | |||
// calculate number of element in list | |||
() => frappe.timeout(1), | |||
() => total_elements = cur_list.data.length, | |||
() => frappe.set_route("List", "ToDo", "Kanban", "Kanban test"), | |||
() => frappe.timeout(1), | |||
() => { | |||
assert.equal('Kanban', cur_list.current_view, | |||
"Current view is kanban."); | |||
assert.equal("Kanban test", cur_list.list_renderer.page_title, | |||
"Kanban view opened successfully."); | |||
// check if all elements are visible in kanban view | |||
assert.equal(total_elements, cur_list.data.length, | |||
"All elements are visible in kanban view."); | |||
}, | |||
() => done() | |||
]); | |||
}); |
@@ -1,35 +0,0 @@ | |||
QUnit.module('views'); | |||
QUnit.test("Test option click [Module view]", function(assert) { | |||
assert.expect(4); | |||
let done = assert.async(); | |||
frappe.run_serially([ | |||
//click Document Share Report in Permissions section [Report] | |||
() => frappe.set_route("modules", "Setup"), | |||
() => frappe.timeout(0.5), | |||
() => frappe.tests.click_and_wait('a.small:contains("Document Share Report")', 0), | |||
() => assert.deepEqual(frappe.get_route(), ["Report", "DocShare", "Document Share Report"], "First click test."), | |||
//click Print Setting in Printing section [Form] | |||
() => frappe.set_route("modules", "Setup"), | |||
() => frappe.timeout(0.5), | |||
() => frappe.tests.click_and_wait('a.small:contains("Print Setting")', 0), | |||
() => assert.deepEqual(frappe.get_route(), ["Form", "Print Settings"], "Second click test."), | |||
//click Workflow Action in Workflow section [List] | |||
() => frappe.set_route("modules", "Setup"), | |||
() => frappe.timeout(0.5), | |||
() => frappe.tests.click_and_wait('a.small:contains(" Workflow Action ")', 0), | |||
() => assert.deepEqual(frappe.get_route(), ["List", "Workflow Action", "List"], "Third click test."), | |||
//click Application Installer in Applications section | |||
() => frappe.set_route("modules", "Setup"), | |||
() => frappe.timeout(0.5), | |||
() => frappe.tests.click_and_wait('a.small:contains("Application Installer")', 0), | |||
() => assert.deepEqual(frappe.get_route(), ["applications"], "Fourth click test."), | |||
() => done() | |||
]); | |||
}); |
@@ -0,0 +1,76 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import unittest, frappe, requests, time | |||
from frappe.test_runner import make_test_records | |||
from frappe.utils.selenium_testdriver import TestDriver | |||
try: | |||
from urllib.parse import urlparse | |||
except ImportError: | |||
from urlparse import urlparse | |||
class TestOAuth20(unittest.TestCase): | |||
def setUp(self): | |||
self.driver = TestDriver() | |||
make_test_records("OAuth Client") | |||
make_test_records("User") | |||
self.client_id = frappe.get_all("OAuth Client", fields=["*"])[0].get("client_id") | |||
# Set Frappe server URL reqired for id_token generation | |||
frappe.db.set_value("Social Login Keys", None, "frappe_server_url", "http://localhost:8000") | |||
frappe.db.commit() | |||
def test_login_to_authorize_url(self): | |||
# Go to Authorize url | |||
self.driver.get( | |||
"api/method/frappe.integrations.oauth2.authorize?client_id=" + | |||
self.client_id + | |||
"&scope=all%20openid&response_type=code&redirect_uri=http%3A%2F%2Flocalhost" | |||
) | |||
time.sleep(2) | |||
# Login | |||
username = self.driver.find("#login_email")[0] | |||
username.send_keys("test@example.com") | |||
password = self.driver.find("#login_password")[0] | |||
password.send_keys("Eastern_43A1W") | |||
sign_in = self.driver.find(".btn-login")[0] | |||
sign_in.submit() | |||
time.sleep(2) | |||
# Allow access to resource | |||
allow = self.driver.find("#allow")[0] | |||
allow.click() | |||
time.sleep(2) | |||
# Get authorization code from redirected URL | |||
auth_code = urlparse(self.driver.driver.current_url).query.split("=")[1] | |||
payload = "grant_type=authorization_code&code=" | |||
payload += auth_code | |||
payload += "&redirect_uri=http%3A%2F%2Flocalhost&client_id=" | |||
payload += self.client_id | |||
headers = {'content-type':'application/x-www-form-urlencoded'} | |||
# Request for bearer token | |||
token_response = requests.post( frappe.get_site_config().host_name + | |||
"/api/method/frappe.integrations.oauth2.get_token", data=payload, headers=headers) | |||
# Parse bearer token json | |||
bearer_token = token_response.json() | |||
self.assertTrue(bearer_token.get("access_token")) | |||
self.assertTrue(bearer_token.get("expires_in")) | |||
self.assertTrue(bearer_token.get("id_token")) | |||
self.assertTrue(bearer_token.get("refresh_token")) | |||
self.assertTrue(bearer_token.get("scope")) | |||
self.assertTrue(bearer_token.get("token_type") == "Bearer") |
@@ -2,4 +2,8 @@ frappe/tests/ui/test_number_format.js | |||
frappe/tests/ui/test_list/test_list_filter.js | |||
frappe/tests/ui/test_list/test_list_paging.js | |||
frappe/tests/ui/test_module_view.js | |||
frappe/tests/ui/test_calendar_view.js | |||
frappe/tests/ui/test_calendar_view.js | |||
frappe/tests/ui/test_kanban/test_kanban_creation.js | |||
frappe/tests/ui/test_kanban/test_kanban_view.js | |||
frappe/tests/ui/test_kanban/test_kanban_filters.js | |||
frappe/tests/ui/test_kanban/test_kanban_column.js |