Merge branch 'develop' of https://github.com/frappe/frappe into website-bootstrap-4

This commit is contained in:
Faris Ansari 2019-02-01 00:06:21 +05:30
commit b6d5607ebc
414 changed files with 8766 additions and 7764 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

BIN
.github/frappe-framework-logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -11,17 +11,13 @@ setup_mariadb_env() {
if [[ $DB == 'mariadb' ]]; then
setup_mariadb_env 'test_frappe'
bench --site test_site reinstall --yes
bench --site test_site setup-help
bench setup-global-help --root_password travis
bench --site test_site scheduler disable
bench --site test_site run-tests --coverage
elif [[ $TEST_TYPE == 'ui' ]]; then
setup_mariadb_env 'test_site_ui'
bench --site test_site_ui --force restore ./apps/frappe/test_sites/test_site_ui/20181116_225029-test_site_ui-database.sql.gz
bench --site test_site_ui --force restore ./apps/frappe/test_sites/test_site_ui/test_site_ui-database.sql.gz
bench --site test_site_ui migrate
bench --site test_site_ui setup-help
bench setup-global-help --root_password travis
bench --site test_site_ui scheduler disable
cd apps/frappe && yarn && yarn cypress:run
@ -29,8 +25,6 @@ elif [[ $DB == 'postgres' ]]; then
psql -c "CREATE DATABASE test_frappe;" -U postgres
psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe';" -U postgres
bench --site test_site_postgres reinstall --yes
bench --site test_site_postgres setup-help
bench setup-global-help --db_type=postgres --root_password travis
bench --site test_site_postgres scheduler disable
bench --site test_site_postgres run-tests --coverage
fi

View file

@ -1,5 +1,5 @@
<div align="center">
<img src=".github/frappe-bird.png" height="150">
<img src=".github/frappe-framework-logo.png" height="150">
<h1>
<a href="https://frappe.io">
frappe
@ -22,7 +22,7 @@
</a>
<a href='https://www.codetriage.com/frappe/frappe'>
<img src='https://www.codetriage.com/frappe/frappe/badges/users.svg'>
</a>
</a>
<a href='https://coveralls.io/github/frappe/frappe?branch=develop'>
<img src='https://coveralls.io/repos/github/frappe/frappe/badge.svg?branch=develop'>
</a>

View file

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View file

@ -1,10 +1,12 @@
context('Login', () => {
beforeEach(() => {
cy.request('/api/method/logout');
cy.visit('/login');
cy.location().should('be', '/login');
});
it('greets with login screen', () => {
cy.get('.page-card-head').contains('Sign In');
cy.get('.page-card-head').contains('Login');
});
it('validates password', () => {

View file

@ -0,0 +1,50 @@
context('Relative Timeframe', () => {
beforeEach(() => {
cy.login('Administrator', 'qwe');
cy.visit('/desk');
});
before(() => {
cy.login('Administrator', 'qwe');
cy.visit('/desk');
cy.window().its('frappe').then(frappe => {
frappe.call("frappe.tests.test_utils.create_todo_records");
});
});
it('set relative filter for Previous and check list', () => {
cy.visit('/desk#List/ToDo/List');
cy.get('.list-row:contains("this is fourth todo")').should('exist');
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
cy.get('.fieldname-select-area').should('exist');
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
cy.get('select.condition.form-control').select("Previous");
cy.get('.filter-field select.input-with-feedback.form-control').select("1 week");
cy.server();
cy.route({
method: 'POST',
url: '/'
}).as('applyFilter');
cy.get('.filter-box .btn:contains("Apply")').click();
cy.wait('@applyFilter');
cy.get('.list-row-container').its('length').should('eq', 1);
cy.get('.list-row-container').should('contain', 'this is second todo');
cy.get('.remove-filter.btn').click();
});
it('set relative filter for Next and check list', () => {
cy.visit('/desk#List/ToDo/List');
cy.get('.list-row:contains("this is fourth todo")').should('exist');
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
cy.get('select.condition.form-control').select("Next");
cy.get('.filter-field select.input-with-feedback.form-control').select("1 week");
cy.server();
cy.route({
method: 'POST',
url: '/'
}).as('applyFilter');
cy.get('.filter-box .btn:contains("Apply")').click();
cy.wait('@applyFilter');
cy.get('.list-row-container').its('length').should('eq', 1);
cy.get('.list-row').should('contain', 'this is first todo');
cy.get('.remove-filter.btn').click();
});
});

View file

@ -0,0 +1,50 @@
context('Table MultiSelect', () => {
beforeEach(() => {
cy.login('Administrator', 'qwe');
});
it('select value from multiselect dropdown', () => {
cy.visit('/desk#Form/ToDo/New ToDo 1');
cy.fill_field('description', 'asdf', 'Text Editor').blur();
cy.get('input[data-fieldname="assign_to"]').focus().as('input');
cy.get('input[data-fieldname="assign_to"] + ul').should('be.visible');
cy.get('@input').type('faris{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname="assign_to"] .form-control .tb-selected-value')
.first().as('selected-value');
cy.get('@selected-value').should('contain', 'faris@erpnext.com');
cy.server();
cy.route('POST', '/').as('save_form');
// trigger save
cy.get('.primary-action').click();
cy.wait('@save_form').its('status').should('eq', 200);
cy.get('@selected-value').should('contain', 'faris@erpnext.com');
});
it('delete value using backspace', () => {
cy.visit('/desk#List/ToDo/List');
cy.get('.list-row a').should('exist');
cy.get('.list-subject').last().find('a').click();
cy.get('input[data-fieldname="assign_to"]').focus().type('{backspace}');
cy.get('.frappe-control[data-fieldname="assign_to"] .form-control .tb-selected-value')
.should('not.exist');
});
it('delete value using x', () => {
cy.visit('/desk#List/ToDo/List');
cy.get('.list-row a').should('exist');
cy.get('.list-subject').last().find('a').click();
cy.get('.frappe-control[data-fieldname="assign_to"] .form-control .tb-selected-value').as('existing_value');
cy.get('@existing_value').find('.btn-remove').click();
cy.get('@existing_value').should('not.exist');
});
it('navigate to selected value', () => {
cy.visit('/desk#List/ToDo/List');
cy.get('.list-row a').should('exist');
cy.get('.list-subject').last().find('a').click();
cy.get('.frappe-control[data-fieldname="assign_to"] .form-control .tb-selected-value').as('existing_value');
cy.get('@existing_value').find('.btn-link-to-form').click();
cy.location('hash').should('contain', 'Form/User/faris@erpnext.com');
});
});

View file

@ -17,7 +17,13 @@ from faker import Faker
from .exceptions import *
from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader)
__version__ = '10.1.68'
# Hamless for Python 3
# For Python 2 set default encoding to utf-8
if sys.version[0] == '2':
reload(sys)
sys.setdefaultencoding("utf-8")
__version__ = '11.1.3'
__title__ = "Frappe Framework"
local = Local()

View file

@ -49,6 +49,10 @@ def clear_defaults_cache(user=None):
elif frappe.flags.in_install!="frappe":
frappe.cache().delete_key("defaults")
def clear_document_cache():
frappe.local.document_cache = {}
frappe.cache().delete_key("document_cache")
def clear_doctype_cache(doctype=None):
cache = frappe.cache()
@ -70,7 +74,8 @@ def clear_doctype_cache(doctype=None):
# clear all parent doctypes
for dt in frappe.db.get_all('DocField', 'parent', dict(fieldtype='Table', options=doctype)):
for dt in frappe.db.get_all('DocField', 'parent',
dict(fieldtype=['in', frappe.model.table_fields], options=doctype)):
clear_single(dt.parent)
# clear all notifications
@ -81,3 +86,6 @@ def clear_doctype_cache(doctype=None):
for name in groups:
cache.delete_value(name)
# Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured
clear_document_cache()

View file

@ -0,0 +1,19 @@
- Dynamic [Frappe Charts](https://github.com/frappe/charts) with Report Builder (built by @pratu16x7)
- New Frappe Chat for easier internal communication (built by @achillesrasquinha)
- [Frappe DataTable](https://github.com/frappe/datatable) for better reports (built by @netchampfaris)
- Google Calendar can now be integrated with Frappe
- Files can now be uploaded using drag-and-drop
- Enhanced List View and Tree View
- Bulk Actions from List View
- Quill editor has been introduced in place of Summernote
- HTML Editor has been introduced
- New User Permissions
- Subscriptions in ERPNext now moved to Auto Repeat in Frappe
- Support for Razorpay and PayPal subscriptions
- Better Social login, Workflow
- Messages for when user goes online/offline
- Logout from all sessions on password change
- Desktop icons can now be selected from a dialog box
- Changes have been made to ensure Frappe is compatible with Python 3
- Better documentation is now available with support for more languages
- A lot of other fixes have been done to ensure a better overall user experience

View file

@ -1,2 +1,2 @@
### User Permissions
- User Permission is now a DocType, a new UX for the existing Role and User Permission managers to make it easy to enter permissions. For more details please check <a href="https://erpnext.org/docs/user/manual/en/setting-up/users-and-permissions/user-permissions">User Permissions</a>
- User Permission is now a DocType, a new UX for the existing Role and User Permission managers to make it easy to enter permissions. For more details please check <a href="https://erpnext.com/docs/user/manual/en/setting-up/users-and-permissions/user-permissions">User Permissions</a>

View file

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import frappe
from frappe import _

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - standard imports
import json

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - standard imports
import unittest

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - module imports
from frappe.model.document import Document
from frappe import _

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - standard imports
import unittest
@ -16,7 +18,7 @@ class TestChatProfile(unittest.TestCase):
# def test_create(self):
# with self.assertRaises(frappe.ValidationError):
# chat_profile.create(test_user)
# user = get_user_doc(session.user)
# if not user.chat_profile:
# chat_profile.create(user.name)
@ -47,7 +49,7 @@ class TestChatProfile(unittest.TestCase):
# ))
# user = get_user_doc(session.user)
# prev = chat_profile.get(user.name)
# prev = chat_profile.get(user.name)
# chat_profile.update(user.name, data = dict(
# status = 'Offline'

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - standard imports
import json

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - module imports
from frappe.model.document import Document
import frappe

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - module imports
from frappe.chat.util.util import (
get_user_doc,

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - standard imports
import unittest
@ -7,6 +9,7 @@ from frappe.chat.util import (
safe_json_loads
)
import frappe
import six
class TestChatUtil(unittest.TestCase):
def test_safe_json_loads(self):
@ -15,10 +18,10 @@ class TestChatUtil(unittest.TestCase):
number = safe_json_loads("1.0")
self.assertEqual(type(number), float)
string = safe_json_loads("foobar")
self.assertEqual(type(string), str)
self.assertEqual(type(string), six.text_type)
array = safe_json_loads('[{ "foo": "bar" }]')
self.assertEqual(type(array), list)

View file

@ -1,3 +1,5 @@
from __future__ import unicode_literals
# imports - third-party imports
import requests
@ -26,7 +28,7 @@ def get_user_doc(user = None):
user = user or session.user
user = frappe.get_doc('User', user)
return user
def squashify(what):
@ -43,9 +45,9 @@ def safe_json_loads(*args):
arg = json.loads(arg)
except Exception as e:
pass
results.append(arg)
return squashify(results)
def filter_dict(what, keys, ignore = False):
@ -68,7 +70,7 @@ def get_if_empty(a, b):
if not a:
a = b
return a
def listify(arg):
if not isinstance(arg, list):
arg = [arg]
@ -89,7 +91,7 @@ def check_url(what, raise_err = False):
raise ValueError('{what} not a valid URL.')
else:
return False
return True
def create_test_user(module):
@ -110,5 +112,5 @@ def get_emojis():
if resp.ok:
emojis = resp.json()
redis.hset('frappe_emojis', 'emojis', emojis)
return dictify(emojis)

View file

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import frappe
from frappe.chat.util import filter_dict, safe_json_loads

View file

@ -1,6 +1,6 @@
from __future__ import unicode_literals, absolute_import, print_function
import click
import json, sys
import sys
import frappe
from frappe.utils import cint
from frappe.commands import pass_context, get_site

View file

@ -1,6 +1,5 @@
from __future__ import unicode_literals, absolute_import, print_function
import click
import frappe
from frappe.commands import pass_context, get_site
# translation

View file

@ -343,6 +343,7 @@ def mariadb(context):
frappe.conf.db_name,
'-h', frappe.conf.db_host or "localhost",
'--pager=less -SFX',
'--safe-updates',
"-A"])
@click.command('postgres')
@ -600,58 +601,27 @@ def get_version():
@click.option('--db_type')
@click.option('--root_password')
def setup_global_help(db_type=None, root_password=None):
'''setup help table in a separate database that will be
'''Deprecated: setup help table in a separate database that will be
shared by the whole bench and set `global_help_setup` as 1 in
common_site_config.json'''
from frappe.installer import update_site_config
frappe.local.flags = frappe._dict()
frappe.local.flags.in_setup_help = True
frappe.local.flags.in_install = True
frappe.local.lang = 'en'
frappe.local.conf = frappe.get_site_config(sites_path='.')
update_site_config('global_help_setup', 1,
site_config_path=os.path.join('.', 'common_site_config.json'))
if root_password:
frappe.local.conf.root_password = root_password
if not frappe.local.conf.db_type:
frappe.local.conf.db_type = db_type
from frappe.utils.help import sync
sync()
print_in_app_help_deprecation()
@click.command('get-docs-app')
@click.argument('app')
def get_docs_app(app):
'''Get the docs app for given app'''
from frappe.utils.help import setup_apps_for_docs
setup_apps_for_docs(app)
'''Deprecated: Get the docs app for given app'''
print_in_app_help_deprecation()
@click.command('get-all-docs-apps')
def get_all_docs_apps():
'''Get docs apps for all apps'''
from frappe.utils.help import setup_apps_for_docs
for app in frappe.get_installed_apps():
setup_apps_for_docs(app)
'''Deprecated: Get docs apps for all apps'''
print_in_app_help_deprecation()
@click.command('setup-help')
@pass_context
def setup_help(context):
'''Setup help table in the current site (called after migrate)'''
from frappe.utils.help import sync
for site in context.sites:
try:
frappe.init(site)
frappe.connect()
sync()
finally:
frappe.destroy()
'''Deprecated: Setup help table in the current site (called after migrate)'''
print_in_app_help_deprecation()
@click.command('rebuild-global-search')
@pass_context
@ -715,6 +685,10 @@ def auto_deploy(context, app, migrate=False, restart=False, remote='upstream'):
else:
print('No Updates')
def print_in_app_help_deprecation():
print("In app help has been removed.\nYou can access the documentation on erpnext.com/docs or frappe.io/docs")
return
commands = [
build,
clear_cache,
@ -747,6 +721,5 @@ commands = [
add_to_email_queue,
setup_global_help,
setup_help,
rebuild_global_search,
auto_deploy
rebuild_global_search
]

View file

@ -80,5 +80,12 @@ def get_data():
"color": '#FF4136',
'standard': 1,
'idx': 15
},
{
"module_name": 'Settings',
"color": "#bdc3c7",
"reverse": 1,
"icon": "octicon octicon-settings",
"type": "module"
}
]

51
frappe/config/settings.py Normal file
View file

@ -0,0 +1,51 @@
from frappe import _
def get_data():
return [{
"label": _("Settings"),
"icon": "fa fa-wrench",
"items": [
{
"type": "doctype",
"name": "System Settings",
"label": _("System Settings"),
"description": _("Language, Date and Time settings"),
"hide_count": True
},
{
"type": "doctype",
"name": "Domain Settings",
"label": _("Domain Settings"),
"description": _("Enable / Disable Domains"),
"hide_count": True
},
{
"type": "doctype",
"name": "Print Settings",
"label": _("Print Settings"),
"description": _("Print Style, PDF Size"),
"hide_count": True
},
{
"type": "doctype",
"name": "Website Settings",
"label": _("Website Settings"),
"description": _("Landing Page, Website Theme, Brand Setup and more"),
"hide_count": True
},
{
"type": "doctype",
"name": "S3 Backup Settings",
"label": _("S3 Backup Settings"),
"description": _("Enable / Disable Backup, Backup Frequency"),
"hide_count": True
},
{
"type": "doctype",
"name": "SMS Settings",
"label": _("SMS Settings"),
"description": _("SMS Gateway URL, Message & Receiver Parameter"),
"hide_count": True
}
]
}]

View file

@ -2,7 +2,6 @@
# For license information, please see license.txt
from __future__ import unicode_literals
from six.moves import range
from six import iteritems
import frappe
@ -53,11 +52,11 @@ def get_reference_addresses_and_contact(reference_doctype, reference_name):
filters = { "name": reference_name }
reference_list = [d[0] for d in frappe.get_list(reference_doctype, filters=filters, fields=["name"], as_list=True)]
for d in reference_list:
reference_details.setdefault(d, frappe._dict())
reference_details = get_reference_details(reference_doctype, reference_list, "Address", reference_details)
reference_details = get_reference_details(reference_doctype, reference_list, "Contact", reference_details)
reference_details = get_reference_details(reference_doctype, "Address", reference_list, reference_details)
reference_details = get_reference_details(reference_doctype, "Contact", reference_list, reference_details)
for reference_name, details in iteritems(reference_details):
addresses = details.get("address", [])
@ -68,21 +67,14 @@ def get_reference_addresses_and_contact(reference_doctype, reference_name):
result.extend(add_blank_columns_for("Address"))
data.append(result)
else:
addresses = map(list, addresses)
contacts = map(list, contacts)
result = [reference_name]
result.extend(list(addresses) or add_blank_columns_for("Address"))
result.extend(list(contacts) or add_blank_columns_for("Contact"))
data.append(result)
max_length = max(len(addresses), len(contacts))
for idx in range(0, max_length):
result = [reference_name]
address = addresses[idx] if idx < len(addresses) else add_blank_columns_for("Address")
contact = contacts[idx] if idx < len(contacts) else add_blank_columns_for("Contact")
result.extend(address)
result.extend(contact)
data.append(result)
return data
def get_reference_details(reference_doctype, reference_list, doctype, reference_details):
def get_reference_details(reference_doctype, doctype, reference_list, reference_details):
filters = [
["Dynamic Link", "link_doctype", "=", reference_doctype],
["Dynamic Link", "link_name", "in", reference_list]
@ -91,9 +83,7 @@ def get_reference_details(reference_doctype, reference_list, doctype, reference_
records = frappe.get_list(doctype, filters=filters, fields=fields, as_list=True)
for d in records:
details = reference_details.get(d[0]) or {}
details.setdefault(frappe.scrub(doctype), []).append(d[1:])
reference_details[d[0]][frappe.scrub(doctype)] = d[1:]
return reference_details
def add_blank_columns_for(doctype):

View file

@ -0,0 +1,107 @@
from __future__ import unicode_literals
import frappe
import frappe.defaults
import unittest
from frappe.contacts.report.addresses_and_contacts.addresses_and_contacts import get_data
def get_custom_linked_doctype():
if bool(frappe.get_all("DocType", filters={'name':'Test Custom Doctype'})):
return
doc = frappe.get_doc({
"doctype": "DocType",
"module": "Core",
"custom": 1,
"fields": [{
"label": "Test Field",
"fieldname": "test_field",
"fieldtype": "Data"
},
{
"label": "Contact HTML",
"fieldname": "contact_html",
"fieldtype": "HTML"
},
{
"label": "Address HTML",
"fieldname": "address_html",
"fieldtype": "HTML"
}],
"permissions": [{
"role": "System Manager",
"read": 1
}],
"name": "Test Custom Doctype",
})
doc.insert()
def get_custom_doc_for_address_and_contacts():
get_custom_linked_doctype()
linked_doc = frappe.get_doc({
"doctype": "Test Custom Doctype",
"test_field": "Hello",
}).insert()
return linked_doc
def create_linked_address(link_list):
if frappe.flags.test_address_created:
return
address = frappe.get_doc({
"doctype": "Address",
"address_title": "_Test Address",
"address_type": "Billing",
"address_line1": "test address line 1",
"address_line2": "test address line 2",
"city": "Milan",
"country": "Italy"
})
for name in link_list:
address.append("links",{
'link_doctype': 'Test Custom Doctype',
'link_name': name
})
address.insert()
frappe.flags.test_address_created = True
def create_linked_contact(link_list):
if frappe.flags.test_contact_created:
return
contact = frappe.get_doc({
"doctype": "Contact",
"salutation": "Mr",
"email_id": "test_contact@example.com",
"first_name": "_Test First Name",
"last_name": "_Test Last Name",
"is_primary_contact": 1,
"phone": "+91 0000000000",
"status": "Open"
})
for name in link_list:
contact.append("links",{
'link_doctype': 'Test Custom Doctype',
'link_name': name
})
contact.insert()
frappe.flags.test_contact_created = True
class TestAddressesAndContacts(unittest.TestCase):
def test_get_data(self):
linked_docs = [get_custom_doc_for_address_and_contacts(), get_custom_doc_for_address_and_contacts(), get_custom_doc_for_address_and_contacts()]
links_list = [item.name for item in linked_docs]
create_linked_contact(links_list)
create_linked_address(links_list)
report_data = get_data({"reference_doctype": "Test Custom Doctype"})
for link in links_list:
test_item = [link, 'test address line 1', 'test address line 2', 'Milan', None, None, 'Italy', 0, '_Test First Name', '_Test Last Name', '+91 0000000000', None, 'test_contact@example.com', 1]
self.assertIn(test_item, report_data)
def tearDown(self):
frappe.db.rollback()

View file

@ -75,8 +75,8 @@ def get_feed_match_conditions(user=None, force=True):
if user_permissions:
can_read_docs = []
for doctype, obj in user_permissions.items():
for n in obj.get("docs", []):
can_read_docs.append('{}|{}'.format(doctype, frappe.db.escape(n)))
for n in obj:
can_read_docs.append('{}|{}'.format(doctype, frappe.db.escape(n.get('doc', ''))))
if can_read_docs:
conditions.append("concat_ws('|', `tabCommunication`.reference_doctype, `tabCommunication`.reference_name) in ({})".format(

View file

@ -96,15 +96,17 @@ def notify_mentions(doc):
recipients = [frappe.db.get_value("User", {"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1}, "email")
for name in mentions]
link = get_link_to_form(doc.reference_doctype, doc.reference_name, label=parent_doc_label)
frappe.sendmail(
recipients=recipients,
sender=frappe.session.user,
subject=subject,
template="mentioned_in_comment",
args={
"sender_fullname": sender_fullname,
"body_content": _("{0} mentioned you in a comment in {1}").format(sender_fullname, link),
"comment": doc,
"link": get_link_to_form(doc.reference_doctype, doc.reference_name, label=parent_doc_label)
"link": link
},
header=[_('New Mention'), 'orange']
)

View file

@ -34,8 +34,10 @@ def import_data(data_import):
frappe.db.set_value("Data Import", data_import, "import_status", "In Progress", update_modified=False)
frappe.publish_realtime("data_import_progress", {"progress": "0",
"data_import": data_import, "reload": True}, user=frappe.session.user)
from frappe.core.page.background_jobs.background_jobs import get_info
enqueued_jobs = [d.get("job_name") for d in get_info()]
if data_import not in enqueued_jobs:
enqueue(upload, queue='default', timeout=6000, event='data_import', job_name=data_import,
data_import_doc=data_import, from_data_import="Yes", user=frappe.session.user)

View file

@ -434,15 +434,28 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
except Exception as e:
error_flag = True
err_msg = frappe.local.message_log and "\n".join([json.loads(msg).get('message') for msg in frappe.local.message_log]) or cstr(e)
# build error message
if frappe.local.message_log:
err_msg = "\n".join(['<p class="border-bottom small">{}</p>'.format(json.loads(msg).get('message')) for msg in frappe.local.message_log])
else:
err_msg = '<p class="border-bottom small">{}</p>'.format(cstr(e))
error_trace = frappe.get_traceback()
if error_trace:
error_log_doc = frappe.log_error(error_trace)
error_link = get_url_to_form("Error Log", error_log_doc.name)
else:
error_link = None
log(**{"row": row_idx + 1, "title":'Error for row %s' % (len(row)>1 and frappe.safe_decode(row[1]) or ""), "message": err_msg,
"indicator": "red", "link":error_link})
log(**{
"row": row_idx + 1,
"title": 'Error for row %s' % (len(row)>1 and frappe.safe_decode(row[1]) or ""),
"message": err_msg,
"indicator": "red",
"link":error_link
})
# data with error to create a new file
# include the errored data in the last row as last_error_row_idx will not be updated for the last row
if skip_errors:

View file

@ -6,19 +6,19 @@
<th style="width:40%"> {{ __("Row Status") }} </th>
<th style="width:50%"> {{ __("Message") }} </th>
</tr>
{% for row in data %}
{% if (!show_only_errors) || (show_only_errors && row.indicator == "red") %}
<tr>
<td>
<span>{{ row.row }} </span>
<span>{{ row.row }} </span>
</td>
<td>
<span class="indicator {{ row.indicator }}"> {{ row.title }} </span>
</td>
<td>
{% if (import_status != "Failed" || (row.indicator == "red")) { %}
<span> {{ row.message }} </span>
<div>{{ row.message }}</div>
{% if row.link %}
<span style="width: 10%; float:right;">
<a class="btn-open no-decoration" title="Open Link" href="{{ row.link }}">

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import frappe.share
import unittest

View file

@ -10,7 +10,7 @@ import frappe
from frappe import _
from frappe.utils import now, cint
from frappe.model import no_value_fields, default_fields, data_fieldtypes
from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields
from frappe.model.document import Document
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.desk.notifications import delete_notification_count_for
@ -82,7 +82,7 @@ class DocType(Document):
if not [d.fieldname for d in self.fields if d.in_list_view]:
cnt = 0
for d in self.fields:
if d.reqd and not d.hidden and not d.fieldtype == "Table":
if d.reqd and not d.hidden and not d.fieldtype in table_fields:
d.in_list_view = 1
cnt += 1
if cnt == 4: break
@ -171,7 +171,8 @@ class DocType(Document):
"""Change the timestamp of parent DocType if the current one is a child to clear caches."""
if frappe.flags.in_import:
return
parent_list = frappe.db.get_all('DocField', 'parent', dict(fieldtype='Table', options=self.name))
parent_list = frappe.db.get_all('DocField', 'parent',
dict(fieldtype=['in', frappe.model.table_fields], options=self.name))
for p in parent_list:
frappe.db.sql('UPDATE `tabDocType` SET modified=%s WHERE `name`=%s', (now(), p.parent))
@ -511,11 +512,11 @@ def validate_fields(meta):
validate_column_length(fieldname)
def check_illegal_mandatory(d):
if (d.fieldtype in no_value_fields) and d.fieldtype!="Table" and d.reqd:
if (d.fieldtype in no_value_fields) and d.fieldtype not in table_fields and d.reqd:
frappe.throw(_("Field {0} of type {1} cannot be mandatory").format(d.label, d.fieldtype))
def check_link_table_options(d):
if d.fieldtype in ("Link", "Table"):
if d.fieldtype in ("Link",) + table_fields:
if not d.options:
frappe.throw(_("Options required for Link or Table type field {0} in row {1}").format(d.label, d.idx))
if d.options=="[Select]" or d.options==d.parent:
@ -692,6 +693,19 @@ def validate_fields(meta):
re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", depends_on):
frappe.throw(_("Invalid {0} condition").format(frappe.unscrub(field)), frappe.ValidationError)
def check_table_multiselect_option(docfield):
'''check if the doctype provided in Option has atleast 1 Link field'''
if not docfield.fieldtype == 'Table MultiSelect': return
doctype = docfield.options
meta = frappe.get_meta(doctype)
link_field = [df for df in meta.fields if df.fieldtype == 'Link']
if not link_field:
frappe.throw(_('DocType <b>{0}</b> provided for the field <b>{1}</b> must have atleast one Link field')
.format(doctype, docfield.fieldname), frappe.ValidationError)
fields = meta.get("fields")
fieldname_list = [d.fieldname for d in fields]
@ -702,7 +716,7 @@ def validate_fields(meta):
for d in fields:
if not d.permlevel: d.permlevel = 0
if d.fieldtype != "Table": d.allow_bulk_edit = 0
if d.fieldtype not in table_fields: d.allow_bulk_edit = 0
if d.fieldtype == "Barcode": d.ignore_xss_filter = 1
if not d.fieldname:
d.fieldname = d.fieldname.lower()
@ -719,6 +733,7 @@ def validate_fields(meta):
check_illegal_default(d)
check_unique_and_text(d)
check_illegal_depends_on_conditions(d)
check_table_multiselect_option(d)
check_fold(fields)
check_search_fields(meta, fields)

View file

@ -94,7 +94,7 @@ class File(NestedSet):
self.validate_file_name()
self.validate_folder()
if not self.flags.ignore_file_validate:
if not self.file_url and not self.flags.ignore_file_validate:
if not self.is_folder:
self.validate_file()
self.generate_content_hash()
@ -386,9 +386,11 @@ class File(NestedSet):
elif file_path.startswith("/files/"):
file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/"))
elif file_path.startswith("http"):
pass
else:
elif not self.file_url:
frappe.throw(_("There is some problem with the file url: {0}").format(file_path))
return file_path
@ -817,12 +819,9 @@ def download_file(file_url):
"""
file_doc = frappe.get_doc("File", {"file_url": file_url})
file_doc.check_permission("read")
path = os.path.join(get_files_path(), os.path.basename(file_url))
with open(path, "rb") as fileobj:
filedata = fileobj.read()
frappe.local.response.filename = os.path.basename(file_url)
frappe.local.response.filecontent = filedata
frappe.local.response.filecontent = file_doc.get_content()
frappe.local.response.type = "download"
def extract_images_from_doc(doc, fieldname):

View file

@ -65,8 +65,8 @@ def create_json_gz_file(data, dt, dn):
"file_name": json_filename,
"attached_to_doctype": dt,
"attached_to_name": dn,
"content": compressed_content,
"decode": True})
"content": compressed_content
})
_file.save()
@frappe.whitelist()

View file

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@ -42,7 +43,7 @@
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"unique": 1
},
{
"allow_bulk_edit": 0,
@ -562,7 +563,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "prepared_report",
"fieldname": "disable_prepared_report",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
@ -571,15 +572,47 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Disable Prepared Report",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "prepared_report",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Prepared Report",
"length": 0,
"no_copy": 0,
"options": "Yes\nNo",
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@ -600,7 +633,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-06-27 14:48:49.989952",
"modified": "2019-01-25 12:04:50.833264",
"modified_by": "Administrator",
"module": "Core",
"name": "Report",

View file

@ -118,14 +118,16 @@ class Report(Document):
if fieldtype and '/' in fieldtype:
fieldtype, options = fieldtype.split('/')
columns.append(frappe._dict(label=parts[0], fieldtype=fieldtype, fieldname=parts[0]))
columns.append(frappe._dict(label=parts[0], fieldtype=fieldtype, fieldname=parts[0], options=options))
out += data.get('result')
else:
# standard report
params = json.loads(self.json)
if params.get('columns'):
if params.get('fields'):
columns = params.get('fields')
elif params.get('columns'):
columns = params.get('columns')
elif params.get('fields'):
columns = params.get('fields')
@ -165,6 +167,7 @@ class Report(Document):
user=user)
_columns = []
for column in columns:
meta = frappe.get_meta(column[1])
field = [meta.get_field(column[0]) or frappe._dict(label=meta.get_label(column[0]), fieldname=column[0])]
@ -192,3 +195,8 @@ class Report(Document):
@Document.whitelist
def toggle_disable(self, disable):
self.db_set("disabled", cint(disable))
@frappe.whitelist()
def is_prepared_report_disabled(report):
return frappe.db.get_value('Report',
report, 'disable_prepared_report') or 0

View file

@ -9,10 +9,14 @@ frappe.ui.form.on('Role Permission for Page and Report', {
refresh: function(frm) {
frm.disable_save();
frm.role_area.hide();
frm.add_custom_button(__("Reset to defaults"),
function(){ frm.trigger("reset_roles") });
frm.add_custom_button(__("Update"),
function(){ frm.trigger("update_roles") }).addClass('btn-primary');
frm.add_custom_button(__("Reset to defaults"), function() {
frm.trigger("reset_roles");
});
frm.add_custom_button(__("Update"), function() {
frm.trigger("update_report_page_data");
}).addClass('btn-primary');
},
onload: function(frm) {
@ -45,22 +49,22 @@ frappe.ui.form.on('Role Permission for Page and Report', {
page: function(frm) {
if(frm.doc.page) {
frm.trigger("get_roles")
frm.trigger("set_report_page_data");
}
},
report: function(frm){
if(frm.doc.report) {
frm.trigger("get_roles")
frm.trigger("set_report_page_data");
}
},
get_roles: function(frm) {
set_report_page_data: function(frm) {
frm.toggle_display('roles_html', true)
frm.role_area.show();
return frm.call({
method:"get_custom_roles",
method:"set_report_page_data",
doc: frm.doc,
callback: function(r) {
refresh_field('roles')
@ -69,14 +73,14 @@ frappe.ui.form.on('Role Permission for Page and Report', {
})
},
update_roles: function(frm) {
update_report_page_data: function(frm) {
frm.trigger("validate_mandatory_fields")
if(frm.roles_editor) {
frm.roles_editor.set_roles_in_table()
}
return frm.call({
method:"set_custom_roles",
method:"update_report_page_data",
doc: frm.doc,
callback: function(r) {
refresh_field('roles')

View file

@ -1,5 +1,6 @@
{
"allow_copy": 1,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@ -13,6 +14,8 @@
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -24,7 +27,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Set Role For",
"length": 0,
@ -40,9 +43,12 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -71,9 +77,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -102,9 +111,77 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "report",
"fetch_from": "",
"fieldname": "disable_prepared_report",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Disable Prepared Report",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -131,9 +208,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -161,9 +241,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -191,6 +274,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
@ -204,7 +288,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-03-11 02:35:32.369043",
"modified": "2019-01-25 12:08:57.250719",
"modified_by": "Administrator",
"module": "Core",
"name": "Role Permission for Page and Report",
@ -213,7 +297,6 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
@ -239,5 +322,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View file

@ -4,10 +4,15 @@
from __future__ import unicode_literals
import frappe
from frappe.core.doctype.report.report import is_prepared_report_disabled
from frappe.model.document import Document
class RolePermissionforPageandReport(Document):
def get_custom_roles(self):
def set_report_page_data(self):
self.set_custom_roles()
self.check_prepared_report_disabled()
def set_custom_roles(self):
args = self.get_args()
self.set('roles', [])
@ -19,7 +24,11 @@ class RolePermissionforPageandReport(Document):
roles = self.get_standard_roles()
self.set('roles', roles)
def check_prepared_report_disabled(self):
if self.report:
self.disable_prepared_report = is_prepared_report_disabled(self.report)
def get_standard_roles(self):
doctype = self.set_role_for
docname = self.page if self.set_role_for == 'Page' else self.report
@ -29,9 +38,14 @@ class RolePermissionforPageandReport(Document):
def reset_roles(self):
roles = self.get_standard_roles()
self.set('roles', roles)
self.set_custom_roles()
self.update_custom_roles()
self.update_disable_prepared_report()
def set_custom_roles(self):
def update_report_page_data(self):
self.update_custom_roles()
self.update_disable_prepared_report()
def update_custom_roles(self):
args = self.get_args()
name = frappe.db.get_value('Custom Role', args, "name")
@ -50,6 +64,10 @@ class RolePermissionforPageandReport(Document):
else:
frappe.get_doc(args).insert()
def update_disable_prepared_report(self):
if self.report:
frappe.db.set_value('Report', self.report, 'disable_prepared_report', self.disable_prepared_report)
def get_args(self, row=None):
name = self.page if self.set_role_for == 'Page' else self.report
check_for_field = self.set_role_for.replace(" ","_").lower()

View file

@ -1624,7 +1624,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-01 17:35:13.043855",
"modified": "2019-01-30 11:02:41.011412",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",

View file

@ -0,0 +1,16 @@
# Transaction Log Changelog
## v1.0.0
Initial version
The line hash summarizes:
- The index of the row
- The timestamp
- The document raw data
The chain hash summarizes:
- The previous line hash
- The current line hash
## v1.0.1
Modification of the timestamp fieldtype from "Time" to "Datetime"

View file

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@ -178,7 +179,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "timestamp",
"fieldtype": "Time",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -436,7 +437,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-09-21 08:49:07.915376",
"modified": "2018-12-27 15:22:34.533766",
"modified_by": "Administrator",
"module": "Core",
"name": "Transaction Log",

View file

@ -6,14 +6,14 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import now, cint
from frappe.utils import cint, now_datetime
import hashlib
class TransactionLog(Document):
def before_insert(self):
index = get_current_index()
self.row_index = index
self.timestamp = now()
self.timestamp = now_datetime()
if index != 1:
prev_hash = frappe.db.sql(
"SELECT `chaining_hash` FROM `tabTransaction Log` WHERE `row_index` = '{0}'".format(index - 1))
@ -25,7 +25,7 @@ class TransactionLog(Document):
self.previous_hash = self.hash_line()
self.transaction_hash = self.hash_line()
self.chaining_hash = self.hash_chain()
self.checksum_version = "v1.0.0"
self.checksum_version = "v1.0.1"
def hash_line(self):
sha = hashlib.sha256()

View file

@ -891,10 +891,9 @@ def get_active_website_users():
def get_permission_query_conditions(user):
if user=="Administrator":
return ""
else:
return """(`tabUser`.name not in ({standard_users}))""".format(
standard_users='"' + '", "'.join(STANDARD_USERS) + '"')
standard_users = ", ".join(frappe.db.escape(user) for user in STANDARD_USERS))
def has_permission(doc, user):
if (user != "Administrator") and (doc.name in STANDARD_USERS):
@ -932,7 +931,7 @@ def handle_password_test_fail(result):
suggestions = result['feedback']['suggestions'][0] if result['feedback']['suggestions'] else ''
warning = result['feedback']['warning'] if 'warning' in result['feedback'] else ''
suggestions += "<br>" + _("Hint: Include symbols, numbers and capital letters in the password") + '<br>'
frappe.throw(_('Invalid Password: ' + ' '.join([warning, suggestions])))
frappe.throw(' '.join([_('Invalid Password:'), warning, suggestions]))
def update_gravatar(name):
gravatar = has_gravatar(name)

View file

@ -56,10 +56,10 @@ def get_user_permissions(user=None):
if not out.get(perm.allow):
out[perm.allow] = []
out[perm.allow].append({
out[perm.allow].append(frappe._dict({
'doc': doc_name,
'applicable_for': perm.get('applicable_for')
})
}))
try:
for perm in frappe.get_all('User Permission',
@ -74,6 +74,7 @@ def get_user_permissions(user=None):
for doc in decendants:
add_doc_to_perm(perm, doc)
out = frappe._dict(out)
frappe.cache().hset("user_permissions", user, out)
except frappe.db.SQLError:
if frappe.db.is_table_missing():
@ -94,6 +95,7 @@ def get_applicable_for_doctype_list(doctype, txt, searchfield, start, page_len,
linked_doctypes = get_linked_doctypes(doctype, True).keys()
linked_doctypes = list(linked_doctypes)
linked_doctypes += [doctype]
if txt:
linked_doctypes = [d for d in linked_doctypes if txt in d.lower()]
@ -108,3 +110,13 @@ def get_applicable_for_doctype_list(doctype, txt, searchfield, start, page_len,
def get_permitted_documents(doctype):
return [d.get('doc') for d in get_user_permissions().get(doctype, []) \
if d.get('doc')]
@frappe.whitelist()
def clear_user_permissions(user, for_doctype):
frappe.only_for('System Manager')
total = frappe.db.count('User Permission', filters = dict(user=user, allow=for_doctype))
if total:
frappe.db.sql('DELETE FROM `tabUser Permission` WHERE `user`=%s AND `allow`=%s', (user, for_doctype))
frappe.clear_cache()
return total

View file

@ -0,0 +1,52 @@
frappe.listview_settings['User Permission'] = {
onload: function(list_view) {
list_view.page.add_menu_item(__("Clear User Permissions"), () => {
const dialog = new frappe.ui.Dialog({
title: __('Clear User Permissions'),
fields: [
{
'fieldname': 'user',
'label': __('For User'),
'fieldtype': 'Link',
'options': 'User',
'reqd': 1
},
{
'fieldname': 'for_doctype',
'label': __('For Document Type'),
'fieldtype': 'Link',
'options': 'DocType',
'reqd': 1
},
],
primary_action: (data) => {
// mandatory not filled
if (!data) return;
frappe.confirm(__('Are you sure?'), () => {
frappe
.xcall('frappe.core.doctype.user_permission.user_permission.clear_user_permissions', data)
.then(data => {
dialog.hide();
let message = '';
if (data === 0) {
message = __('No records deleted');
} else {
message = __('{0} records deleted', [data]);
}
frappe.show_alert({
message,
indicator: 'green'
});
list_view.refresh();
});
});
},
primary_action_label: __('Clear')
});
dialog.show();
});
}
};

View file

@ -7,7 +7,7 @@ from __future__ import unicode_literals
import frappe, json
from frappe.model.document import Document
from frappe.model import no_value_fields
from frappe.model import no_value_fields, table_fields
class Version(Document):
def set_diff(self, old, new):
@ -42,12 +42,12 @@ def get_diff(old, new, for_child=False):
}'''
out = frappe._dict(changed = [], added = [], removed = [], row_changed = [])
for df in new.meta.fields:
if df.fieldtype in no_value_fields and df.fieldtype != 'Table':
if df.fieldtype in no_value_fields and df.fieldtype not in table_fields:
continue
old_value, new_value = old.get(df.fieldname), new.get(df.fieldname)
if df.fieldtype=='Table':
if df.fieldtype in table_fields:
# make maps
old_row_by_name, new_row_by_name = {}, {}
for d in old_value:

View file

@ -2,15 +2,15 @@
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: View log", function (assert) {
QUnit.test("test: View Log", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new View log
() => frappe.tests.make('View log', [
// insert a new View Log
() => frappe.tests.make('View Log', [
// values to be set
{key: 'value'}
]),

View file

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe
import unittest
class TestViewlog(unittest.TestCase):
class TestViewLog(unittest.TestCase):
def tearDown(self):
frappe.set_user('Administrator')
@ -25,7 +25,7 @@ class TestViewlog(unittest.TestCase):
# load the form
getdoc('Event', ev.name)
a = frappe.get_value(
doctype="View log",
doctype="View Log",
filters={
"reference_doctype": "Event",
"reference_name": ev.name

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('View log', {
frappe.ui.form.on('View Log', {
refresh: function(frm) {
}

View file

@ -1,160 +1,161 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-05-27 02:20:11.193944",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-05-27 02:20:11.193944",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "viewed_by",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Viewed By",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "viewed_by",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Viewed By",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference doctype",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference doctype",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-05-30 11:21:35.258019",
"modified_by": "shridhar.p@zerodha.com",
"module": "Core",
"name": "View log",
"name_case": "",
"owner": "shridhar.p@zerodha.com",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-03 13:04:31.389182",
"modified_by": "Administrator",
"module": "Core",
"name": "View Log",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 0
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View file

@ -6,5 +6,5 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class Viewlog(Document):
class ViewLog(Document):
pass

View file

@ -1,7 +1,11 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Transaction Log Report"] = {
onload: function(query_report) {
query_report.add_make_chart_button = function() {
//
};
}
}

View file

@ -6,7 +6,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2018-06-29 15:46:46.884862",
"modified": "2018-12-27 18:10:29.785415",
"modified_by": "Administrator",
"module": "Core",
"name": "Transaction Log Report",
@ -18,6 +18,9 @@
"roles": [
{
"role": "Administrator"
},
{
"role": "System Manager"
}
]
}

View file

@ -1,10 +1,11 @@
# Copyright (c) 2013, Frappe Technologies and contributors
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import hashlib
from frappe import _
from frappe.utils import format_datetime
def execute(filters=None):
columns, data = get_columns(filters), get_data(filters)
@ -24,9 +25,9 @@ def get_data(filters=None):
else:
integrity = check_data_integrity(l.chaining_hash, l.transaction_hash, l.previous_hash, previous_hash[0][0])
result.append([str(integrity), l.reference_doctype, l.document_name, l.owner, l.modified_by, l.timestamp])
result.append([_(str(integrity)), _(l.reference_doctype), l.document_name, l.owner, l.modified_by, format_datetime(l.timestamp, "YYYYMMDDHHmmss")])
else:
result.append([_("First Transaction"), l.reference_doctype, l.document_name, l.owner, l.modified_by, l.timestamp])
result.append([_("First Transaction"), _(l.reference_doctype), l.document_name, l.owner, l.modified_by, format_datetime(l.timestamp, "YYYYMMDDHHmmss")])
return result

View file

@ -1,8 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
def get_parent_doc(doc):
"""Returns document of `reference_doctype`, `reference_doctype`"""

View file

@ -18,7 +18,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
"modified": "2018-11-01 19:35:43.552429",
"modified": "2019-01-28 12:45:17.158069",
"modified_by": "Administrator",
"module": "Core",
"name": "edit-profile",
@ -129,19 +129,6 @@
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "roles",
"fieldtype": "Table",
"hidden": 0,
"label": "Roles Assigned",
"max_length": 0,
"max_value": 0,
"options": "Has Role",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
}
]
}

View file

@ -9,6 +9,9 @@ frappe.ui.form.on('Custom Field', {
frm.set_query('dt', function(doc) {
var filters = [
['DocType', 'issingle', '=', 0],
['DocType', 'custom', '=', 0],
['DocType', 'name', 'not in', frappe.model.core_doctypes_list],
['DocType', 'restrict_to_domain', 'in', frappe.boot.active_domains]
];
if(frappe.session.user!=="Administrator") {
filters.push(['DocType', 'module', 'not in', ['Core', 'Custom']])
@ -32,15 +35,21 @@ frappe.ui.form.on('Custom Field', {
return frappe.call({
method: 'frappe.custom.doctype.custom_field.custom_field.get_fields_label',
args: { doctype: frm.doc.dt, fieldname: frm.doc.fieldname },
callback: function(r, rt) {
set_field_options('insert_after', r.message);
var fieldnames = $.map(r.message, function(v) { return v.value; });
callback: function(r) {
if(r) {
if(r._server_messages && r._server_messages.length) {
frm.set_value("dt", "");
} else {
set_field_options('insert_after', r.message);
var fieldnames = $.map(r.message, function(v) { return v.value; });
if(insert_after==null || !in_list(fieldnames, insert_after)) {
insert_after = fieldnames[-1];
if(insert_after==null || !in_list(fieldnames, insert_after)) {
insert_after = fieldnames[-1];
}
frm.set_value('insert_after', insert_after);
}
}
frm.set_value('insert_after', insert_after);
}
});

View file

@ -234,7 +234,7 @@
"no_copy": 0,
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@ -1302,7 +1302,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-23 19:56:43.328280",
"modified": "2018-12-19 18:34:46.031246",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Field",

View file

@ -8,6 +8,7 @@ from frappe.utils import cstr
from frappe import _
from frappe.model.document import Document
from frappe.model.docfield import supports_translation
from frappe.model import core_doctypes_list
class CustomField(Document):
def autoname(self):
@ -85,6 +86,14 @@ class CustomField(Document):
@frappe.whitelist()
def get_fields_label(doctype=None):
meta = frappe.get_meta(doctype)
if doctype in core_doctypes_list:
return frappe.msgprint(_("Custom Fields cannot be added to core DocTypes."))
if meta.custom:
return frappe.msgprint(_("Custom Fields can only be added to a standard DocType."))
return [{"value": df.fieldname or "", "label": _(df.label or "")}
for df in frappe.get_meta(doctype).get("fields")]

View file

@ -13,9 +13,7 @@ frappe.ui.form.on("Customize Form", {
filters: [
['DocType', 'issingle', '=', 0],
['DocType', 'custom', '=', 0],
['DocType', 'name', 'not in', 'DocType, DocField, DocPerm, User, Role, Has Role, \
Page, Has Role, Module Def, Print Format, Report, Customize Form, \
Customize Form Field, Property Setter, Custom Field, Custom Script'],
['DocType', 'name', 'not in', frappe.model.core_doctypes_list],
['DocType', 'restrict_to_domain', 'in', frappe.boot.active_domains]
]
};
@ -39,8 +37,14 @@ frappe.ui.form.on("Customize Form", {
doc: frm.doc,
freeze: true,
callback: function(r) {
frm.refresh();
frm.trigger("setup_sortable");
if(r) {
if(r._server_messages && r._server_messages.length) {
frm.set_value("doc_type", "");
} else {
frm.refresh();
frm.trigger("setup_sortable");
}
}
}
});
} else {

View file

@ -11,7 +11,7 @@ import frappe.translate
from frappe import _
from frappe.utils import cint
from frappe.model.document import Document
from frappe.model import no_value_fields
from frappe.model import no_value_fields, core_doctypes_list
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
from frappe.model.docfield import supports_translation
@ -69,7 +69,7 @@ docfield_properties = {
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature', 'HTML Editor'), ('Data', 'Select'),
('Text', 'Small Text'), ('Text', 'Data', 'Barcode'), ('Code', 'Geolocation'))
('Text', 'Small Text'), ('Text', 'Data', 'Barcode'), ('Code', 'Geolocation'), ('Table', 'Table MultiSelect'))
allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select', 'Data')
@ -85,6 +85,12 @@ class CustomizeForm(Document):
meta = frappe.get_meta(self.doc_type)
if self.doc_type in core_doctypes_list:
return frappe.msgprint(_("Core DocTypes cannot be customized."))
if meta.custom:
return frappe.msgprint(_("Only standard DocTypes are allowed to be customized from Customize Form."))
# doctype properties
for property in doctype_properties:
self.set(property, meta.get(property))

View file

@ -9,28 +9,28 @@ from frappe.core.doctype.doctype.doctype import InvalidFieldNameError
test_dependencies = ["Custom Field", "Property Setter"]
class TestCustomizeForm(unittest.TestCase):
def insert_custom_field(self):
frappe.delete_doc_if_exists("Custom Field", "User-test_custom_field")
frappe.delete_doc_if_exists("Custom Field", "Event-test_custom_field")
frappe.get_doc({
"doctype": "Custom Field",
"dt": "User",
"dt": "Event",
"label": "Test Custom Field",
"description": "A Custom Field for Testing",
"fieldtype": "Select",
"in_list_view": 1,
"options": "\nCustom 1\nCustom 2\nCustom 3",
"default": "Custom 3",
"insert_after": frappe.get_meta('User').fields[-1].fieldname
"insert_after": frappe.get_meta('Event').fields[-1].fieldname
}).insert()
def setUp(self):
self.insert_custom_field()
frappe.db.commit()
frappe.clear_cache(doctype="User")
frappe.clear_cache(doctype="Event")
def tearDown(self):
frappe.delete_doc("Custom Field", "User-test_custom_field")
frappe.delete_doc("Custom Field", "Event-test_custom_field")
frappe.db.commit()
frappe.clear_cache(doctype="User")
frappe.clear_cache(doctype="Event")
def get_customize_form(self, doctype=None):
d = frappe.get_doc("Customize Form")
@ -45,78 +45,67 @@ class TestCustomizeForm(unittest.TestCase):
self.assertEqual(len(d.get("fields")), 0)
d = self.get_customize_form("Event")
self.assertEqual(d.doc_type, "Event")
self.assertEqual(len(d.get("fields")), 27)
self.assertEquals(d.doc_type, "Event")
self.assertEquals(len(d.get("fields")), 28)
d = self.get_customize_form("User")
self.assertEqual(d.doc_type, "User")
d = self.get_customize_form("Event")
self.assertEquals(d.doc_type, "Event")
self.assertEqual(len(d.get("fields")),
len(frappe.get_doc("DocType", d.doc_type).fields) + 1)
self.assertEqual(d.get("fields")[-1].fieldname, "test_custom_field")
self.assertEqual(d.get("fields", {"fieldname": "location"})[0].in_list_view, 1)
self.assertEquals(d.get("fields")[-1].fieldname, "test_custom_field")
self.assertEquals(d.get("fields", {"fieldname": "event_type"})[0].in_list_view, 1)
return d
def test_save_customization_property(self):
d = self.get_customize_form("User")
self.assertEqual(frappe.db.get_value("Property Setter",
{"doc_type": "User", "property": "allow_copy"}, "value"), None)
d = self.get_customize_form("Event")
self.assertEquals(frappe.db.get_value("Property Setter",
{"doc_type": "Event", "property": "allow_copy"}, "value"), None)
d.allow_copy = 1
d.run_method("save_customization")
self.assertEqual(frappe.db.get_value("Property Setter",
{"doc_type": "User", "property": "allow_copy"}, "value"), '1')
self.assertEquals(frappe.db.get_value("Property Setter",
{"doc_type": "Event", "property": "allow_copy"}, "value"), '1')
d.allow_copy = 0
d.run_method("save_customization")
self.assertEqual(frappe.db.get_value("Property Setter",
{"doc_type": "User", "property": "allow_copy"}, "value"), None)
self.assertEquals(frappe.db.get_value("Property Setter",
{"doc_type": "Event", "property": "allow_copy"}, "value"), None)
def test_save_customization_field_property(self):
d = self.get_customize_form("User")
self.assertEqual(frappe.db.get_value("Property Setter",
{"doc_type": "User", "property": "reqd", "field_name": "location"}, "value"), None)
d = self.get_customize_form("Event")
self.assertEquals(frappe.db.get_value("Property Setter",
{"doc_type": "Event", "property": "reqd", "field_name": "repeat_this_event"}, "value"), None)
location_field = d.get("fields", {"fieldname": "location"})[0]
location_field.reqd = 1
repeat_this_event_field = d.get("fields", {"fieldname": "repeat_this_event"})[0]
repeat_this_event_field.reqd = 1
d.run_method("save_customization")
self.assertEqual(frappe.db.get_value("Property Setter",
{"doc_type": "User", "property": "reqd", "field_name": "location"}, "value"), '1')
self.assertEquals(frappe.db.get_value("Property Setter",
{"doc_type": "Event", "property": "reqd", "field_name": "repeat_this_event"}, "value"), '1')
location_field = d.get("fields", {"fieldname": "location"})[0]
location_field.reqd = 0
repeat_this_event_field = d.get("fields", {"fieldname": "repeat_this_event"})[0]
repeat_this_event_field.reqd = 0
d.run_method("save_customization")
self.assertEqual(frappe.db.get_value("Property Setter",
{"doc_type": "User", "property": "reqd", "field_name": "location"}, "value"), None)
# for not allowing to change mandatory property of standard fields
self.assertEqual(frappe.db.get_value("Property Setter",
{"doc_type": "User", "property": "reqd", "field_name": "email"}, "value"), None)
email_field = d.get("fields", {"fieldname": "email"})[0]
email_field.reqd = 0
d.run_method("save_customization")
self.assertEqual(frappe.db.get_value("Property Setter",
{"doc_type": "User", "property": "reqd", "field_name": "email"}, "value"), None)
self.assertEquals(frappe.db.get_value("Property Setter",
{"doc_type": "Event", "property": "reqd", "field_name": "repeat_this_event"}, "value"), None)
def test_save_customization_custom_field_property(self):
d = self.get_customize_form("User")
self.assertEqual(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 0)
d = self.get_customize_form("Event")
self.assertEquals(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 0)
custom_field = d.get("fields", {"fieldname": "test_custom_field"})[0]
custom_field.reqd = 1
d.run_method("save_customization")
self.assertEqual(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 1)
self.assertEquals(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 1)
custom_field = d.get("fields", {"is_custom_field": True})[0]
custom_field.reqd = 0
d.run_method("save_customization")
self.assertEqual(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 0)
self.assertEquals(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 0)
def test_save_customization_new_field(self):
d = self.get_customize_form("User")
d = self.get_customize_form("Event")
last_fieldname = d.fields[-1].fieldname
d.append("fields", {
"label": "Test Add Custom Field Via Customize Form",
@ -124,19 +113,19 @@ class TestCustomizeForm(unittest.TestCase):
"is_custom_field": 1
})
d.run_method("save_customization")
self.assertEqual(frappe.db.get_value("Custom Field",
"User-test_add_custom_field_via_customize_form", "fieldtype"), "Data")
self.assertEquals(frappe.db.get_value("Custom Field",
"Event-test_add_custom_field_via_customize_form", "fieldtype"), "Data")
self.assertEqual(frappe.db.get_value("Custom Field",
"User-test_add_custom_field_via_customize_form", 'insert_after'), last_fieldname)
self.assertEquals(frappe.db.get_value("Custom Field",
"Event-test_add_custom_field_via_customize_form", 'insert_after'), last_fieldname)
frappe.delete_doc("Custom Field", "User-test_add_custom_field_via_customize_form")
self.assertEqual(frappe.db.get_value("Custom Field",
"User-test_add_custom_field_via_customize_form"), None)
frappe.delete_doc("Custom Field", "Event-test_add_custom_field_via_customize_form")
self.assertEquals(frappe.db.get_value("Custom Field",
"Event-test_add_custom_field_via_customize_form"), None)
def test_save_customization_remove_field(self):
d = self.get_customize_form("User")
d = self.get_customize_form("Event")
custom_field = d.get("fields", {"fieldname": "test_custom_field"})[0]
d.get("fields").remove(custom_field)
d.run_method("save_customization")
@ -148,24 +137,24 @@ class TestCustomizeForm(unittest.TestCase):
def test_reset_to_defaults(self):
d = frappe.get_doc("Customize Form")
d.doc_type = "User"
d.doc_type = "Event"
d.run_method('reset_to_defaults')
self.assertEqual(d.get("fields", {"fieldname": "location"})[0].in_list_view, 0)
self.assertEquals(d.get("fields", {"fieldname": "repeat_this_event"})[0].in_list_view, 0)
frappe.local.test_objects["Property Setter"] = []
make_test_records_for_doctype("Property Setter")
def test_set_allow_on_submit(self):
d = self.get_customize_form("User")
d.get("fields", {"fieldname": "first_name"})[0].allow_on_submit = 1
d = self.get_customize_form("Event")
d.get("fields", {"fieldname": "subject"})[0].allow_on_submit = 1
d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit = 1
d.run_method("save_customization")
d = self.get_customize_form("User")
d = self.get_customize_form("Event")
# don't allow for standard fields
self.assertEqual(d.get("fields", {"fieldname": "first_name"})[0].allow_on_submit or 0, 0)
self.assertEquals(d.get("fields", {"fieldname": "subject"})[0].allow_on_submit or 0, 0)
# allow for custom field
self.assertEqual(d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit, 1)
@ -193,4 +182,12 @@ class TestCustomizeForm(unittest.TestCase):
# undo
df.default = None
d.run_method("save_customization")
d.run_method("save_customization")
def test_core_doctype_customization(self):
d = self.get_customize_form('User')
e = self.get_customize_form('Custom Field')
# core doctype is invalid, hence no attributes are set
self.assertEquals(d.get("fields"), [])
self.assertEquals(e.get("fields"), [])

View file

@ -37,12 +37,16 @@ class PropertySetter(Document):
and property = %(property)s""", self.get_valid_dict())
def get_property_list(self, dt):
return frappe.db.sql("""select fieldname, label, fieldtype
from tabDocField
where parent=%s
and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table', 'Fold')
and coalesce(fieldname, '') != ''
order by label asc""", dt, as_dict=1)
return frappe.db.get_all('DocField',
fields=['fieldname', 'label', 'fieldtype'],
filters={
'parent': dt,
'fieldtype': ['not in', ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Fold') + frappe.model.table_fields],
'fieldname': ['!=', '']
},
order_by='label asc',
as_dict=1
)
def get_setup_data(self):
return {

View file

@ -1,3 +1,4 @@
from __future__ import unicode_literals
from six import with_metaclass
from abc import ABCMeta, abstractmethod
from frappe.utils.password import get_decrypted_password

View file

@ -18,6 +18,7 @@ from time import time
from frappe.utils import now, getdate, cast_fieldtype
from frappe.utils.background_jobs import execute_job, get_queue
from frappe.model.utils.link_count import flush_local_link_count
from frappe.utils import cint
# imports - compatibility imports
from six import (
@ -538,9 +539,9 @@ class Database(object):
`tabSingles` where `doctype`=%s and `field`=%s""", (doctype, fieldname))
val = val[0][0] if val else None
if val=="0" or val=="1":
# check type
val = int(val)
df = frappe.get_meta(doctype).get_field(fieldname)
if df.fieldtype in frappe.model.numeric_fieldtypes:
val = cint(val)
self.value_cache[doctype][fieldname] = val

View file

@ -103,7 +103,7 @@ CREATE TABLE `tabDocPerm` (
DROP TABLE IF EXISTS `tabDocType`;
CREATE TABLE `tabDocType` (
`name` varchar(255) NOT NULL DEFAULT '',
`name` varchar(255) NOT NULL,
`creation` datetime(6) DEFAULT NULL,
`modified` datetime(6) DEFAULT NULL,
`modified_by` varchar(255) DEFAULT NULL,

View file

@ -104,7 +104,7 @@ create index on "tabDocPerm" ("parent");
DROP TABLE IF EXISTS "tabDocType";
CREATE TABLE "tabDocType" (
"name" varchar(255) NOT NULL DEFAULT '',
"name" varchar(255) NOT NULL,
"creation" timestamp(6) DEFAULT NULL,
"modified" timestamp(6) DEFAULT NULL,
"modified_by" varchar(255) DEFAULT NULL,

View file

@ -307,7 +307,7 @@ def make_user_copy(module_name, user):
'module_name': module_name
})
for key in ('app', 'label', 'route', 'type', '_doctype', 'idx', 'reverse', 'force_show'):
for key in ('app', 'label', 'route', 'type', '_doctype', 'idx', 'reverse', 'force_show', 'link', 'icon', 'color'):
if original.get(key):
desktop_icon.set(key, original.get(key))
@ -410,7 +410,7 @@ def get_user_icons(user):
add = False
if not icon.custom:
if icon.module_name=='Learn':
if icon.module_name==['Help', 'Settings']:
pass
elif icon.type=="page" and icon.link not in allowed_pages:

View file

@ -92,7 +92,6 @@ def get_permission_query_conditions(user):
}
def has_permission(doc, user):
frappe.log_error(doc.owner)
if doc.event_type=="Public" or doc.owner==user:
return True

View file

@ -8,7 +8,6 @@ import json
from frappe import _
from frappe.model.document import Document
from six import iteritems
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
class KanbanBoard(Document):
@ -130,17 +129,8 @@ def update_order(board_name, order):
@frappe.whitelist()
def quick_kanban_board(doctype, board_name, field_name, project=None):
'''Create new KanbanBoard quickly with default options'''
doc = frappe.new_doc('Kanban Board')
if field_name == 'kanban_column':
create_custom_field(doctype, {
'label': 'Kanban Column',
'fieldname': 'kanban_column',
'fieldtype': 'Select',
'hidden': 1,
'owner': 'Administrator'
})
meta = frappe.get_meta(doctype)
options = ''

View file

@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest

View file

@ -42,9 +42,10 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
linkmeta = link_meta_bundle[0]
if not linkmeta.get("issingle"):
fields = [d.fieldname for d in linkmeta.get("fields", {"in_list_view":1,
"fieldtype": ["not in", ["Image", "HTML", "Button", "Table"]]})] \
+ ["name", "modified", "docstatus"]
fields = [d.fieldname for d in linkmeta.get("fields", {
"in_list_view": 1,
"fieldtype": ["not in", ("Image", "HTML", "Button") + frappe.model.table_fields]
})] + ["name", "modified", "docstatus"]
if link.get("add_fields"):
fields += link["add_fields"]
@ -116,7 +117,7 @@ def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False)
ret.update(get_linked_fields(doctype, without_ignore_user_permissions_enabled))
ret.update(get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled))
filters=[['fieldtype','=','Table'], ['options', '=', doctype]]
filters=[['fieldtype', 'in', frappe.model.table_fields], ['options', '=', doctype]]
if without_ignore_user_permissions_enabled: filters.append(['ignore_user_permissions', '!=', 1])
# find links of parents
links = frappe.get_all("DocField", fields=["parent as dt"], filters=filters)
@ -159,7 +160,7 @@ def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
for doctype_name in links_dict:
ret[doctype_name] = { "fieldname": links_dict.get(doctype_name) }
table_doctypes = frappe.get_all("DocType", filters=[["istable", "=", "1"], ["name", "in", tuple(links_dict)]])
child_filters = [['fieldtype','=', 'Table'], ['options', 'in', tuple(doctype.name for doctype in table_doctypes)]]
child_filters = [['fieldtype','in', frappe.model.table_fields], ['options', 'in', tuple(doctype.name for doctype in table_doctypes)]]
if without_ignore_user_permissions_enabled: child_filters.append(['ignore_user_permissions', '!=', 1])
# find out if linked in a child table

View file

@ -80,7 +80,7 @@ def getdoctype(doctype, with_parent=False, cached_timestamp=None):
def get_meta_bundle(doctype):
bundle = [frappe.desk.form.meta.get_meta(doctype)]
for df in bundle[0].fields:
if df.fieldtype=="Table":
if df.fieldtype in frappe.model.table_fields:
bundle.append(frappe.desk.form.meta.get_meta(df.options, not frappe.conf.developer_mode))
return bundle
@ -219,11 +219,11 @@ def get_view_logs(doctype, docname):
""" get and return the latest view logs if available """
logs = []
if hasattr(frappe.get_meta(doctype), 'track_views') and frappe.get_meta(doctype).track_views:
view_logs = frappe.get_all("View log", filters={
view_logs = frappe.get_all("View Log", filters={
"reference_doctype": doctype,
"reference_name": docname,
}, fields=["name", "creation"], order_by="creation desc")
}, fields=["name", "creation", "owner"], order_by="creation desc")
if view_logs:
if view_logs:
logs = view_logs
return logs

View file

@ -12,8 +12,15 @@ import json
@frappe.whitelist()
@frappe.read_only()
def get_notifications():
if frappe.flags.in_install:
return
if (frappe.flags.in_install or
not frappe.db.get_single_value('System Settings', 'setup_complete')):
return {
"open_count_doctype": {},
"open_count_module": {},
"open_count_other": {},
"targets": {},
"new_messages": []
}
config = get_notification_config()
@ -108,7 +115,8 @@ def get_notifications_for_doctypes(config, notification_count):
except Exception as e:
# OperationalError: (1412, 'Table definition has changed, please retry transaction')
if e.args[0]!=1412:
# InternalError: (1684, 'Table definition is being modified by concurrent DDL statement')
if e.args[0] not in (1412, 1684):
raise
else:
@ -148,7 +156,7 @@ def get_notifications_for_targets(config, notification_percent):
frappe.clear_messages()
pass
except Exception as e:
if e.args[0]!=1412:
if e.args[0] not in (1412, 1684):
raise
else:

View file

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import os
import frappe
from frappe import _

View file

@ -220,9 +220,6 @@ def runquery(q='', ret=0, from_export=0):
def runquery_csv():
global out
# run query
res = runquery(from_export = 1)
q = frappe.form_dict.get('query')
rep_name = frappe.form_dict.get('report_name')
@ -234,9 +231,6 @@ def runquery_csv():
if not rep_name: rep_name = 'DataExport'
# Headings
heads = []
rows = [[rep_name], out['colnames']] + out['values']
from six import StringIO

View file

@ -57,7 +57,7 @@ def generate_report_result(report, filters=None, user=None):
module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module")
if report.is_standard == "Yes":
method_name = get_report_module_dotted_path(module, report.name) + ".execute"
threshold = 10
threshold = 30
res = []
start_time = datetime.datetime.now()
@ -66,9 +66,13 @@ def generate_report_result(report, filters=None, user=None):
end_time = datetime.datetime.now()
if (end_time - start_time).seconds > threshold and not report.prepared_report:
execution_time = (end_time - start_time).seconds
if execution_time > threshold and not report.prepared_report:
report.db_set('prepared_report', 1)
frappe.cache().hset('report_execution_time', report.name, execution_time)
columns, result = res[0], res[1]
if len(res) > 2:
message = res[2]
@ -89,7 +93,8 @@ def generate_report_result(report, filters=None, user=None):
"message": message,
"chart": chart,
"data_to_be_printed": data_to_be_printed,
"status": status
"status": status,
"execution_time": frappe.cache().hget('report_execution_time', report.name) or 0
}
@frappe.whitelist()
@ -147,7 +152,8 @@ def get_script(report_name):
return {
"script": render_include(script),
"html_format": html_format
"html_format": html_format,
"execution_time": frappe.cache().hget('report_execution_time', report_name) or 0
}
@ -164,7 +170,7 @@ def run(report_name, filters=None, user=None):
result = None
if report.prepared_report:
if report.prepared_report and not report.disable_prepared_report:
if filters:
if isinstance(filters, string_types):
filters = json.loads(filters)
@ -185,7 +191,9 @@ def run(report_name, filters=None, user=None):
def get_prepared_report_result(report, filters, dn="", user=None):
latest_report_data = {}
# Only look for completed prepared reports with given filters.
doc_list = frappe.get_all("Prepared Report", filters={"status": "Completed", "report_name": report.name, "filters": json.dumps(filters), "owner": user})
doc_list = frappe.get_all("Prepared Report",
filters={"status": "Completed", "report_name": report.name, "filters": filters, "owner": user})
doc = None
if len(doc_list):
if dn:
@ -254,7 +262,7 @@ def export_query():
if row and (i in visible_idx):
row_list = []
for idx in range(len(data.columns)):
row_list.append(row.get(columns[idx]["fieldname"],""))
row_list.append(row.get(columns[idx]["fieldname"], row.get(columns[idx]["label"], "")))
result.append(row_list)
elif not row:
result.append([])

View file

@ -42,7 +42,6 @@ def get_form_params():
else:
data["save_user_settings"] = True
doctype = data["doctype"]
fields = data["fields"]
for field in fields:
@ -213,7 +212,7 @@ def delete_items():
"""delete selected items"""
import json
il = sorted(json.loads(frappe.form_dict.get('items')), reverse=True)
il = sorted(json.loads(frappe.form_dict.get('items')), reverse=True, key=frappe.safe_decode)
doctype = frappe.form_dict.get('doctype')
failed = []

View file

@ -74,6 +74,8 @@ class AutoEmailReport(Document):
return None
if self.format == 'HTML':
columns, data = make_links(columns, data)
return self.get_html_table(columns, data)
elif self.format == 'XLSX':
@ -195,3 +197,15 @@ def send_monthly():
'''Check reports to be sent monthly'''
for report in frappe.get_all('Auto Email Report', {'enabled': 1, 'frequency': 'Monthly'}):
frappe.get_doc('Auto Email Report', report.name).send()
def make_links(columns, data):
for row in data:
for col in columns:
if col.fieldtype == "Link" and col.options != "Currency":
if col.options and row[col.fieldname]:
row[col.fieldname] = get_link_to_form(col.options, row[col.fieldname])
elif col.fieldtype == "Dynamic Link":
if col.options and row[col.fieldname]:
row[col.fieldname] = get_link_to_form(row[col.options], row[col.fieldname])
return columns, data

View file

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe
import unittest, json
from frappe.utils import get_link_to_form
# test_records = frappe.get_test_records('Auto Email Report')
@ -25,8 +26,8 @@ class TestAutoEmailReport(unittest.TestCase):
)).insert()
data = auto_email_report.get_report_content()
self.assertTrue('<td>DocShare</td>' in data)
self.assertTrue('<td>Core</td>' in data)
self.assertTrue('<td>'+str(get_link_to_form('Module Def', 'Core'))+'</td>' in data)
auto_email_report.format = 'CSV'

View file

@ -1130,6 +1130,39 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "no_smtp_authentication",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Disable SMTP server authentication",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -1530,7 +1563,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-01 19:37:43.234891",
"modified": "2019-01-30 11:02:41.011412",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",
@ -1566,4 +1599,4 @@
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}
}

View file

@ -72,7 +72,7 @@ class EmailAccount(Document):
if self.enable_outgoing:
self.check_smtp()
else:
if self.enable_incoming or self.enable_outgoing:
if self.enable_incoming or (self.enable_outgoing and not self.no_smtp_authentication):
frappe.throw(_("Password is required or select Awaiting Password"))
if self.notify_if_unreplied:
@ -134,8 +134,9 @@ class EmailAccount(Document):
port = cint(self.smtp_port),
use_tls = cint(self.use_tls)
)
if self.password:
if self.password and not self.no_smtp_authentication:
server.password = self.get_password()
server.sess
def get_incoming_server(self, in_receive=False, email_sync_rule="UNSEEN"):

View file

@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe, os
import unittest, email

View file

@ -559,24 +559,55 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "retry",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Retry",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-envelope",
"idx": 1,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-09 15:34:07.229657",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Queue",
"owner": "Administrator",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-envelope",
"idx": 1,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-11 09:05:04.175368",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Queue",
"owner": "Administrator",
"permissions": [
{
"amend": 0,

View file

@ -6,7 +6,7 @@ import frappe, re, os
from frappe.utils.pdf import get_pdf
from frappe.email.smtp import get_outgoing_email_account
from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint,
split_emails, to_markdown, markdown, encode, random_string, parse_addr)
split_emails, to_markdown, markdown, random_string, parse_addr)
import email.utils
from six import iteritems, text_type, string_types
from email.mime.multipart import MIMEMultipart

View file

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import frappe
import json

View file

@ -382,7 +382,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
email = frappe.db.sql('''select
name, status, communication, message, sender, reference_doctype,
reference_name, unsubscribe_param, unsubscribe_method, expose_recipients,
show_as_cc, add_unsubscribe_link, attachments
show_as_cc, add_unsubscribe_link, attachments, retry
from
`tabEmail Queue`
where
@ -464,12 +464,16 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
except Exception as e:
frappe.db.rollback()
if any("Sent" == s.status for s in recipients_list):
frappe.db.sql("""update `tabEmail Queue` set status='Partially Errored', error=%s where name=%s""",
(text_type(e), email.name), auto_commit=auto_commit)
if email.retry < 3:
frappe.db.sql("""update `tabEmail Queue` set status='Not Sent', modified=%s, retry=retry+1 where name=%s""",
(now_datetime(), email.name), auto_commit=auto_commit)
else:
frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s
where name=%s""", (text_type(e), email.name), auto_commit=auto_commit)
if any("Sent" == s.status for s in recipients_list):
frappe.db.sql("""update `tabEmail Queue` set status='Partially Errored', error=%s where name=%s""",
(text_type(e), email.name), auto_commit=auto_commit)
else:
frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s
where name=%s""", (text_type(e), email.name), auto_commit=auto_commit)
if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)

View file

@ -553,6 +553,7 @@ class Email:
# fix due to a python bug in poplib that limits it to 2048
poplib._MAXLINE = 20480
imaplib._MAXLINE = 20480
class TimerMixin(object):
def __init__(self, *args, **kwargs):

View file

@ -88,7 +88,7 @@ def get_outgoing_email_account(raise_exception_not_set=True, append_to=None, sen
if email_account:
if email_account.enable_outgoing and not getattr(email_account, 'from_site_config', False):
raise_exception = True
if email_account.smtp_server in ['localhost','127.0.0.1']:
if email_account.smtp_server in ['localhost','127.0.0.1'] or email_account.no_smtp_authentication:
raise_exception = False
email_account.password = email_account.get_password(raise_exception=raise_exception)
email_account.default_sender = email.utils.formataddr((email_account.name, email_account.get("email_id")))
@ -170,11 +170,14 @@ class SMTPServer:
self.email_account = get_outgoing_email_account(raise_exception_not_set=False, append_to=append_to, sender=sender)
if self.email_account:
self.server = self.email_account.smtp_server
self.login = getattr(self.email_account, "login_id", None) or self.email_account.email_id
if self.email_account.ascii_encode_password:
self.password = frappe.safe_encode(self.email_account.password, 'ascii')
self.login = (getattr(self.email_account, "login_id", None) or self.email_account.email_id)
if not self.email_account.no_smtp_authentication:
if self.email_account.ascii_encode_password:
self.password = frappe.safe_encode(self.email_account.password, 'ascii')
else:
self.password = self.email_account.password
else:
self.password = self.email_account.password
self.password = None
self.port = self.email_account.smtp_port
self.use_tls = self.email_account.use_tls
self.sender = self.email_account.email_id
@ -210,7 +213,7 @@ class SMTPServer:
self._sess.ehlo()
if self.login and self.password:
ret = self._sess.login((self.login or ""), (self.password or ""))
ret = self._sess.login(str(self.login or ""), str(self.password or ""))
# check if logged correctly
if ret[0]!=235:

View file

@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe, unittest, os, base64
import unittest, os, base64
from frappe.email.email_body import (replace_filename_with_cid,
get_email, inline_style_in_html, get_header)

View file

@ -1,4 +1,4 @@
from __future__ import print_function
from __future__ import print_function, unicode_literals
import requests
import json
import frappe

View file

@ -1,6 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: See license.txt
from __future__ import unicode_literals
import frappe
test_records = frappe.get_test_records('Country')

Some files were not shown because too many files have changed in this diff Show more