Merge branch 'develop' into new-autorepeat

This commit is contained in:
tundebabzy 2018-04-17 10:45:00 +01:00
commit 95945661af
577 changed files with 47274 additions and 48334 deletions

View file

@ -1,9 +0,0 @@
#### Expected Behaviour
#### Actual Behaviour
#### Steps to reproduce:
1.
Frappé version:

View file

@ -3,32 +3,16 @@ dist: trusty
python:
- "2.7"
- "3.6"
services:
- mysql
install:
- sudo rm /etc/apt/sources.list.d/mongodb*.list
- pip install flake8==3.3.0
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
- sudo rm /etc/apt/sources.list.d/docker.list
- sudo apt-get purge -y mysql-common mysql-server mysql-client
- nvm install v7.10.0
- wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py
- sudo python install.py --develop --user travis --without-bench-setup
- sudo pip install -e ~/bench
- rm $TRAVIS_BUILD_DIR/.git/shallow
- cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
before_script:
- mysql -u root -ptravis -e 'create database test_frappe'
- mysql -u root -ptravis -e 'CREATE DATABASE test_frappe'
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
- cd ~/frappe-bench
- bench use test_site
- bench reinstall --yes
@ -37,6 +21,20 @@ before_script:
- bench start &
- sleep 20
install:
- sudo rm /etc/apt/sources.list.d/mongodb*.list
- sudo rm /etc/apt/sources.list.d/docker.list
- sudo apt-get purge -y mysql-common mysql-server mysql-client
- nvm install v8.10.0
- wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py
- sudo python install.py --develop --user travis --without-bench-setup
- sudo pip install -e ~/bench
- rm $TRAVIS_BUILD_DIR/.git/shallow
- cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
script:
# - set -e
- bench run-tests

View file

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2016-2017 Frappé Technologies Pvt. Ltd. <developers@frappe.io>
Copyright (c) 2016-2018 Frappe Technologies Pvt. Ltd. <developers@frappe.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.

View file

@ -1,4 +1,4 @@
## Frappé framework includes these public works
## Frappe framework includes these public works
### Javascript / CSS

View file

@ -11,11 +11,13 @@ from werkzeug.local import Local, release_local
import os, sys, importlib, inspect, json
from past.builtins import cmp
from faker import Faker
# public
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template
__version__ = '10.1.2'
__version__ = '10.1.23'
__title__ = "Frappe Framework"
local = Local()
@ -271,6 +273,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
"""
from frappe.utils import encode
msg = safe_decode(msg)
out = _dict(message=msg)
def _raise_exception():
@ -280,9 +283,9 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
import inspect
if inspect.isclass(raise_exception) and issubclass(raise_exception, Exception):
raise raise_exception(encode(msg))
raise raise_exception(msg)
else:
raise ValidationError(encode(msg))
raise ValidationError(msg)
if flags.mute_messages:
_raise_exception()
@ -499,16 +502,15 @@ def clear_cache(user=None, doctype=None):
:param user: If user is given, only user cache is cleared.
:param doctype: If doctype is given, only DocType cache is cleared."""
import frappe.sessions
import frappe.cache_manager
if doctype:
import frappe.model.meta
frappe.model.meta.clear_cache(doctype)
frappe.cache_manager.clear_doctype_cache(doctype)
reset_metadata_version()
elif user:
frappe.sessions.clear_cache(user)
frappe.cache_manager.clear_user_cache(user)
else: # everything
from frappe import translate
frappe.sessions.clear_cache()
frappe.cache_manager.clear_user_cache()
translate.clear_cache()
reset_metadata_version()
local.cache = {}
@ -1251,7 +1253,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, html=None,
else:
return html
def attach_print(doctype, name, file_name=None, print_format=None, style=None, html=None, doc=None, lang=None, print_letterhead=False):
def attach_print(doctype, name, file_name=None, print_format=None, style=None, html=None, doc=None, lang=None, print_letterhead=True):
from frappe.utils import scrub_urls
if not file_name: file_name = name
@ -1486,7 +1488,23 @@ def safe_decode(param, encoding = 'utf-8'):
except Exception:
pass
return param
def parse_json(val):
from frappe.utils import parse_json
return parse_json(val)
return parse_json(val)
def mock(type, size = 1, locale = 'en'):
results = [ ]
faker = Faker(locale)
if not type in dir(faker):
raise ValueError('Not a valid mock type.')
else:
for i in range(size):
data = getattr(faker, type)()
results.append(data)
from frappe.chat.util import squashify
results = squashify(results)
return results

View file

@ -143,7 +143,7 @@ def handle_exception(e):
http_status_code = getattr(e, "http_status_code", 500)
return_as_message = False
if frappe.local.is_ajax or 'application/json' in frappe.get_request_header('Accept'):
if frappe.get_request_header('Accept') and (frappe.local.is_ajax or 'application/json' in frappe.get_request_header('Accept')):
# handle ajax responses first
# if the request is ajax, send back the trace or error message
response = frappe.utils.response.report_error(http_status_code)

View file

@ -89,6 +89,8 @@ def publish_realtime(event=None, message=None, room=None,
room = get_user_room(user)
elif doctype and docname:
room = get_doc_room(doctype, docname)
else:
room = get_site_room()
else:
# frappe.chat
room = get_chat_room(room)

View file

@ -84,8 +84,9 @@ def get_bootinfo():
def get_letter_heads():
letter_heads = {}
for letter_head in frappe.get_all("Letter Head", fields = ["name", "content"]):
letter_heads.setdefault(letter_head.name, {'header': letter_head.content, 'footer': letter_head.footer})
for letter_head in frappe.get_all("Letter Head", fields = ["name", "content", "footer"]):
letter_heads.setdefault(letter_head.name,
{'header': letter_head.content, 'footer': letter_head.footer})
return letter_heads
@ -100,12 +101,12 @@ def load_desktop_icons(bootinfo):
bootinfo.desktop_icons = get_desktop_icons()
def get_allowed_pages():
return get_user_page_or_report('Page')
return get_user_pages_or_reports('Page')
def get_allowed_reports():
return get_user_page_or_report('Report')
return get_user_pages_or_reports('Report')
def get_user_page_or_report(parent):
def get_user_pages_or_reports(parent):
roles = frappe.get_roles()
has_role = {}
column = get_column(parent)

View file

@ -1,360 +0,0 @@
/*eslint-disable no-console */
const path = require('path');
const fs = require('fs');
const babel = require('babel-core');
const less = require('less');
const chokidar = require('chokidar');
const path_join = path.resolve;
// for file watcher
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const touch = require("touch");
// basic setup
const sites_path = path_join(__dirname, '..', '..', '..', 'sites');
const apps_path = path_join(__dirname, '..', '..', '..', 'apps'); // the apps folder
const apps_contents = fs.readFileSync(path_join(sites_path, 'apps.txt'), 'utf8');
const apps = apps_contents.split('\n');
const app_paths = apps.map(app => path_join(apps_path, app, app)) // base_path of each app
const assets_path = path_join(sites_path, 'assets');
let build_map = make_build_map();
let compiled_js_cache = {}; // cache each js file after it is compiled
const file_watcher_port = get_conf().file_watcher_port;
// command line args
const action = process.argv[2] || '--build';
if (['--build', '--watch'].indexOf(action) === -1) {
console.log('Invalid argument: ', action);
process.exit();
}
if (action === '--build') {
const minify = process.argv[3] === '--minify' ? true : false;
build(minify);
}
if (action === '--watch') {
watch();
}
function build(minify) {
for (const output_path in build_map) {
pack(output_path, build_map[output_path], minify);
}
touch(path_join(sites_path, '.build'), {force:true});
}
let socket_connection = false;
function watch() {
http.listen(file_watcher_port, function () {
console.log('file watching on *:', file_watcher_port);
});
if (process.env.CI) {
// don't watch inside CI
return;
}
compile_less().then(() => {
build();
watch_less(function (filename) {
if(socket_connection) {
io.emit('reload_css', filename);
}
});
watch_js(//function (filename) {
// if(socket_connection) {
// io.emit('reload_js', filename);
// }
//}
);
watch_build_json();
});
io.on('connection', function (socket) {
socket_connection = true;
socket.on('disconnect', function() {
socket_connection = false;
})
});
}
function pack(output_path, inputs, minify, file_changed) {
let output_txt = '';
for (const file of inputs) {
if (!fs.existsSync(file)) {
console.log('File not found: ', file);
continue;
}
let force_compile = false;
if (file_changed) {
// if file_changed is passed and is equal to file, force_compile it
force_compile = file_changed === file;
}
let file_content = get_compiled_file(file, output_path, minify, force_compile);
if(!minify) {
output_txt += `\n/*\n *\t${file}\n */\n`
}
output_txt += file_content;
output_txt = output_txt.replace(/['"]use strict['"];/, '');
}
const target = path_join(assets_path, output_path);
try {
fs.writeFileSync(target, output_txt);
console.log(`Wrote ${output_path} - ${get_file_size(target)}`);
return target;
} catch (e) {
console.log('Error writing to file', output_path);
console.log(e);
}
}
function get_compiled_file(file, output_path, minify, force_compile) {
const output_type = output_path.split('.').pop();
let file_content;
if (force_compile === false) {
// force compile is false
// attempt to get from cache
file_content = compiled_js_cache[file];
if (file_content) {
return file_content;
}
}
file_content = fs.readFileSync(file, 'utf-8');
if (file.endsWith('.html') && output_type === 'js') {
file_content = html_to_js_template(file, file_content);
}
if(file.endsWith('class.js')) {
file_content = minify_js(file_content, file);
}
if (minify && file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) {
file_content = babelify(file_content, file, minify);
}
compiled_js_cache[file] = file_content;
return file_content;
}
function babelify(content, path, minify) {
let presets = ['env'];
const plugins = ['transform-object-rest-spread']
// Minification doesn't work when loading Frappe Desk
// Avoid for now, trace the error and come back.
try {
return babel.transform(content, {
presets: presets,
plugins: plugins,
comments: false
}).code;
} catch (e) {
console.log('Cannot babelify', path);
console.log(e);
return content;
}
}
function minify_js(content, path) {
try {
return babel.transform(content, {
comments: false
}).code;
} catch (e) {
console.log('Cannot minify', path);
console.log(e);
return content;
}
}
function make_build_map() {
const build_map = {};
for (const app_path of app_paths) {
const build_json_path = path_join(app_path, 'public', 'build.json');
if (!fs.existsSync(build_json_path)) continue;
let build_json = fs.readFileSync(build_json_path);
try {
build_json = JSON.parse(build_json);
} catch (e) {
console.log(e);
continue;
}
for (const target in build_json) {
const sources = build_json[target];
const new_sources = [];
for (const source of sources) {
const s = path_join(app_path, source);
new_sources.push(s);
}
if (new_sources.length)
build_json[target] = new_sources;
else
delete build_json[target];
}
Object.assign(build_map, build_json);
}
return build_map;
}
function compile_less() {
return new Promise(function (resolve) {
const promises = [];
for (const app_path of app_paths) {
const public_path = path_join(app_path, 'public');
const less_path = path_join(public_path, 'less');
if (!fs.existsSync(less_path)) continue;
const files = fs.readdirSync(less_path);
for (const file of files) {
if(file.includes('variables.less')) continue;
promises.push(compile_less_file(file, less_path, public_path))
}
}
Promise.all(promises).then(() => {
console.log('Less files compiled');
resolve();
});
});
}
function compile_less_file(file, less_path, public_path) {
const file_content = fs.readFileSync(path_join(less_path, file), 'utf8');
const output_file = file.split('.')[0] + '.css';
console.log('compiling', file);
return less.render(file_content, {
paths: [less_path],
filename: file,
sourceMap: false
}).then(output => {
const out_css = path_join(public_path, 'css', output_file);
fs.writeFileSync(out_css, output.css);
return out_css;
}).catch(e => {
console.log('Error compiling ', file);
console.log(e);
});
}
function watch_less(ondirty) {
const less_paths = app_paths.map(path => path_join(path, 'public', 'less'));
const to_watch = filter_valid_paths(less_paths);
chokidar.watch(to_watch).on('change', (filename) => {
console.log(filename, 'dirty');
var last_index = filename.lastIndexOf('/');
const less_path = filename.slice(0, last_index);
const public_path = path_join(less_path, '..');
filename = filename.split('/').pop();
compile_less_file(filename, less_path, public_path)
.then(css_file_path => {
// build the target css file for which this css file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(css_file_path)) {
pack(target, sources);
ondirty && ondirty(target);
break;
}
}
});
touch(path_join(sites_path, '.build'), {force:true});
});
}
function watch_js(ondirty) {
chokidar.watch([
path_join(apps_path, '**', '*.js'),
path_join(apps_path, '**', '*.html')
]).on('change', (filename) => {
// build the target js file for which this js/html file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(filename)) {
console.log(filename, 'dirty');
pack(target, sources, null, filename);
ondirty && ondirty(target);
// break;
}
}
touch(path_join(sites_path, '.build'), {force:true});
});
}
function watch_build_json() {
const build_json_paths = app_paths.map(path => path_join(path, 'public', 'build.json'));
const to_watch = filter_valid_paths(build_json_paths);
chokidar.watch(to_watch).on('change', (filename) => {
console.log(filename, 'updated');
build_map = make_build_map();
});
}
function filter_valid_paths(paths) {
return paths.filter(path => fs.existsSync(path));
}
function html_to_js_template(path, content) {
let key = path.split('/');
key = key[key.length - 1];
key = key.split('.')[0];
content = scrub_html_template(content);
return `frappe.templates['${key}'] = '${content}';\n`;
}
function scrub_html_template(content) {
content = content.replace(/\s/g, ' ');
content = content.replace(/(<!--.*?-->)/g, '');
return content.replace("'", "\'");
}
function get_file_size(filepath) {
const stats = fs.statSync(filepath);
const size = stats.size;
// convert it to humanly readable format.
const i = Math.floor(Math.log(size) / Math.log(1024));
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
}
function get_conf() {
// defaults
var conf = {
file_watcher_port: 6787
};
var read_config = function(path) {
if (!fs.existsSync(path)) return;
var bench_config = JSON.parse(fs.readFileSync(path));
for (var key in bench_config) {
if (bench_config[key]) {
conf[key] = bench_config[key];
}
}
}
read_config(path_join(sites_path, 'common_site_config.json'));
return conf;
}

77
frappe/cache_manager.py Normal file
View file

@ -0,0 +1,77 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
import frappe.defaults
from frappe.desk.notifications import delete_notification_count_for, clear_notifications
common_default_keys = ["__default", "__global"]
def clear_user_cache(user=None):
cache = frappe.cache()
groups = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
"defaults", "user_permissions", "home_page", "linked_with",
"desktop_icons", 'portal_menu_items')
if user:
for name in groups:
cache.hdel(name, user)
cache.delete_keys("user:" + user)
clear_defaults_cache(user)
else:
for name in groups:
cache.delete_key(name)
clear_global_cache()
clear_defaults_cache()
clear_notifications(user)
def clear_global_cache():
clear_doctype_cache()
frappe.cache().delete_value(["app_hooks", "installed_apps",
"app_modules", "module_app", "notification_config", 'system_settings',
'scheduler_events', 'time_zone', 'webhooks', 'active_domains', 'active_modules'])
frappe.setup_module_map()
def clear_defaults_cache(user=None):
if user:
for p in ([user] + common_default_keys):
frappe.cache().hdel("defaults", p)
elif frappe.flags.in_install!="frappe":
frappe.cache().delete_key("defaults")
def clear_doctype_cache(doctype=None):
cache = frappe.cache()
if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_cache):
del frappe.local.meta_cache[doctype]
for key in ('is_table', 'doctype_modules'):
cache.delete_value(key)
groups = ["meta", "form_meta", "table_columns", "last_modified",
"linked_doctypes", 'email_alerts', 'workflow']
def clear_single(dt):
for name in groups:
cache.hdel(name, dt)
if doctype:
clear_single(doctype)
# clear all parent doctypes
for dt in frappe.db.sql("""select parent from tabDocField
where fieldtype="Table" and options=%s""", (doctype,)):
clear_single(dt[0])
# clear all notifications
delete_notification_count_for(doctype)
else:
# clear all
for name in groups:
cache.delete_value(name)

View file

@ -3,8 +3,8 @@
- Better error handling
- Background processing for large files
- Frappé now has a github connector
- Frappe now has a github connector
- Any doctype can have a calendar view
- Frappé has a new simple, responsive, modern SVG [charts library](https://github.com/frappe/charts), developed by us
- Frappe has a new simple, responsive, modern SVG [charts library](https://github.com/frappe/charts), developed by us

View file

@ -7,7 +7,7 @@
- Delete selected rows
- Map selected rows from one document to another.
- Show Totals button in report
- OpenID Connect for Frappé
- OpenID Connect for Frappe
- Threading based on message id in Email Queue
- New control object daterangepicker for filtering
- Orientation selection in PDF

View file

@ -0,0 +1,22 @@
import frappe
from frappe import _
session = frappe.session
def authenticate(user, raise_err = True):
if session.user == 'Guest':
if not frappe.db.exists('Chat Token', user):
if raise_err:
frappe.throw(_("Sorry, you're not authorized."))
else:
return False
else:
return True
else:
if user != session.user:
if raise_err:
frappe.throw(_("Sorry, you're not authorized."))
else:
return False
else:
return True

View file

@ -1,247 +1,285 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 1,
"creation": "2017-11-10 11:10:40.011099",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 1,
"creation": "2017-11-10 11:10:40.011099",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "room_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Room Type",
"length": 0,
"no_copy": 0,
"options": "Direct\nGroup\nVisitor",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "room_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Room Type",
"length": 0,
"no_copy": 0,
"options": "Direct\nGroup\nVisitor",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"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": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "type",
"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": "Type",
"length": 0,
"no_copy": 0,
"options": "Content\nFile",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "room",
"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": "Room",
"length": 0,
"no_copy": 0,
"options": "Chat Room",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"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": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"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,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "content",
"fieldtype": "Text",
"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": "Content",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "room",
"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": "Room",
"length": 0,
"no_copy": 0,
"options": "Chat Room",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mentions",
"fieldtype": "Code",
"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": "Mentions",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "content",
"fieldtype": "Text",
"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": "Content",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "urls",
"fieldtype": "Data",
"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": "URLs",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mentions",
"fieldtype": "Code",
"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": "Mentions",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "urls",
"fieldtype": "Data",
"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": "URLs",
"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
}
],
"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-01-21 12:35:12.069954",
"modified_by": "faris@erpnext.com",
"module": "Chat",
"name": "Chat Message",
"name_case": "",
"owner": "arjun@gmail.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": "2018-03-29 12:41:10.890765",
"modified_by": "achilles@erpnext.com",
"module": "Chat",
"name": "Chat Message",
"name_case": "",
"owner": "arjun@gmail.com",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"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,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"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": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "content, user",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "content",
"track_changes": 1,
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "content, user",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "content",
"track_changes": 1,
"track_seen": 1
}

View file

@ -11,8 +11,9 @@ from frappe import _, _dict
import frappe
# imports - frappe module imports
from frappe.chat import authenticate
from frappe.chat.util import (
assign_if_empty,
get_if_empty,
check_url,
dictify,
get_emojis,
@ -86,7 +87,7 @@ def sanitize_message_content(content):
return content
def get_new_chat_message_doc(user, room, content, link = True):
def get_new_chat_message_doc(user, room, content, type = "Content", link = True):
user = get_user_doc(user)
room = frappe.get_doc('Chat Room', room)
@ -95,6 +96,7 @@ def get_new_chat_message_doc(user, room, content, link = True):
mess.room = room.name
mess.room_type = room.type
mess.content = sanitize_message_content(content)
mess.type = type
mess.user = user.name
mess.mentions = json.dumps(meta.mentions)
@ -109,15 +111,15 @@ def get_new_chat_message_doc(user, room, content, link = True):
return mess
def get_new_chat_message(user, room, content):
mess = get_new_chat_message_doc(user, room, content)
def get_new_chat_message(user, room, content, type = "Content"):
mess = get_new_chat_message_doc(user, room, content, type)
resp = dict(
name = mess.name,
user = mess.user,
room = mess.room,
room_type = mess.room_type,
content = mess.content,
content = json.loads(mess.content) if mess.type in ["File"] else mess.content,
urls = mess.urls,
mentions = json.loads(mess.mentions),
creation = mess.creation,
@ -126,15 +128,17 @@ def get_new_chat_message(user, room, content):
return resp
@frappe.whitelist()
def send(user, room, content):
mess = get_new_chat_message(user, room, content)
@frappe.whitelist(allow_guest = True)
def send(user, room, content, type = "Content"):
mess = get_new_chat_message(user, room, content, type)
frappe.publish_realtime('frappe.chat.message:create', mess, room = room,
after_commit = True)
@frappe.whitelist()
@frappe.whitelist(allow_guest = True)
def seen(message, user = None):
authenticate(user)
mess = frappe.get_doc('Chat Message', message)
mess.add_seen(user)
@ -151,7 +155,7 @@ def history(room, fields = None, limit = 10, start = None, end = None):
('Chat Message', 'room_type', '=', room.type)
],
fields = fields if fields else [
'name', 'room_type', 'room', 'content', 'user', 'mentions', 'urls', 'creation', '_seen'
'name', 'room_type', 'room', 'content', 'type', 'user', 'mentions', 'urls', 'creation', '_seen'
],
order_by = 'creation'
)
@ -160,6 +164,9 @@ def history(room, fields = None, limit = 10, start = None, end = None):
for m in mess:
m['seen'] = json.loads(m._seen) if m._seen else [ ]
del m['_seen']
if not fields or 'content' in fields:
for m in mess:
m['content'] = json.loads(m.content) if m.type in ["File"] else m.content
return mess
@ -173,11 +180,12 @@ def get(name, rooms = None, fields = None):
user = dmess.user,
room = dmess.room,
room_type = dmess.room_type,
content = dmess.content,
content = json.loads(dmess.content) if dmess.type in ["File"] else dmess.content,
type = dmess.type,
urls = dmess.urls,
mentions = dmess.mentions,
creation = dmess.creation,
seen = assign_if_empty(dmess._seen, [ ])
seen = get_if_empty(dmess._seen, [ ])
)
return data

View file

@ -76,7 +76,13 @@ def create(user, exists_ok = False, fields = None):
exists_ok, fields = safe_json_loads(exists_ok, fields)
if frappe.db.exists('Chat Profile', user):
result = frappe.db.sql("""
SELECT *
FROM `tabChat Profile`
WHERE user = "{user}"
""".format(user = user))
if result:
if not exists_ok:
frappe.throw(_('Chat Profile for User {user} exists.'.format(user = user)))
else:

View file

@ -1,314 +1,322 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "CR.#####",
"beta": 1,
"creation": "2017-11-08 15:27:21.156667",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "CR.#####",
"beta": 1,
"creation": "2017-11-08 15:27:21.156667",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Direct",
"fieldname": "type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Type",
"length": 0,
"no_copy": 0,
"options": "Direct\nGroup\nVisitor",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 1,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Direct",
"fieldname": "type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Type",
"length": 0,
"no_copy": 0,
"options": "Direct\nGroup\nVisitor",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.type==\"Group\"",
"fieldname": "room_name",
"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": "Name",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.type==\"Group\"",
"fieldname": "room_name",
"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": "Name",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.type==\"Group\"",
"fieldname": "avatar",
"fieldtype": "Attach Image",
"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": "Avatar",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.type==\"Group\"",
"fieldname": "avatar",
"fieldtype": "Attach Image",
"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": "Avatar",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "last_message",
"fieldtype": "Data",
"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": "Last Message",
"length": 0,
"no_copy": 0,
"options": "",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "last_message",
"fieldtype": "Data",
"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": "Last Message",
"length": 0,
"no_copy": 0,
"options": "",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "message_count",
"fieldtype": "Int",
"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": "Message Count",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "message_count",
"fieldtype": "Int",
"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": "Message Count",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "owner",
"fieldtype": "Link",
"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": "Owner",
"length": 0,
"no_copy": 0,
"options": "User",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "owner",
"fieldtype": "Data",
"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": "Owner",
"length": 0,
"no_copy": 0,
"options": "",
"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,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user_list",
"fieldtype": "Section 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,
"label": "Users",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user_list",
"fieldtype": "Section 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,
"label": "Users",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "users",
"fieldtype": "Table",
"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": "Users",
"length": 0,
"no_copy": 0,
"options": "Chat Room User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "users",
"fieldtype": "Table",
"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": "Users",
"length": 0,
"no_copy": 0,
"options": "Chat Room User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_field": "avatar",
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-01-20 18:14:02.384039",
"modified_by": "faris@erpnext.com",
"module": "Chat",
"name": "Chat Room",
"name_case": "",
"owner": "Administrator",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_field": "avatar",
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-03-23 17:07:17.351554",
"modified_by": "Administrator",
"module": "Chat",
"name": "Chat Room",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 1,
"share": 1,
"submit": 0,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 1,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "room_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "room_name",
"track_changes": 1,
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "room_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "room_name",
"track_changes": 1,
"track_seen": 0
}

View file

@ -7,6 +7,7 @@ from frappe import _
import frappe
# imports - frappe module imports
from frappe.chat import authenticate
from frappe.core.doctype.version.version import get_diff
from frappe.chat.doctype.chat_message import chat_message
from frappe.chat.util import (
@ -14,7 +15,7 @@ from frappe.chat.util import (
dictify,
listify,
squashify,
assign_if_empty
get_if_empty
)
session = frappe.session
@ -53,7 +54,7 @@ class ChatRoom(Document):
users = users
))
if self.type in ("Direct", "Visitor"):
if self.type == "Direct":
if len(self.users) != 1:
frappe.throw(_('{type} room must have atmost one user.'.format(type = self.type)))
@ -95,23 +96,19 @@ class ChatRoom(Document):
frappe.publish_realtime('frappe.chat.room:update', update, room = self.name, after_commit = True)
def authenticate(user):
if user != session.user:
frappe.throw(_("Sorry, you're not authorized."))
@frappe.whitelist()
@frappe.whitelist(allow_guest = True)
def get(user, rooms = None, fields = None, filters = None):
# There is this horrible bug out here.
# Looks like if frappe.call sends optional arguments (not in right order), the argument turns to an empty string.
# I'm not even going to think searching for it.
# Hence, the hack was assign_if_empty (previous assign_if_none)
# Hence, the hack was get_if_empty (previous assign_if_none)
# - Achilles Rasquinha achilles@frappe.io
authenticate(user)
rooms, fields, filters = safe_json_loads(rooms, fields, filters)
rooms = listify(assign_if_empty(rooms, [ ]))
fields = listify(assign_if_empty(fields, [ ]))
rooms = listify(get_if_empty(rooms, [ ]))
fields = listify(get_if_empty(fields, [ ]))
const = [ ] # constraints
if rooms:
@ -157,27 +154,51 @@ def get(user, rooms = None, fields = None, filters = None):
return rooms
@frappe.whitelist()
@frappe.whitelist(allow_guest = True)
def create(kind, owner, users = None, name = None):
authenticate(owner)
users = safe_json_loads(users)
users = safe_json_loads(users)
create = True
room = frappe.new_doc('Chat Room')
room.type = kind
room.owner = owner
room.room_name = name
if kind == 'Visitor':
room = squashify(frappe.db.sql("""
SELECT name
FROM `tabChat Room`
WHERE owner = "{owner}"
""".format(owner = owner), as_dict = True))
dusers = [ ]
if room:
room = frappe.get_doc('Chat Room', room.name)
create = False
if users:
users = listify(users)
for user in users:
duser = frappe.new_doc('Chat Room User')
duser.user = user
dusers.append(duser)
room.users = dusers
if create:
room = frappe.new_doc('Chat Room')
room.type = kind
room.owner = owner
room.room_name = name
dusers = [ ]
if kind != 'Visitor':
if users:
users = listify(users)
for user in users:
duser = frappe.new_doc('Chat Room User')
duser.user = user
dusers.append(duser)
room.users = dusers
else:
dsettings = frappe.get_single('Website Settings')
room.room_name = dsettings.chat_room_name
users = [user for user in room.users] if hasattr(room, 'users') else [ ]
for user in dsettings.chat_operators:
if user.user not in users:
room.append('users', user)
room.save(ignore_permissions = True)
room = get(owner, rooms = room.name)
@ -188,9 +209,10 @@ def create(kind, owner, users = None, name = None):
return room
@frappe.whitelist()
@frappe.whitelist(allow_guest = True)
def history(room, user, fields = None, limit = 10, start = None, end = None):
authenticate(user)
if frappe.get_doc('Chat Room', room).type != 'Visitor':
authenticate(user)
fields = safe_json_loads(fields)

View file

@ -0,0 +1,8 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Chat Token', {
refresh: function(frm) {
}
});

View file

@ -0,0 +1,156 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:token",
"beta": 1,
"creation": "2018-03-26 18:20:13.825652",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "token",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Token",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ip_address",
"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": "IP Address",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "country",
"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": "Country",
"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
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-03-26 18:35:55.651273",
"modified_by": "Administrator",
"module": "Chat",
"name": "Chat Token",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"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": 1
}
],
"quick_entry": 1,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class ChatToken(Document):
pass

View file

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Chat Token", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Chat Token
() => frappe.tests.make('Chat Token', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestChatToken(unittest.TestCase):
pass

View file

@ -4,7 +4,7 @@ from frappe.chat.util.util import (
squashify,
safe_json_loads,
filter_dict,
assign_if_empty,
get_if_empty,
listify,
dictify,
check_url,

View file

@ -64,7 +64,7 @@ def filter_dict(what, keys, ignore = False):
return copy
def assign_if_empty(a, b):
def get_if_empty(a, b):
if not a:
a = b
return a

View file

@ -0,0 +1,41 @@
import frappe
from frappe.chat.util import filter_dict, safe_json_loads
from frappe.sessions import get_geo_ip_country
@frappe.whitelist(allow_guest = True)
def settings(fields = None):
fields = safe_json_loads(fields)
dsettings = frappe.get_single('Website Settings')
response = dict(
socketio = dict(
port = frappe.conf.socketio_port
),
enable = bool(dsettings.chat_enable),
enable_from = dsettings.chat_enable_from,
enable_to = dsettings.chat_enable_to,
room_name = dsettings.chat_room_name,
welcome_message = dsettings.chat_welcome_message,
operators = [
duser.user for duser in dsettings.chat_operators
]
)
if fields:
response = filter_dict(response, fields)
return response
@frappe.whitelist(allow_guest = True)
def token():
dtoken = frappe.new_doc('Chat Token')
dtoken.token = frappe.generate_hash()
dtoken.ip_address = frappe.local.request_ip
country = get_geo_ip_country(dtoken.ip_address)
if country:
dtoken.country = country['iso_code']
dtoken.save(ignore_permissions = True)
return dtoken.token

View file

@ -19,7 +19,7 @@ Requests via FrappeClient are also handled here.
@frappe.whitelist()
def get_list(doctype, fields=None, filters=None, order_by=None,
limit_start=None, limit_page_length=20):
limit_start=None, limit_page_length=20, parent=None):
'''Returns a list of records by filters, fields, ordering and limit
:param doctype: DocType of the data to be queried
@ -28,16 +28,22 @@ def get_list(doctype, fields=None, filters=None, order_by=None,
:param order_by: Order by this fieldname
:param limit_start: Start at this index
:param limit_page_length: Number of records to be returned (default 20)'''
if frappe.is_table(doctype):
check_parent_permission(parent)
return frappe.get_list(doctype, fields=fields, filters=filters, order_by=order_by,
limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=False)
@frappe.whitelist()
def get(doctype, name=None, filters=None):
def get(doctype, name=None, filters=None, parent=None):
'''Returns a document by name or filters
:param doctype: DocType of the document to be returned
:param name: return document of this `name`
:param filters: If name is not set, filter by these values and return the first match'''
if frappe.is_table(doctype):
check_parent_permission(parent)
if filters and not name:
name = frappe.db.get_value(doctype, json.loads(filters))
if not name:
@ -50,12 +56,14 @@ def get(doctype, name=None, filters=None):
return frappe.get_doc(doctype, name).as_dict()
@frappe.whitelist()
def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False):
def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, parent=None):
'''Returns a value form a document
:param doctype: DocType to be queried
:param fieldname: Field to be returned (default `name`)
:param filters: dict or string for identifying the record'''
if frappe.is_table(doctype):
check_parent_permission(parent)
if not frappe.has_permission(doctype):
frappe.throw(_("No permission for {0}".format(doctype)), frappe.PermissionError)
@ -177,7 +185,9 @@ def save(doc):
if isinstance(doc, string_types):
doc = json.loads(doc)
doc = frappe.get_doc(doc).save()
doc = frappe.get_doc(doc)
doc.save()
return doc.as_dict()
@frappe.whitelist()
@ -344,3 +354,10 @@ def attach_file(filename=None, filedata=None, doctype=None, docname=None, folder
doc.save()
return f.as_dict()
def check_parent_permission(parent):
if parent:
if frappe.permissions.has_permission(parent):
return
# Either parent not passed or the user doesn't have permission on parent doctype of child table!
raise frappe.PermissionError

View file

@ -329,7 +329,7 @@ def console(context):
frappe.connect()
frappe.local.lang = frappe.db.get_default("lang")
import IPython
IPython.embed(disable_banner = True)
IPython.embed(display_banner = "")
@click.command('run-tests')
@click.option('--app', help="For App")
@ -460,18 +460,25 @@ def make_app(destination, app_name):
@click.command('set-config')
@click.argument('key')
@click.argument('value')
@click.option('-g', '--global', 'global_', is_flag = True, default = False, help = 'Set Global Site Config')
@click.option('--as-dict', is_flag=True, default=False)
@pass_context
def set_config(context, key, value, as_dict=False):
def set_config(context, key, value, global_ = False, as_dict=False):
"Insert/Update a value in site_config.json"
from frappe.installer import update_site_config
import ast
if as_dict:
value = ast.literal_eval(value)
for site in context.sites:
frappe.init(site=site)
update_site_config(key, value, validate=False)
frappe.destroy()
if global_:
sites_path = os.getcwd() # big assumption.
common_site_config_path = os.path.join(sites_path, 'common_site_config.json')
update_site_config(key, value, validate = False, site_config_path = common_site_config_path)
else:
for site in context.sites:
frappe.init(site=site)
update_site_config(key, value, validate=False)
frappe.destroy()
@click.command('version')
def get_version():

View file

@ -70,8 +70,33 @@ def get_data():
]
},
{
"label": _("External Documents"),
"label": _("Webhook"),
"items": [
{
"type": "doctype",
"name": "Webhook",
"description": _("Webhooks calling API requests into web apps"),
}
]
},
{
"label": _("Google Services"),
"items": [
{
"type": "doctype",
"name": "Google Maps",
"description": _("Google Maps integration"),
},
{
"type": "doctype",
"name": "GCalendar Settings",
"description": _("Configure your google calendar integration"),
},
{
"type": "doctype",
"name": "GCalendar Account",
"description": _("Configure accounts for google calendar"),
},
{
"type": "doctype",
"name": "GSuite Settings",
@ -81,21 +106,6 @@ def get_data():
"type": "doctype",
"name": "GSuite Templates",
"description": _("Google GSuite Templates to integration with DocTypes"),
},
{
"type": "doctype",
"name": "Webhook",
"description": _("Webhooks calling API requests into web apps"),
}
]
},
{
"label": _("Maps"),
"items": [
{
"type": "doctype",
"name": "Google Maps",
"description": _("Google Maps integration"),
}
]
}

