Merge branch 'develop' into new-autorepeat
This commit is contained in:
commit
95945661af
577 changed files with 47274 additions and 48334 deletions
9
.github/ISSUE_TEMPLATE.md
vendored
9
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -1,9 +0,0 @@
|
|||
#### Expected Behaviour
|
||||
|
||||
#### Actual Behaviour
|
||||
|
||||
#### Steps to reproduce:
|
||||
|
||||
1.
|
||||
|
||||
Frappé version:
|
||||
36
.travis.yml
36
.travis.yml
|
|
@ -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
|
||||
|
|
|
|||
4
LICENSE
4
LICENSE
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
## Frappé framework includes these public works
|
||||
## Frappe framework includes these public works
|
||||
|
||||
### Javascript / CSS
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
360
frappe/build.js
360
frappe/build.js
|
|
@ -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
77
frappe/cache_manager.py
Normal 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)
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
0
frappe/chat/doctype/chat_token/__init__.py
Normal file
0
frappe/chat/doctype/chat_token/__init__.py
Normal file
8
frappe/chat/doctype/chat_token/chat_token.js
Normal file
8
frappe/chat/doctype/chat_token/chat_token.js
Normal 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) {
|
||||
|
||||
}
|
||||
});
|
||||
156
frappe/chat/doctype/chat_token/chat_token.json
Normal file
156
frappe/chat/doctype/chat_token/chat_token.json
Normal 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
|
||||
}
|
||||
10
frappe/chat/doctype/chat_token/chat_token.py
Normal file
10
frappe/chat/doctype/chat_token/chat_token.py
Normal 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
|
||||
23
frappe/chat/doctype/chat_token/test_chat_token.js
Normal file
23
frappe/chat/doctype/chat_token/test_chat_token.js
Normal 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()
|
||||
]);
|
||||
|
||||
});
|
||||
10
frappe/chat/doctype/chat_token/test_chat_token.py
Normal file
10
frappe/chat/doctype/chat_token/test_chat_token.py
Normal 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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
41
frappe/chat/website/__init__.py
Normal file
41
frappe/chat/website/__init__.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
0
frappe/core/doctype/data_export/__init__.py
Normal file
0
frappe/core/doctype/data_export/__init__.py
Normal file
137
frappe/core/doctype/data_export/data_export.js
Normal file
137
frappe/core/doctype/data_export/data_export.js
Normal 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);
|
||||
250
frappe/core/doctype/data_export/data_export.json
Normal file
250
frappe/core/doctype/data_export/data_export.json
Normal 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
|
||||
}
|
||||
9
frappe/core/doctype/data_export/data_export.py
Normal file
9
frappe/core/doctype/data_export/data_export.py
Normal 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
|
||||
339
frappe/core/doctype/data_export/exporter.py
Normal file
339
frappe/core/doctype/data_export/exporter.py
Normal 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'
|
||||
|
||||
|
|
@ -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."));
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]']]});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
0
frappe/core/report/transaction_log_report/__init__.py
Normal file
0
frappe/core/report/transaction_log_report/__init__.py
Normal 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"] = {
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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))))
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue