Quellcode durchsuchen

Merge branch 'staging'

version-14
mbauskar vor 8 Jahren
Ursprung
Commit
286c92aef0
36 geänderte Dateien mit 772 neuen und 240 gelöschten Zeilen
  1. +1
    -1
      frappe/__init__.py
  2. +28
    -3
      frappe/core/doctype/doctype/test_doctype.py
  3. +32
    -1
      frappe/core/web_form/edit_profile/edit_profile.json
  4. +1
    -29
      frappe/desk/page/setup_wizard/setup_wizard.css
  5. +1
    -10
      frappe/desk/page/setup_wizard/setup_wizard.js
  6. +7
    -0
      frappe/desk/tags.py
  7. +4
    -4
      frappe/email/doctype/email_group/email_group.py
  8. +1
    -1
      frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
  9. +16
    -0
      frappe/integrations/doctype/oauth_client/test_records.json
  10. +1
    -1
      frappe/model/db_query.py
  11. +1
    -1
      frappe/model/db_schema.py
  12. +2
    -1
      frappe/patches.txt
  13. +0
    -0
      frappe/patches/v8_5/__init__.py
  14. +20
    -0
      frappe/patches/v8_5/delete_email_group_member_with_invalid_emails.py
  15. +27
    -3
      frappe/public/css/form.css
  16. +63
    -0
      frappe/public/css/mobile.css
  17. +79
    -47
      frappe/public/js/frappe/form/control.js
  18. +7
    -5
      frappe/public/js/frappe/form/footer/timeline.js
  19. +89
    -69
      frappe/public/js/frappe/form/footer/timeline_item.html
  20. +7
    -4
      frappe/public/js/frappe/list/list_renderer.js
  21. +65
    -15
      frappe/public/js/frappe/misc/datetime.js
  22. +2
    -2
      frappe/public/js/frappe/ui/graph.js
  23. +2
    -2
      frappe/public/js/frappe/ui/toolbar/notifications.js
  24. +31
    -3
      frappe/public/less/form.less
  25. +66
    -0
      frappe/public/less/mobile.less
  26. +1
    -1
      frappe/templates/emails/auto_email_report.html
  27. +8
    -1
      frappe/templates/includes/login/login.css
  28. +3
    -1
      frappe/templates/includes/login/login.js
  29. +13
    -0
      frappe/tests/test_db_query.py
  30. +31
    -0
      frappe/tests/ui/test_kanban/test_kanban_column.js
  31. +30
    -0
      frappe/tests/ui/test_kanban/test_kanban_creation.js
  32. +27
    -0
      frappe/tests/ui/test_kanban/test_kanban_filters.js
  33. +26
    -0
      frappe/tests/ui/test_kanban/test_kanban_view.js
  34. +0
    -35
      frappe/tests/ui/test_module/test_module_option.js
  35. +76
    -0
      frappe/tests/ui/test_oauth20.py
  36. +4
    -0
      frappe/tests/ui/tests.txt

+ 1
- 1
frappe/__init__.py Datei anzeigen

@@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template

__version__ = '8.6.5'
__version__ = '8.6.6'
__title__ = "Frappe Framework"

local = Local()


+ 28
- 3
frappe/core/doctype/doctype/test_doctype.py Datei anzeigen

@@ -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()

+ 32
- 1
frappe/core/web_form/edit_profile/edit_profile.json Datei anzeigen

@@ -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


+ 1
- 29
frappe/desk/page/setup_wizard/setup_wizard.css Datei anzeigen

@@ -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,


+ 1
- 10
frappe/desk/page/setup_wizard/setup_wizard.js Datei anzeigen

@@ -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>`);
}
});

+ 7
- 0
frappe/desk/tags.py Datei anzeigen

@@ -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):


+ 4
- 4
frappe/email/doctype/email_group/email_group.py Datei anzeigen

@@ -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


+ 1
- 1
frappe/integrations/doctype/dropbox_settings/dropbox_settings.py Datei anzeigen

@@ -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'])


+ 16
- 0
frappe/integrations/doctype/oauth_client/test_records.json Datei anzeigen

@@ -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
}
]

+ 1
- 1
frappe/model/db_query.py Datei anzeigen

@@ -291,7 +291,7 @@ class DatabaseQuery(object):

# prepare in condition
if f.operator.lower() in ('in', 'not in'):
values = f.value
values = f.value or ''
if not isinstance(values, (list, tuple)):
values = values.split(",")



+ 1
- 1
frappe/model/db_schema.py Datei anzeigen

@@ -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:


+ 2
- 1
frappe/patches.txt Datei anzeigen

@@ -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
frappe/patches/v8_5/__init__.py Datei anzeigen


+ 20
- 0
frappe/patches/v8_5/delete_email_group_member_with_invalid_emails.py Datei anzeigen

@@ -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)

+ 27
- 3
frappe/public/css/form.css Datei anzeigen

@@ -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;
}


+ 63
- 0
frappe/public/css/mobile.css Datei anzeigen

@@ -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*/
}
}

+ 79
- 47
frappe/public/js/frappe/form/control.js Datei anzeigen

@@ -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);


+ 7
- 5
frappe/public/js/frappe/form/footer/timeline.js Datei anzeigen

@@ -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 = {


+ 89
- 69
frappe/public/js/frappe/form/footer/timeline_item.html Datei anzeigen

@@ -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) { %}
&ndash;
<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;">
&ndash; {%= 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) { %}
&ndash;
<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">&ndash;</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">&ndash;</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">&ndash;</span>
{%= __("Details") %}
{% } else { %}
{% if (frappe.model.can_read(\'Communication\')) { %}
<span class="text-muted n-dash">&ndash;</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">
&ndash; {%= data.comment_on %}</span>
<span class="text-muted commented-on-small">
&ndash; {%= 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;">
&ndash; {%= 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;">
&ndash; {%= data.comment_on %}</span>
</div>
{% } %}


+ 7
- 4
frappe/public/js/frappe/list/list_renderer.js Datei anzeigen

@@ -410,7 +410,7 @@ frappe.views.ListRenderer = Class.extend({
},

get_indicator_html: function (doc) {
var indicator = frappe.get_indicator(doc, this.doctype);
var indicator = this.get_indicator_from_doc(doc);
if (indicator) {
return `<span class='indicator ${indicator[1]} filterable'
data-filter='${indicator[2]}'>
@@ -419,15 +419,18 @@ frappe.views.ListRenderer = Class.extend({
}
return '';
},

get_indicator_dot: function (doc) {
var indicator = frappe.get_indicator(doc, this.doctype);
var indicator = this.get_indicator_from_doc(doc);
if (!indicator) {
return '';
}
return `<span class='indicator ${indicator[1]}' title='${__(indicator[0])}'></span>`;
},

get_indicator_from_doc: function (doc) {
var workflow = frappe.workflow.workflows[this.doctype];
var override = workflow ? workflow['override_status'] : true;
return frappe.get_indicator(doc, this.doctype, override);
},
prepare_data: function (data) {
if (data.modified)
this.prepare_when(data, data.modified);


+ 65
- 15
frappe/public/js/frappe/misc/datetime.js Datei anzeigen

@@ -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();
},

});


+ 2
- 2
frappe/public/js/frappe/ui/graph.js Datei anzeigen

@@ -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",
})


+ 2
- 2
frappe/public/js/frappe/ui/toolbar/notifications.js Datei anzeigen

@@ -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>


+ 31
- 3
frappe/public/less/form.less Datei anzeigen

@@ -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;
}


+ 66
- 0
frappe/public/less/mobile.less Datei anzeigen

@@ -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*/
}
}
}

+ 1
- 1
frappe/templates/emails/auto_email_report.html Datei anzeigen

@@ -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 %}


+ 8
- 1
frappe/templates/includes/login/login.css Datei anzeigen

@@ -26,8 +26,15 @@
background-color: #7575ff;
}

.for-login {
display: none;
}

.for-forgot {
display: none;
}

section {
.for-signup {
display: none;
}



+ 3
- 1
frappe/templates/includes/login/login.js Datei anzeigen

@@ -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')


+ 13
- 0
frappe/tests/test_db_query.py Datei anzeigen

@@ -31,6 +31,19 @@ class TestReportview(unittest.TestCase):
self.assertTrue({"name":"DocField"} \
in DatabaseQuery("DocType").execute(filters={"name": "DocField"}))

def test_in_not_in_filters(self):
self.assertFalse(DatabaseQuery("DocType").execute(filters={"name": ["in", None]}))
self.assertTrue({"name":"DocType"} \
in DatabaseQuery("DocType").execute(filters={"name": ["not in", None]}))

for result in [{"name":"DocType"}, {"name":"DocField"}]:
self.assertTrue(result
in DatabaseQuery("DocType").execute(filters={"name": ["in", 'DocType,DocField']}))

for result in [{"name":"DocType"}, {"name":"DocField"}]:
self.assertFalse(result
in DatabaseQuery("DocType").execute(filters={"name": ["not in", 'DocType,DocField']}))

def test_or_filters(self):
data = DatabaseQuery("DocField").execute(
filters={"parent": "DocType"}, fields=["fieldname", "fieldtype"],


+ 31
- 0
frappe/tests/ui/test_kanban/test_kanban_column.js Datei anzeigen

@@ -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()
]);
});

+ 30
- 0
frappe/tests/ui/test_kanban/test_kanban_creation.js Datei anzeigen

@@ -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()
]);
});

+ 27
- 0
frappe/tests/ui/test_kanban/test_kanban_filters.js Datei anzeigen

@@ -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()
]);
});

+ 26
- 0
frappe/tests/ui/test_kanban/test_kanban_view.js Datei anzeigen

@@ -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()
]);
});

+ 0
- 35
frappe/tests/ui/test_module/test_module_option.js Datei anzeigen

@@ -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()
]);
});

+ 76
- 0
frappe/tests/ui/test_oauth20.py Datei anzeigen

@@ -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")

+ 4
- 0
frappe/tests/ui/tests.txt Datei anzeigen

@@ -3,5 +3,9 @@ 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_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
frappe/tests/ui/test_linked_with.js
frappe/custom/doctype/customize_form/test_customize_form.js

Laden…
Abbrechen
Speichern