View file

@ -95,9 +95,16 @@ def get_data():
{
"type": "doctype",
"name": "Data Import",
"label": _("Import / Export Data"),
"label": _("Import Data"),
"icon": "octicon octicon-cloud-upload",
"description": _("Import / Export Data from CSV and Excel files.")
"description": _("Import Data from CSV / Excel files.")
},
{
"type": "doctype",
"name": "Data Export",
"label": _("Export Data"),
"icon": "octicon octicon-cloud-upload",
"description": _("Export Data in CSV / Excel format.")
},
{
"type": "doctype",

View file

@ -8,8 +8,9 @@ frappe.ui.form.on("Contact", {
if(frm.doc.__islocal) {
var last_route = frappe.route_history.slice(-2, -1)[0];
let docname = last_route[2];
if (last_route.length > 3)
if (last_route && last_route.length > 3) {
docname = last_route.slice(2).join("/");
}
if(frappe.dynamic_link && frappe.dynamic_link.doc
&& frappe.dynamic_link.doc.name==docname) {
frm.add_child('links', {

View file

@ -2,7 +2,7 @@
{
"doctype": "Contact",
"salutation": "Mr",
"email_id": "test_conctact@example.com",
"email_id": "test_contact@example.com",
"first_name": "_Test Contact For _Test Customer",
"is_primary_contact": 1,
"phone": "+91 0000000000",

View file

@ -65,7 +65,7 @@ def get_feed_match_conditions(user=None, force=True):
can_read = frappe.get_user().get_can_read()
can_read_doctypes = ['"{}"'.format(doctype) for doctype in
list(set(can_read) - set(user_permissions.keys()))]
list(set(can_read) - set(list(user_permissions)))]
if can_read_doctypes:
conditions += ["""(`tabCommunication`.reference_doctype is null

View file

@ -89,7 +89,17 @@ frappe.ui.form.on("Communication", {
}, "Actions");
}
}
if(frm.doc.communication_type=="Communication"
&& frm.doc.communication_medium == "Phone"
&& frm.doc.sent_or_received == "Received"){
frm.add_custom_button(__("Add Contact"), function() {
frm.trigger('add_to_contact');
}, "Actions");
}
},
show_relink_dialog: function(frm){
var lib = "frappe.email";
var d = new frappe.ui.Dialog ({
@ -210,9 +220,10 @@ frappe.ui.form.on("Communication", {
var last_name = names.length >= 2? names[names.length - 1]: ""
frappe.route_options = {
"email_id": frm.doc.sender,
"email_id": frm.doc.sender || "",
"first_name": first_name,
"last_name": last_name,
"mobile_no": frm.doc.phone_no || ""
}
frappe.new_doc("Contact")
},

View file

@ -41,6 +41,7 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -72,6 +73,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -104,6 +106,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -135,6 +138,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -164,6 +168,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -195,6 +200,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -227,6 +233,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -259,6 +266,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -289,6 +297,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -321,6 +330,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -350,6 +360,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -379,6 +390,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "400"
},
@ -410,6 +422,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -440,6 +453,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -472,6 +486,7 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -503,6 +518,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -532,6 +548,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -564,6 +581,7 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -595,6 +613,7 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -624,6 +643,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -654,6 +674,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -684,6 +705,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -713,6 +735,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -743,6 +766,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -773,6 +797,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -804,6 +829,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -835,6 +861,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -866,6 +893,7 @@
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -898,6 +926,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -929,6 +958,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -960,6 +990,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -989,6 +1020,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1020,6 +1052,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1051,6 +1084,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1082,6 +1116,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1113,6 +1148,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1144,6 +1180,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1175,6 +1212,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1205,6 +1243,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1234,6 +1273,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1264,6 +1304,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1294,6 +1335,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1324,6 +1366,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1355,6 +1398,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1385,6 +1429,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1416,6 +1461,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1446,6 +1492,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
@ -1476,6 +1523,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
@ -1490,7 +1538,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-11-13 12:00:44.238575",
"modified": "2018-04-03 16:18:01.251522",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",

View file

@ -88,7 +88,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
frappe.db.commit()
if cint(send_email):
frappe.flags.print_letterhead = print_letterhead
frappe.flags.print_letterhead = cint(print_letterhead)
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy)
return {
@ -130,7 +130,7 @@ def notify(doc, print_html=None, print_format=None, attachments=None,
recipients, cc, bcc = get_recipients_cc_and_bcc(doc, recipients, cc, bcc,
fetched_from_email_account=fetched_from_email_account)
if not recipients:
if not recipients and not cc:
return
doc.emails_not_sent_to = set(doc.all_email_addresses) - set(doc.sent_email_addresses)
@ -175,7 +175,7 @@ def _notify(doc, print_html=None, print_format=None, attachments=None,
communication=doc.name,
read_receipt=doc.read_receipt,
is_notification=True if doc.sent_or_received =="Received" else False,
print_letterhead=True if frappe.flags.print_letterhead=='true' else False
print_letterhead=frappe.flags.print_letterhead
)
def update_parent_mins_to_first_response(doc):
@ -233,15 +233,20 @@ def get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_accou
# don't cc to people who already received the mail from sender's email service
cc = list(set(cc) - set(original_cc) - set(original_recipients))
remove_administrator_from_email_list(cc)
original_bcc = split_emails(doc.bcc)
bcc = list(set(bcc) - set(original_bcc) - set(original_recipients))
remove_administrator_from_email_list(bcc)
if 'Administrator' in recipients:
recipients.remove('Administrator')
remove_administrator_from_email_list(recipients)
return recipients, cc, bcc
def remove_administrator_from_email_list(email_list):
if 'Administrator' in email_list:
email_list.remove('Administrator')
def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None):
"""Prepare to make multipart MIME Email
@ -373,11 +378,6 @@ def get_bcc(doc, recipients=None, fetched_from_email_account=False):
"""Build a list of email addresses for BCC"""
bcc = split_emails(doc.bcc)
if doc.reference_doctype and doc.reference_name:
if fetched_from_email_account:
bcc.append(get_owner_email(doc))
bcc += get_assignees(doc)
if bcc:
exclude = []
exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)]

View file

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "hash",
@ -13,6 +14,7 @@
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -23,6 +25,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Role and Level",
@ -38,9 +41,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -51,6 +56,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Role",
@ -70,39 +76,12 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "150px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Filter records based on User Permissions defined for a user",
"fieldname": "apply_user_permissions",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Apply User Permissions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -114,6 +93,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "If user is the owner",
@ -129,9 +109,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -142,6 +124,7 @@
"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,
@ -156,9 +139,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -170,6 +155,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Level",
@ -188,40 +174,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "40px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"description": "JSON list of DocTypes used to apply User Permissions. If empty, all linked DocTypes will be used to apply User Permissions.",
"fieldname": "user_permission_doctypes",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "User Permission DocTypes",
"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
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -232,6 +190,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Permissions",
@ -247,9 +206,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -261,6 +222,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Read",
@ -279,10 +241,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "32px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -294,6 +258,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Write",
@ -312,10 +277,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "32px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -327,6 +294,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Create",
@ -345,10 +313,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "32px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -360,6 +330,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Delete",
@ -375,9 +346,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -388,6 +361,7 @@
"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,
@ -402,9 +376,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -415,6 +391,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Submit",
@ -433,10 +410,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "32px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -447,6 +426,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Cancel",
@ -465,10 +445,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "32px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -479,6 +461,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amend",
@ -497,10 +480,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "32px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -511,6 +496,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Additional Permissions",
@ -526,9 +512,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -540,6 +528,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Report",
@ -556,10 +545,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "32px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -571,6 +562,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Export",
@ -586,9 +578,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -599,6 +593,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Import",
@ -614,9 +609,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -628,6 +625,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Set User Permissions",
@ -643,9 +641,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -656,6 +656,7 @@
"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,
@ -670,9 +671,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -684,6 +687,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Share",
@ -699,9 +703,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -713,6 +719,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print",
@ -728,9 +735,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -742,6 +751,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email",
@ -757,20 +767,21 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-01-11 04:21:35.217943",
"modified": "2018-03-26 11:55:43.405113",
"modified_by": "Administrator",
"module": "Core",
"name": "Custom DocPerm",
@ -801,6 +812,7 @@
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 0,

View file

@ -0,0 +1,137 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Data Export', {
refresh: frm => {
frm.disable_save();
frm.page.set_primary_action('Export', () => {
can_export(frm) ? export_data(frm) : null;
});
},
onload: (frm) => {
frm.set_query("reference_doctype", () => {
return {
"filters": {
"issingle": 0,
"istable": 0,
"name": ['in', frappe.boot.user.can_export]
}
};
});
},
reference_doctype: frm => {
const doctype = frm.doc.reference_doctype;
if (doctype) {
frappe.model.with_doctype(doctype, () => set_field_options(frm));
} else {
reset_filter_and_field(frm);
}
}
});
const can_export = frm => {
const doctype = frm.doc.reference_doctype;
const parent_multicheck_options = frm.fields_multicheck[doctype] ?
frm.fields_multicheck[doctype].get_checked_options() : [];
let is_valid_form = false;
if (!doctype) {
frappe.msgprint(__('Please select the Document Type.'));
} else if (!parent_multicheck_options.length) {
frappe.msgprint(__('Atleast one field of Parent Document Type is mandatory'));
} else {
is_valid_form = true;
}
return is_valid_form;
};
const export_data = frm => {
let get_template_url = '/api/method/frappe.core.doctype.data_export.exporter.export_data';
var export_params = () => {
let columns = {};
Object.keys(frm.fields_multicheck).forEach(dt => {
const options = frm.fields_multicheck[dt].get_checked_options();
columns[dt] = options;
});
return {
doctype: frm.doc.reference_doctype,
select_columns: JSON.stringify(columns),
filters: frm.filter_list.get_filters().map(filter => filter.slice(1, 4)),
file_type: frm.doc.file_type,
template: true,
with_data: true
};
};
open_url_post(get_template_url, export_params());
};
const reset_filter_and_field = (frm) => {
const parent_wrapper = frm.fields_dict.fields_multicheck.$wrapper;
const filter_wrapper = frm.fields_dict.filter_list.$wrapper;
parent_wrapper.empty();
filter_wrapper.empty();
frm.filter_list = [];
frm.fields_multicheck = {};
};
const set_field_options = (frm) => {
const parent_wrapper = frm.fields_dict.fields_multicheck.$wrapper;
const filter_wrapper = frm.fields_dict.filter_list.$wrapper;
const doctype = frm.doc.reference_doctype;
const related_doctypes = get_doctypes(doctype);
parent_wrapper.empty();
filter_wrapper.empty();
frm.filter_list = new frappe.ui.FilterGroup({
parent: filter_wrapper,
doctype: doctype,
on_change: () => { },
});
frm.fields_multicheck = {};
related_doctypes.forEach(dt => {
frm.fields_multicheck[dt] = add_doctype_field_multicheck_control(dt, parent_wrapper);
});
frm.refresh();
};
const get_doctypes = parentdt => {
return [parentdt].concat(
frappe.meta.get_table_fields(parentdt).map(df => df.options)
);
};
const add_doctype_field_multicheck_control = (doctype, parent_wrapper) => {
const fields = get_fields(doctype);
const options = fields
.map(df => {
return {
label: df.label + (df.reqd ? ' (M)' : ''),
value: df.fieldname,
checked: 1
};
});
const multicheck_control = frappe.ui.form.make_control({
parent: parent_wrapper,
df: {
"label": doctype,
"fieldname": doctype + '_fields',
"fieldtype": "MultiCheck",
"options": options,
"select_all": options.length > 5,
"columns": 3,
"hidden": 1,
},
render_input: true
});
multicheck_control.refresh_input();
return multicheck_control;
};
const filter_fields = df => frappe.model.is_value_type(df) && !df.hidden;
const get_fields = dt => frappe.meta.get_docfields(dt).filter(filter_fields);

View file

@ -0,0 +1,250 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-03-07 10:09:49.794764",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 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": "Select 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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "CSV",
"fieldname": "file_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "File Type",
"length": 0,
"no_copy": 0,
"options": "Excel\nCSV",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "reference_doctype",
"fieldname": "section_break",
"fieldtype": "Section 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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "filter_list",
"fieldtype": "HTML",
"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": "Filter List",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fields_multicheck",
"fieldtype": "HTML",
"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": "Fields Multicheck",
"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
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 1,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-03-21 13:23:05.623052",
"modified_by": "Administrator",
"module": "Core",
"name": "Data Export",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class DataExport(Document):
pass

View file

@ -0,0 +1,339 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
import frappe.permissions
import re, csv, os
from frappe.utils.csvutils import UnicodeWriter
from frappe.utils import cstr, formatdate, format_datetime, parse_json, cint
from frappe.core.doctype.data_import.importer import get_data_keys
from six import string_types
reflags = {
"I":re.I,
"L":re.L,
"M":re.M,
"U":re.U,
"S":re.S,
"X":re.X,
"D": re.DEBUG
}
@frappe.whitelist()
def export_data(doctype=None, parent_doctype=None, all_doctypes=True, with_data=False,
select_columns=None, file_type='CSV', template=False, filters=None):
exporter = DataExporter(doctype=doctype, parent_doctype=parent_doctype, all_doctypes=all_doctypes, with_data=with_data,
select_columns=select_columns, file_type=file_type, template=template, filters=filters)
exporter.build_response()
class DataExporter:
def __init__(self, doctype=None, parent_doctype=None, all_doctypes=True, with_data=False,
select_columns=None, file_type='CSV', template=False, filters=None):
self.doctype = doctype
self.parent_doctype = parent_doctype
self.all_doctypes = all_doctypes
self.with_data = cint(with_data)
self.select_columns = select_columns
self.file_type = file_type
self.template = template
self.filters = filters
self.data_keys = get_data_keys()
self.prepare_args()
def prepare_args(self):
if self.select_columns:
self.select_columns = parse_json(self.select_columns)
if self.filters:
self.filters = parse_json(self.filters)
self.docs_to_export = {}
if self.doctype:
if isinstance(self.doctype, string_types):
self.doctype = [self.doctype]
if len(self.doctype) > 1:
self.docs_to_export = self.doctype[1]
self.doctype = self.doctype[0]
if not self.parent_doctype:
self.parent_doctype = self.doctype
self.column_start_end = {}
if self.all_doctypes:
self.child_doctypes = []
for df in frappe.get_meta(self.doctype).get_table_fields():
self.child_doctypes.append(dict(doctype=df.options, parentfield=df.fieldname))
def build_response(self):
self.writer = UnicodeWriter()
self.name_field = 'parent' if self.parent_doctype != self.doctype else 'name'
if self.template:
self.add_main_header()
self.writer.writerow([''])
self.tablerow = [self.data_keys.doctype, ""]
self.labelrow = [_("Column Labels:"), "ID"]
self.fieldrow = [self.data_keys.columns, self.name_field]
self.mandatoryrow = [_("Mandatory:"), _("Yes")]
self.typerow = [_('Type:'), 'Data (text)']
self.inforow = [_('Info:'), '']
self.columns = [self.name_field]
self.build_field_columns(self.doctype)
if self.all_doctypes:
for d in self.child_doctypes:
self.append_empty_field_column()
if (self.select_columns and self.select_columns.get(d['doctype'], None)) or not self.select_columns:
# if atleast one column is selected for this doctype
self.build_field_columns(d['doctype'], d['parentfield'])
self.add_field_headings()
self.add_data()
if self.with_data and not self.data:
frappe.respond_as_web_page(_('No Data'), _('There is no data to be exported'), indicator_color='orange')
if self.file_type == 'Excel':
self.build_response_as_excel()
else:
# write out response as a type csv
frappe.response['result'] = cstr(self.writer.getvalue())
frappe.response['type'] = 'csv'
frappe.response['doctype'] = self.doctype
def add_main_header(self):
self.writer.writerow([_('Data Import Template')])
self.writer.writerow([self.data_keys.main_table, self.doctype])
if self.parent_doctype != self.doctype:
self.writer.writerow([self.data_keys.parent_table, self.parent_doctype])
else:
self.writer.writerow([''])
self.writer.writerow([''])
self.writer.writerow([_('Notes:')])
self.writer.writerow([_('Please do not change the template headings.')])
self.writer.writerow([_('First data column must be blank.')])
self.writer.writerow([_('If you are uploading new records, leave the "name" (ID) column blank.')])
self.writer.writerow([_('If you are uploading new records, "Naming Series" becomes mandatory, if present.')])
self.writer.writerow([_('Only mandatory fields are necessary for new records. You can delete non-mandatory columns if you wish.')])
self.writer.writerow([_('For updating, you can update only selective columns.')])
self.writer.writerow([_('You can only upload upto 5000 records in one go. (may be less in some cases)')])
if self.name_field == "parent":
self.writer.writerow([_('"Parent" signifies the parent table in which this row must be added')])
self.writer.writerow([_('If you are updating, please select "Overwrite" else existing rows will not be deleted.')])
def build_field_columns(self, dt, parentfield=None):
meta = frappe.get_meta(dt)
# build list of valid docfields
tablecolumns = []
for f in frappe.db.sql('desc `tab%s`' % dt):
field = meta.get_field(f[0])
if field and ((self.select_columns and f[0] in self.select_columns[dt]) or not self.select_columns):
tablecolumns.append(field)
tablecolumns.sort(key = lambda a: int(a.idx))
_column_start_end = frappe._dict(start=0)
if dt==self.doctype:
_column_start_end = frappe._dict(start=0)
else:
_column_start_end = frappe._dict(start=len(self.columns))
self.append_field_column(frappe._dict({
"fieldname": "name",
"parent": dt,
"label": "ID",
"fieldtype": "Data",
"reqd": 1,
"idx": 0,
"info": _("Leave blank for new records")
}), True)
for docfield in tablecolumns:
self.append_field_column(docfield, True)
# all non mandatory fields
for docfield in tablecolumns:
self.append_field_column(docfield, False)
# if there is one column, add a blank column (?)
if len(self.columns)-_column_start_end.start == 1:
self.append_empty_field_column()
# append DocType name
self.tablerow[_column_start_end.start + 1] = dt
if parentfield:
self.tablerow[_column_start_end.start + 2] = parentfield
_column_start_end.end = len(self.columns) + 1
self.column_start_end[(dt, parentfield)] = _column_start_end
def append_field_column(self, docfield, for_mandatory):
if not docfield:
return
if for_mandatory and not docfield.reqd:
return
if not for_mandatory and docfield.reqd:
return
if docfield.fieldname in ('parenttype', 'trash_reason'):
return
if docfield.hidden:
return
if self.select_columns and docfield.fieldname not in self.select_columns.get(docfield.parent, []):
return
self.tablerow.append("")
self.fieldrow.append(docfield.fieldname)
self.labelrow.append(_(docfield.label))
self.mandatoryrow.append(docfield.reqd and 'Yes' or 'No')
self.typerow.append(docfield.fieldtype)
self.inforow.append(self.getinforow(docfield))
self.columns.append(docfield.fieldname)
def append_empty_field_column(self):
self.tablerow.append("~")
self.fieldrow.append("~")
self.labelrow.append("")
self.mandatoryrow.append("")
self.typerow.append("")
self.inforow.append("")
self.columns.append("")
@staticmethod
def getinforow(docfield):
"""make info comment for options, links etc."""
if docfield.fieldtype == 'Select':
if not docfield.options:
return ''
else:
return _("One of") + ': %s' % ', '.join(filter(None, docfield.options.split('\n')))
elif docfield.fieldtype == 'Link':
return 'Valid %s' % docfield.options
elif docfield.fieldtype == 'Int':
return 'Integer'
elif docfield.fieldtype == "Check":
return "0 or 1"
elif docfield.fieldtype in ["Date", "Datetime"]:
return cstr(frappe.defaults.get_defaults().date_format)
elif hasattr(docfield, "info"):
return docfield.info
else:
return ''
def add_field_headings(self):
self.writer.writerow(self.tablerow)
self.writer.writerow(self.labelrow)
self.writer.writerow(self.fieldrow)
self.writer.writerow(self.mandatoryrow)
self.writer.writerow(self.typerow)
self.writer.writerow(self.inforow)
if self.template:
self.writer.writerow([self.data_keys.data_separator])
def add_data(self):
if self.template and not self.with_data:
return
frappe.permissions.can_export(self.parent_doctype, raise_exception=True)
# sort nested set doctypes by `lft asc`
order_by = None
table_columns = frappe.db.get_table_columns(self.parent_doctype)
if 'lft' in table_columns and 'rgt' in table_columns:
order_by = '`tab{doctype}`.`lft` asc'.format(doctype=self.parent_doctype)
# get permitted data only
self.data = frappe.get_list(self.doctype, fields=["*"], filters=self.filters, limit_page_length=None, order_by=order_by)
for doc in self.data:
op = self.docs_to_export.get("op")
names = self.docs_to_export.get("name")
if names and op:
if op == '=' and doc.name not in names:
continue
elif op == '!=' and doc.name in names:
continue
elif names:
try:
sflags = self.docs_to_export.get("flags", "I,U").upper()
flags = 0
for a in re.split('\W+',sflags):
flags = flags | reflags.get(a,0)
c = re.compile(names, flags)
m = c.match(doc.name)
if not m:
continue
except Exception:
if doc.name not in names:
continue
# add main table
rows = []
self.add_data_row(rows, self.doctype, None, doc, 0)
if self.all_doctypes:
# add child tables
for c in self.child_doctypes:
for ci, child in enumerate(frappe.db.sql("""select * from `tab{0}`
where parent=%s and parentfield=%s order by idx""".format(c['doctype']),
(doc.name, c['parentfield']), as_dict=1)):
self.add_data_row(rows, c['doctype'], c['parentfield'], child, ci)
for row in rows:
self.writer.writerow(row)
def add_data_row(self, rows, dt, parentfield, doc, rowidx):
d = doc.copy()
meta = frappe.get_meta(dt)
if self.all_doctypes:
d.name = '"'+ d.name+'"'
if len(rows) < rowidx + 1:
rows.append([""] * (len(self.columns) + 1))
row = rows[rowidx]
_column_start_end = self.column_start_end.get((dt, parentfield))
if _column_start_end:
for i, c in enumerate(self.columns[_column_start_end.start:_column_start_end.end]):
df = meta.get_field(c)
fieldtype = df.fieldtype if df else "Data"
value = d.get(c, "")
if value:
if fieldtype == "Date":
value = formatdate(value)
elif fieldtype == "Datetime":
value = format_datetime(value)
row[_column_start_end.start + i + 1] = value
def build_response_as_excel(self):
filename = frappe.generate_hash("", 10)
with open(filename, 'wb') as f:
f.write(cstr(self.writer.getvalue()).encode('utf-8'))
f = open(filename)
reader = csv.reader(f)
from frappe.utils.xlsxutils import make_xlsx
xlsx_file = make_xlsx(reader, "Data Import Template" if self.template else 'Data Export')
f.close()
os.remove(filename)
# write out response as a xlsx type
frappe.response['filename'] = self.doctype + '.xlsx'
frappe.response['filecontent'] = xlsx_file.getvalue()
frappe.response['type'] = 'binary'

View file

@ -22,7 +22,7 @@ frappe.ui.form.on('Data Import', {
let progress_bar = $(frm.dashboard.progress_area).find(".progress-bar");
if (progress_bar) {
$(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped");
$(progress_bar).css("width", data.progress+"%");
$(progress_bar).css("width", data.progress + "%");
}
}
}
@ -38,10 +38,10 @@ frappe.ui.form.on('Data Import', {
if (frm.doc.import_status) {
const listview_settings = frappe.listview_settings['Data Import'];
const indicator = listview_settings.get_indicator(frm.doc);
frm.page.set_indicator(indicator[0], indicator[1]);
if (frm.doc.import_status==="In Progress") {
if (frm.doc.import_status === "In Progress") {
frm.dashboard.add_progress("Data Import Progress", "0");
frm.set_read_only();
frm.refresh_fields();
@ -57,14 +57,14 @@ frappe.ui.form.on('Data Import', {
frappe.help.show_video("6wiriRKPhmg");
});
if(frm.doc.reference_doctype && frm.doc.docstatus === 0) {
if (frm.doc.reference_doctype && frm.doc.docstatus === 0) {
frm.add_custom_button(__("Download template"), function() {
frappe.data_import.download_dialog(frm).show();
});
}
if (frm.doc.reference_doctype && frm.doc.import_file && frm.doc.total_rows &&
frm.doc.docstatus === 0 && (!frm.doc.import_status || frm.doc.import_status=="Failed")) {
frm.doc.docstatus === 0 && (!frm.doc.import_status || frm.doc.import_status == "Failed")) {
frm.page.set_primary_action(__("Start Import"), function() {
frappe.call({
method: "frappe.core.doctype.data_import.data_import.import_data",
@ -88,10 +88,6 @@ frappe.ui.form.on('Data Import', {
}
},
// import_file: function(frm) {
// frm.save();
// },
overwrite: function(frm) {
if (frm.doc.overwrite === 0) {
frm.doc.only_update = 0;
@ -126,8 +122,11 @@ frappe.ui.form.on('Data Import', {
create_log_table: function(frm) {
let msg = JSON.parse(frm.doc.log_details);
var $log_wrapper = $(frm.fields_dict.import_log.wrapper).empty();
$(frappe.render_template("log_details", {data: msg.messages, show_only_errors: frm.doc.show_only_errors,
import_status: frm.doc.import_status})).appendTo($log_wrapper);
$(frappe.render_template("log_details", {
data: msg.messages,
import_status: frm.doc.import_status,
show_only_errors: frm.doc.show_only_errors,
})).appendTo($log_wrapper);
}
});
@ -146,7 +145,7 @@ frappe.data_import.download_dialog = function(frm) {
const get_doctype_checkbox_fields = () => {
return dialog.fields.filter(df => df.fieldname.endsWith('_fields'))
.map(df => dialog.fields_dict[df.fieldname]);
}
};
const doctype_fields = get_fields(frm.doc.reference_doctype)
.map(df => ({
@ -245,13 +244,13 @@ frappe.data_import.download_dialog = function(frm) {
doctype: frm.doc.reference_doctype,
parent_doctype: frm.doc.reference_doctype,
select_columns: JSON.stringify(columns),
with_data: data.with_data ? 'Yes' : 'No',
all_doctypes: 'Yes',
from_data_import: 'Yes',
excel_format: data.file_type === 'Excel' ? 'Yes' : 'No'
with_data: data.with_data,
all_doctypes: true,
file_type: data.file_type,
template: true
};
};
let get_template_url = '/api/method/frappe.core.doctype.data_import.exporter.get_template';
let get_template_url = '/api/method/frappe.core.doctype.data_export.exporter.export_data';
open_url_post(get_template_url, export_params());
} else {
frappe.msgprint(__("Please select the Document Type."));

View file

@ -99,9 +99,9 @@ def export_json(doctype, path, filters=None, or_filters=None, name=None, order_b
def export_csv(doctype, path):
from frappe.core.doctype.data_import.exporter import get_template
from frappe.core.doctype.data_export.exporter import export_data
with open(path, "wb") as csvfile:
get_template(doctype=doctype, all_doctypes="Yes", with_data="Yes")
export_data(doctype=doctype, all_doctypes=True, template=True, with_data=True)
csvfile.write(frappe.response.result.encode("utf-8"))

View file

@ -1,309 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe, json
from frappe import _
import frappe.permissions
import re, csv, os
from frappe.utils.csvutils import UnicodeWriter
from frappe.utils import cstr, formatdate, format_datetime
from frappe.core.doctype.data_import.importer import get_data_keys
from six import string_types
reflags = {
"I":re.I,
"L":re.L,
"M":re.M,
"U":re.U,
"S":re.S,
"X":re.X,
"D": re.DEBUG
}
@frappe.whitelist()
def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data="No", select_columns=None,
from_data_import="No", excel_format="No"):
all_doctypes = all_doctypes=="Yes"
if select_columns:
select_columns = json.loads(select_columns);
docs_to_export = {}
if doctype:
if isinstance(doctype, string_types):
doctype = [doctype];
if len(doctype) > 1:
docs_to_export = doctype[1]
doctype = doctype[0]
if not parent_doctype:
parent_doctype = doctype
column_start_end = {}
if all_doctypes:
child_doctypes = []
for df in frappe.get_meta(doctype).get_table_fields():
child_doctypes.append(dict(doctype=df.options, parentfield=df.fieldname))
def get_data_keys_definition():
return get_data_keys()
def add_main_header():
w.writerow([_('Data Import Template')])
w.writerow([get_data_keys_definition().main_table, doctype])
if parent_doctype != doctype:
w.writerow([get_data_keys_definition().parent_table, parent_doctype])
else:
w.writerow([''])
w.writerow([''])
w.writerow([_('Notes:')])
w.writerow([_('Please do not change the template headings.')])
w.writerow([_('First data column must be blank.')])
w.writerow([_('If you are uploading new records, leave the "name" (ID) column blank.')])
w.writerow([_('If you are uploading new records, "Naming Series" becomes mandatory, if present.')])
w.writerow([_('Only mandatory fields are necessary for new records. You can delete non-mandatory columns if you wish.')])
w.writerow([_('For updating, you can update only selective columns.')])
w.writerow([_('You can only upload upto 5000 records in one go. (may be less in some cases)')])
if key == "parent":
w.writerow([_('"Parent" signifies the parent table in which this row must be added')])
w.writerow([_('If you are updating, please select "Overwrite" else existing rows will not be deleted.')])
def build_field_columns(dt, parentfield=None):
meta = frappe.get_meta(dt)
# build list of valid docfields
tablecolumns = []
for f in frappe.db.sql('desc `tab%s`' % dt):
field = meta.get_field(f[0])
if field and ((select_columns and f[0] in select_columns[dt]) or not select_columns):
tablecolumns.append(field)
tablecolumns.sort(key = lambda a: int(a.idx))
_column_start_end = frappe._dict(start=0)
if dt==doctype:
_column_start_end = frappe._dict(start=0)
else:
_column_start_end = frappe._dict(start=len(columns))
append_field_column(frappe._dict({
"fieldname": "name",
"parent": dt,
"label": "ID",
"fieldtype": "Data",
"reqd": 1,
"idx": 0,
"info": _("Leave blank for new records")
}), True)
for docfield in tablecolumns:
append_field_column(docfield, True)
# all non mandatory fields
for docfield in tablecolumns:
append_field_column(docfield, False)
# if there is one column, add a blank column (?)
if len(columns)-_column_start_end.start == 1:
append_empty_field_column()
# append DocType name
tablerow[_column_start_end.start + 1] = dt
if parentfield:
tablerow[_column_start_end.start + 2] = parentfield
_column_start_end.end = len(columns) + 1
column_start_end[(dt, parentfield)] = _column_start_end
def append_field_column(docfield, for_mandatory):
if not docfield:
return
if for_mandatory and not docfield.reqd:
return
if not for_mandatory and docfield.reqd:
return
if docfield.fieldname in ('parenttype', 'trash_reason'):
return
if docfield.hidden:
return
if select_columns and docfield.fieldname not in select_columns.get(docfield.parent, []):
return
tablerow.append("")
fieldrow.append(docfield.fieldname)
labelrow.append(_(docfield.label))
mandatoryrow.append(docfield.reqd and 'Yes' or 'No')
typerow.append(docfield.fieldtype)
inforow.append(getinforow(docfield))
columns.append(docfield.fieldname)
def append_empty_field_column():
tablerow.append("~")
fieldrow.append("~")
labelrow.append("")
mandatoryrow.append("")
typerow.append("")
inforow.append("")
columns.append("")
def getinforow(docfield):
"""make info comment for options, links etc."""
if docfield.fieldtype == 'Select':
if not docfield.options:
return ''
else:
return _("One of") + ': %s' % ', '.join(filter(None, docfield.options.split('\n')))
elif docfield.fieldtype == 'Link':
return 'Valid %s' % docfield.options
elif docfield.fieldtype == 'Int':
return 'Integer'
elif docfield.fieldtype == "Check":
return "0 or 1"
elif docfield.fieldtype in ["Date", "Datetime"]:
return cstr(frappe.defaults.get_defaults().date_format)
elif hasattr(docfield, "info"):
return docfield.info
else:
return ''
def add_field_headings():
w.writerow(tablerow)
w.writerow(labelrow)
w.writerow(fieldrow)
w.writerow(mandatoryrow)
w.writerow(typerow)
w.writerow(inforow)
w.writerow([get_data_keys_definition().data_separator])
def add_data():
def add_data_row(row_group, dt, parentfield, doc, rowidx):
d = doc.copy()
meta = frappe.get_meta(dt)
if all_doctypes:
d.name = '"'+ d.name+'"'
if len(row_group) < rowidx + 1:
row_group.append([""] * (len(columns) + 1))
row = row_group[rowidx]
_column_start_end = column_start_end.get((dt, parentfield))
if _column_start_end:
for i, c in enumerate(columns[_column_start_end.start:_column_start_end.end]):
df = meta.get_field(c)
fieldtype = df.fieldtype if df else "Data"
value = d.get(c, "")
if value:
if fieldtype == "Date":
value = formatdate(value)
elif fieldtype == "Datetime":
value = format_datetime(value)
row[_column_start_end.start + i + 1] = value
if with_data=='Yes':
frappe.permissions.can_export(parent_doctype, raise_exception=True)
# sort nested set doctypes by `lft asc`
order_by = None
table_columns = frappe.db.get_table_columns(parent_doctype)
if 'lft' in table_columns and 'rgt' in table_columns:
order_by = '`tab{doctype}`.`lft` asc'.format(doctype=parent_doctype)
# get permitted data only
data = frappe.get_list(doctype, fields=["*"], limit_page_length=None, order_by=order_by)
for doc in data:
op = docs_to_export.get("op")
names = docs_to_export.get("name")
if names and op:
if op == '=' and doc.name not in names:
continue
elif op == '!=' and doc.name in names:
continue
elif names:
try:
sflags = docs_to_export.get("flags", "I,U").upper()
flags = 0
for a in re.split('\W+',sflags):
flags = flags | reflags.get(a,0)
c = re.compile(names, flags)
m = c.match(doc.name)
if not m:
continue
except:
if doc.name not in names:
continue
# add main table
row_group = []
add_data_row(row_group, doctype, None, doc, 0)
if all_doctypes:
# add child tables
for c in child_doctypes:
for ci, child in enumerate(frappe.db.sql("""select * from `tab{0}`
where parent=%s and parentfield=%s order by idx""".format(c['doctype']),
(doc.name, c['parentfield']), as_dict=1)):
add_data_row(row_group, c['doctype'], c['parentfield'], child, ci)
for row in row_group:
w.writerow(row)
w = UnicodeWriter()
key = 'parent' if parent_doctype != doctype else 'name'
add_main_header()
w.writerow([''])
tablerow = [get_data_keys_definition().doctype, ""]
labelrow = [_("Column Labels:"), "ID"]
fieldrow = [get_data_keys_definition().columns, key]
mandatoryrow = [_("Mandatory:"), _("Yes")]
typerow = [_('Type:'), 'Data (text)']
inforow = [_('Info:'), '']
columns = [key]
build_field_columns(doctype)
if all_doctypes:
for d in child_doctypes:
append_empty_field_column()
if (select_columns and select_columns.get(d['doctype'], None)) or not select_columns:
# if atleast one column is selected for this doctype
build_field_columns(d['doctype'], d['parentfield'])
add_field_headings()
add_data()
if from_data_import == "Yes" and excel_format == "Yes":
filename = frappe.generate_hash("", 10)
with open(filename, 'wb') as f:
f.write(cstr(w.getvalue()).encode("utf-8"))
f = open(filename)
reader = csv.reader(f)
from frappe.utils.xlsxutils import make_xlsx
xlsx_file = make_xlsx(reader, "Data Import Template")
f.close()
os.remove(filename)
# write out response as a xlsx type
frappe.response['filename'] = doctype + '.xlsx'
frappe.response['filecontent'] = xlsx_file.getvalue()
frappe.response['type'] = 'binary'
else:
# write out response as a type csv
frappe.response['result'] = cstr(w.getvalue())
frappe.response['type'] = 'csv'
frappe.response['doctype'] = doctype

View file

@ -198,8 +198,15 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
doc['doctype'] = doctype
return doc
# used in testing whether a row is empty or parent row or child row
# checked only 3 first columns since first two columns can be blank for example the case of
# importing the item variant where item code and item name will be blank.
def main_doc_empty(row):
return not (row and ((len(row) > 1 and row[1]) or (len(row) > 2 and row[2])))
if row:
for i in range(3,1,-1):
if len(row) > i and row[i]:
return False
return True
def validate_naming(doc):
autoname = frappe.get_meta(doctype).autoname

View file

@ -4,24 +4,24 @@
from __future__ import unicode_literals
import frappe, unittest
from frappe.core.doctype.data_import import exporter
from frappe.core.doctype.data_export import exporter
from frappe.core.doctype.data_import import importer
from frappe.utils.csvutils import read_csv_content
class TestDataImport(unittest.TestCase):
def test_export(self):
exporter.get_template("User", all_doctypes="No", with_data="No")
exporter.export_data("User", all_doctypes=True, template=True)
content = read_csv_content(frappe.response.result)
self.assertTrue(content[1][1], "User")
def test_export_with_data(self):
exporter.get_template("User", all_doctypes="No", with_data="Yes")
exporter.export_data("User", all_doctypes=True, template=True, with_data=True)
content = read_csv_content(frappe.response.result)
self.assertTrue(content[1][1], "User")
self.assertTrue("Administrator" in [c[1] for c in content if len(c)>1])
self.assertTrue('"Administrator"' in [c[1] for c in content if len(c)>1])
def test_export_with_all_doctypes(self):
exporter.get_template("User", all_doctypes="Yes", with_data="Yes")
exporter.export_data("User", all_doctypes="Yes", template=True, with_data=True)
content = read_csv_content(frappe.response.result)
self.assertTrue(content[1][1], "User")
self.assertTrue('"Administrator"' in [c[1] for c in content if len(c)>1])
@ -33,14 +33,14 @@ class TestDataImport(unittest.TestCase):
if frappe.db.exists("Blog Category", "test-category"):
frappe.delete_doc("Blog Category", "test-category")
exporter.get_template("Blog Category", all_doctypes="No", with_data="No")
exporter.export_data("Blog Category", all_doctypes=True, template=True)
content = read_csv_content(frappe.response.result)
content.append(["", "", "test-category", "Test Cateogry"])
importer.upload(content)
self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "Test Category")
# export with data
exporter.get_template("Blog Category", all_doctypes="No", with_data="Yes")
exporter.export_data("Blog Category", all_doctypes=True, template=True, with_data=True)
content = read_csv_content(frappe.response.result)
# overwrite
@ -55,7 +55,7 @@ class TestDataImport(unittest.TestCase):
frappe.get_doc({"doctype": "User", "email": user_email, "first_name": "Test Import UserRole"}).insert()
exporter.get_template("Has Role", "User", all_doctypes="No", with_data="No")
exporter.export_data("Has Role", "User", all_doctypes=True, template=True)
content = read_csv_content(frappe.response.result)
content.append(["", "test_import_userrole@example.com", "Blogger"])
importer.upload(content)
@ -65,7 +65,7 @@ class TestDataImport(unittest.TestCase):
self.assertTrue(user.get("roles")[0].role, "Blogger")
# overwrite
exporter.get_template("Has Role", "User", all_doctypes="No", with_data="No")
exporter.export_data("Has Role", "User", all_doctypes=True, template=True)
content = read_csv_content(frappe.response.result)
content.append(["", "test_import_userrole@example.com", "Website Manager"])
importer.upload(content, overwrite=True)
@ -77,7 +77,7 @@ class TestDataImport(unittest.TestCase):
def test_import_with_children(self): #pylint: disable=R0201
if frappe.db.exists("Event", "EV00001"):
frappe.delete_doc("Event", "EV00001")
exporter.get_template("Event", all_doctypes="Yes", with_data="No")
exporter.export_data("Event", all_doctypes="Yes", template=True)
content = read_csv_content(frappe.response.result)
content.append([None] * len(content[-2]))
@ -93,7 +93,7 @@ class TestDataImport(unittest.TestCase):
if frappe.db.exists("Event", "EV00001"):
frappe.delete_doc("Event", "EV00001")
exporter.get_template("Event", all_doctypes="No", with_data="No", from_data_import="Yes", excel_format="Yes")
exporter.export_data("Event", all_doctypes=True, template=True, file_type="Excel")
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
content = read_xlsx_file_from_attached_file(fcontent=frappe.response.filecontent)
content.append(["", "EV00001", "_test", "Private", "05-11-2017 13:51:48", "0", "0", "", "1", "blue"])

File diff suppressed because it is too large Load diff

View file

@ -717,11 +717,7 @@ def validate_permissions(doctype, for_remove=False):
similar_because_of = ""
for p in permissions:
if p.role==d.role and p.permlevel==d.permlevel and p!=d:
if p.apply_user_permissions==d.apply_user_permissions:
has_similar = True
similar_because_of = _("Apply User Permissions")
break
elif p.if_owner==d.if_owner:
if p.if_owner==d.if_owner:
similar_because_of = _("If Owner")
has_similar = True
break
@ -765,9 +761,7 @@ def validate_permissions(doctype, for_remove=False):
d.set("import", 0)
d.set("export", 0)
for ptype, label in (
("set_user_permissions", _("Set User Permissions")),
("apply_user_permissions", _("Apply User Permissions"))):
for ptype, label in [["set_user_permissions", _("Set User Permissions")]]:
if d.get(ptype):
d.set(ptype, 0)
frappe.msgprint(_("{0} cannot be set for Single types").format(label))

View file

@ -5,14 +5,18 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
class DomainSettings(Document):
def set_active_domains(self, domains):
self.active_domains = []
active_domains = [d.domain for d in self.active_domains]
added = False
for d in domains:
self.append('active_domains', dict(domain=d))
self.save()
if not d in active_domains:
self.append('active_domains', dict(domain=d))
added = True
if added:
self.save()
def on_update(self):
for i, d in enumerate(self.active_domains):
@ -28,7 +32,7 @@ class DomainSettings(Document):
def restrict_roles_and_modules(self):
'''Disable all restricted roles and set `restrict_to_domain` property in Module Def'''
active_domains = frappe.get_active_domains()
all_domains = (frappe.get_hooks('domains') or {}).keys()
all_domains = list((frappe.get_hooks('domains') or {}))
def remove_role(role):
frappe.db.sql('delete from `tabHas Role` where role=%s', role)

View file

@ -173,7 +173,7 @@ class File(NestedSet):
if self.file_url.startswith("/files/"):
try:
with open(get_files_path(self.file_name.lstrip("/")), "r") as f:
with open(get_files_path(self.file_name.lstrip("/")), "rb") as f:
self.content_hash = get_content_hash(f.read())
except IOError:
frappe.msgprint(_("File {0} does not exist").format(self.file_url))
@ -454,4 +454,7 @@ def get_attached_images(doctype, names):
out[i.docname] = out.get(i.docname, [])
out[i.docname].append(i.file_url)
return out
return out
def on_doctype_update():
frappe.db.add_index("File", ["lft", "rgt"])

View file

@ -54,7 +54,7 @@ cur_frm.cscript.refresh = function(doc) {
frappe.ui.form.on('Report', {
refresh: function(frm) {
if(!frappe.boot.developer_mode && user != 'Administrator') {
if(!frappe.boot.developer_mode && frappe.session.user != 'Administrator') {
// make the document read-only
frm.set_read_only();
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -34,6 +34,9 @@ class TestTransactionLog(unittest.TestCase):
sha = hashlib.sha256()
sha.update(str(third_log.transaction_hash) + str(second_log.chaining_hash))
sha.update(
frappe.safe_encode(str(third_log.transaction_hash)) +
frappe.safe_encode(str(second_log.chaining_hash))
)
self.assertEqual(sha.hexdigest(), third_log.chaining_hash)

View file

@ -29,12 +29,19 @@ class TransactionLog(Document):
def hash_line(self):
sha = hashlib.sha256()
sha.update(str(self.row_index) + str(self.timestamp) + str(self.data))
sha.update(
frappe.safe_encode(str(self.row_index)) + \
frappe.safe_encode(str(self.timestamp)) + \
frappe.safe_encode(str(self.data))
)
return sha.hexdigest()
def hash_chain(self):
sha = hashlib.sha256()
sha.update(str(self.transaction_hash) + str(self.previous_hash))
sha.update(
frappe.safe_encode(str(self.transaction_hash)) + \
frappe.safe_encode(str(self.previous_hash))
)
return sha.hexdigest()

File diff suppressed because it is too large Load diff

View file

@ -68,6 +68,7 @@ class User(Document):
self.validate_user_email_inbox()
ask_pass_update()
self.validate_roles()
self.validate_user_image()
if self.language == "Loading...":
self.language = None
@ -81,6 +82,10 @@ class User(Document):
self.set('roles', [])
self.append_roles(*[role.role for role in role_profile.roles])
def validate_user_image(self):
if self.user_image and len(self.user_image) > 2000:
frappe.throw(_("Not a valid User Image."))
def on_update(self):
# clear new password
self.validate_user_limit()

View file

@ -2,8 +2,8 @@
// For license information, please see license.txt
frappe.ui.form.on('User Permission', {
setup: function(frm) {
frm.set_query("allow", function() {
setup: frm => {
frm.set_query("allow", () => {
return {
"filters": {
issingle: 0,
@ -13,9 +13,38 @@ frappe.ui.form.on('User Permission', {
});
},
refresh: function(frm) {
refresh: frm => {
frm.add_custom_button(__('View Permitted Documents'),
() => frappe.set_route('query-report', 'Permitted Documents For User',
{user: frm.doc.user}));
{ user: frm.doc.user }));
frm.trigger('set_help');
},
allow: frm => {
frm.trigger('set_help');
if(frm.doc.for_value) {
cur_frm.fields_dict.for_value.set_input(null);
}
},
set_help: frm => {
const help_wrapper = frm.fields_dict.linked_doctypes.$wrapper;
help_wrapper.empty();
if (frm.doc.allow) {
frappe.call({
method: "frappe.desk.form.linked_with.get_linked_doctypes",
args: {
doctype: frm.doc.allow
},
callback: (r) => {
const linked_doctypes = r.message;
if (linked_doctypes) {
$(frappe.render_template("user_permission_help", { linked_doctypes: linked_doctypes }))
.appendTo(help_wrapper);
}
}
});
}
}
});

View file

@ -1,188 +1,190 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"beta": 0,
"creation": "2017-07-17 14:25:27.881871",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"beta": 0,
"creation": "2017-07-17 14:25:27.881871",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Allow",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Allow",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "for_value",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "For Value",
"length": 0,
"no_copy": 0,
"options": "allow",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "for_value",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "For Value",
"length": 0,
"no_copy": 0,
"options": "allow",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "If you un-check this, you will have to apply manually for each Role + Document Type combination",
"fieldname": "apply_for_all_roles",
"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": "Apply for all Roles for this User",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "linked_doctypes",
"fieldtype": "HTML",
"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": "Linked Doctypes",
"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
}
],
"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": "2017-10-26 09:51:47.663104",
"modified_by": "Administrator",
"module": "Core",
"name": "User Permission",
"name_case": "",
"owner": "Administrator",
],
"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-03-29 16:07:43.789338",
"modified_by": "Administrator",
"module": "Core",
"name": "User Permission",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"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,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"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": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "user",
"track_changes": 1,
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "user",
"track_changes": 1,
"track_seen": 0
}

View file

@ -12,44 +12,6 @@ class UserPermission(Document):
def on_update(self):
frappe.cache().delete_value('user_permissions')
if self.apply_for_all_roles:
self.apply_user_permissions_to_all_roles()
def apply_user_permissions_to_all_roles(self):
# add apply user permissions for all roles that
# for this doctype
def show_progress(i, l):
if l > 2:
frappe.publish_realtime("progress",
dict(progress=[i, l], title=_('Updating...')),
user=frappe.session.user)
roles = frappe.get_roles(self.user)
linked = frappe.db.sql('''select distinct parent from tabDocField
where fieldtype="Link" and options=%s''', self.allow)
for i, link in enumerate(linked):
doctype = link[0]
for perm in get_valid_perms(doctype, self.user):
# if the role is applicable to the user
show_progress(i+1, len(linked))
if perm.role in roles:
if not perm.apply_user_permissions:
update_permission_property(doctype, perm.role, 0,
'apply_user_permissions', '1')
try:
user_permission_doctypes = json.loads(perm.user_permission_doctypes or '[]')
except ValueError:
user_permission_doctypes = []
if self.allow not in user_permission_doctypes:
user_permission_doctypes.append(self.allow)
update_permission_property(doctype, perm.role, 0,
'user_permission_doctypes', json.dumps(user_permission_doctypes), validate=False)
show_progress(len(linked), len(linked))
def on_trash(self): # pylint: disable=no-self-use
frappe.cache().delete_value('user_permissions')
@ -60,17 +22,13 @@ def get_user_permissions(user=None):
out = frappe.cache().hget("user_permissions", user)
if not out:
if out is None:
out = {}
try:
for perm in frappe.get_all('User Permission',
fields=['allow', 'for_value'], filters=dict(user=user)):
out.setdefault(perm.allow, []).append(perm.for_value)
# add profile match
if user not in out.get("User", []):
out.setdefault("User", []).append(user)
frappe.cache().hset("user_permissions", user, out)
except frappe.SQLError as e:
if e.args[0]==1146:

View file

@ -0,0 +1,8 @@
<div style="padding-top: 15px">
<h5>{%= __("Records for following doctypes will be filtered") %}</h5>
<ul class="row">
{% Object.keys(linked_doctypes).forEach(key => { %}
<li class={{ Object.keys(linked_doctypes).length >= 10 ? "col-md-4" : "" }}>{%= __(key) %}</li>
{% }) %}
</ul>
</div>

View file

@ -60,6 +60,7 @@ def get_unread_emails():
FROM `tabCommunication`
WHERE communication_type='Communication'
AND communication_medium="Email"
AND sent_or_received="Received"
AND email_status not in ("Spam", "Trash")
AND email_account in (
SELECT distinct email_account from `tabUser Email` WHERE parent=%(user)s

View file

@ -119,7 +119,7 @@ $.extend(frappe.desktop, {
frappe.desktop.open_module($(this));
});
} else {
frappe.desktop.wrapper.on("click", ".app-icon", function() {
frappe.desktop.wrapper.on("click", ".app-icon, .app-icon-svg", function() {
if ( !frappe.desktop.wiggling ) {
frappe.desktop.open_module($(this).parent());
}
@ -294,23 +294,31 @@ $.extend(frappe.desktop, {
var module_doctypes = frappe.boot.notification_info.module_doctypes[module.module_name];
var sum = 0;
if(module_doctypes) {
if(frappe.boot.notification_info.open_count_doctype) {
// sum all doctypes for a module
for (var j=0, k=module_doctypes.length; j < k; j++) {
var doctype = module_doctypes[j];
sum += (frappe.boot.notification_info.open_count_doctype[doctype] || 0);
}
if(module_doctypes && frappe.boot.notification_info.open_count_doctype) {
// sum all doctypes for a module
for (var j=0, k=module_doctypes.length; j < k; j++) {
var doctype = module_doctypes[j];
let count = (frappe.boot.notification_info.open_count_doctype[doctype] || 0);
count = typeof count == "string" ? parseInt(count) : count;
sum += count;
}
} else if(frappe.boot.notification_info.open_count_doctype
}
if(frappe.boot.notification_info.open_count_doctype
&& frappe.boot.notification_info.open_count_doctype[module.module_name]!=null) {
// notification count explicitly for doctype
sum = frappe.boot.notification_info.open_count_doctype[module.module_name];
let count = frappe.boot.notification_info.open_count_doctype[module.module_name] || 0;
count = typeof count == "string" ? parseInt(count) : count;
sum += count;
}
} else if(frappe.boot.notification_info.open_count_module
if(frappe.boot.notification_info.open_count_module
&& frappe.boot.notification_info.open_count_module[module.module_name]!=null) {
// notification count explicitly for module
sum = frappe.boot.notification_info.open_count_module[module.module_name];
let count = frappe.boot.notification_info.open_count_module[module.module_name] || 0;
count = typeof count == "string" ? parseInt(count) : count;
sum += count;
}
// if module found

View file

@ -1,4 +1,4 @@
frappe.pages['permission-manager'].on_page_load = function(wrapper) {
frappe.pages['permission-manager'].on_page_load = (wrapper) => {
var page = frappe.ui.make_app_page({
parent: wrapper,
title: __('Role Permissions Manager'),
@ -12,11 +12,11 @@ frappe.pages['permission-manager'].on_page_load = function(wrapper) {
$(frappe.render_template("permission_manager_help", {})).appendTo(page.main);
wrapper.permission_engine = new frappe.PermissionEngine(wrapper);
}
};
frappe.pages['permission-manager'].refresh = function(wrapper) {
wrapper.permission_engine.set_from_route();
}
};
frappe.PermissionEngine = Class.extend({
init: function(wrapper) {
@ -66,7 +66,9 @@ frappe.PermissionEngine = Class.extend({
var me = this;
if(!this.doctype_select) {
// selects not yet loaded, call again after a bit
setTimeout(function() { me.set_from_route(); }, 500);
setTimeout(() => {
me.set_from_route();
}, 500);
return;
}
if(frappe.get_route()[1]) {
@ -105,7 +107,9 @@ frappe.PermissionEngine = Class.extend({
args: {
doctype: me.get_doctype(),
},
callback: function() { me.refresh(); }
callback: function() {
me.refresh();
}
});
});
@ -188,7 +192,9 @@ frappe.PermissionEngine = Class.extend({
});
$.each(perm_list, function(i, d) {
if(d.parent==="DocType") { return; }
if(d.parent==="DocType") {
return;
}
if(!d.permlevel) d.permlevel = 0;
var row = $("<tr>").appendTo(me.table.find("tbody"));
me.add_cell(row, d, "parent");
@ -196,7 +202,7 @@ frappe.PermissionEngine = Class.extend({
me.set_show_users(role_cell, d.role);
if (d.permlevel===0) {
me.setup_user_permissions(d, role_cell);
// me.setup_user_permissions(d, role_cell);
me.setup_if_owner(d, role_cell);
}
@ -225,7 +231,7 @@ frappe.PermissionEngine = Class.extend({
.html(__(d[fieldname]));
},
add_check: function(cell, d, fieldname, label) {
add_check: (cell, d, fieldname, label, description="") => {
var me = this;
if(!label) label = toTitle(fieldname.replace(/_/g, " "));
@ -233,9 +239,14 @@ frappe.PermissionEngine = Class.extend({
return;
}
var checkbox = $("<div class='col-md-4'><div class='checkbox'>\
<label><input type='checkbox'>"+__(label)+"</input></label>"
+ (d.help || "") + "</div></div>").appendTo(cell)
var checkbox = $(
`<div class='col-md-4'>
<div class='checkbox'>
<label><input type='checkbox'>${__(label)}</input></label>
<p class='help-box small text-muted'>${__(description)}</p>
</div>
</div>`)
.appendTo(cell)
.attr("data-fieldname", fieldname);
checkbox.find("input")
@ -243,7 +254,7 @@ frappe.PermissionEngine = Class.extend({
.attr("data-ptype", fieldname)
.attr("data-role", d.role)
.attr("data-permlevel", d.permlevel)
.attr("data-doctype", d.parent)
.attr("data-doctype", d.parent);
checkbox.find("label")
.css("text-transform", "capitalize");
@ -251,38 +262,8 @@ frappe.PermissionEngine = Class.extend({
return checkbox;
},
setup_user_permissions: function(d, role_cell) {
var me = this;
d.help = `<ul class="user-permission-help small hidden"
style="margin-left: -10px;">
<li style="margin-top: 7px;"><a class="show-user-permission-doctypes">
${__("Select Document Types")}</a></li>
<li style="margin-top: 3px;"><a class="show-user-permissions">
${__("Show User Permissions")}</a></li>
</ul>`;
var checkbox = this.add_check(role_cell, d, "apply_user_permissions")
.removeClass("col-md-4")
.css({"margin-top": "15px"});
checkbox.find(".show-user-permission-doctypes").on("click", function() {
me.show_user_permission_doctypes(d);
});
var toggle_user_permissions = function() {
checkbox.find(".user-permission-help").toggleClass("hidden", !checkbox.find("input").prop("checked"));
};
toggle_user_permissions();
checkbox.find("input").on('click', function() {
toggle_user_permissions();
});
d.help = "";
},
setup_if_owner: function(d, role_cell) {
var checkbox = this.add_check(role_cell, d, "if_owner")
this.add_check(role_cell, d, "if_owner", "Only If Creator")
.removeClass("col-md-4")
.css({"margin-top": "15px"});
},
@ -306,14 +287,15 @@ frappe.PermissionEngine = Class.extend({
callback: function(r) {
r.message = $.map(r.message, function(p) {
return $.format('<a href="#Form/User/{0}">{1}</a>', [p, p]);
})
});
frappe.msgprint(__("Users with role {0}:", [__(role)])
+ "<br>" + r.message.join("<br>"));
}
})
});
return false;
})
});
},
add_delete_button: function(row, d) {
var me = this;
$("<button class='btn btn-default btn-sm'><i class='fa fa-remove'></i></button>")
@ -338,9 +320,10 @@ frappe.PermissionEngine = Class.extend({
me.refresh();
}
}
})
});
});
},
add_check_events: function() {
var me = this;
@ -357,7 +340,7 @@ frappe.PermissionEngine = Class.extend({
doctype: chk.attr("data-doctype"),
ptype: chk.attr("data-ptype"),
value: chk.prop("checked") ? 1 : 0
}
};
return frappe.call({
module: "frappe.core",
page: "permission_manager",
@ -371,9 +354,10 @@ frappe.PermissionEngine = Class.extend({
me.get_perm(args.role)[args.ptype]=args.value;
}
}
})
})
});
});
},
show_add_rule: function() {
var me = this;
$("<button class='btn btn-default btn-primary btn-sm'><i class='fa fa-plus'></i> "
@ -419,94 +403,13 @@ frappe.PermissionEngine = Class.extend({
me.refresh();
}
}
})
});
d.hide();
});
d.show();
});
},
show_user_permission_doctypes: function(d) {
var me = this;
if (!d.dialog) {
var fields = [];
for (var i=0, l=d.linked_doctypes.length; i<l; i++) {
fields.push({
fieldtype: "Check",
label: __("If {0} is permitted", ["<b>" + __(d.linked_doctypes[i]) + "</b>"]),
fieldname: d.linked_doctypes[i]
});
}
fields.push({
fieldtype: "Button",
label: __("Set"),
fieldname: "set_user_permission_doctypes"
})
var dialog = new frappe.ui.Dialog({
title: __('Apply Rule'),
fields: fields
});
var fields_to_check = d.user_permission_doctypes
? JSON.parse(d.user_permission_doctypes) : [];
for (var i=0, l=fields_to_check.length; i<l; i++) {
dialog.set_value(fields_to_check[i], 1);
}
var btn = dialog.get_input("set_user_permission_doctypes");
btn.on("click", function() {
var values = dialog.get_values();
var user_permission_doctypes = [];
$.each(values, function(key, val) {
if (val) {
user_permission_doctypes.push(key);
}
});
if (!user_permission_doctypes || !user_permission_doctypes.length ||
user_permission_doctypes.length === d.linked_doctypes.length) {
// if all checked
user_permission_doctypes = undefined;
} else {
user_permission_doctypes.sort();
user_permission_doctypes = JSON.stringify(user_permission_doctypes);
}
frappe.call({
module: "frappe.core",
page: "permission_manager",
method: "update",
args: {
doctype: d.parent,
role: d.role,
permlevel: d.permlevel,
ptype: "user_permission_doctypes",
value: user_permission_doctypes
},
callback: function(r) {
if(r.exc) {
frappe.msgprint(__("Did not set"));
} else {
var msg = frappe.msgprint(__("Saved!"));
setTimeout(function() { msg.hide(); }, 3000);
d.user_permission_doctypes = user_permission_doctypes;
dialog.hide();
if(r.message==='refresh') {
me.refresh();
}
}
}
});
});
d.dialog = dialog;
}
d.dialog.show();
},
make_reset_button: function() {
var me = this;
$('<button class="btn btn-default btn-sm" style="margin-left: 10px;">\
@ -516,12 +419,15 @@ frappe.PermissionEngine = Class.extend({
me.get_standard_permissions(function(data) {
me.reset_std_permissions(data);
});
})
});
},
get_perm: function(role) {
return $.map(this.perm_list, function(d) { if(d.role==role) return d; })[0];
return $.map(this.perm_list, function(d) {
if(d.role==role) return d;
})[0];
},
get_link_fields: function(doctype) {
return frappe.get_children("DocType", doctype, "fields",
{fieldtype:"Link", options:["not in", ["User", '[Select]']]});

View file

@ -57,7 +57,7 @@
</div>
<span class="indicator blue" style="margin-right: 20px;">
{{ __("Database Size:") }} {%= limits.space_usage.files_size %} MB
{{ __("Database Size:") }} {%= limits.space_usage.database_size %} MB
</span>
<span class="indicator purple" style="margin-right: 20px;">
{{ __("Files Size:") }} {%= limits.space_usage.files_size %} MB

View file

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

View file

@ -0,0 +1,23 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2018-03-15 18:37:48.783779",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2018-03-15 18:37:48.783779",
"modified_by": "Administrator",
"module": "Core",
"name": "Transaction Log Report",
"owner": "Administrator",
"ref_doctype": "Transaction Log",
"report_name": "Transaction Log Report",
"report_type": "Script Report",
"roles": [
{
"role": "Administrator"
}
]
}

View file

@ -0,0 +1,89 @@
# Copyright (c) 2013, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import hashlib
from frappe import _
def execute(filters=None):
columns, data = get_columns(filters), get_data(filters)
return columns, data
def get_data(filters=None):
logs = frappe.db.sql("SELECT * FROM `tabTransaction Log` order by creation desc ", as_dict=1)
result = []
for l in logs:
row_index = int(l.row_index)
if row_index > 1:
previous_hash = frappe.db.sql("SELECT chaining_hash FROM `tabTransaction Log` WHERE row_index = {0}".format(row_index - 1))
if not previous_hash:
integrity = False
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])
else:
result.append([_("First Transaction"), l.reference_doctype, l.document_name, l.owner, l.modified_by, l.timestamp])
return result
def check_data_integrity(chaining_hash, transaction_hash, registered_previous_hash, previous_hash):
if registered_previous_hash != previous_hash:
return False
calculated_chaining_hash = calculate_chain(transaction_hash, previous_hash)
if calculated_chaining_hash != chaining_hash:
return False
else:
return True
def calculate_chain(transaction_hash, previous_hash):
sha = hashlib.sha256()
sha.update(str(transaction_hash) + str(previous_hash))
return sha.hexdigest()
def get_columns(filters=None):
columns = [
{
"label": _("Chain Integrity"),
"fieldname": "chain_integrity",
"fieldtype": "Data",
"width": 150
},
{
"label": _("Reference Doctype"),
"fieldname": "reference_doctype",
"fieldtype": "Data",
"width": 150
},
{
"label": _("Reference Name"),
"fieldname": "reference_name",
"fieldtype": "Data",
"width": 150
},
{
"label": _("Owner"),
"fieldname": "owner",
"fieldtype": "Data",
"width": 100
},
{
"label": _("Modified By"),
"fieldname": "modified_by",
"fieldtype": "Data",
"width": 100
},
{
"label": _("Timestamp"),
"fieldname": "timestamp",
"fieldtype": "Data",
"width": 100
}
]
return columns

View file

@ -16,11 +16,16 @@ class CustomField(Document):
def set_fieldname(self):
if not self.fieldname:
if not self.label:
frappe.throw(_("Label is mandatory"))
label = self.label
if not label:
if self.fieldtype in ["Section Break", "Column Break"]:
label = self.fieldtype + "_" + str(self.idx)
else:
frappe.throw(_("Label is mandatory"))
# remove special characters from fieldname
self.fieldname = "".join(filter(lambda x: x.isdigit() or x.isalpha() or '_',
cstr(self.label).lower().replace(' ','_')))
cstr(label).replace(' ','_')))
# fieldnames should be lowercase
self.fieldname = self.fieldname.lower()
@ -122,7 +127,10 @@ def create_custom_fields(custom_fields):
for df in fields:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df["fieldname"]})
if not field:
create_custom_field(doctype, df)
try:
create_custom_field(doctype, df)
except frappe.exceptions.DuplicateEntryError:
pass
else:
custom_field = frappe.get_doc("Custom Field", field)
custom_field.update(df)
@ -132,4 +140,4 @@ def create_custom_fields(custom_fields):
@frappe.whitelist()
def add_custom_field(doctype, df):
df = json.loads(df)
return create_custom_field(doctype, df)
return create_custom_field(doctype, df)

View file

@ -0,0 +1,239 @@
from __future__ import unicode_literals
import frappe
from frappe.data_migration.doctype.data_migration_connector.connectors.base import BaseConnection
import googleapiclient.discovery
import google.oauth2.credentials
from googleapiclient.errors import HttpError
import time
from datetime import datetime
from frappe.utils import add_days
class CalendarConnector(BaseConnection):
def __init__(self, connector):
self.connector = connector
settings = frappe.get_doc("GCalendar Settings", None)
self.account = frappe.get_doc("GCalendar Account", connector.username)
self.credentials_dict = {
'token': self.account.get_password(fieldname='session_token', raise_exception=False),
'refresh_token': self.account.get_password(fieldname='refresh_token', raise_exception=False),
'token_uri': 'https://www.googleapis.com/oauth2/v4/token',
'client_id': settings.client_id,
'client_secret': settings.get_password(fieldname='client_secret', raise_exception=False),
'scopes':'https://www.googleapis.com/auth/calendar'
}
self.name_field = 'id'
self.credentials = google.oauth2.credentials.Credentials(**self.credentials_dict)
self.gcalendar = googleapiclient.discovery.build('calendar', 'v3', credentials=self.credentials)
self.check_remote_calendar()
def check_remote_calendar(self):
def _create_calendar():
timezone = frappe.db.get_value("System Settings", None, "time_zone")
calendar = {
'summary': self.account.calendar_name,
'timeZone': timezone
}
try:
created_calendar = self.gcalendar.calendars().insert(body=calendar).execute()
frappe.db.set_value("GCalendar Account", self.account.name, "gcalendar_id", created_calendar["id"])
except Exception:
frappe.log_error(frappe.get_traceback())
try:
if self.account.gcalendar_id is not None:
try:
self.gcalendar.calendars().get(calendarId=self.account.gcalendar_id).execute()
except Exception:
frappe.log_error(frappe.get_traceback())
else:
_create_calendar()
except HttpError as err:
if err.resp.status in [403, 500, 503]:
time.sleep(5)
elif err.resp.status in [404]:
_create_calendar()
else: raise
def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10):
return self.get_events(remote_objectname, filters, page_length)
def insert(self, doctype, doc):
if doctype == 'Events':
from frappe.desk.doctype.event.event import has_permission
d = frappe.get_doc("Event", doc["name"])
if has_permission(d, self.account.name):
if doc["start_datetime"] >= datetime.now():
try:
doctype = "Event"
e = self.insert_events(doctype, doc)
return e
except Exception:
frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")
def update(self, doctype, doc, migration_id):
if doctype == 'Events':
from frappe.desk.doctype.event.event import has_permission
d = frappe.get_doc("Event", doc["name"])
if has_permission(d, self.account.name):
if doc["start_datetime"] >= datetime.now() and migration_id is not None:
try:
doctype = "Event"
return self.update_events(doctype, doc, migration_id)
except Exception:
frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")
def delete(self, doctype, migration_id):
if doctype == 'Events':
try:
return self.delete_events(migration_id)
except Exception:
frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")
def get_events(self, remote_objectname, filters, page_length):
page_token = None
results = []
while True:
events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, singleEvents=False, showDeleted=True).execute()
for event in events['items']:
results.append(event)
page_token = events.get('nextPageToken')
if not page_token:
break
return list(results)
def insert_events(self, doctype, doc, migration_id=None):
event = {
'summary': doc.summary,
'description': doc.description
}
dates = self.return_dates(doc)
event.update(dates)
if migration_id:
event.update({"id": migration_id})
if doc.repeat_this_event != 0:
recurrence = self.return_recurrence(doctype, doc)
if not not recurrence:
event.update({"recurrence": ["RRULE:" + str(recurrence)]})
try:
remote_event = self.gcalendar.events().insert(calendarId=self.account.gcalendar_id, body=event).execute()
return {self.name_field: remote_event["id"]}
except Exception:
frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")
def update_events(self, doctype, doc, migration_id):
try:
event = self.gcalendar.events().get(calendarId=self.account.gcalendar_id, eventId=migration_id).execute()
event = {
'summary': doc.summary,
'description': doc.description
}
if doc.event_type == "Cancel":
event.update({"status": "cancelled"})
dates = self.return_dates(doc)
event.update(dates)
if doc.repeat_this_event != 0:
recurrence = self.return_recurrence(doctype, doc)
if recurrence:
event.update({"recurrence": ["RRULE:" + str(recurrence)]})
try:
updated_event = self.gcalendar.events().update(calendarId=self.account.gcalendar_id, eventId=migration_id, body=event).execute()
return {self.name_field: updated_event["id"]}
except Exception as e:
frappe.log_error(e, "GCalendar Synchronization Error")
except HttpError as err:
if err.resp.status in [404]:
self.insert_events(doctype, doc, migration_id)
else:
frappe.log_error(err.resp, "GCalendar Synchronization Error")
def delete_events(self, migration_id):
try:
self.gcalendar.events().delete(calendarId=self.account.gcalendar_id, eventId=migration_id).execute()
except HttpError as err:
if err.resp.status in [410]:
pass
def return_dates(self, doc):
timezone = frappe.db.get_value("System Settings", None, "time_zone")
if doc.end_datetime is None:
doc.end_datetime = doc.start_datetime
if doc.all_day == 1:
return {
'start': {
'date': doc.start_datetime.date().isoformat(),
'timeZone': timezone,
},
'end': {
'date': doc.start_datetime.date().isoformat(),
'timeZone': timezone,
}
}
else:
return {
'start': {
'dateTime': doc.start_datetime.isoformat(),
'timeZone': timezone,
},
'end': {
'dateTime': doc.end_datetime.isoformat(),
'timeZone': timezone,
}
}
def return_recurrence(self, doctype, doc):
e = frappe.get_doc(doctype, doc.name)
if e.repeat_till is not None:
end_date = datetime.combine(e.repeat_till, datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ')
else:
end_date = None
day = []
if e.repeat_on == "Every Day":
if e.monday is not None:
day.append("MO")
if e.tuesday is not None:
day.append("TU")
if e.wednesday is not None:
day.append("WE")
if e.thursday is not None:
day.append("TH")
if e.friday is not None:
day.append("FR")
if e.saturday is not None:
day.append("SA")
if e.sunday is not None:
day.append("SU")
day = "BYDAY=" + ",".join(str(d) for d in day)
frequency = "FREQ=DAILY"
elif e.repeat_on == "Every Week":
frequency = "FREQ=WEEKLY"
elif e.repeat_on == "Every Month":
frequency = "FREQ=MONTHLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1"
end_date = datetime.combine(add_days(e.repeat_till, 1), datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ')
elif e.repeat_on == "Every Year":
frequency = "FREQ=YEARLY"
else:
return None
wst = "WKST=SU"
elements = [frequency, end_date, wst, day]
return ";".join(str(e) for e in elements if e is not None and not not e)

View file

@ -10,7 +10,6 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model.document import Document
class DataMigrationPlan(Document):
def on_update(self):
# update custom fields in mappings
self.make_custom_fields_for_mappings()
@ -40,7 +39,8 @@ class DataMigrationPlan(Document):
'fieldtype': 'Data',
'hidden': 1,
'read_only': 1,
'unique': 1
'unique': 1,
'no_copy': 1
}
for m in self.mappings:

View file

@ -77,6 +77,7 @@ class DataMigrationRun(Document):
def get_last_modified_condition(self):
last_run_timestamp = frappe.db.get_value('Data Migration Run', dict(
data_migration_plan=self.data_migration_plan,
data_migration_connector=self.data_migration_connector,
name=('!=', self.name)
), 'modified')
if last_run_timestamp:

View file

@ -12,11 +12,13 @@ import frappe.defaults
import frappe.async
import re
import frappe.model.meta
from frappe.utils import now, get_datetime, cstr
from frappe.utils import now, get_datetime, cstr, cast_fieldtype
from frappe import _
from frappe.model.utils.link_count import flush_local_link_count
from frappe.model.utils import STANDARD_FIELD_CONVERSION_MAP
from frappe.utils.background_jobs import execute_job, get_queue
from frappe import as_unicode
import six
# imports - compatibility imports
from six import (
@ -34,6 +36,22 @@ from pymysql.constants import ER, FIELD_TYPE
from pymysql.converters import conversions
import pymysql
# Helpers
def _cast_result(doctype, result):
batch = [ ]
try:
for field, value in result:
df = frappe.get_meta(doctype).get_field(field)
if df:
value = cast_fieldtype(df.fieldtype, value)
batch.append(tuple([field, value]))
except frappe.exceptions.DoesNotExistError:
return result
return tuple(batch)
class Database:
"""
Open a database connection with the given parmeters, if use_default is True, use the
@ -81,10 +99,14 @@ class Database:
conversions.update({
FIELD_TYPE.NEWDECIMAL: float,
FIELD_TYPE.DATETIME: get_datetime,
TimeDelta: conversions[binary_type],
UnicodeWithAttrs: conversions[text_type]
})
if six.PY2:
conversions.update({
TimeDelta: conversions[binary_type]
})
if usessl:
self._conn = pymysql.connect(self.host, self.user or '', self.password or '',
charset='utf8mb4', use_unicode = True, ssl=self.ssl, conv = conversions, local_infile = self.local_infile)
@ -539,6 +561,7 @@ class Database:
from tabSingles where field in (%s) and doctype=%s""" \
% (', '.join(['%s'] * len(fields)), '%s'),
tuple(fields) + (doctype,), as_dict=False, debug=debug)
# r = _cast_result(doctype, r)
if as_dict:
if r:
@ -551,7 +574,7 @@ class Database:
else:
return r and [[i[1] for i in r]] or []
def get_singles_dict(self, doctype):
def get_singles_dict(self, doctype, debug = False):
"""Get Single DocType as dict.
:param doctype: DocType of the single object whose value is requested
@ -561,9 +584,16 @@ class Database:
# Get coulmn and value of the single doctype Accounts Settings
account_settings = frappe.db.get_singles_dict("Accounts Settings")
"""
result = self.sql("""
SELECT field, value
FROM `tabSingles`
WHERE doctype = %s
""", doctype)
# result = _cast_result(doctype, result)
return frappe._dict(self.sql("""select field, value from
tabSingles where doctype=%s""", doctype))
dict_ = frappe._dict(result)
return dict_
def get_all(self, *args, **kwargs):
return frappe.get_all(*args, **kwargs)
@ -584,7 +614,7 @@ class Database:
"""
value = self.value_cache.setdefault(doctype, {}).get(fieldname)
if value:
if value is not None:
return value
val = self.sql("""select value from
@ -686,7 +716,7 @@ class Database:
else:
# for singles
keys = to_update.keys()
keys = list(to_update)
self.sql('''
delete from tabSingles
where field in ({0}) and
@ -817,16 +847,26 @@ class Database:
except:
return None
def count(self, dt, filters=None, debug=False):
def count(self, dt, filters=None, debug=False, cache=False):
"""Returns `COUNT(*)` for given DocType and filters."""
if cache and not filters:
cache_count = frappe.cache().get_value('doctype:count:{}'.format(dt))
if cache_count is not None:
return cache_count
if filters:
conditions, filters = self.build_conditions(filters)
return frappe.db.sql("""select count(*)
count = frappe.db.sql("""select count(*)
from `tab%s` where %s""" % (dt, conditions), filters, debug=debug)[0][0]
return count
else:
return frappe.db.sql("""select count(*)
count = frappe.db.sql("""select count(*)
from `tab%s`""" % (dt,))[0][0]
if cache:
frappe.cache().set_value('doctype:count:{}'.format(dt), count, expires_in_sec = 86400)
return count
def get_creation_count(self, doctype, minutes):
"""Get count of records created in the last x minutes"""

View file

@ -4,12 +4,11 @@
from __future__ import unicode_literals
import frappe
from frappe.desk.notifications import clear_notifications
from frappe.cache_manager import clear_defaults_cache, common_default_keys
# Note: DefaultValue records are identified by parenttype
# __default, __global or 'User Permission'
common_keys = ["__default", "__global"]
def set_user_default(key, value, user=None, parenttype=None):
set_default(key, value, user or frappe.session.user, parenttype)
@ -154,10 +153,10 @@ def clear_default(key=None, value=None, parent=None, name=None, parenttype=None)
values.append(parenttype)
if parent:
clear_cache(parent)
clear_defaults_cache(parent)
else:
clear_cache("__default")
clear_cache("__global")
clear_defaults_cache("__default")
clear_defaults_cache("__global")
if not conditions:
raise Exception("[clear_default] No key specified.")
@ -194,15 +193,8 @@ def get_defaults_for(parent="__default"):
return defaults
def _clear_cache(parent):
if parent in common_keys:
if parent in common_default_keys:
frappe.clear_cache()
else:
clear_notifications(user=parent)
frappe.clear_cache(user=parent)
def clear_cache(user=None):
if user:
for p in ([user] + common_keys):
frappe.cache().hdel("defaults", p)
elif frappe.flags.in_install!="frappe":
frappe.cache().delete_key("defaults")

View file

@ -117,13 +117,13 @@ class TestEvent(unittest.TestCase):
ev.insert()
ev_list = get_events("2014-02-01", "2014-02-01", "Administrator", for_reminder=True)
self.assertTrue(list(filter(lambda e: e.name==ev.name, ev_list)))
self.assertTrue(bool(list(filter(lambda e: e.name==ev.name, ev_list))))
ev_list1 = get_events("2015-01-20", "2015-01-20", "Administrator", for_reminder=True)
self.assertFalse(list(filter(lambda e: e.name==ev.name, ev_list1)))
self.assertFalse(bool(list(filter(lambda e: e.name==ev.name, ev_list1))))
ev_list2 = get_events("2014-02-20", "2014-02-20", "Administrator", for_reminder=True)
self.assertFalse(list(filter(lambda e: e.name==ev.name, ev_list2)))
self.assertFalse(bool(list(filter(lambda e: e.name==ev.name, ev_list2))))
ev_list3 = get_events("2015-02-01", "2015-02-01", "Administrator", for_reminder=True)
self.assertTrue(list(filter(lambda e: e.name==ev.name, ev_list3)))
self.assertTrue(bool(list(filter(lambda e: e.name==ev.name, ev_list3))))

View file

@ -119,7 +119,7 @@ def _get_linked_doctypes(doctype):
if not dt in ret:
ret[dt] = {"get_parent": True}
for dt in list(ret.keys()):
for dt in list(ret):
try:
doctype_module = load_doctype_module(dt)
except ImportError:

View file

@ -7,7 +7,6 @@ from __future__ import unicode_literals
import frappe, os
from frappe.model.meta import Meta
from frappe.modules import scrub, get_module_path, load_doctype_module
from frappe.model.workflow import get_workflow_name
from frappe.utils import get_html_format
from frappe.translate import make_dict_from_messages, extract_messages_from_code
from frappe.model.utils import render_include
@ -122,7 +121,8 @@ class FormMeta(Meta):
if df.options:
search_fields = frappe.get_meta(df.options).search_fields
if search_fields:
df.search_fields = map(lambda sf: sf.strip(), search_fields.split(","))
search_fields = search_fields.split(",")
df.search_fields = [sf.strip() for sf in search_fields]
def add_linked_document_type(self):
for df in self.get("fields", {"fieldtype": "Link"}):
@ -142,7 +142,7 @@ class FormMeta(Meta):
def load_workflows(self):
# get active workflow
workflow_name = get_workflow_name(self.name)
workflow_name = self.get_workflow()
workflow_docs = []
if workflow_name and frappe.db.exists("Workflow", workflow_name):

View file

@ -14,7 +14,7 @@ def get_notifications():
config = get_notification_config()
groups = list(config.get("for_doctype").keys()) + list(config.get("for_module").keys())
groups = list(config.get("for_doctype")) + list(config.get("for_module"))
cache = frappe.cache()
notification_count = {}
@ -161,8 +161,8 @@ def clear_notifications(user=None):
return
config = get_notification_config()
for_doctype = list(config.get('for_doctype').keys()) if config.get('for_doctype') else []
for_module = list(config.get('for_module').keys()) if config.get('for_module') else []
for_doctype = list(config.get('for_doctype')) if config.get('for_doctype') else []
for_module = list(config.get('for_module')) if config.get('for_module') else []
groups = for_doctype + for_module
cache = frappe.cache()
@ -172,8 +172,11 @@ def clear_notifications(user=None):
else:
cache.delete_key("notification_count:" + name)
frappe.publish_realtime('clear_notifications')
def delete_notification_count_for(doctype):
frappe.cache().delete_key("notification_count:" + doctype)
frappe.publish_realtime('clear_notifications')
def clear_doctype_notifications(doc, method=None, *args, **kwargs):
config = get_notification_config()
@ -191,7 +194,7 @@ def get_notification_info_for_boot():
module_doctypes = {}
doctype_info = dict(frappe.db.sql("""select name, module from tabDocType"""))
for d in list(set(can_read + list(config.for_doctype.keys()))):
for d in list(set(can_read + list(config.for_doctype))):
if d in config.for_doctype:
conditions[d] = config.for_doctype[d]

View file

@ -149,8 +149,7 @@ frappe.activity.render_heatmap = function(page) {
method: "frappe.desk.page.activity.activity.get_heatmap_data",
callback: function(r) {
if(r.message) {
var heatmap = new Chart({
parent: ".heatmap",
var heatmap = new Chart(".heatmap", {
type: 'heatmap',
height: 100,
start: new Date(moment().subtract(1, 'year').toDate()),
@ -174,6 +173,7 @@ frappe.views.Activity = class Activity extends frappe.views.BaseList {
setup_defaults() {
super.setup_defaults();
this.page_title = __('Activity');
this.doctype = 'Communication';
this.method = 'frappe.desk.page.activity.activity.get_feed';
@ -187,6 +187,10 @@ frappe.views.Activity = class Activity extends frappe.views.BaseList {
}
setup_side_bar() {
}
get_args() {
return {
start: this.start,

View file

@ -264,7 +264,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
this.$working_state = this.get_message(
__("Setting up your system"),
__("Starting Frappé ...")).appendTo(this.parent);
__("Starting Frappe ...")).appendTo(this.parent);
this.attach_abort_button();

View file

@ -103,7 +103,7 @@ def run(report_name, filters=None, user=None):
if len(res) > 4:
data_to_be_printed = res[4]
if report.apply_user_permissions and result:
if result:
result = get_filtered_data(report.ref_doctype, columns, result, user)
if cint(report.add_total_row) and result:
@ -235,7 +235,7 @@ def add_total_row(result, columns, meta = None):
def get_filtered_data(ref_doctype, columns, data, user):
result = []
linked_doctypes = get_linked_doctypes(columns, data)
match_filters_per_doctype = get_user_match_filters(linked_doctypes, ref_doctype)
match_filters_per_doctype = get_user_match_filters(linked_doctypes, user=user)
shared = frappe.share.get_shared(ref_doctype, user)
columns_dict = get_columns_dict(columns)
@ -380,11 +380,11 @@ def get_columns_dict(columns):
return columns_dict
def get_user_match_filters(doctypes, ref_doctype):
def get_user_match_filters(doctypes, user):
match_filters = {}
for dt in doctypes:
filter_list = frappe.desk.reportview.build_match_conditions(dt, False)
filter_list = frappe.desk.reportview.build_match_conditions(dt, user, False)
if filter_list:
match_filters[dt] = filter_list

View file

@ -8,18 +8,16 @@ import json
import copy
@frappe.whitelist()
def get_data(doctypes, last_modified):
def get_data(doctypes, last_modified):
data_map = {}
for dump_report_map in frappe.get_hooks().dump_report_map:
data_map.update(frappe.get_attr(dump_report_map))
import datetime
out = {}
doctypes = json.loads(doctypes)
last_modified = json.loads(last_modified)
start = datetime.datetime.now()
for d in doctypes:
args = copy.deepcopy(data_map[d])
dt = d.find("[") != -1 and d[:d.find("[")] or d
@ -29,7 +27,7 @@ def get_data(doctypes, last_modified):
modified_table = "item."
else:
modified_table = ""
conditions = order_by = ""
table = args.get("from") or ("`tab%s`" % dt)
@ -39,30 +37,30 @@ def get_data(doctypes, last_modified):
args['conditions'].append(modified_table + "modified > '" + last_modified[d] + "'")
out[dt]["modified_names"] = frappe.db.sql_list("""select %sname from %s
where %smodified > %s""" % (modified_table, table, modified_table, "%s"), last_modified[d])
if args.get("force_index"):
conditions = " force index (%s) " % args["force_index"]
if args.get("conditions"):
conditions += " where " + " and ".join(args["conditions"])
if args.get("order_by"):
order_by = " order by " + args["order_by"]
out[dt]["data"] = [list(t) for t in frappe.db.sql("""select %s from %s %s %s""" \
% (",".join(args["columns"]), table, conditions, order_by))]
# last modified
modified_table = table
if "," in table:
modified_table = " ".join(table.split(",")[0].split(" ")[:-1])
tmp = frappe.db.sql("""select `modified`
tmp = frappe.db.sql("""select `modified`
from %s order by modified desc limit 1""" % modified_table)
out[dt]["last_modified"] = tmp and tmp[0][0] or ""
out[dt]["columns"] = map(lambda c: c.split(" as ")[-1], args["columns"])
out[dt]["columns"] = list(map(lambda c: c.split(" as ")[-1], args["columns"]))
if args.get("links"):
out[dt]["links"] = args["links"]
for d in out:
unused_links = []
# only compress full dumps (not partial)
@ -70,25 +68,28 @@ def get_data(doctypes, last_modified):
for link_key in out[d]["links"]:
link = out[d]["links"][link_key]
if link[0] in out and (link[0] not in last_modified):
# make a map of link ids
# to index
link_map = {}
doctype_data = out[link[0]]
col_idx = doctype_data["columns"].index(link[1])
for row_idx in range(len(doctype_data["data"])):
row = doctype_data["data"][row_idx]
link_map[row[col_idx]] = row_idx
for row in out[d]["data"]:
col_idx = out[d]["columns"].index(link_key)
# replace by id
if row[col_idx]:
row[col_idx] = link_map.get(row[col_idx])
columns = list(out[d]["columns"])
if link_key in columns:
col_idx = columns.index(link_key)
# replace by id
if row[col_idx]:
row[col_idx] = link_map.get(row[col_idx])
else:
unused_links.append(link_key)
for link in unused_links:
del out[d]["links"][link]
return out

View file

@ -78,7 +78,7 @@ def compress(data, args = {}):
if not data: return data
values = []
keys = data[0].keys()
keys = list(data[0])
for row in data:
new_row = []
for key in keys:
@ -341,8 +341,8 @@ def get_match_cond(doctype):
cond = DatabaseQuery(doctype).build_match_conditions()
return ((' and ' + cond) if cond else "").replace("%", "%%")
def build_match_conditions(doctype, as_condition=True):
match_conditions = DatabaseQuery(doctype).build_match_conditions(as_condition=as_condition)
def build_match_conditions(doctype, user=None, as_condition=True):
match_conditions = DatabaseQuery(doctype, user=user).build_match_conditions(as_condition=as_condition)
if as_condition:
return match_conditions.replace("%", "%%")
else:

View file

@ -91,7 +91,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
# In order_by, `idx` gets second priority, because it stores link count
from frappe.model.db_query import get_order_by
order_by_based_on_meta = get_order_by(doctype, meta)
order_by = "if(_relevance, _relevance, 99999), idx desc, {0}".format(order_by_based_on_meta)
order_by = "if(_relevance, _relevance, 99999), {0}, `tab{1}`.idx desc".format(order_by_based_on_meta, doctype)
values = frappe.get_list(doctype,
filters=filters, fields=formatted_fields,

View file

@ -1,25 +1,25 @@
# Frappé Framework
# Frappe Framework
### Tutorials, API documentation and Model Reference
Frappé is a full stack web application framework written in Python,
Frappe is a full stack web application framework written in Python,
Javascript, HTML/CSS with MySQL as the backend. It was built for ERPNext
but is pretty generic and can be used to build database driven apps.
The key differece in Frappé compared to other frameworks is that Frappé
is that meta-data is also treated as data and is used to build front-ends
very easily. Frappé comes with a full blown admin UI called the **Desk**
The key differece in Frappe compared to other frameworks is that in Frappe
meta-data is also treated as data and is used to build front-ends
very easily. Frappe comes with a full blown admin UI called the **Desk**
that handles forms, navigation, lists, menus, permissions, file attachment
and much more out of the box.
Frappé also has a plug-in architecture that can be used to build plugins
Frappe also has a plug-in architecture that can be used to build plugins
to ERPNext.
Frappé Framework was designed to build [ERPNext](https://erpnext.com), open source
Frappe Framework was designed to build [ERPNext](https://erpnext.com), an open source
ERP for managing small and medium sized businesses.
[Get started with the Tutorial](/docs/user/)
### Feedback
You're encouraged to help improve the quality of this documentation, by sending a pull request on the [GitHub Repository](https://github.com/frappe/erpnext). If you would like to have a discussion regarding the documentation, you can do so [at the forum](https://discuss.erpnext.com).
You're encouraged to help improve the quality of this documentation, by sending a pull request on the [GitHub Repository](https://github.com/frappe/erpnext). If you would like to have a discussion regarding the documentation, you can do so [at the forum](https://discuss.erpnext.com).

View file

@ -51,7 +51,7 @@ Basic Usage
* Add site
Frappé apps are run by frappe sites and you will have to create at least one
Frappe apps are run by frappe sites and you will have to create at least one
site. The new-site command allows you to do that.
bench new-site site1.local
@ -62,7 +62,7 @@ Basic Usage
bench start
To login to Frappé / ERPNext, open your browser and go to `localhost:8000`
To login to Frappe / ERPNext, open your browser and go to `localhost:8000`
The default user name is "Administrator" and password is what you set when you created the new site.

View file

@ -1,6 +1,6 @@
# Setting Limits for your Site
Frappé v7 has added support for setting limits and restrictions for your site.
Frappe v7 has added support for setting limits and restrictions for your site.
These restrictions are set in the `site_config.json` file inside the site's folder.
{

View file

@ -10,7 +10,7 @@ These steps are automated if you run `sudo bench setup production`
Supervisor
----------
Supervisor makes sure that the process that power the Frappé system keep running
Supervisor makes sure that the process that power the Frappe system keep running
and it restarts them if they happen to crash. You can generate the required
configuration for supervisor using the command `bench setup supervisor`. The
configuration will be available in `config/supervisor.conf` directory. You can

View file

@ -8,7 +8,7 @@ External services
* nginx (for production deployment)
* supervisor (for production deployment)
Frappé Processes
Frappe Processes
----------------
@ -21,12 +21,12 @@ Frappé Processes
* Redis Worker Processes
* The Celery worker processes execute background jobs in the Frappé system.
* The Celery worker processes execute background jobs in the Frappe system.
These processes are automatically started when `bench start` is run and
for production are configured in supervisor configuration.
* Scheduler Process
* The Scheduler process schedules enqeueing of scheduled jobs in the
Frappé system. This process is automatically started when `bench start` is
Frappe system. This process is automatically started when `bench start` is
run and for production are configured in supervisor configuration.

View file

@ -10,10 +10,10 @@ Example:
def get_data():
return {
"Frappé Apps": {
"Frappe Apps": {
"color": "orange",
"icon": "assets/frappe/images/frappe.svg",
"label": _("Frappé.io Portal"),
"label": _("Frappe.io Portal"),
"type": "module"
}
}

View file

@ -1,6 +1,6 @@
# Dialogs Types
Frappé provides a group of standard dialogs that are very useful while coding.
Frappe provides a group of standard dialogs that are very useful while coding.
## Alert Dialog

View file

@ -1,6 +1,6 @@
# Generating Documentation Website for your App
Frappé version 6.7 onwards includes a full-blown documentation generator so that you can easily create a website for your app that has both user docs and developers docs (auto-generated).
Frappe version 6.7 onwards includes a full-blown documentation generator so that you can easily create a website for your app that has both user docs and developers docs (auto-generated).
Version 8.7 onwards, these will be generated in a target app.

View file

@ -1,4 +1,4 @@
# How Enable Developer Mode In Frappé
# How Enable Developer Mode In Frappe
When you are in application design mode and you want the changes in your DocTypes, Reports etc to affect the app repository, you must be in **Developer Mode**.

View file

@ -2,7 +2,7 @@
Your custom app can automatically add **Custom Fields** to DocTypes outside of your app when it is installed to a new site.
To do this, add the new custom fields that your app requires, using the Frappé web application.
To do this, add the new custom fields that your app requires, using the Frappe web application.
In your `hooks.py` file, add `"Custom Fields"`

View file

@ -1,6 +1,6 @@
# How To Improve A Standard Control
Frappé has a couple of elegant and useful widgets, but some times we need to edit them to add small improvements. This small article will describe how to add new resources to the standard widgets.
Frappe has a couple of elegant and useful widgets, but some times we need to edit them to add small improvements. This small article will describe how to add new resources to the standard widgets.
Let me explain first our goal:

View file

@ -2,7 +2,7 @@
Sometimes you may not want a user request to be executed immediately but added to a queue that will be executed by a background worker. The advantage of doing this is that your web workers remain free to execute other requests and longer jobs do not eat up all of your resources.
From version 7, Frappé uses Python RQ to run background jobs.
From version 7, Frappe uses Python RQ to run background jobs.
To enqueue a job,

View file

@ -8,4 +8,4 @@ The data in Single DocType is stored in `tabSingles` (`doctype`, `field`, `value
#### Examples
In Frappé, Single types are **System Settings** and **Customize Form**
In Frappe, Single types are **System Settings** and **Customize Form**

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