Merge branch 'develop' into file-api
This commit is contained in:
commit
6e84bbe80d
272 changed files with 39962 additions and 33149 deletions
53
.travis.yml
53
.travis.yml
|
|
@ -1,44 +1,41 @@
|
|||
language: python
|
||||
dist: trusty
|
||||
sudo: required
|
||||
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.6"
|
||||
- 2.7
|
||||
- 3.6
|
||||
|
||||
env:
|
||||
- DB=mariadb
|
||||
- DB=postgres
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
||||
|
||||
addons:
|
||||
postgresql: "9.5"
|
||||
hosts:
|
||||
- test_site
|
||||
- test_site_postgres
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: 2.7
|
||||
env: DB=postgres
|
||||
fast_finish: true
|
||||
|
||||
install:
|
||||
- sudo rm /etc/apt/sources.list.d/mongodb*.list
|
||||
- sudo rm /etc/apt/sources.list.d/docker.list
|
||||
- sudo apt-get install hhvm && rm -rf /home/travis/.kiex/
|
||||
- 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/
|
||||
- $TRAVIS_BUILD_DIR/.travis/install.sh
|
||||
|
||||
before_script:
|
||||
- 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
|
||||
- bench setup-help
|
||||
- bench setup-global-help --mariadb_root_password travis
|
||||
- bench scheduler disable
|
||||
- sed -i 's/9000/9001/g' sites/common_site_config.json
|
||||
- bench start &
|
||||
- sleep 10
|
||||
|
||||
script:
|
||||
- bench run-tests
|
||||
- $TRAVIS_BUILD_DIR/.travis/run-tests.sh
|
||||
|
||||
after_script:
|
||||
- coveralls -b apps/frappe -d ../../sites/.coverage
|
||||
22
.travis/install.sh
Executable file
22
.travis/install.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
sudo rm /etc/apt/sources.list.d/mongodb*.list
|
||||
sudo rm /etc/apt/sources.list.d/docker.list
|
||||
sudo apt-get install hhvm && rm -rf /home/travis/.kiex/
|
||||
sudo apt-get purge -y mysql-common mysql-server mysql-client
|
||||
source ~/.nvm/nvm.sh
|
||||
nvm install v8.10.0
|
||||
|
||||
pip install python-coveralls
|
||||
|
||||
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/
|
||||
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site_postgres ~/frappe-bench/sites/
|
||||
22
.travis/run-tests.sh
Executable file
22
.travis/run-tests.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [[ $DB == 'mariadb' ]]; then
|
||||
mysql -u root -ptravis -e 'create database test_frappe'
|
||||
mysql -u root -ptravis -e "USE mysql; CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'; FLUSH PRIVILEGES; "
|
||||
mysql -u root -ptravis -e "USE mysql; GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';"
|
||||
bench --site test_site reinstall --yes
|
||||
bench --site test_site setup-help
|
||||
bench setup-global-help --root_password travis
|
||||
bench --site test_site scheduler disable
|
||||
bench --site test_site run-tests --coverage
|
||||
elif [[ $DB == 'postgres' ]]; then
|
||||
psql -c "CREATE DATABASE test_frappe;" -U postgres
|
||||
psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe';" -U postgres
|
||||
bench --site test_site_postgres reinstall --yes
|
||||
bench --site test_site_postgres setup-help
|
||||
bench setup-global-help --db_type=postgres --root_password travis
|
||||
bench --site test_site_postgres scheduler disable
|
||||
bench --site test_site_postgres run-tests --coverage
|
||||
fi
|
||||
|
|
@ -20,8 +20,16 @@
|
|||
<a href='https://frappe.io/docs'>
|
||||
<img src='https://img.shields.io/badge/docs-📖-7575FF.svg?style=flat-square'/>
|
||||
</a>
|
||||
<a href='https://www.codetriage.com/frappe/frappe'>
|
||||
<img src='https://www.codetriage.com/frappe/frappe/badges/users.svg'>
|
||||
</a>
|
||||
<a href='https://coveralls.io/github/frappe/frappe?branch=develop'>
|
||||
<img src='https://coveralls.io/repos/github/frappe/frappe/badge.svg?branch=develop'>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com)
|
||||
|
||||
### Table of Contents
|
||||
|
|
|
|||
1
bandit.yml
Normal file
1
bandit.yml
Normal file
|
|
@ -0,0 +1 @@
|
|||
skips: ['B605', 'B404', 'B603', 'B607']
|
||||
|
|
@ -17,7 +17,7 @@ from faker import Faker
|
|||
from .exceptions import *
|
||||
from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader)
|
||||
|
||||
__version__ = '10.1.46'
|
||||
__version__ = '10.1.48'
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
|
|
@ -168,17 +168,17 @@ def connect(site=None, db_name=None):
|
|||
|
||||
:param site: If site is given, calls `frappe.init`.
|
||||
:param db_name: Optional. Will use from `site_config.json`."""
|
||||
from frappe.database import Database
|
||||
from frappe.database import get_db
|
||||
if site:
|
||||
init(site)
|
||||
|
||||
local.db = Database(user=db_name or local.conf.db_name)
|
||||
local.db = get_db(user=db_name or local.conf.db_name)
|
||||
set_user("Administrator")
|
||||
|
||||
def connect_read_only():
|
||||
from frappe.database import Database
|
||||
from frappe.database import get_db
|
||||
|
||||
local.read_only_db = Database(local.conf.slave_host, local.conf.slave_db_name,
|
||||
local.read_only_db = get_db(local.conf.slave_host, local.conf.slave_db_name,
|
||||
local.conf.slave_db_password)
|
||||
|
||||
# swap db connections
|
||||
|
|
@ -259,7 +259,7 @@ def errprint(msg):
|
|||
:param msg: Message."""
|
||||
msg = as_unicode(msg)
|
||||
if not request or (not "cmd" in local.form_dict) or conf.developer_mode:
|
||||
print(msg.encode('utf-8'))
|
||||
print(msg)
|
||||
|
||||
error_log.append(msg)
|
||||
|
||||
|
|
@ -589,7 +589,7 @@ def has_website_permission(doc=None, ptype='read', user=None, verbose=False, doc
|
|||
|
||||
# check permission in controller
|
||||
if hasattr(doc, 'has_website_permission'):
|
||||
return doc.has_website_permission(doc, ptype, user, verbose=verbose)
|
||||
return doc.has_website_permission(ptype, user, verbose=verbose)
|
||||
|
||||
hooks = (get_hooks("has_website_permission") or {}).get(doctype, [])
|
||||
if hooks:
|
||||
|
|
|
|||
|
|
@ -25,12 +25,6 @@ from frappe.utils.error import make_error_snapshot
|
|||
from frappe.core.doctype.communication.comment import update_comments_in_parent_after_request
|
||||
from frappe import _
|
||||
|
||||
# imports - third-party imports
|
||||
import pymysql
|
||||
from pymysql.constants import ER
|
||||
|
||||
# imports - module imports
|
||||
|
||||
local_manager = LocalManager([frappe.local])
|
||||
|
||||
_site = None
|
||||
|
|
@ -148,8 +142,8 @@ def handle_exception(e):
|
|||
response = frappe.utils.response.report_error(http_status_code)
|
||||
|
||||
elif (http_status_code==500
|
||||
and isinstance(e, pymysql.InternalError)
|
||||
and e.args[0] in (ER.LOCK_WAIT_TIMEOUT, ER.LOCK_DEADLOCK)):
|
||||
and (frappe.db and isinstance(e, frappe.db.InternalError))
|
||||
and (frappe.db and (frappe.db.is_deadlocked(e) or frappe.db.is_timedout(e)))):
|
||||
http_status_code = 508
|
||||
|
||||
elif http_status_code==401:
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class HTTPRequest:
|
|||
|
||||
def connect(self, ac_name = None):
|
||||
"""connect to db, from ac_name or db_name"""
|
||||
frappe.local.db = frappe.database.Database(user = self.get_db_name(), \
|
||||
frappe.local.db = frappe.database.get_db(user = self.get_db_name(), \
|
||||
password = getattr(conf, 'db_password', ''))
|
||||
|
||||
class LoginManager:
|
||||
|
|
|
|||
|
|
@ -128,19 +128,19 @@ def get_user_pages_or_reports(parent):
|
|||
|
||||
standard_roles = frappe.db.sql("""
|
||||
select distinct
|
||||
tab{parent}.name,
|
||||
tab{parent}.modified,
|
||||
`tab{parent}`.name as name,
|
||||
`tab{parent}`.modified,
|
||||
{column}
|
||||
from `tabHas Role`, `tab{parent}`
|
||||
where
|
||||
`tabHas Role`.role in ({roles})
|
||||
and `tabHas Role`.parent = `tab{parent}`.name
|
||||
and tab{parent}.name not in (
|
||||
and `tab{parent}`.`name` not in (
|
||||
select `tabCustom Role`.{field} from `tabCustom Role`
|
||||
where `tabCustom Role`.{field} is not null)
|
||||
{condition}
|
||||
""".format(parent=parent, column=column, roles = ', '.join(['%s']*len(roles)),
|
||||
field=parent.lower(), condition="and tabReport.disabled=0" if parent == "Report" else ""),
|
||||
field=parent.lower(), condition="and `tabReport`.disabled=0" if parent == "Report" else ""),
|
||||
roles, as_dict=True)
|
||||
|
||||
for p in standard_roles:
|
||||
|
|
@ -157,7 +157,7 @@ def get_user_pages_or_reports(parent):
|
|||
from `tab{parent}`
|
||||
where
|
||||
(select count(*) from `tabHas Role`
|
||||
where `tabHas Role`.parent=tab{parent}.name) = 0
|
||||
where `tabHas Role`.parent=`tab{parent}`.`name`) = 0
|
||||
""".format(parent=parent, column=column), as_dict=1)
|
||||
|
||||
for p in pages_with_no_roles:
|
||||
|
|
@ -173,7 +173,7 @@ def get_user_pages_or_reports(parent):
|
|||
def get_column(doctype):
|
||||
column = "`tabPage`.title as title"
|
||||
if doctype == "Report":
|
||||
column = "`tabReport`.name as name, `tabReport`.name as title, `tabReport`.ref_doctype, `tabReport`.report_type"
|
||||
column = "`tabReport`.`name` as title, `tabReport`.ref_doctype, `tabReport`.report_type"
|
||||
|
||||
return column
|
||||
|
||||
|
|
@ -193,9 +193,9 @@ def load_translations(bootinfo):
|
|||
|
||||
def get_fullnames():
|
||||
"""map of user fullnames"""
|
||||
ret = frappe.db.sql("""select name, full_name as fullname,
|
||||
user_image as image, gender, email, username
|
||||
from tabUser where enabled=1 and user_type!="Website User" """, as_dict=1)
|
||||
ret = frappe.db.sql("""select `name`, full_name as fullname,
|
||||
user_image as image, gender, email, username
|
||||
from tabUser where enabled=1 and user_type!='Website User'""", as_dict=1)
|
||||
|
||||
d = {}
|
||||
for r in ret:
|
||||
|
|
@ -245,10 +245,10 @@ def load_print_css(bootinfo, print_settings):
|
|||
bootinfo.print_css = frappe.www.printview.get_print_style(print_settings.print_style or "Modern", for_legacy=True)
|
||||
|
||||
def get_unseen_notes():
|
||||
return frappe.db.sql('''select name, title, content, notify_on_every_login from tabNote where notify_on_login=1
|
||||
return frappe.db.sql('''select `name`, title, content, notify_on_every_login from `tabNote` where notify_on_login=1
|
||||
and expire_notification_on > %s and %s not in
|
||||
(select user from `tabNote Seen By` nsb
|
||||
where nsb.parent=tabNote.name)''', (frappe.utils.now(), frappe.session.user), as_dict=True)
|
||||
where nsb.parent=`tabNote`.name)''', (frappe.utils.now(), frappe.session.user), as_dict=True)
|
||||
|
||||
def get_gsuite_status():
|
||||
return (frappe.get_value('Gsuite Settings', None, 'enable') == '1')
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ def clear_doctype_cache(doctype=None):
|
|||
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])
|
||||
|
||||
for dt in frappe.db.get_all('DocField', 'parent', dict(fieldtype='Table', options=doctype)):
|
||||
clear_single(dt.parent)
|
||||
|
||||
# clear all notifications
|
||||
delete_notification_count_for(doctype)
|
||||
|
|
|
|||
|
|
@ -79,12 +79,12 @@ def create(user, exists_ok = False, fields = None):
|
|||
result = frappe.db.sql("""
|
||||
SELECT *
|
||||
FROM `tabChat Profile`
|
||||
WHERE user = "{user}"
|
||||
WHERE `user` = '{user}'
|
||||
""".format(user = user))
|
||||
|
||||
if result:
|
||||
if not exists_ok:
|
||||
frappe.throw(_('Chat Profile for User {user} exists.'.format(user = user)))
|
||||
frappe.throw(_('Chat Profile for User {0} exists.').format(user))
|
||||
else:
|
||||
dprof = frappe.new_doc('Chat Profile')
|
||||
dprof.user = user
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def is_direct(owner, other, bidirectional = False):
|
|||
exists = len(get_room(owner, other)) == 1
|
||||
if bidirectional:
|
||||
exists = exists or len(get_room(other, owner)) == 1
|
||||
|
||||
|
||||
return exists
|
||||
|
||||
def get_chat_room_user_set(users, filter_ = None):
|
||||
|
|
@ -56,19 +56,17 @@ class ChatRoom(Document):
|
|||
|
||||
if self.type == "Direct":
|
||||
if len(self.users) != 1:
|
||||
frappe.throw(_('{type} room must have atmost one user.'.format(type = self.type)))
|
||||
frappe.throw(_('{0} room must have atmost one user.').format(self.type))
|
||||
|
||||
other = squashify(self.users)
|
||||
|
||||
if self.is_new():
|
||||
if is_direct(self.owner, other.user, bidirectional = True):
|
||||
frappe.throw(_('Direct room with {other} already exists.'.format(
|
||||
other = other.user
|
||||
)))
|
||||
frappe.throw(_('Direct room with {0} already exists.').format(other.user))
|
||||
|
||||
if self.type == "Group" and not self.room_name:
|
||||
frappe.throw(_('Group name cannot be empty.'))
|
||||
|
||||
|
||||
def before_save(self):
|
||||
if not self.is_new():
|
||||
self.get_doc_before_save()
|
||||
|
|
@ -83,12 +81,12 @@ class ChatRoom(Document):
|
|||
update = { }
|
||||
for changed in diff.changed:
|
||||
field, old, new = changed
|
||||
|
||||
|
||||
if field == 'last_message':
|
||||
new = chat_message.get(new)
|
||||
|
||||
update.update({ field: new })
|
||||
|
||||
|
||||
if diff.added or diff.removed:
|
||||
update.update(dict(users = [u.user for u in self.users]))
|
||||
|
||||
|
|
@ -121,7 +119,7 @@ def get(user, rooms = None, fields = None, filters = None):
|
|||
|
||||
default = ['name', 'type', 'room_name', 'creation', 'owner', 'avatar']
|
||||
handle = ['users', 'last_message']
|
||||
|
||||
|
||||
param = [f for f in fields if f not in handle]
|
||||
|
||||
rooms = frappe.get_all('Chat Room',
|
||||
|
|
@ -151,7 +149,7 @@ def get(user, rooms = None, fields = None, filters = None):
|
|||
rooms[i]['last_message'] = None
|
||||
|
||||
rooms = squashify(dictify(rooms))
|
||||
|
||||
|
||||
return rooms
|
||||
|
||||
@frappe.whitelist(allow_guest = True)
|
||||
|
|
@ -177,7 +175,7 @@ def create(kind, owner, users = None, name = None):
|
|||
room.type = kind
|
||||
room.owner = owner
|
||||
room.room_name = name
|
||||
|
||||
|
||||
dusers = [ ]
|
||||
|
||||
if kind != 'Visitor':
|
||||
|
|
@ -198,7 +196,7 @@ def create(kind, owner, users = None, name = None):
|
|||
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)
|
||||
|
|
@ -218,5 +216,5 @@ def history(room, user, fields = None, limit = 10, start = None, end = None):
|
|||
|
||||
mess = chat_message.history(room, limit = limit, start = start, end = end)
|
||||
mess = squashify(mess)
|
||||
|
||||
|
||||
return dictify(mess)
|
||||
|
|
@ -97,7 +97,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
|
|||
@frappe.whitelist()
|
||||
def get_single_value(doctype, field):
|
||||
if not frappe.has_permission(doctype):
|
||||
frappe.throw(_("No permission for {doctype}".format(doctype = doctype)), frappe.PermissionError)
|
||||
frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError)
|
||||
value = frappe.db.get_single_value(doctype, field)
|
||||
return value
|
||||
|
||||
|
|
|
|||
|
|
@ -10,15 +10,10 @@ from frappe.installer import update_site_config
|
|||
from frappe.utils import touch_file, get_site_path
|
||||
from six import text_type
|
||||
|
||||
# imports - third-party imports
|
||||
from pymysql.constants import ER
|
||||
|
||||
# imports - module imports
|
||||
from frappe.exceptions import SQLError
|
||||
|
||||
@click.command('new-site')
|
||||
@click.argument('site')
|
||||
@click.option('--db-name', help='Database name')
|
||||
@click.option('--db-type', default='mariadb', type=click.Choice(['mariadb', 'postgres']), help='Optional "postgres" or "mariadb". Default is "mariadb"')
|
||||
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
|
||||
@click.option('--mariadb-root-password', help='Root password for MariaDB')
|
||||
@click.option('--admin-password', help='Administrator password for new site', default=None)
|
||||
|
|
@ -26,22 +21,27 @@ from frappe.exceptions import SQLError
|
|||
@click.option('--force', help='Force restore if site/database already exists', is_flag=True, default=False)
|
||||
@click.option('--source_sql', help='Initiate database with a SQL file')
|
||||
@click.option('--install-app', multiple=True, help='Install app after installation')
|
||||
def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None, verbose=False, install_apps=None, source_sql=None, force=None, install_app=None, db_name=None):
|
||||
def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None,
|
||||
verbose=False, install_apps=None, source_sql=None, force=None, install_app=None,
|
||||
db_name=None, db_type=None):
|
||||
"Create a new site"
|
||||
frappe.init(site=site, new_site=True)
|
||||
|
||||
_new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password,
|
||||
verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force)
|
||||
_new_site(db_name, site, mariadb_root_username=mariadb_root_username,
|
||||
mariadb_root_password=mariadb_root_password, admin_password=admin_password,
|
||||
verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force,
|
||||
db_type = db_type)
|
||||
|
||||
if len(frappe.utils.get_sites()) == 1:
|
||||
use(site)
|
||||
|
||||
def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None,
|
||||
verbose=False, install_apps=None, source_sql=None,force=False, reinstall=False):
|
||||
def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None,
|
||||
admin_password=None, verbose=False, install_apps=None, source_sql=None,force=False,
|
||||
reinstall=False, db_type=None):
|
||||
"""Install a new Frappe site"""
|
||||
|
||||
if not db_name:
|
||||
db_name = hashlib.sha1(site.encode()).hexdigest()[:16]
|
||||
db_name = '_' + hashlib.sha1(site.encode()).hexdigest()[:16]
|
||||
|
||||
from frappe.installer import install_db, make_site_dirs
|
||||
from frappe.installer import install_app as _install_app
|
||||
|
|
@ -61,8 +61,9 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
|
|||
try:
|
||||
installing = touch_file(get_site_path('locks', 'installing.lock'))
|
||||
|
||||
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name,
|
||||
admin_password=admin_password, verbose=verbose, source_sql=source_sql,force=force, reinstall=reinstall)
|
||||
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password,
|
||||
db_name=db_name, admin_password=admin_password, verbose=verbose,
|
||||
source_sql=source_sql,force=force, reinstall=reinstall, db_type=db_type)
|
||||
|
||||
apps_to_install = ['frappe'] + (frappe.conf.get("install_apps") or []) + (list(install_apps) or [])
|
||||
for app in apps_to_install:
|
||||
|
|
@ -347,8 +348,7 @@ def drop_site(site, root_login='root', root_password=None, archived_sites_path=N
|
|||
|
||||
def _drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False):
|
||||
"Remove site from database and filesystem"
|
||||
from frappe.installer import get_root_connection
|
||||
from frappe.model.db_schema import DbManager
|
||||
from frappe.database import drop_user_and_database
|
||||
from frappe.utils.backups import scheduled_backup
|
||||
|
||||
frappe.init(site=site)
|
||||
|
|
@ -356,25 +356,20 @@ def _drop_site(site, root_login='root', root_password=None, archived_sites_path=
|
|||
|
||||
try:
|
||||
scheduled_backup(ignore_files=False, force=True)
|
||||
except SQLError as err:
|
||||
if err[0] == ER.NO_SUCH_TABLE:
|
||||
if force:
|
||||
pass
|
||||
else:
|
||||
click.echo("="*80)
|
||||
click.echo("Error: The operation has stopped because backup of {s}'s database failed.".format(s=site))
|
||||
click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n"))
|
||||
click.echo("Fix the issue and try again.")
|
||||
click.echo(
|
||||
"Hint: Use 'bench drop-site {s} --force' to force the removal of {s}".format(sep="\n", tab="\t", s=site)
|
||||
)
|
||||
sys.exit(1)
|
||||
except Exception as err:
|
||||
if force:
|
||||
pass
|
||||
else:
|
||||
click.echo("="*80)
|
||||
click.echo("Error: The operation has stopped because backup of {s}'s database failed.".format(s=site))
|
||||
click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n"))
|
||||
click.echo("Fix the issue and try again.")
|
||||
click.echo(
|
||||
"Hint: Use 'bench drop-site {s} --force' to force the removal of {s}".format(sep="\n", tab="\t", s=site)
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
db_name = frappe.local.conf.db_name
|
||||
frappe.local.db = get_root_connection(root_login, root_password)
|
||||
dbman = DbManager(frappe.local.db)
|
||||
dbman.delete_user(db_name)
|
||||
dbman.drop_database(db_name)
|
||||
drop_user_and_database(frappe.conf.db_name, root_login, root_password)
|
||||
|
||||
if not archived_sites_path:
|
||||
archived_sites_path = os.path.join(frappe.get_app_path('frappe'), '..', '..', '..', 'archived_sites')
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ import json, os, sys, subprocess
|
|||
from distutils.spawn import find_executable
|
||||
import frappe
|
||||
from frappe.commands import pass_context, get_site
|
||||
from frappe.utils import update_progress_bar
|
||||
from frappe.utils import update_progress_bar, get_bench_path
|
||||
from frappe.utils.response import json_handler
|
||||
from coverage import Coverage
|
||||
|
||||
@click.command('build')
|
||||
@click.option('--app', help='Build assets for app')
|
||||
|
|
@ -315,6 +316,18 @@ def mariadb(context):
|
|||
'--pager=less -SFX',
|
||||
"-A"])
|
||||
|
||||
@click.command('postgres')
|
||||
@pass_context
|
||||
def postgres(context):
|
||||
"""
|
||||
Enter into postgres console for a given site.
|
||||
"""
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
# This is assuming you're within the bench instance.
|
||||
psql = find_executable('psql')
|
||||
subprocess.run([ psql, '-d', frappe.conf.db_name])
|
||||
|
||||
@click.command('jupyter')
|
||||
@pass_context
|
||||
def jupyter(context):
|
||||
|
|
@ -374,10 +387,16 @@ def console(context):
|
|||
@click.option('--ui-tests', is_flag=True, default=False, help="Run UI Tests")
|
||||
@click.option('--module', help="Run tests in a module")
|
||||
@click.option('--profile', is_flag=True, default=False)
|
||||
@click.option('--coverage', is_flag=True, default=False)
|
||||
@click.option('--skip-test-records', is_flag=True, default=False, help="Don't create test records")
|
||||
@click.option('--skip-before-tests', is_flag=True, default=False, help="Don't run before tests hook")
|
||||
@click.option('--junit-xml-output', help="Destination file path for junit xml report")
|
||||
@click.option('--failfast', is_flag=True, default=False)
|
||||
@pass_context
|
||||
def run_tests(context, app=None, module=None, doctype=None, test=(),
|
||||
driver=None, profile=False, junit_xml_output=False, ui_tests = False, doctype_list_path=None):
|
||||
driver=None, profile=False, coverage=False, junit_xml_output=False, ui_tests = False,
|
||||
doctype_list_path=None, skip_test_records=False, skip_before_tests=False, failfast=False):
|
||||
|
||||
"Run tests"
|
||||
import frappe.test_runner
|
||||
tests = test
|
||||
|
|
@ -385,9 +404,23 @@ def run_tests(context, app=None, module=None, doctype=None, test=(),
|
|||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
|
||||
frappe.flags.skip_before_tests = skip_before_tests
|
||||
frappe.flags.skip_test_records = skip_test_records
|
||||
|
||||
if coverage:
|
||||
# Generate coverage report only for app that is being tested
|
||||
source_path = os.path.join(get_bench_path(), 'apps', app or 'frappe')
|
||||
cov = Coverage(source=[source_path], omit=['*.html', '*.js', '*.css'])
|
||||
cov.start()
|
||||
|
||||
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests,
|
||||
force=context.force, profile=profile, junit_xml_output=junit_xml_output,
|
||||
ui_tests = ui_tests, doctype_list_path = doctype_list_path)
|
||||
ui_tests = ui_tests, doctype_list_path = doctype_list_path, failfast=failfast)
|
||||
|
||||
if coverage:
|
||||
cov.stop()
|
||||
cov.save()
|
||||
|
||||
if len(ret.failures) == 0 and len(ret.errors) == 0:
|
||||
ret = 0
|
||||
|
||||
|
|
@ -533,8 +566,9 @@ def get_version():
|
|||
|
||||
|
||||
@click.command('setup-global-help')
|
||||
@click.option('--mariadb_root_password')
|
||||
def setup_global_help(mariadb_root_password=None):
|
||||
@click.option('--db_type')
|
||||
@click.option('--root_password')
|
||||
def setup_global_help(db_type=None, root_password=None):
|
||||
'''setup help table in a separate database that will be
|
||||
shared by the whole bench and set `global_help_setup` as 1 in
|
||||
common_site_config.json'''
|
||||
|
|
@ -550,8 +584,12 @@ def setup_global_help(mariadb_root_password=None):
|
|||
update_site_config('global_help_setup', 1,
|
||||
site_config_path=os.path.join('.', 'common_site_config.json'))
|
||||
|
||||
if mariadb_root_password:
|
||||
frappe.local.conf.root_password = mariadb_root_password
|
||||
if root_password:
|
||||
frappe.local.conf.root_password = root_password
|
||||
|
||||
if not frappe.local.conf.db_type:
|
||||
frappe.local.conf.db_type = db_type
|
||||
|
||||
|
||||
from frappe.utils.help import sync
|
||||
sync()
|
||||
|
|
@ -663,6 +701,7 @@ commands = [
|
|||
make_app,
|
||||
mysql,
|
||||
mariadb,
|
||||
postgres,
|
||||
request,
|
||||
reset_perms,
|
||||
run_tests,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -250,10 +250,10 @@ def address_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
`tabAddress`.idx desc, `tabAddress`.name
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
mcond=get_match_cond(doctype),
|
||||
key=frappe.db.escape(searchfield),
|
||||
key=searchfield,
|
||||
condition=condition or ""),
|
||||
{
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'txt': frappe.db.escape('%' + txt + '%'),
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@ frappe.ui.form.on("Contact", {
|
|||
frappe.model.remove_from_locals(d.link_doctype, d.link_name);
|
||||
});
|
||||
}
|
||||
},
|
||||
after_save: function() {
|
||||
frappe.run_serially([
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
var last_route = frappe.route_history.slice(-2, -1)[0];
|
||||
if(frappe.dynamic_link && frappe.dynamic_link.doc
|
||||
&& last_route.length > 2 && frappe.dynamic_link.doc.name == last_route[2]){
|
||||
frappe.set_route(last_route[0], last_route[1], last_route[2]);
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -151,9 +151,9 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
`tabContact`.idx desc, `tabContact`.name
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
mcond=get_match_cond(doctype),
|
||||
key=frappe.db.escape(searchfield)),
|
||||
key=searchfield),
|
||||
{
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'txt': frappe.db.escape('%' + txt + '%'),
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len,
|
||||
|
|
|
|||
|
|
@ -46,4 +46,4 @@ def add_authentication_log(subject, user, operation="Login", status="Success"):
|
|||
def clear_authentication_logs():
|
||||
"""clear 100 day old authentication logs"""
|
||||
frappe.db.sql("""delete from `tabActivity Log` where \
|
||||
creation<DATE_SUB(NOW(), INTERVAL 100 DAY)""")
|
||||
creation< (NOW() - INTERVAL '100' DAY)""")
|
||||
|
|
@ -59,7 +59,7 @@ def logout_feed(user, reason):
|
|||
def get_feed_match_conditions(user=None, force=True):
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
conditions = ['`tabCommunication`.owner="{user}" or `tabCommunication`.reference_owner="{user}"'.format(user=frappe.db.escape(user))]
|
||||
conditions = ['`tabCommunication`.owner="{user}" or `tabCommunication`.reference_owner={user}'.format(user=frappe.db.escape(user))]
|
||||
|
||||
user_permissions = frappe.permissions.get_user_permissions(user)
|
||||
can_read = frappe.get_user().get_can_read()
|
||||
|
|
@ -76,7 +76,7 @@ def get_feed_match_conditions(user=None, force=True):
|
|||
can_read_docs = []
|
||||
for doctype, obj in user_permissions.items():
|
||||
for n in obj.get("docs", []):
|
||||
can_read_docs.append('"{}|{}"'.format(doctype, frappe.db.escape(n)))
|
||||
can_read_docs.append('{}|{}'.format(doctype, frappe.db.escape(n)))
|
||||
|
||||
if can_read_docs:
|
||||
conditions.append("concat_ws('|', `tabCommunication`.reference_doctype, `tabCommunication`.reference_name) in ({})".format(
|
||||
|
|
|
|||
|
|
@ -39,9 +39,10 @@ class TestActivityLog(unittest.TestCase):
|
|||
frappe.local.form_dict = frappe._dict()
|
||||
|
||||
def get_auth_log(self, operation='Login'):
|
||||
names = frappe.db.sql_list("""select name from `tabActivity Log`
|
||||
where user='Administrator' and operation='{operation}' order by
|
||||
creation desc""".format(operation=operation))
|
||||
names = frappe.db.get_all('Activity Log', filters={
|
||||
'user': 'Administrator',
|
||||
'operation': operation,
|
||||
}, order_by='`creation` DESC')
|
||||
|
||||
name = names[0]
|
||||
auth_log = frappe.get_doc('Activity Log', name)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import json
|
|||
from frappe.core.doctype.user.user import extract_mentions
|
||||
from frappe.utils import get_fullname, get_link_to_form
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.model.db_schema import add_column
|
||||
from frappe.database.schema import add_column
|
||||
from frappe.exceptions import ImplicitCommitError
|
||||
|
||||
def on_trash(doc):
|
||||
|
|
@ -114,9 +114,7 @@ def get_comments_from_parent(doc):
|
|||
_comments = frappe.db.get_value(doc.reference_doctype, doc.reference_name, "_comments") or "[]"
|
||||
|
||||
except Exception as e:
|
||||
if e.args[0] in (1146, 1054):
|
||||
# 1146 = no table
|
||||
# 1054 = missing column
|
||||
if frappe.db.is_missing_table_or_column(e):
|
||||
_comments = "[]"
|
||||
|
||||
else:
|
||||
|
|
@ -140,7 +138,7 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments):
|
|||
"%s", "%s"), (json.dumps(_comments), reference_name))
|
||||
|
||||
except Exception as e:
|
||||
if e.args[0] == 1054 and getattr(frappe.local, 'request', None):
|
||||
if frappe.db.is_column_missing(e) and getattr(frappe.local, 'request', None):
|
||||
# missing column and in request, add column and update after commit
|
||||
frappe.local._comments = (getattr(frappe.local, "_comments", [])
|
||||
+ [(reference_doctype, reference_name, _comments)])
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ import time
|
|||
from frappe import _
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
|
||||
# imports - third-party imports
|
||||
import pymysql
|
||||
from pymysql.constants import ER
|
||||
|
||||
@frappe.whitelist()
|
||||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
|
||||
sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False,
|
||||
|
|
@ -495,9 +491,9 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments
|
|||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, cc=cc, bcc=bcc)
|
||||
|
||||
except pymysql.InternalError as e:
|
||||
except frappe.db.InternalError:
|
||||
# deadlock, try again
|
||||
if e.args[0] == ER.LOCK_DEADLOCK:
|
||||
if frappe.db.is_deadlocked():
|
||||
frappe.db.rollback()
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -144,8 +144,9 @@ const add_doctype_field_multicheck_control = (doctype, parent_wrapper) => {
|
|||
const options = fields
|
||||
.map(df => {
|
||||
return {
|
||||
label: df.label + (df.reqd ? ' (M)' : ''),
|
||||
label: df.label,
|
||||
value: df.fieldname,
|
||||
danger: df.reqd,
|
||||
checked: 1
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -134,9 +134,10 @@ class DataExporter:
|
|||
|
||||
# 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):
|
||||
table_name = 'tab' + dt
|
||||
for f in frappe.db.get_table_columns_description(table_name):
|
||||
field = meta.get_field(f.name)
|
||||
if field and ((self.select_columns and f.name in self.select_columns[dt]) or not self.select_columns):
|
||||
tablecolumns.append(field)
|
||||
|
||||
tablecolumns.sort(key = lambda a: int(a.idx))
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ frappe.ui.form.on('Data Import', {
|
|||
frm.disable_save();
|
||||
frm.dashboard.clear_headline();
|
||||
if (frm.doc.reference_doctype && !frm.doc.import_file) {
|
||||
frm.page.set_indicator(__('Please attach a file to import'), 'orange');
|
||||
frm.page.set_indicator(__('Attach file'), 'orange');
|
||||
} else {
|
||||
if (frm.doc.import_status) {
|
||||
const listview_settings = frappe.listview_settings['Data Import'];
|
||||
|
|
@ -147,24 +147,22 @@ frappe.data_import.download_dialog = function(frm) {
|
|||
const filter_fields = df => frappe.model.is_value_type(df) && !df.hidden;
|
||||
const get_fields = dt => frappe.meta.get_docfields(dt).filter(filter_fields);
|
||||
|
||||
const get_doctypes = parentdt => {
|
||||
return [parentdt].concat(
|
||||
frappe.meta.get_table_fields(parentdt).map(df => df.options)
|
||||
);
|
||||
};
|
||||
|
||||
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 => ({
|
||||
label: df.label + ((df.reqd || df.fieldname == 'naming_series') ? ' (M)' : ''),
|
||||
reqd: (df.reqd || df.fieldname == 'naming_series') ? 1 : 0,
|
||||
value: df.fieldname,
|
||||
checked: 1
|
||||
}));
|
||||
.map(df => {
|
||||
let reqd = (df.reqd || df.fieldname == 'naming_series') ? 1 : 0;
|
||||
return {
|
||||
label: df.label,
|
||||
reqd: reqd,
|
||||
danger: reqd,
|
||||
value: df.fieldname,
|
||||
checked: 1
|
||||
};
|
||||
});
|
||||
|
||||
let fields = [
|
||||
{
|
||||
|
|
@ -176,15 +174,14 @@ frappe.data_import.download_dialog = function(frm) {
|
|||
"onchange": function() {
|
||||
const fields = get_doctype_checkbox_fields();
|
||||
fields.map(f => f.toggle(true));
|
||||
if(this.value == 'Mandatory') {
|
||||
if(this.value == 'Mandatory' || this.value == 'Manually') {
|
||||
checkbox_toggle(true);
|
||||
fields.map(multicheck_field => {
|
||||
multicheck_field.options.map(option => {
|
||||
if(!option.reqd) return;
|
||||
$(multicheck_field.$wrapper).find(`:checkbox[data-unit="${option.value}"]`)
|
||||
.prop('checked', false)
|
||||
.trigger('click')
|
||||
.prop('disabled', true);
|
||||
.trigger('click');
|
||||
});
|
||||
});
|
||||
} else if(this.value == 'All'){
|
||||
|
|
@ -237,10 +234,11 @@ frappe.data_import.download_dialog = function(frm) {
|
|||
"options": frappe.meta.get_docfields(df.options)
|
||||
.filter(filter_fields)
|
||||
.map(df => ({
|
||||
label: df.label + (df.reqd ? ' (M)' : ''),
|
||||
label: df.label,
|
||||
reqd: df.reqd ? 1 : 0,
|
||||
value: df.fieldname,
|
||||
checked: 1
|
||||
checked: 1,
|
||||
danger: df.reqd
|
||||
})),
|
||||
"columns": 2,
|
||||
"hidden": 1
|
||||
|
|
|
|||
|
|
@ -11,14 +11,11 @@ class DefaultValue(Document):
|
|||
|
||||
def on_doctype_update():
|
||||
"""Create indexes for `tabDefaultValue` on `(parent, defkey)`"""
|
||||
if not frappe.db.sql("""show index from `tabDefaultValue`
|
||||
where Key_name="defaultvalue_parent_defkey_index" """):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql("""alter table `tabDefaultValue`
|
||||
add index defaultvalue_parent_defkey_index(parent, defkey)""")
|
||||
frappe.db.commit()
|
||||
frappe.db.add_index(doctype='DefaultValue',
|
||||
fields=['parent', 'defkey'],
|
||||
index_name='defaultvalue_parent_defkey_index')
|
||||
|
||||
if not frappe.db.sql("""show index from `tabDefaultValue`
|
||||
where Key_name="defaultvalue_parent_parenttype_index" """):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql("""alter table `tabDefaultValue`
|
||||
add index defaultvalue_parent_parenttype_index(parent, parenttype)""")
|
||||
frappe.db.add_index(doctype='DefaultValue',
|
||||
fields=['parent', 'parenttype'],
|
||||
index_name='defaultvalue_parent_parenttype_index')
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -80,6 +82,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -113,6 +116,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -147,6 +151,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -180,6 +185,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -209,6 +215,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -242,6 +249,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -275,6 +283,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -305,6 +314,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -333,41 +343,43 @@
|
|||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "track_views",
|
||||
"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": "Track Views",
|
||||
"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,
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "track_views",
|
||||
"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": "Track Views",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -401,6 +413,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -431,6 +444,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -462,6 +476,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -495,6 +510,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -525,11 +541,12 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Naming Options:\n<ol><li><b>field:[fieldname]</b> - By Field</li><li><b>naming_series:</b> - By Naming Series (field called naming_series must be present</li><li><b>Prompt</b> - Prompt user for a name</li><li><b>[series]</b> - Series by prefix (separated by a dot); for example PRE.#####</li>\n<li><b>concatenate:[fieldname1],[fieldname2],...[fieldnameX]</b> - By fieldname concatenation (you can concatenate as many fields as you like. Series option also works)</li></ol>",
|
||||
"description": "Naming Options:\n<ol><li><b>field:[fieldname]</b> - By Field</li><li><b>naming_series:</b> - By Naming Series (field called naming_series must be present</li><li><b>Prompt</b> - Prompt user for a name</li><li><b>[series]</b> - Series by prefix (separated by a dot); for example PRE.#####</li>\n<li><b>format:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####}</b> - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used.</li></ol>",
|
||||
"fieldname": "autoname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -558,6 +575,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -591,6 +609,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -621,6 +640,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -653,6 +673,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -684,6 +705,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -716,6 +738,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -749,6 +772,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -781,6 +805,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -814,6 +839,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -844,6 +870,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -878,6 +905,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -910,6 +938,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -940,6 +969,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -971,6 +1001,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1002,6 +1033,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1034,6 +1066,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1066,6 +1099,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1098,6 +1132,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1130,6 +1165,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1161,6 +1197,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1192,6 +1229,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1226,6 +1264,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1256,6 +1295,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1286,6 +1326,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1316,6 +1357,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1347,6 +1389,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1379,6 +1422,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1409,6 +1453,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1441,6 +1486,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1471,6 +1517,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1502,6 +1549,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1535,6 +1583,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1568,6 +1617,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1598,6 +1648,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1630,6 +1681,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1662,6 +1714,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1693,6 +1746,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1725,6 +1779,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1758,6 +1813,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1788,6 +1844,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1820,6 +1877,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1853,6 +1911,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
|
|
@ -1884,6 +1943,7 @@
|
|||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -1928,7 +1988,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-04-27 09:27:59.444089",
|
||||
"modified": "2018-08-28 01:14:38.862469",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
@ -1981,5 +2041,6 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,19 +10,15 @@ import frappe
|
|||
from frappe import _
|
||||
|
||||
from frappe.utils import now, cint
|
||||
from frappe.model import no_value_fields, default_fields
|
||||
from frappe.model import no_value_fields, default_fields, data_fieldtypes
|
||||
from frappe.model.document import Document
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.desk.notifications import delete_notification_count_for
|
||||
from frappe.modules import make_boilerplate, get_doc_path
|
||||
from frappe.model.db_schema import validate_column_name, validate_column_length, type_map
|
||||
from frappe.database.schema import validate_column_name, validate_column_length
|
||||
from frappe.model.docfield import supports_translation
|
||||
import frappe.website.render
|
||||
|
||||
# imports - third-party imports
|
||||
import pymysql
|
||||
from pymysql.constants import ER
|
||||
|
||||
class InvalidFieldNameError(frappe.ValidationError): pass
|
||||
|
||||
form_grid_templates = {
|
||||
|
|
@ -123,20 +119,31 @@ class DocType(Document):
|
|||
link_fieldname, source_fieldname = df.fetch_from.split('.', 1)
|
||||
link_df = new_meta.get_field(link_fieldname)
|
||||
|
||||
self.flags.update_fields_to_fetch_queries.append('''update
|
||||
`tab{link_doctype}` source,
|
||||
`tab{doctype}` target
|
||||
set
|
||||
target.`{fieldname}` = source.`{source_fieldname}`
|
||||
where
|
||||
target.`{link_fieldname}` = source.name
|
||||
and ifnull(target.`{fieldname}`, '')="" '''.format(
|
||||
link_doctype = link_df.options,
|
||||
source_fieldname = source_fieldname,
|
||||
doctype = self.name,
|
||||
fieldname = df.fieldname,
|
||||
link_fieldname = link_fieldname
|
||||
))
|
||||
if frappe.conf.db_type == 'postgres':
|
||||
update_query = '''
|
||||
UPDATE `tab{doctype}`
|
||||
SET `{fieldname}` = source.`{source_fieldname}`
|
||||
FROM `tab{link_doctype}` as source
|
||||
WHERE `{link_fieldname}` = source.name
|
||||
AND ifnull(`{fieldname}`, '')=''
|
||||
'''
|
||||
else:
|
||||
update_query = '''
|
||||
UPDATE `tab{doctype}` as target
|
||||
INNER JOIN `tab{link_doctype}` as source
|
||||
ON `target`.`{link_fieldname}` = `source`.`name`
|
||||
SET `target`.`{fieldname}` = `source`.`{source_fieldname}`
|
||||
WHERE ifnull(`target`.`{fieldname}`, '')=""
|
||||
'''
|
||||
|
||||
self.flags.update_fields_to_fetch_queries.append(update_query.format(
|
||||
link_doctype = link_df.options,
|
||||
source_fieldname = source_fieldname,
|
||||
doctype = self.name,
|
||||
fieldname = df.fieldname,
|
||||
link_fieldname = link_fieldname
|
||||
)
|
||||
)
|
||||
|
||||
def update_fields_to_fetch(self):
|
||||
'''Update fetch values based on queries setup'''
|
||||
|
|
@ -164,10 +171,9 @@ class DocType(Document):
|
|||
"""Change the timestamp of parent DocType if the current one is a child to clear caches."""
|
||||
if frappe.flags.in_import:
|
||||
return
|
||||
parent_list = frappe.db.sql("""SELECT parent
|
||||
from tabDocField where fieldtype="Table" and options=%s""", self.name)
|
||||
parent_list = frappe.db.get_all('DocField', 'parent', dict(fieldtype='Table', options=self.name))
|
||||
for p in parent_list:
|
||||
frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0]))
|
||||
frappe.db.sql('UPDATE `tabDocType` SET modified=%s WHERE `name`=%s', (now(), p.parent))
|
||||
|
||||
def scrub_field_names(self):
|
||||
"""Sluggify fieldnames if not set from Label."""
|
||||
|
|
@ -199,12 +205,12 @@ class DocType(Document):
|
|||
"""Strip options for whitespaces"""
|
||||
for field in self.fields:
|
||||
if field.fieldtype == "Select" and field.options is not None:
|
||||
new_options = ""
|
||||
for option in field.options.split("\n"):
|
||||
new_options += option.strip()
|
||||
new_options += "\n"
|
||||
new_options.rstrip("\n")
|
||||
field.options = new_options
|
||||
options_list = []
|
||||
for i, option in enumerate(field.options.split("\n")):
|
||||
_option = option.strip()
|
||||
if i==0 or _option:
|
||||
options_list.append(_option)
|
||||
field.options = '\n'.join(options_list)
|
||||
|
||||
def validate_series(self, autoname=None, name=None):
|
||||
"""Validate if `autoname` property is correctly set."""
|
||||
|
|
@ -229,19 +235,24 @@ class DocType(Document):
|
|||
if autoname and (not autoname.startswith('field:')) \
|
||||
and (not autoname.startswith('eval:')) \
|
||||
and (not autoname.lower() in ('prompt', 'hash')) \
|
||||
and (not autoname.startswith('naming_series:')):
|
||||
and (not autoname.startswith('naming_series:')) \
|
||||
and (not autoname.startswith('format:')):
|
||||
|
||||
prefix = autoname.split('.')[0]
|
||||
used_in = frappe.db.sql('select name from tabDocType where substring_index(autoname, ".", 1) = %s and name!=%s', (prefix, name))
|
||||
used_in = frappe.db.sql("""
|
||||
SELECT `name`
|
||||
FROM `tabDocType`
|
||||
WHERE `autoname` LIKE CONCAT(%s, '.%%')
|
||||
AND `name`!=%s
|
||||
""", (prefix, name))
|
||||
if used_in:
|
||||
frappe.throw(_("Series {0} already used in {1}").format(prefix, used_in[0][0]))
|
||||
|
||||
def on_update(self):
|
||||
"""Update database schema, make controller templates if `custom` is not set and clear cache."""
|
||||
from frappe.model.db_schema import updatedb
|
||||
self.delete_duplicate_custom_fields()
|
||||
try:
|
||||
updatedb(self.name, self)
|
||||
frappe.db.updatedb(self.name, self)
|
||||
except Exception as e:
|
||||
print("\n\nThere was an issue while migrating the DocType: {}\n".format(self.name))
|
||||
raise e
|
||||
|
|
@ -280,7 +291,9 @@ class DocType(Document):
|
|||
def delete_duplicate_custom_fields(self):
|
||||
if not (frappe.db.table_exists(self.name) and frappe.db.table_exists("Custom Field")):
|
||||
return
|
||||
fields = [d.fieldname for d in self.fields if d.fieldtype in type_map]
|
||||
|
||||
fields = [d.fieldname for d in self.fields if d.fieldtype in data_fieldtypes]
|
||||
|
||||
frappe.db.sql('''delete from
|
||||
`tabCustom Field`
|
||||
where
|
||||
|
|
@ -514,6 +527,9 @@ def validate_fields(meta):
|
|||
options = frappe.db.get_value("DocType", d.options, "name")
|
||||
if not options:
|
||||
frappe.throw(_("Options must be a valid DocType for field {0} in row {1}").format(d.label, d.idx))
|
||||
elif not (options == d.options):
|
||||
frappe.throw(_("Options {0} must be the same as doctype name {1} for the field {2}")
|
||||
.format(d.options, options, d.label))
|
||||
else:
|
||||
# fix case
|
||||
d.options = options
|
||||
|
|
@ -568,8 +584,8 @@ def validate_fields(meta):
|
|||
group by `{fieldname}` having count(*) > 1 limit 1""".format(
|
||||
doctype=d.parent, fieldname=d.fieldname))
|
||||
|
||||
except pymysql.InternalError as e:
|
||||
if e.args and e.args[0] == ER.BAD_FIELD_ERROR:
|
||||
except frappe.db.InternalError as e:
|
||||
if frappe.db.is_missing_column(e):
|
||||
# ignore if missing column, else raise
|
||||
# this happens in case of Custom Field
|
||||
pass
|
||||
|
|
@ -740,13 +756,15 @@ def validate_permissions_for_doctype(doctype, for_remove=False):
|
|||
def clear_permissions_cache(doctype):
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
delete_notification_count_for(doctype)
|
||||
for user in frappe.db.sql_list("""select
|
||||
distinct `tabHas Role`.parent
|
||||
from
|
||||
for user in frappe.db.sql_list("""
|
||||
SELECT
|
||||
DISTINCT `tabHas Role`.`parent`
|
||||
FROM
|
||||
`tabHas Role`,
|
||||
tabDocPerm
|
||||
where tabDocPerm.parent = %s
|
||||
and tabDocPerm.role = `tabHas Role`.role""", doctype):
|
||||
`tabDocPerm`
|
||||
WHERE `tabDocPerm`.`parent` = %s
|
||||
AND `tabDocPerm`.`role` = `tabHas Role`.`role`
|
||||
""", doctype):
|
||||
frappe.clear_cache(user=user)
|
||||
|
||||
def validate_permissions(doctype, for_remove=False):
|
||||
|
|
@ -849,7 +867,8 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):
|
|||
not frappe.db.exists('Domain', doc.restrict_to_domain):
|
||||
frappe.get_doc(dict(doctype='Domain', domain=doc.restrict_to_domain)).insert()
|
||||
|
||||
if not frappe.db.exists("Module Def", doc.module):
|
||||
if ("tabModule Def" in frappe.db.get_tables()
|
||||
and not frappe.db.exists("Module Def", doc.module)):
|
||||
m = frappe.get_doc({"doctype": "Module Def", "module_name": doc.module})
|
||||
m.app_name = frappe.local.module_app[frappe.scrub(doc.module)]
|
||||
m.flags.ignore_mandatory = m.flags.ignore_permissions = True
|
||||
|
|
@ -865,8 +884,8 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):
|
|||
r.insert()
|
||||
except frappe.DoesNotExistError as e:
|
||||
pass
|
||||
except frappe.SQLError as e:
|
||||
if e.args[0]==1146:
|
||||
except frappe.db.ProgrammingError as e:
|
||||
if frappe.db.is_table_missing(e):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ class ErrorLog(Document):
|
|||
|
||||
def set_old_logs_as_seen():
|
||||
# set logs as seen
|
||||
frappe.db.sql("""update `tabError Log` set seen=1
|
||||
where seen=0 and datediff(curdate(), creation) > 7""")
|
||||
frappe.db.sql("""UPDATE `tabError Log` SET `seen`=1
|
||||
WHERE `seen`=0 AND `creation` < (NOW() - INTERVAL '7' DAY)""")
|
||||
|
||||
# clear old logs
|
||||
frappe.db.sql("""delete from `tabError Log` where datediff(curdate(), creation) > 30""")
|
||||
frappe.db.sql("""DELETE FROM `tabError Log` WHERE `creation` < (NOW() - INTERVAL '30' DAY)""")
|
||||
|
||||
@frappe.whitelist()
|
||||
def clear_error_logs():
|
||||
'''Flush all Error Logs'''
|
||||
frappe.only_for('System Manager')
|
||||
frappe.db.sql('''delete from `tabError Log`''')
|
||||
frappe.db.sql('''DELETE FROM `tabError Log`''')
|
||||
|
|
@ -35,4 +35,4 @@ def is_valid_feedback_request(key=None):
|
|||
|
||||
def delete_feedback_request():
|
||||
""" clear 100 days old feedback request """
|
||||
frappe.db.sql("""delete from `tabFeedback Request` where creation<DATE_SUB(NOW(), INTERVAL 100 DAY)""")
|
||||
frappe.db.sql("""delete from `tabFeedback Request` where `creation` < (NOW() - INTERVAL '100' DAY)""")
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 1
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
|
|
@ -600,7 +600,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-06-26 14:48:49.989952",
|
||||
"modified": "2018-06-27 14:48:49.989952",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Report",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class TransactionLog(Document):
|
|||
self.timestamp = now()
|
||||
if index != 1:
|
||||
prev_hash = frappe.db.sql(
|
||||
"SELECT chaining_hash FROM `tabTransaction Log` WHERE row_index = {0}".format(index - 1))
|
||||
"SELECT `chaining_hash` FROM `tabTransaction Log` WHERE `row_index` = '{0}'".format(index - 1))
|
||||
if prev_hash:
|
||||
self.previous_hash = prev_hash[0][0]
|
||||
else:
|
||||
|
|
@ -46,16 +46,18 @@ class TransactionLog(Document):
|
|||
|
||||
|
||||
def get_current_index():
|
||||
current = frappe.db.sql(
|
||||
"SELECT `current` FROM tabSeries WHERE name='TRANSACTLOG' FOR UPDATE")
|
||||
current = frappe.db.sql("""SELECT `current`
|
||||
FROM `tabSeries`
|
||||
WHERE `name` = 'TRANSACTLOG'
|
||||
FOR UPDATE""")
|
||||
if current and current[0][0] is not None:
|
||||
current = current[0][0]
|
||||
|
||||
frappe.db.sql(
|
||||
"UPDATE tabSeries SET current = current+1 where name='TRANSACTLOG'")
|
||||
frappe.db.sql("""UPDATE `tabSeries`
|
||||
SET `current` = `current` + 1
|
||||
where `name` = 'TRANSACTLOG'""")
|
||||
current = cint(current) + 1
|
||||
else:
|
||||
frappe.db.sql(
|
||||
"INSERT INTO tabSeries (name, current) VALUES ('TRANSACTLOG', 1)")
|
||||
frappe.db.sql("INSERT INTO `tabSeries` (name, current) VALUES ('TRANSACTLOG', 1)")
|
||||
current = 1
|
||||
return current
|
||||
|
|
|
|||
|
|
@ -278,4 +278,4 @@ class TestUser(unittest.TestCase):
|
|||
self.assertEqual(extract_mentions(user_name)[0], "test.user@example.com")
|
||||
|
||||
def delete_contact(user):
|
||||
frappe.db.sql("delete from tabContact where email_id='%s'" % frappe.db.escape(user))
|
||||
frappe.db.sql("DELETE FROM `tabContact` WHERE `email_id`= %s", user)
|
||||
|
|
|
|||
|
|
@ -97,8 +97,7 @@ class User(Document):
|
|||
if self.name not in ('Administrator', 'Guest') and not self.user_image:
|
||||
frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name)
|
||||
|
||||
|
||||
def has_website_permission(self, ptype, verbose=False):
|
||||
def has_website_permission(self, ptype, user, verbose=False):
|
||||
"""Returns true if current user is the session user"""
|
||||
return self.name == frappe.session.user
|
||||
|
||||
|
|
@ -230,12 +229,12 @@ class User(Document):
|
|||
return link
|
||||
|
||||
def get_other_system_managers(self):
|
||||
return frappe.db.sql("""select distinct user.name from `tabHas Role` user_role, tabUser user
|
||||
return frappe.db.sql("""select distinct `user`.`name` from `tabHas Role` as `user_role`, `tabUser` as `user`
|
||||
where user_role.role='System Manager'
|
||||
and user.docstatus<2
|
||||
and user.enabled=1
|
||||
and user_role.parent = user.name
|
||||
and user_role.parent not in ('Administrator', %s) limit 1""", (self.name,))
|
||||
and `user`.docstatus<2
|
||||
and `user`.enabled=1
|
||||
and `user_role`.parent = `user`.name
|
||||
and `user_role`.parent not in ('Administrator', %s) limit 1""", (self.name,))
|
||||
|
||||
def get_fullname(self):
|
||||
"""get first_name space last_name"""
|
||||
|
|
@ -312,8 +311,8 @@ class User(Document):
|
|||
frappe.local.login_manager.logout(user=self.name)
|
||||
|
||||
# delete todos
|
||||
frappe.db.sql("""delete from `tabToDo` where owner=%s""", (self.name,))
|
||||
frappe.db.sql("""update tabToDo set assigned_by=null where assigned_by=%s""",
|
||||
frappe.db.sql("""DELETE FROM `tabToDo` WHERE `owner`=%s""", (self.name,))
|
||||
frappe.db.sql("""UPDATE `tabToDo` SET `assigned_by`=NULL WHERE `assigned_by`=%s""",
|
||||
(self.name,))
|
||||
|
||||
# delete events
|
||||
|
|
@ -346,23 +345,23 @@ class User(Document):
|
|||
validate_email_add(email.strip(), True)
|
||||
|
||||
def after_rename(self, old_name, new_name, merge=False):
|
||||
tables = frappe.db.sql("show tables")
|
||||
tables = frappe.db.get_tables()
|
||||
for tab in tables:
|
||||
desc = frappe.db.sql("desc `%s`" % tab[0], as_dict=1)
|
||||
desc = frappe.db.get_table_columns_description(tab)
|
||||
has_fields = []
|
||||
for d in desc:
|
||||
if d.get('Field') in ['owner', 'modified_by']:
|
||||
has_fields.append(d.get('Field'))
|
||||
if d.get('name') in ['owner', 'modified_by']:
|
||||
has_fields.append(d.get('name'))
|
||||
for field in has_fields:
|
||||
frappe.db.sql("""\
|
||||
update `%s` set `%s`=%s
|
||||
where `%s`=%s""" % \
|
||||
(tab[0], field, '%s', field, '%s'), (new_name, old_name))
|
||||
frappe.db.sql("""UPDATE `%s`
|
||||
SET `%s` = %s
|
||||
WHERE `%s` = %s""" %
|
||||
(tab, field, '%s', field, '%s'), (new_name, old_name))
|
||||
|
||||
# set email
|
||||
frappe.db.sql("""\
|
||||
update `tabUser` set email=%s
|
||||
where name=%s""", (new_name, new_name))
|
||||
frappe.db.sql("""UPDATE `tabUser`
|
||||
SET email = %s
|
||||
WHERE name = %s""", (new_name, new_name))
|
||||
|
||||
def append_roles(self, *roles):
|
||||
"""Add roles to user"""
|
||||
|
|
@ -688,9 +687,8 @@ def setup_user_email_inbox(email_account, awaiting_password, email_id, enable_ou
|
|||
"awaiting_password": awaiting_password or 0
|
||||
})
|
||||
else:
|
||||
frappe.msgprint(_("Enabled email inbox for user {users}".format(
|
||||
users=" and ".join([frappe.bold(user.get("name")) for user in user_names])
|
||||
)))
|
||||
users = " and ".join([frappe.bold(user.get("name")) for user in user_names])
|
||||
frappe.msgprint(_("Enabled email inbox for user {0}").format(users))
|
||||
|
||||
ask_pass_update()
|
||||
|
||||
|
|
@ -824,31 +822,33 @@ def user_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
user_type_condition = ''
|
||||
|
||||
txt = "%{}%".format(txt)
|
||||
return frappe.db.sql("""select name, concat_ws(' ', first_name, middle_name, last_name)
|
||||
from `tabUser`
|
||||
where enabled=1
|
||||
return frappe.db.sql("""SELECT `name`, CONCAT_WS(' ', first_name, middle_name, last_name)
|
||||
FROM `tabUser`
|
||||
WHERE `enabled`=1
|
||||
{user_type_condition}
|
||||
and docstatus < 2
|
||||
and name not in ({standard_users})
|
||||
and ({key} like %(txt)s
|
||||
or concat_ws(' ', first_name, middle_name, last_name) like %(txt)s)
|
||||
AND `docstatus` < 2
|
||||
AND `name` NOT IN ({standard_users})
|
||||
AND ({key} LIKE %(txt)s
|
||||
OR CONCAT_WS(' ', first_name, middle_name, last_name) LIKE %(txt)s)
|
||||
{mcond}
|
||||
order by
|
||||
case when name like %(txt)s then 0 else 1 end,
|
||||
case when concat_ws(' ', first_name, middle_name, last_name) like %(txt)s
|
||||
then 0 else 1 end,
|
||||
name asc
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
ORDER BY
|
||||
CASE WHEN `name` LIKE %(txt)s THEN 0 ELSE 1 END,
|
||||
CASE WHEN concat_ws(' ', first_name, middle_name, last_name) LIKE %(txt)s
|
||||
THEN 0 ELSE 1 END,
|
||||
NAME asc
|
||||
LIMIT %(page_len)s OFFSET %(start)s""".format(
|
||||
user_type_condition = user_type_condition,
|
||||
standard_users=", ".join(["'{0}'".format(frappe.db.escape(u)) for u in STANDARD_USERS]),
|
||||
standard_users=", ".join([frappe.db.escape(u) for u in STANDARD_USERS]),
|
||||
key=searchfield, mcond=get_match_cond(doctype)),
|
||||
dict(start=start, page_len=page_len, txt=txt))
|
||||
|
||||
def get_total_users():
|
||||
"""Returns total no. of system users"""
|
||||
return frappe.db.sql('''select sum(simultaneous_sessions) from `tabUser`
|
||||
where enabled=1 and user_type="System User"
|
||||
and name not in ({})'''.format(", ".join(["%s"]*len(STANDARD_USERS))), STANDARD_USERS)[0][0]
|
||||
return frappe.db.sql('''SELECT SUM(`simultaneous_sessions`)
|
||||
FROM `tabUser`
|
||||
WHERE `enabled` = 1
|
||||
AND `user_type` = 'System User'
|
||||
AND `name` NOT IN ({})'''.format(", ".join(["%s"]*len(STANDARD_USERS))), STANDARD_USERS)[0][0]
|
||||
|
||||
def get_system_users(exclude_users=None, limit=None):
|
||||
if not exclude_users:
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ def get_user_permissions(user=None):
|
|||
if meta.is_nested_set():
|
||||
out[perm.allow]["docs"].extend(frappe.db.get_descendants(perm.allow, perm.for_value))
|
||||
frappe.cache().hset("user_permissions", user, out)
|
||||
except frappe.SQLError as e:
|
||||
if e.args[0]==1146:
|
||||
except frappe.db.SQLError:
|
||||
if frappe.db.is_table_missing():
|
||||
# called from patch
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ def get_unseen_likes():
|
|||
return frappe.db.sql("""select count(*) from `tabCommunication`
|
||||
where
|
||||
communication_type='Comment'
|
||||
and modified >= DATE_SUB(NOW(),INTERVAL 1 YEAR)
|
||||
and modified >= (NOW() - INTERVAL '1' YEAR)
|
||||
and comment_type='Like'
|
||||
and owner is not null and owner!=%(user)s
|
||||
and reference_owner=%(user)s
|
||||
|
|
@ -60,12 +60,12 @@ def get_unread_emails():
|
|||
SELECT count(*)
|
||||
FROM `tabCommunication`
|
||||
WHERE communication_type='Communication'
|
||||
AND communication_medium="Email"
|
||||
AND sent_or_received="Received"
|
||||
AND email_status not in ("Spam", "Trash")
|
||||
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
|
||||
)
|
||||
AND modified >= DATE_SUB(NOW(),INTERVAL 1 YEAR)
|
||||
AND modified >= (NOW() - INTERVAL '1' YEAR)
|
||||
AND seen=0
|
||||
""", {"user": frappe.session.user})[0][0]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import frappe
|
|||
from frappe import _, throw
|
||||
import frappe.utils.user
|
||||
from frappe.permissions import check_admin_or_system_manager
|
||||
from frappe.model.db_schema import type_map
|
||||
from frappe.model import data_fieldtypes
|
||||
|
||||
def execute(filters=None):
|
||||
user, doctype, show_permissions = filters.get("user"), filters.get("doctype"), filters.get("show_permissions")
|
||||
|
|
@ -34,7 +34,7 @@ def get_columns_and_fields(doctype):
|
|||
columns = ["Name:Link/{}:200".format(doctype)]
|
||||
fields = ["`name`"]
|
||||
for df in frappe.get_meta(doctype).fields:
|
||||
if df.in_list_view and df.fieldtype in type_map:
|
||||
if df.in_list_view and df.fieldtype in data_fieldtypes:
|
||||
fields.append("`{0}`".format(df.fieldname))
|
||||
fieldtype = "Link/{}".format(df.options) if df.fieldtype=="Link" else df.fieldtype
|
||||
columns.append("{label}:{fieldtype}:{width}".format(label=df.label, fieldtype=fieldtype, width=df.width or 100))
|
||||
|
|
|
|||
|
|
@ -63,8 +63,7 @@ class CustomField(Document):
|
|||
if not frappe.db.get_value('DocType', self.dt, 'issingle'):
|
||||
if (self.fieldname not in frappe.db.get_table_columns(self.dt)
|
||||
or getattr(self, "_old_fieldtype", None) != self.fieldtype):
|
||||
from frappe.model.db_schema import updatedb
|
||||
updatedb(self.dt)
|
||||
frappe.db.updatedb(self.dt)
|
||||
|
||||
def on_trash(self):
|
||||
# delete property setter entries
|
||||
|
|
|
|||
|
|
@ -10,7 +10,5 @@ import unittest
|
|||
|
||||
test_records = frappe.get_test_records('Custom Field')
|
||||
|
||||
from frappe.model.db_schema import InvalidColumnName
|
||||
|
||||
class TestCustomField(unittest.TestCase):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -149,8 +149,7 @@ class CustomizeForm(Document):
|
|||
validate_fields_for_doctype(self.doc_type)
|
||||
|
||||
if self.flags.update_db:
|
||||
from frappe.model.db_schema import updatedb
|
||||
updatedb(self.doc_type)
|
||||
frappe.db.updatedb(self.doc_type)
|
||||
|
||||
if not hasattr(self, 'hide_success') or not self.hide_success:
|
||||
frappe.msgprint(_("{0} updated").format(_(self.doc_type)))
|
||||
|
|
@ -183,7 +182,7 @@ class CustomizeForm(Document):
|
|||
continue
|
||||
|
||||
elif property == "reqd" and \
|
||||
((frappe.db.get_value("DocField",
|
||||
((frappe.db.get_value("DocField",
|
||||
{"parent":self.doc_type,"fieldname":df.fieldname}, "reqd") == 1) \
|
||||
and (df.get(property) == 0)):
|
||||
frappe.msgprint(_("Row {0}: Not allowed to disable Mandatory for standard fields")\
|
||||
|
|
@ -322,7 +321,7 @@ class CustomizeForm(Document):
|
|||
try:
|
||||
property_value = frappe.db.get_value("DocType", self.doc_type, property_name)
|
||||
except Exception as e:
|
||||
if e.args[0]==1054:
|
||||
if frappe.db.is_column_missing(e):
|
||||
property_value = None
|
||||
else:
|
||||
raise
|
||||
|
|
@ -342,7 +341,8 @@ class CustomizeForm(Document):
|
|||
if not self.doc_type:
|
||||
return
|
||||
|
||||
frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s
|
||||
and !(`field_name`='naming_series' and `property`='options')""", self.doc_type)
|
||||
frappe.db.sql("""DELETE FROM `tabProperty Setter` WHERE doc_type=%s
|
||||
and `field_name`!='naming_series'
|
||||
and `property`!='options'""", self.doc_type)
|
||||
frappe.clear_cache(doctype=self.doc_type)
|
||||
self.fetch_to_customize()
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class PropertySetter(Document):
|
|||
frappe.db.sql("""delete from `tabProperty Setter` where
|
||||
doctype_or_field = %(doctype_or_field)s
|
||||
and doc_type = %(doc_type)s
|
||||
and ifnull(field_name,'') = ifnull(%(field_name)s, '')
|
||||
and coalesce(field_name,'') = coalesce(%(field_name)s, '')
|
||||
and property = %(property)s""", self.get_valid_dict())
|
||||
|
||||
def get_property_list(self, dt):
|
||||
|
|
@ -41,7 +41,7 @@ class PropertySetter(Document):
|
|||
from tabDocField
|
||||
where parent=%s
|
||||
and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table', 'Fold')
|
||||
and ifnull(fieldname, '') != ''
|
||||
and coalesce(fieldname, '') != ''
|
||||
order by label asc""", dt, as_dict=1)
|
||||
|
||||
def get_setup_data(self):
|
||||
|
|
@ -69,7 +69,7 @@ class PropertySetter(Document):
|
|||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
|
||||
validate_fields_for_doctype(self.doc_type)
|
||||
|
||||
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False,
|
||||
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False,
|
||||
validate_fields_for_doctype=True):
|
||||
# WARNING: Ignores Permissions
|
||||
property_setter = frappe.get_doc({
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"app_url": "https://github.com/revant/civil_contracting.git",
|
||||
"app_name": "civil_contracting",
|
||||
"app_icon": "octicon octicon-file-directory",
|
||||
"app_color": "grey",
|
||||
"app_description": "Civil Contracting App to manage workers, wages and measurements",
|
||||
"app_publisher": "Revant Nandgaonkar",
|
||||
"app_email": "revant@mntechnique.com",
|
||||
"repo_url": "https://github.com/revant/civil_contracting.git",
|
||||
"app_title": "Civil Contracting",
|
||||
"app_version": "1.5.0",
|
||||
"app_category": "Integrations",
|
||||
"featured": 1
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"app_url": "https://github.com/frappe/erpnext_shopify.git",
|
||||
"app_name": "erpnext_shopify",
|
||||
"app_icon": "octicon octicon-file-directory",
|
||||
"app_color": "grey",
|
||||
"app_description": "Shopify connector for ERPNext",
|
||||
"app_publisher": "Frappe",
|
||||
"app_email": "hello@frappe.io",
|
||||
"repo_url": "https://github.com/frappe/erpnext_shopify.git",
|
||||
"app_title": "ERPNext Shopify",
|
||||
"app_version": "1.0.0",
|
||||
"app_category": "Integrations",
|
||||
"featured": 1
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"app_url": "https://github.com/frappe/mandrill_integration",
|
||||
"app_name": "mandrill_integration",
|
||||
"app_icon": "octicon octicon-inbox",
|
||||
"app_color": "#4CB6E6",
|
||||
"app_description": "Set email communication status (Sent, Bounced etc) from Mandrill via webhooks.",
|
||||
"app_publisher": "Frappe Technologies Pvt Ltd, Sponsored by Rohit Industries Group Pvt Ltd",
|
||||
"app_email": "hello@frappe.io",
|
||||
"repo_url": "https://github.com/frappe/mandrill_integration.git",
|
||||
"app_title": "Mandrill Integration",
|
||||
"app_version": "0.1.0",
|
||||
"app_category": "Integrations"
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"app_url": "https://github.com/semilimes/sendgrid_integration",
|
||||
"app_name": "sendgrid_integration",
|
||||
"app_icon": "octicon octicon-inbox",
|
||||
"app_color": "#4CB6E6",
|
||||
"app_description": "Set email communication status from SendGrid via webhook.",
|
||||
"app_publisher": "Semilimes",
|
||||
"app_email": "all@semilimes.com",
|
||||
"repo_url": "https://github.com/semilimes/sendgrid_integration.git",
|
||||
"app_title": "SendGrid Integration",
|
||||
"app_version": "0.0.1",
|
||||
"app_category": "Integrations"
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
{
|
||||
"db_name": "testdb",
|
||||
"db_password": "password",
|
||||
"mute_emails": true,
|
||||
|
||||
"limits": {
|
||||
"emails": 1500,
|
||||
"space": 0.157,
|
||||
"expiry": "2016-07-25",
|
||||
"users": 1
|
||||
}
|
||||
|
||||
"developer_mode": 1,
|
||||
"auto_cache_clear": true,
|
||||
"disable_website_cache": true,
|
||||
"max_file_size": 1000000,
|
||||
|
||||
"mail_server": "localhost",
|
||||
"mail_login": null,
|
||||
"mail_password": null,
|
||||
"mail_port": 25,
|
||||
"use_ssl": 0,
|
||||
"auto_email_id": "hello@example.com",
|
||||
|
||||
"google_login": {
|
||||
"client_id": "google_client_id",
|
||||
"client_secret": "google_client_secret"
|
||||
},
|
||||
"github_login": {
|
||||
"client_id": "github_client_id",
|
||||
"client_secret": "github_client_secret"
|
||||
},
|
||||
"facebook_login": {
|
||||
"client_id": "facebook_client_id",
|
||||
"client_secret": "facebook_client_secret"
|
||||
},
|
||||
|
||||
"celery_broker": "redis://localhost",
|
||||
"celery_result_backend": null,
|
||||
"scheduler_interval": 300,
|
||||
"celery_queue_per_site": true
|
||||
}
|
||||
|
|
@ -45,11 +45,11 @@ class TestDataMigrationRun(unittest.TestCase):
|
|||
created_todo = frappe.get_doc('ToDo', {'description': event_subject})
|
||||
self.assertEqual(created_todo.description, event_subject)
|
||||
|
||||
todo_list = frappe.get_list('ToDo', filters={'description': 'Data migration todo'}, fields=['name'])
|
||||
todo_list = frappe.get_list('ToDo', filters={'description': 'data migration todo'}, fields=['name'])
|
||||
todo_name = todo_list[0].name
|
||||
|
||||
todo = frappe.get_doc('ToDo', todo_name)
|
||||
todo.description = 'Data migration todo updated'
|
||||
todo.description = 'data migration todo updated'
|
||||
todo.save()
|
||||
|
||||
run = frappe.get_doc({
|
||||
|
|
@ -77,7 +77,7 @@ def create_plan():
|
|||
{ 'remote_fieldname': 'starts_on', 'local_fieldname': 'eval:frappe.utils.get_datetime_str(frappe.utils.get_datetime())' }
|
||||
],
|
||||
'condition': '{"description": "data migration todo" }'
|
||||
}).insert()
|
||||
}).insert(ignore_if_duplicate=True)
|
||||
|
||||
frappe.get_doc({
|
||||
'doctype': 'Data Migration Mapping',
|
||||
|
|
@ -91,23 +91,24 @@ def create_plan():
|
|||
'fields': [
|
||||
{ 'remote_fieldname': 'subject', 'local_fieldname': 'description' }
|
||||
]
|
||||
}).insert()
|
||||
}).insert(ignore_if_duplicate=True)
|
||||
|
||||
frappe.get_doc({
|
||||
'doctype': 'Data Migration Plan',
|
||||
'plan_name': 'ToDo sync',
|
||||
'plan_name': 'ToDo Sync',
|
||||
'module': 'Core',
|
||||
'mappings': [
|
||||
{ 'mapping': 'Todo to Event' },
|
||||
{ 'mapping': 'Event to ToDo' }
|
||||
]
|
||||
}).insert()
|
||||
}).insert(ignore_if_duplicate=True)
|
||||
|
||||
frappe.get_doc({
|
||||
'doctype': 'Data Migration Connector',
|
||||
'connector_name': 'Local Connector',
|
||||
'connector_type': 'Frappe',
|
||||
'hostname': 'http://localhost:8000',
|
||||
# connect to same host.
|
||||
'hostname': frappe.conf.host_name,
|
||||
'username': 'Administrator',
|
||||
'password': 'admin'
|
||||
}).insert()
|
||||
}).insert(ignore_if_duplicate=True)
|
||||
|
|
|
|||
42
frappe/database/__init__.py
Normal file
42
frappe/database/__init__.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
# Database Module
|
||||
# --------------------
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
def setup_database(force, source_sql=None, verbose=None):
|
||||
import frappe
|
||||
if frappe.conf.db_type == 'postgres':
|
||||
import frappe.database.postgres.setup_db
|
||||
return frappe.database.postgres.setup_db.setup_database(force, source_sql, verbose)
|
||||
else:
|
||||
import frappe.database.mariadb.setup_db
|
||||
return frappe.database.mariadb.setup_db.setup_database(force, source_sql, verbose)
|
||||
|
||||
def drop_user_and_database(db_name, root_login=None, root_password=None):
|
||||
import frappe
|
||||
if frappe.conf.db_type == 'postgres':
|
||||
pass
|
||||
else:
|
||||
import frappe.database.mariadb.setup_db
|
||||
return frappe.database.mariadb.setup_db.drop_user_and_database(db_name, root_login, root_password)
|
||||
|
||||
def get_db(host=None, user=None, password=None):
|
||||
import frappe
|
||||
if frappe.conf.db_type == 'postgres':
|
||||
import frappe.database.postgres.database
|
||||
return frappe.database.postgres.database.PostgresDatabase(host, user, password)
|
||||
else:
|
||||
import frappe.database.mariadb.database
|
||||
return frappe.database.mariadb.database.MariaDBDatabase(host, user, password)
|
||||
|
||||
def setup_help_database(help_db_name):
|
||||
import frappe
|
||||
if frappe.conf.db_type == 'postgres':
|
||||
import frappe.database.postgres.setup_db
|
||||
return frappe.database.postgres.setup_db.setup_help_database(help_db_name)
|
||||
else:
|
||||
import frappe.database.mariadb.setup_db
|
||||
return frappe.database.mariadb.setup_db.setup_help_database(help_db_name)
|
||||
|
|
@ -5,66 +5,55 @@
|
|||
# --------------------
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import warnings
|
||||
import datetime
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
from time import time
|
||||
|
||||
import re
|
||||
import time
|
||||
import frappe
|
||||
import datetime
|
||||
import frappe.defaults
|
||||
import frappe.model.meta
|
||||
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 time import time
|
||||
from frappe.utils import now, getdate, cast_fieldtype
|
||||
from frappe.utils.background_jobs import execute_job, get_queue
|
||||
from frappe import as_unicode
|
||||
import six
|
||||
from frappe.model.utils.link_count import flush_local_link_count
|
||||
|
||||
# imports - compatibility imports
|
||||
from six import (
|
||||
integer_types,
|
||||
string_types,
|
||||
binary_type,
|
||||
text_type,
|
||||
iteritems
|
||||
)
|
||||
|
||||
# imports - third-party imports
|
||||
from markdown2 import UnicodeWithAttrs
|
||||
from pymysql.times import TimeDelta
|
||||
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:
|
||||
class Database(object):
|
||||
"""
|
||||
Open a database connection with the given parmeters, if use_default is True, use the
|
||||
login details from `conf.py`. This is called by the request handler and is accessible using
|
||||
the `db` global variable. the `sql` method is also global to run queries
|
||||
"""
|
||||
def __init__(self, host=None, user=None, password=None, ac_name=None, use_default = 0, local_infile = 0):
|
||||
VARCHAR_LEN = 140
|
||||
MAX_COLUMN_LENGTH = 64
|
||||
|
||||
OPTIONAL_COLUMNS = ["_user_tags", "_comments", "_assign", "_liked_by"]
|
||||
DEFAULT_SHORTCUTS = ['_Login', '__user', '_Full Name', 'Today', '__today', "now", "Now"]
|
||||
STANDARD_VARCHAR_COLUMNS = ('name', 'owner', 'modified_by', 'parent', 'parentfield', 'parenttype')
|
||||
DEFAULT_COLUMNS = ['name', 'creation', 'modified', 'modified_by', 'owner', 'docstatus', 'parent',
|
||||
'parentfield', 'parenttype', 'idx']
|
||||
|
||||
class InvalidColumnName(frappe.ValidationError): pass
|
||||
|
||||
|
||||
def __init__(self, host=None, user=None, password=None, ac_name=None, use_default=0):
|
||||
self.setup_type_map()
|
||||
self.host = host or frappe.conf.db_host or 'localhost'
|
||||
self.user = user or frappe.conf.db_name
|
||||
self.db_name = frappe.conf.db_name
|
||||
self._conn = None
|
||||
|
||||
if ac_name:
|
||||
self.user = self.get_db_login(ac_name) or frappe.conf.db_name
|
||||
self.user = ac_name or frappe.conf.db_name
|
||||
|
||||
if use_default:
|
||||
self.user = frappe.conf.db_name
|
||||
|
|
@ -75,63 +64,25 @@ class Database:
|
|||
self.password = password or frappe.conf.db_password
|
||||
self.value_cache = {}
|
||||
|
||||
# this param is to load CSV's with LOCAL keyword.
|
||||
# it can be set in site_config as > bench set-config local_infile 1
|
||||
# once the local-infile is set on MySql Server, the client needs to connect with this option
|
||||
# Connections without this option leads to: 'The used command is not allowed with this MariaDB version' error
|
||||
self.local_infile = local_infile or frappe.conf.local_infile
|
||||
|
||||
def get_db_login(self, ac_name):
|
||||
return ac_name
|
||||
def setup_type_map(self):
|
||||
pass
|
||||
|
||||
def connect(self):
|
||||
"""Connects to a database as set in `site_config.json`."""
|
||||
warnings.filterwarnings('ignore', category=pymysql.Warning)
|
||||
usessl = 0
|
||||
if frappe.conf.db_ssl_ca and frappe.conf.db_ssl_cert and frappe.conf.db_ssl_key:
|
||||
usessl = 1
|
||||
self.ssl = {
|
||||
'ca':frappe.conf.db_ssl_ca,
|
||||
'cert':frappe.conf.db_ssl_cert,
|
||||
'key':frappe.conf.db_ssl_key
|
||||
}
|
||||
|
||||
conversions.update({
|
||||
FIELD_TYPE.NEWDECIMAL: float,
|
||||
FIELD_TYPE.DATETIME: get_datetime,
|
||||
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)
|
||||
else:
|
||||
self._conn = pymysql.connect(self.host, self.user or '', self.password or '',
|
||||
charset='utf8mb4', use_unicode = True, conv = conversions, local_infile = self.local_infile)
|
||||
|
||||
# MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1
|
||||
# # self._conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_OFF)
|
||||
|
||||
self.cur_db_name = self.user
|
||||
self._conn = self.get_connection()
|
||||
self._cursor = self._conn.cursor()
|
||||
if self.user != 'root':
|
||||
self.use(self.user)
|
||||
frappe.local.rollback_observers = []
|
||||
|
||||
def use(self, db_name):
|
||||
"""`USE` db_name."""
|
||||
self._conn.select_db(db_name)
|
||||
self.cur_db_name = db_name
|
||||
|
||||
def validate_query(self, q):
|
||||
"""Throw exception for dangerous queries: `ALTER`, `DROP`, `TRUNCATE` if not `Administrator`."""
|
||||
cmd = q.strip().lower().split()[0]
|
||||
if cmd in ['alter', 'drop', 'truncate'] and frappe.session.user != 'Administrator':
|
||||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||
def get_connection(self):
|
||||
pass
|
||||
|
||||
def get_database_size(self):
|
||||
pass
|
||||
|
||||
def sql(self, query, values=(), as_dict = 0, as_list = 0, formatted = 0,
|
||||
debug=0, ignore_ddl=0, as_utf8=0, auto_commit=0, update=None, explain=False):
|
||||
|
|
@ -161,6 +112,10 @@ class Database:
|
|||
{"name": "a%", "owner":"test@example.com"})
|
||||
|
||||
"""
|
||||
if re.search(r'ifnull\(', query, flags=re.IGNORECASE):
|
||||
# replaces ifnull in query with coalesce
|
||||
query = re.sub(r'ifnull\(', 'coalesce(', query, flags=re.IGNORECASE)
|
||||
|
||||
if not self._conn:
|
||||
self.connect()
|
||||
|
||||
|
|
@ -183,7 +138,7 @@ class Database:
|
|||
if not isinstance(values, (dict, tuple, list)):
|
||||
values = (values,)
|
||||
|
||||
if debug and query.lower().startswith('select'):
|
||||
if debug:
|
||||
try:
|
||||
if explain:
|
||||
self.explain_query(query, values)
|
||||
|
|
@ -214,23 +169,19 @@ class Database:
|
|||
frappe.errprint(("Execution time: {0} sec").format(round(time_end - time_start, 2)))
|
||||
|
||||
except Exception as e:
|
||||
if ignore_ddl and e.args[0] in (ER.BAD_FIELD_ERROR, ER.NO_SUCH_TABLE,
|
||||
ER.CANT_DROP_FIELD_OR_KEY):
|
||||
pass
|
||||
if(frappe.conf.db_type == 'postgres'):
|
||||
self.rollback()
|
||||
|
||||
# NOTE: causes deadlock
|
||||
# elif e.args[0]==2006:
|
||||
# # mysql has gone away
|
||||
# self.connect()
|
||||
# return self.sql(query=query, values=values,
|
||||
# as_dict=as_dict, as_list=as_list, formatted=formatted,
|
||||
# debug=debug, ignore_ddl=ignore_ddl, as_utf8=as_utf8,
|
||||
# auto_commit=auto_commit, update=update)
|
||||
if ignore_ddl and (self.is_missing_column(e) or self.is_missing_table(e) or self.cant_drop_field_or_key(e)):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
if auto_commit: self.commit()
|
||||
|
||||
if not self._cursor.description:
|
||||
return ()
|
||||
|
||||
# scrub output if required
|
||||
if as_dict:
|
||||
ret = self.fetch_as_dict(formatted, as_utf8)
|
||||
|
|
@ -256,7 +207,7 @@ class Database:
|
|||
import json
|
||||
frappe.errprint(json.dumps(self.fetch_as_dict(), indent=1))
|
||||
frappe.errprint("--- query explain end ---")
|
||||
except:
|
||||
except Exception:
|
||||
frappe.errprint("error in query explain")
|
||||
|
||||
def sql_list(self, query, values=(), debug=False):
|
||||
|
|
@ -290,7 +241,7 @@ class Database:
|
|||
self.transaction_writes += 1
|
||||
if self.transaction_writes > 200000:
|
||||
if self.auto_commit_on_many_writes:
|
||||
frappe.db.commit()
|
||||
self.commit()
|
||||
else:
|
||||
frappe.throw(_("Too many writes in one request. Please send smaller requests"), frappe.ValidationError)
|
||||
|
||||
|
|
@ -298,26 +249,21 @@ class Database:
|
|||
"""Internal. Converts results to dict."""
|
||||
result = self._cursor.fetchall()
|
||||
ret = []
|
||||
needs_formatting = self.needs_formatting(result, formatted)
|
||||
if result:
|
||||
keys = [column[0] for column in self._cursor.description]
|
||||
|
||||
for r in result:
|
||||
values = []
|
||||
for i in range(len(r)):
|
||||
if needs_formatting:
|
||||
val = self.convert_to_simple_type(r[i], formatted)
|
||||
else:
|
||||
val = r[i]
|
||||
|
||||
if as_utf8 and type(val) is text_type:
|
||||
val = val.encode('utf-8')
|
||||
values.append(val)
|
||||
for value in r:
|
||||
if as_utf8 and isinstance(value, text_type):
|
||||
value = value.encode('utf-8')
|
||||
values.append(value)
|
||||
|
||||
ret.append(frappe._dict(zip(keys, values)))
|
||||
return ret
|
||||
|
||||
def needs_formatting(self, result, formatted):
|
||||
@staticmethod
|
||||
def needs_formatting(result, formatted):
|
||||
"""Returns true if the first row in the result has a Date, Datetime, Long Int."""
|
||||
if result and result[0]:
|
||||
for v in result[0]:
|
||||
|
|
@ -332,65 +278,21 @@ class Database:
|
|||
"""Returns result metadata."""
|
||||
return self._cursor.description
|
||||
|
||||
def convert_to_simple_type(self, v, formatted=0):
|
||||
"""Format date, time, longint values."""
|
||||
return v
|
||||
|
||||
from frappe.utils import formatdate, fmt_money
|
||||
|
||||
if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, integer_types)):
|
||||
if isinstance(v, datetime.date):
|
||||
v = text_type(v)
|
||||
if formatted:
|
||||
v = formatdate(v)
|
||||
|
||||
# time
|
||||
elif isinstance(v, (datetime.timedelta, datetime.datetime)):
|
||||
v = text_type(v)
|
||||
|
||||
# long
|
||||
elif isinstance(v, integer_types):
|
||||
v=int(v)
|
||||
|
||||
# convert to strings... (if formatted)
|
||||
if formatted:
|
||||
if isinstance(v, float):
|
||||
v=fmt_money(v)
|
||||
elif isinstance(v, int):
|
||||
v = text_type(v)
|
||||
|
||||
return v
|
||||
|
||||
def convert_to_lists(self, res, formatted=0, as_utf8=0):
|
||||
@staticmethod
|
||||
def convert_to_lists(res, formatted=0, as_utf8=0):
|
||||
"""Convert tuple output to lists (internal)."""
|
||||
nres = []
|
||||
needs_formatting = self.needs_formatting(res, formatted)
|
||||
for r in res:
|
||||
nr = []
|
||||
for c in r:
|
||||
if needs_formatting:
|
||||
val = self.convert_to_simple_type(c, formatted)
|
||||
else:
|
||||
val = c
|
||||
if as_utf8 and type(val) is text_type:
|
||||
for val in r:
|
||||
if as_utf8 and isinstance(val, text_type):
|
||||
val = val.encode('utf-8')
|
||||
nr.append(val)
|
||||
nres.append(nr)
|
||||
return nres
|
||||
|
||||
def convert_to_utf8(self, res, formatted=0):
|
||||
"""Encode result as UTF-8."""
|
||||
nres = []
|
||||
for r in res:
|
||||
nr = []
|
||||
for c in r:
|
||||
if type(c) is text_type:
|
||||
c = c.encode('utf-8')
|
||||
nr.append(self.convert_to_simple_type(c, formatted))
|
||||
nres.append(nr)
|
||||
return nres
|
||||
|
||||
def build_conditions(self, filters):
|
||||
@staticmethod
|
||||
def build_conditions(filters):
|
||||
"""Convert filters sent as dict, lists to SQL conditions. filter's key
|
||||
is passed by map function, build conditions like:
|
||||
|
||||
|
|
@ -430,7 +332,7 @@ class Database:
|
|||
|
||||
if "[" in key:
|
||||
split_key = key.split("[")
|
||||
condition = "ifnull(`" + split_key[0] + "`, " + split_key[1][:-1] + ") " \
|
||||
condition = "coalesce(`" + split_key[0] + "`, " + split_key[1][:-1] + ") " \
|
||||
+ _operator + _rhs
|
||||
else:
|
||||
condition = "`" + key + "` " + _operator + _rhs
|
||||
|
|
@ -527,10 +429,10 @@ class Database:
|
|||
try:
|
||||
out = self._get_values_from_table(fields, filters, doctype, as_dict, debug, order_by, update)
|
||||
except Exception as e:
|
||||
if ignore and e.args[0] in (1146, 1054):
|
||||
if ignore and (frappe.db.is_missing_column(e) or frappe.db.is_table_missing(e)):
|
||||
# table or column not found, return None
|
||||
out = None
|
||||
elif (not ignore) and e.args[0]==1146:
|
||||
elif (not ignore) and frappe.db.is_table_missing(e):
|
||||
# table not found, look in singles
|
||||
out = self.get_values_from_single(fields, filters, doctype, as_dict, debug, update)
|
||||
else:
|
||||
|
|
@ -566,14 +468,13 @@ class Database:
|
|||
return values and [values] or []
|
||||
|
||||
if isinstance(fields, list):
|
||||
return [map(lambda d: values.get(d), fields)]
|
||||
return [map(values.get, fields)]
|
||||
|
||||
else:
|
||||
r = self.sql("""select field, value
|
||||
from tabSingles where field in (%s) and doctype=%s""" \
|
||||
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:
|
||||
|
|
@ -607,10 +508,12 @@ class Database:
|
|||
|
||||
return dict_
|
||||
|
||||
def get_all(self, *args, **kwargs):
|
||||
@staticmethod
|
||||
def get_all(*args, **kwargs):
|
||||
return frappe.get_all(*args, **kwargs)
|
||||
|
||||
def get_list(self, *args, **kwargs):
|
||||
@staticmethod
|
||||
def get_list(*args, **kwargs):
|
||||
return frappe.get_list(*args, **kwargs)
|
||||
|
||||
def get_single_value(self, doctype, fieldname, cache=False):
|
||||
|
|
@ -631,8 +534,8 @@ class Database:
|
|||
if fieldname in self.value_cache[doctype]:
|
||||
return self.value_cache[doctype][fieldname]
|
||||
|
||||
val = self.sql("""select value from
|
||||
tabSingles where doctype=%s and field=%s""", (doctype, fieldname))
|
||||
val = self.sql("""select `value` from
|
||||
`tabSingles` where `doctype`=%s and `field`=%s""", (doctype, fieldname))
|
||||
val = val[0][0] if val else None
|
||||
|
||||
if val=="0" or val=="1":
|
||||
|
|
@ -675,8 +578,10 @@ class Database:
|
|||
names = list(filter(None, names))
|
||||
|
||||
if names:
|
||||
return dict(self.sql("select name, `%s` from `tab%s` where name in (%s)" \
|
||||
% (field, doctype, ", ".join(["%s"]*len(names))), names, debug=debug))
|
||||
return self.get_all(doctype,
|
||||
fields=['name', field],
|
||||
filters=[['name', 'in', names]],
|
||||
debug=debug, as_list=1)
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
|
@ -732,12 +637,12 @@ class Database:
|
|||
# for singles
|
||||
keys = list(to_update)
|
||||
self.sql('''
|
||||
delete from tabSingles
|
||||
delete from `tabSingles`
|
||||
where field in ({0}) and
|
||||
doctype=%s'''.format(', '.join(['%s']*len(keys))),
|
||||
list(keys) + [dt], debug=debug)
|
||||
for key, value in iteritems(to_update):
|
||||
self.sql('''insert into tabSingles(doctype, field, value) values (%s, %s, %s)''',
|
||||
self.sql('''insert into `tabSingles` (doctype, field, value) values (%s, %s, %s)''',
|
||||
(dt, key, value), debug=debug)
|
||||
|
||||
if dt in self.value_cache:
|
||||
|
|
@ -745,25 +650,27 @@ class Database:
|
|||
|
||||
frappe.clear_document_cache(dt, dn)
|
||||
|
||||
def set(self, doc, field, val):
|
||||
@staticmethod
|
||||
def set(doc, field, val):
|
||||
"""Set value in document. **Avoid**"""
|
||||
doc.db_set(field, val)
|
||||
|
||||
def touch(self, doctype, docname):
|
||||
"""Update the modified timestamp of this document."""
|
||||
from frappe.utils import now
|
||||
modified = now()
|
||||
frappe.db.sql("""update `tab{doctype}` set `modified`=%s
|
||||
self.sql("""update `tab{doctype}` set `modified`=%s
|
||||
where name=%s""".format(doctype=doctype), (modified, docname))
|
||||
return modified
|
||||
|
||||
def set_temp(self, value):
|
||||
@staticmethod
|
||||
def set_temp(value):
|
||||
"""Set a temperory value and return a key."""
|
||||
key = frappe.generate_hash()
|
||||
frappe.cache().hset("temp", key, value)
|
||||
return key
|
||||
|
||||
def get_temp(self, key):
|
||||
@staticmethod
|
||||
def get_temp(key):
|
||||
"""Return the temperory value and delete it."""
|
||||
return frappe.cache().hget("temp", key)
|
||||
|
||||
|
|
@ -775,20 +682,24 @@ class Database:
|
|||
"""Returns a global key value."""
|
||||
return self.get_default(key, user)
|
||||
|
||||
def set_default(self, key, val, parent="__default", parenttype=None):
|
||||
"""Sets a global / user default value."""
|
||||
frappe.defaults.set_default(key, val, parent, parenttype)
|
||||
|
||||
def add_default(self, key, val, parent="__default", parenttype=None):
|
||||
"""Append a default value for a key, there can be multiple default values for a particular key."""
|
||||
frappe.defaults.add_default(key, val, parent, parenttype)
|
||||
|
||||
def get_default(self, key, parent="__default"):
|
||||
"""Returns default value as a list if multiple or single"""
|
||||
d = self.get_defaults(key, parent)
|
||||
return isinstance(d, list) and d[0] or d
|
||||
|
||||
def get_defaults(self, key=None, parent="__default"):
|
||||
@staticmethod
|
||||
def set_default(key, val, parent="__default", parenttype=None):
|
||||
"""Sets a global / user default value."""
|
||||
frappe.defaults.set_default(key, val, parent, parenttype)
|
||||
|
||||
@staticmethod
|
||||
def add_default(key, val, parent="__default", parenttype=None):
|
||||
"""Append a default value for a key, there can be multiple default values for a particular key."""
|
||||
frappe.defaults.add_default(key, val, parent, parenttype)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_defaults(key=None, parent="__default"):
|
||||
"""Get all defaults"""
|
||||
if key:
|
||||
defaults = frappe.defaults.get_defaults(parent)
|
||||
|
|
@ -800,7 +711,7 @@ class Database:
|
|||
return frappe.defaults.get_defaults(parent)
|
||||
|
||||
def begin(self):
|
||||
self.sql("start transaction")
|
||||
self.sql("START TRANSACTION")
|
||||
|
||||
def commit(self):
|
||||
"""Commit current transaction. Calls SQL `COMMIT`."""
|
||||
|
|
@ -810,7 +721,8 @@ class Database:
|
|||
enqueue_jobs_after_commit()
|
||||
flush_local_link_count()
|
||||
|
||||
def flush_realtime_log(self):
|
||||
@staticmethod
|
||||
def flush_realtime_log():
|
||||
for args in frappe.local.realtime_log:
|
||||
frappe.realtime.emit_via_redis(*args)
|
||||
|
||||
|
|
@ -834,7 +746,7 @@ class Database:
|
|||
return ("tab" + doctype) in self.get_tables()
|
||||
|
||||
def get_tables(self):
|
||||
return [d[0] for d in self.sql("show tables")]
|
||||
return [d[0] for d in self.sql("select table_name from information_schema.tables where table_schema not in ('pg_catalog', 'information_schema')")]
|
||||
|
||||
def a_row_exists(self, doctype):
|
||||
"""Returns True if atleast one row exists."""
|
||||
|
|
@ -850,7 +762,7 @@ class Database:
|
|||
return True # single always exists (!)
|
||||
try:
|
||||
return self.get_value(dt, dn, "name", cache=cache)
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
elif isinstance(dt, dict) and dt.get('doctype'):
|
||||
|
|
@ -858,10 +770,9 @@ class Database:
|
|||
conditions = []
|
||||
for d in dt:
|
||||
if d == 'doctype': continue
|
||||
conditions.append('`%s` = "%s"' % (d, cstr(dt[d]).replace('"', '\"')))
|
||||
return self.sql('select name from `tab%s` where %s' % \
|
||||
(dt['doctype'], " and ".join(conditions)))
|
||||
except:
|
||||
conditions.append([d, '=', dt[d]])
|
||||
return self.get_all(dt['doctype'], filters=conditions, as_list=1)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def count(self, dt, filters=None, debug=False, cache=False):
|
||||
|
|
@ -872,11 +783,11 @@ class Database:
|
|||
return cache_count
|
||||
if filters:
|
||||
conditions, filters = self.build_conditions(filters)
|
||||
count = frappe.db.sql("""select count(*)
|
||||
count = self.sql("""select count(*)
|
||||
from `tab%s` where %s""" % (dt, conditions), filters, debug=debug)[0][0]
|
||||
return count
|
||||
else:
|
||||
count = frappe.db.sql("""select count(*)
|
||||
count = self.sql("""select count(*)
|
||||
from `tab%s`""" % (dt,))[0][0]
|
||||
|
||||
if cache:
|
||||
|
|
@ -884,58 +795,69 @@ class Database:
|
|||
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
def format_date(date):
|
||||
return getdate(date).strftime("%Y-%m-%d")
|
||||
|
||||
@staticmethod
|
||||
def format_datetime(datetime):
|
||||
if not datetime:
|
||||
return '0001-01-01 00:00:00.000000'
|
||||
|
||||
if isinstance(datetime, frappe.string_types):
|
||||
if ':' not in datetime:
|
||||
datetime = datetime + ' 00:00:00.000000'
|
||||
else:
|
||||
datetime = datetime.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
|
||||
return datetime
|
||||
|
||||
def get_creation_count(self, doctype, minutes):
|
||||
"""Get count of records created in the last x minutes"""
|
||||
from frappe.utils import now_datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
return frappe.db.sql("""select count(name) from `tab{doctype}`
|
||||
return self.sql("""select count(name) from `tab{doctype}`
|
||||
where creation >= %s""".format(doctype=doctype),
|
||||
now_datetime() - relativedelta(minutes=minutes))[0][0]
|
||||
|
||||
def get_db_table_columns(self, table):
|
||||
"""Returns list of column names from given table."""
|
||||
return [r[0] for r in self.sql("DESC `%s`" % table)]
|
||||
return [r[0] for r in self.sql('''
|
||||
select column_name
|
||||
from information_schema.columns
|
||||
where table_name = %s ''', table)]
|
||||
|
||||
def get_table_columns(self, doctype):
|
||||
"""Returns list of column names from given doctype."""
|
||||
return self.get_db_table_columns('tab' + doctype)
|
||||
columns = self.get_db_table_columns('tab' + doctype)
|
||||
if not columns:
|
||||
raise self.ProgrammingError
|
||||
return columns
|
||||
|
||||
def has_column(self, doctype, column):
|
||||
"""Returns True if column exists in database."""
|
||||
return column in self.get_table_columns(doctype)
|
||||
|
||||
def get_column_type(self, doctype, column):
|
||||
return frappe.db.sql('''SELECT column_type FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'tab{0}' AND COLUMN_NAME = "{1}"'''.format(doctype, column))[0][0]
|
||||
return self.sql('''SELECT column_type FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'tab{0}' AND column_name = '{1}' '''.format(doctype, column))[0][0]
|
||||
|
||||
def has_index(self, table_name, index_name):
|
||||
pass
|
||||
|
||||
def add_index(self, doctype, fields, index_name=None):
|
||||
"""Creates an index with given fields if not already created.
|
||||
Index name will be `fieldname1_fieldname2_index`"""
|
||||
if not index_name:
|
||||
index_name = "_".join(fields) + "_index"
|
||||
|
||||
# remove index length if present e.g. (10) from index name
|
||||
index_name = re.sub(r"\s*\([^)]+\)\s*", r"", index_name)
|
||||
|
||||
if not frappe.db.sql("""show index from `tab%s` where Key_name="%s" """ % (doctype, index_name)):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql("""alter table `tab%s`
|
||||
add index `%s`(%s)""" % (doctype, index_name, ", ".join(fields)))
|
||||
pass
|
||||
|
||||
def add_unique(self, doctype, fields, constraint_name=None):
|
||||
if isinstance(fields, string_types):
|
||||
fields = [fields]
|
||||
if not constraint_name:
|
||||
constraint_name = "unique_" + "_".join(fields)
|
||||
pass
|
||||
|
||||
if not frappe.db.sql("""select CONSTRAINT_NAME from information_schema.TABLE_CONSTRAINTS
|
||||
where table_name=%s and constraint_type='UNIQUE' and CONSTRAINT_NAME=%s""",
|
||||
('tab' + doctype, constraint_name)):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql("""alter table `tab%s`
|
||||
add unique `%s`(%s)""" % (doctype, constraint_name, ", ".join(fields)))
|
||||
@staticmethod
|
||||
def get_index_name(fields):
|
||||
index_name = "_".join(fields) + "_index"
|
||||
# remove index length if present e.g. (10) from index name
|
||||
index_name = re.sub(r"\s*\([^)]+\)\s*", r"", index_name)
|
||||
return index_name
|
||||
|
||||
def get_system_setting(self, key):
|
||||
def _load_system_settings():
|
||||
|
|
@ -950,20 +872,11 @@ class Database:
|
|||
self._cursor = None
|
||||
self._conn = None
|
||||
|
||||
def escape(self, s, percent=True):
|
||||
@staticmethod
|
||||
def escape(s, percent=True):
|
||||
"""Excape quotes and percent in given string."""
|
||||
# pymysql expects unicode argument to escape_string with Python 3
|
||||
s = as_unicode(pymysql.escape_string(as_unicode(s)), "utf-8").replace("`", "\\`")
|
||||
|
||||
# NOTE separating % escape, because % escape should only be done when using LIKE operator
|
||||
# or when you use python format string to generate query that already has a %s
|
||||
# for example: sql("select name from `tabUser` where name=%s and {0}".format(conditions), something)
|
||||
# defaulting it to True, as this is the most frequent use case
|
||||
# ideally we shouldn't have to use ESCAPE and strive to pass values via the values argument of sql
|
||||
if percent:
|
||||
s = s.replace("%", "%%")
|
||||
|
||||
return s
|
||||
# implemented in specific class
|
||||
pass
|
||||
|
||||
def get_descendants(self, doctype, name):
|
||||
'''Return descendants of the current record'''
|
||||
|
|
@ -971,6 +884,25 @@ class Database:
|
|||
return self.sql_list('''select name from `tab{doctype}`
|
||||
where lft > {lft} and rgt < {rgt}'''.format(doctype=doctype, lft=lft, rgt=rgt))
|
||||
|
||||
def is_missing_table_or_column(self, e):
|
||||
return self.is_missing_column(e) or self.is_missing_table(e)
|
||||
|
||||
def multisql(self, sql_dict, values=(), **kwargs):
|
||||
current_dialect = frappe.conf.db_type or 'mariadb'
|
||||
query = sql_dict.get(current_dialect)
|
||||
return self.sql(query, values, **kwargs)
|
||||
|
||||
def delete(self, doctype, conditions):
|
||||
if conditions:
|
||||
conditions, values = self.build_conditions(conditions)
|
||||
return self.sql("DELETE FROM `tab{doctype}` where {conditions}".format(
|
||||
doctype=doctype,
|
||||
conditions=conditions
|
||||
), values)
|
||||
else:
|
||||
frappe.throw('No conditions provided')
|
||||
|
||||
|
||||
def enqueue_jobs_after_commit():
|
||||
if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0:
|
||||
for job in frappe.flags.enqueue_after_commit:
|
||||
|
|
@ -978,3 +910,19 @@ def enqueue_jobs_after_commit():
|
|||
q.enqueue_call(execute_job, timeout=job.get("timeout"),
|
||||
kwargs=job.get("queue_args"))
|
||||
frappe.flags.enqueue_after_commit = []
|
||||
|
||||
# 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)
|
||||
88
frappe/database/db_manager.py
Normal file
88
frappe/database/db_manager.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import os
|
||||
import frappe
|
||||
|
||||
|
||||
class DbManager:
|
||||
|
||||
def __init__(self, db):
|
||||
"""
|
||||
Pass root_conn here for access to all databases.
|
||||
"""
|
||||
if db:
|
||||
self.db = db
|
||||
|
||||
def get_current_host(self):
|
||||
return self.db.sql("select user()")[0][0].split('@')[1]
|
||||
|
||||
def create_user(self, user, password, host=None):
|
||||
# Create user if it doesn't exist.
|
||||
if not host:
|
||||
host = self.get_current_host()
|
||||
|
||||
if password:
|
||||
self.db.sql("CREATE USER '%s'@'%s' IDENTIFIED BY '%s';" % (user, host, password))
|
||||
else:
|
||||
self.db.sql("CREATE USER '%s'@'%s';" % (user, host))
|
||||
|
||||
def delete_user(self, target, host=None):
|
||||
if not host:
|
||||
host = self.get_current_host()
|
||||
try:
|
||||
self.db.sql("DROP USER '%s'@'%s';" % (target, host))
|
||||
except Exception as e:
|
||||
if e.args[0] == 1396:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
def create_database(self, target):
|
||||
if target in self.get_database_list():
|
||||
self.drop_database(target)
|
||||
|
||||
self.db.sql("CREATE DATABASE `%s` ;" % target)
|
||||
|
||||
def drop_database(self, target):
|
||||
self.db.sql("DROP DATABASE IF EXISTS `%s`;" % target)
|
||||
|
||||
def grant_all_privileges(self, target, user, host=None):
|
||||
if not host:
|
||||
host = self.get_current_host()
|
||||
|
||||
self.db.sql("GRANT ALL PRIVILEGES ON `%s`.* TO '%s'@'%s';" % (target, user, host))
|
||||
|
||||
def flush_privileges(self):
|
||||
self.db.sql("FLUSH PRIVILEGES")
|
||||
|
||||
def get_database_list(self):
|
||||
"""get list of databases"""
|
||||
return [d[0] for d in self.db.sql("SHOW DATABASES")]
|
||||
|
||||
@staticmethod
|
||||
def restore_database(target, source, user, password):
|
||||
from frappe.utils import make_esc
|
||||
esc = make_esc('$ ')
|
||||
|
||||
from distutils.spawn import find_executable
|
||||
pipe = find_executable('pv')
|
||||
if pipe:
|
||||
pipe = '{pipe} {source} |'.format(
|
||||
pipe=pipe,
|
||||
source=source
|
||||
)
|
||||
source = ''
|
||||
else:
|
||||
pipe = ''
|
||||
source = '< {source}'.format(source=source)
|
||||
|
||||
if pipe:
|
||||
print('Creating Database...')
|
||||
|
||||
command = '{pipe} mysql -u {user} -p{password} -h{host} {target} {source}'.format(
|
||||
pipe=pipe,
|
||||
user=esc(user),
|
||||
password=esc(password),
|
||||
host=esc(frappe.db.host),
|
||||
target=esc(target),
|
||||
source=source
|
||||
)
|
||||
os.system(command)
|
||||
0
frappe/database/mariadb/__init__.py
Normal file
0
frappe/database/mariadb/__init__.py
Normal file
282
frappe/database/mariadb/database.py
Normal file
282
frappe/database/mariadb/database.py
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import warnings
|
||||
|
||||
import pymysql
|
||||
from pymysql.times import TimeDelta
|
||||
from pymysql.constants import ER, FIELD_TYPE
|
||||
from pymysql.converters import conversions
|
||||
|
||||
from frappe.utils import get_datetime, cstr
|
||||
from markdown2 import UnicodeWithAttrs
|
||||
from frappe.database.database import Database
|
||||
from six import PY2, binary_type, text_type, string_types
|
||||
from frappe.database.mariadb.schema import MariaDBTable
|
||||
|
||||
|
||||
class MariaDBDatabase(Database):
|
||||
ProgrammingError = pymysql.err.ProgrammingError
|
||||
OperationalError = pymysql.err.OperationalError
|
||||
InternalError = pymysql.err.InternalError
|
||||
SQLError = pymysql.err.ProgrammingError
|
||||
DataError = pymysql.err.DataError
|
||||
REGEX_CHARACTER = 'regexp'
|
||||
|
||||
def setup_type_map(self):
|
||||
self.type_map = {
|
||||
'Currency': ('decimal', '18,6'),
|
||||
'Int': ('int', '11'),
|
||||
'Long Int': ('bigint', '20'), # convert int to bigint if length is more than 11
|
||||
'Float': ('decimal', '18,6'),
|
||||
'Percent': ('decimal', '18,6'),
|
||||
'Check': ('int', '1'),
|
||||
'Small Text': ('text', ''),
|
||||
'Long Text': ('longtext', ''),
|
||||
'Code': ('longtext', ''),
|
||||
'Text Editor': ('longtext', ''),
|
||||
'Date': ('date', ''),
|
||||
'Datetime': ('datetime', '6'),
|
||||
'Time': ('time', '6'),
|
||||
'Text': ('text', ''),
|
||||
'Data': ('varchar', self.VARCHAR_LEN),
|
||||
'Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Dynamic Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Password': ('varchar', self.VARCHAR_LEN),
|
||||
'Select': ('varchar', self.VARCHAR_LEN),
|
||||
'Read Only': ('varchar', self.VARCHAR_LEN),
|
||||
'Attach': ('text', ''),
|
||||
'Attach Image': ('text', ''),
|
||||
'Signature': ('longtext', ''),
|
||||
'Color': ('varchar', self.VARCHAR_LEN),
|
||||
'Barcode': ('longtext', ''),
|
||||
'Geolocation': ('longtext', '')
|
||||
}
|
||||
|
||||
def get_connection(self):
|
||||
warnings.filterwarnings('ignore', category=pymysql.Warning)
|
||||
usessl = 0
|
||||
if frappe.conf.db_ssl_ca and frappe.conf.db_ssl_cert and frappe.conf.db_ssl_key:
|
||||
usessl = 1
|
||||
ssl_params = {
|
||||
'ca':frappe.conf.db_ssl_ca,
|
||||
'cert':frappe.conf.db_ssl_cert,
|
||||
'key':frappe.conf.db_ssl_key
|
||||
}
|
||||
|
||||
conversions.update({
|
||||
FIELD_TYPE.NEWDECIMAL: float,
|
||||
FIELD_TYPE.DATETIME: get_datetime,
|
||||
UnicodeWithAttrs: conversions[text_type]
|
||||
})
|
||||
|
||||
if PY2:
|
||||
conversions.update({
|
||||
TimeDelta: conversions[binary_type]
|
||||
})
|
||||
|
||||
if usessl:
|
||||
conn = pymysql.connect(self.host, self.user or '', self.password or '',
|
||||
charset='utf8mb4', use_unicode = True, ssl=ssl_params,
|
||||
conv = conversions, local_infile = frappe.conf.local_infile)
|
||||
else:
|
||||
conn = pymysql.connect(self.host, self.user or '', self.password or '',
|
||||
charset='utf8mb4', use_unicode = True, conv = conversions,
|
||||
local_infile = frappe.conf.local_infile)
|
||||
|
||||
# MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1
|
||||
# # self._conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_OFF)
|
||||
|
||||
if self.user != 'root':
|
||||
conn.select_db(self.user)
|
||||
|
||||
return conn
|
||||
|
||||
def get_database_size(self):
|
||||
''''Returns database size in MB'''
|
||||
db_size = self.sql('''
|
||||
SELECT `table_schema` as `database_name`,
|
||||
SUM(`data_length` + `index_length`) / 1024 / 1024 AS `database_size`
|
||||
FROM information_schema.tables WHERE `table_schema` = %s GROUP BY `table_schema`
|
||||
''', self.db_name, as_dict=True)
|
||||
|
||||
return db_size[0].get('database_size')
|
||||
|
||||
@staticmethod
|
||||
def escape(s, percent=True):
|
||||
"""Excape quotes and percent in given string."""
|
||||
# pymysql expects unicode argument to escape_string with Python 3
|
||||
s = frappe.as_unicode(pymysql.escape_string(frappe.as_unicode(s)), "utf-8").replace("`", "\\`")
|
||||
|
||||
# NOTE separating % escape, because % escape should only be done when using LIKE operator
|
||||
# or when you use python format string to generate query that already has a %s
|
||||
# for example: sql("select name from `tabUser` where name=%s and {0}".format(conditions), something)
|
||||
# defaulting it to True, as this is the most frequent use case
|
||||
# ideally we shouldn't have to use ESCAPE and strive to pass values via the values argument of sql
|
||||
if percent:
|
||||
s = s.replace("%", "%%")
|
||||
|
||||
return "'" + s + "'"
|
||||
|
||||
# column type
|
||||
@staticmethod
|
||||
def is_type_number(code):
|
||||
return code == pymysql.NUMBER
|
||||
|
||||
@staticmethod
|
||||
def is_type_datetime(code):
|
||||
return code in (pymysql.DATE, pymysql.DATETIME)
|
||||
|
||||
# exception types
|
||||
@staticmethod
|
||||
def is_deadlocked(e):
|
||||
return e.args[0] == ER.LOCK_DEADLOCK
|
||||
|
||||
@staticmethod
|
||||
def is_timedout(e):
|
||||
return e.args[0] == ER.LOCK_WAIT_TIMEOUT
|
||||
|
||||
@staticmethod
|
||||
def is_table_missing(e):
|
||||
return e.args[0] == ER.NO_SUCH_TABLE
|
||||
|
||||
@staticmethod
|
||||
def is_missing_column(e):
|
||||
return e.args[0] == ER.BAD_FIELD_ERROR
|
||||
|
||||
@staticmethod
|
||||
def is_duplicate_fieldname(e):
|
||||
return e.args[0] == ER.DUP_FIELDNAME
|
||||
|
||||
@staticmethod
|
||||
def is_duplicate_entry(e):
|
||||
return e.args[0] == ER.DUP_ENTRY
|
||||
|
||||
@staticmethod
|
||||
def is_access_denied( e):
|
||||
return e.args[0] == ER.ACCESS_DENIED_ERROR
|
||||
|
||||
@staticmethod
|
||||
def cant_drop_field_or_key(e):
|
||||
return e.args[0] == ER.CANT_DROP_FIELD_OR_KEY
|
||||
|
||||
def is_primary_key_violation(self, e):
|
||||
return self.is_duplicate_entry(e) and 'PRIMARY' in cstr(e.args[1])
|
||||
|
||||
def is_unique_key_violation(self, e):
|
||||
return self.is_duplicate_entry(e) and 'Duplicate' in cstr(e.args[1])
|
||||
|
||||
|
||||
def create_auth_table(self):
|
||||
self.sql_ddl("""create table if not exists `__Auth` (
|
||||
`doctype` VARCHAR(140) NOT NULL,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`fieldname` VARCHAR(140) NOT NULL,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`encrypted` INT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`doctype`, `name`, `fieldname`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
|
||||
|
||||
def create_global_search_table(self):
|
||||
if not '__global_search' in self.get_tables():
|
||||
self.sql('''create table __global_search(
|
||||
doctype varchar(100),
|
||||
name varchar({0}),
|
||||
title varchar({0}),
|
||||
content text,
|
||||
fulltext(content),
|
||||
route varchar({0}),
|
||||
published int(1) not null default 0,
|
||||
unique `doctype_name` (doctype, name))
|
||||
COLLATE=utf8mb4_unicode_ci
|
||||
ENGINE=MyISAM
|
||||
CHARACTER SET=utf8mb4'''.format(self.VARCHAR_LEN))
|
||||
|
||||
def create_user_settings_table(self):
|
||||
self.sql_ddl("""create table if not exists __UserSettings (
|
||||
`user` VARCHAR(180) NOT NULL,
|
||||
`doctype` VARCHAR(180) NOT NULL,
|
||||
`data` TEXT,
|
||||
UNIQUE(user, doctype)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8""")
|
||||
|
||||
def create_help_table(self):
|
||||
self.sql('''create table help(
|
||||
path varchar(255),
|
||||
content text,
|
||||
title text,
|
||||
intro text,
|
||||
full_path text,
|
||||
fulltext(title),
|
||||
fulltext(content),
|
||||
index (path))
|
||||
COLLATE=utf8mb4_unicode_ci
|
||||
ENGINE=MyISAM
|
||||
CHARACTER SET=utf8mb4''')
|
||||
|
||||
@staticmethod
|
||||
def get_on_duplicate_update(key=None):
|
||||
return 'ON DUPLICATE key UPDATE '
|
||||
|
||||
def get_table_columns_description(self, table_name):
|
||||
"""Returns list of column and its description"""
|
||||
return self.sql('''select
|
||||
column_name as 'name',
|
||||
column_type as 'type',
|
||||
column_default as 'default',
|
||||
column_key = 'MUL' as 'index',
|
||||
column_key = 'UNI' as 'unique'
|
||||
from information_schema.columns
|
||||
where table_name = '{table_name}' '''.format(table_name=table_name), as_dict=1)
|
||||
|
||||
def has_index(self, table_name, index_name):
|
||||
return self.sql("""SHOW INDEX FROM `{table_name}`
|
||||
WHERE Key_name='{index_name}'""".format(
|
||||
table_name=table_name,
|
||||
index_name=index_name
|
||||
))
|
||||
|
||||
def add_index(self, doctype, fields, index_name=None):
|
||||
"""Creates an index with given fields if not already created.
|
||||
Index name will be `fieldname1_fieldname2_index`"""
|
||||
index_name = index_name or self.get_index_name(fields)
|
||||
table_name = 'tab' + doctype
|
||||
if not self.has_index(table_name, index_name):
|
||||
self.commit()
|
||||
self.sql("""ALTER TABLE `%s`
|
||||
ADD INDEX `%s`(%s)""" % (table_name, index_name, ", ".join(fields)))
|
||||
|
||||
def add_unique(self, doctype, fields, constraint_name=None):
|
||||
if isinstance(fields, string_types):
|
||||
fields = [fields]
|
||||
if not constraint_name:
|
||||
constraint_name = "unique_" + "_".join(fields)
|
||||
|
||||
if not self.sql("""select CONSTRAINT_NAME from information_schema.TABLE_CONSTRAINTS
|
||||
where table_name=%s and constraint_type='UNIQUE' and CONSTRAINT_NAME=%s""",
|
||||
('tab' + doctype, constraint_name)):
|
||||
self.commit()
|
||||
self.sql("""alter table `tab%s`
|
||||
add unique `%s`(%s)""" % (doctype, constraint_name, ", ".join(fields)))
|
||||
|
||||
def updatedb(self, doctype, meta=None):
|
||||
"""
|
||||
Syncs a `DocType` to the table
|
||||
* creates if required
|
||||
* updates columns
|
||||
* updates indices
|
||||
"""
|
||||
res = self.sql("select issingle from `tabDocType` where name=%s", (doctype,))
|
||||
if not res:
|
||||
raise Exception('Wrong doctype {0} in updatedb'.format(doctype))
|
||||
|
||||
if not res[0][0]:
|
||||
db_table = MariaDBTable(doctype, meta)
|
||||
db_table.validate()
|
||||
|
||||
self.commit()
|
||||
db_table.sync()
|
||||
self.begin()
|
||||
|
||||
def get_database_list(self, target):
|
||||
return [d[0] for d in self.sql("SHOW DATABASES;")]
|
||||
86
frappe/database/mariadb/schema.py
Normal file
86
frappe/database/mariadb/schema.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.database.schema import DBTable
|
||||
|
||||
class MariaDBTable(DBTable):
|
||||
def create(self):
|
||||
add_text = ''
|
||||
|
||||
# columns
|
||||
column_defs = self.get_column_definitions()
|
||||
if column_defs: add_text += ',\n'.join(column_defs) + ',\n'
|
||||
|
||||
# index
|
||||
index_defs = self.get_index_definitions()
|
||||
if index_defs: add_text += ',\n'.join(index_defs) + ',\n'
|
||||
|
||||
# create table
|
||||
frappe.db.sql("""create table `%s` (
|
||||
name varchar({varchar_len}) not null primary key,
|
||||
creation datetime(6),
|
||||
modified datetime(6),
|
||||
modified_by varchar({varchar_len}),
|
||||
owner varchar({varchar_len}),
|
||||
docstatus int(1) not null default '0',
|
||||
parent varchar({varchar_len}),
|
||||
parentfield varchar({varchar_len}),
|
||||
parenttype varchar({varchar_len}),
|
||||
idx int(8) not null default '0',
|
||||
%sindex parent(parent),
|
||||
index modified(modified))
|
||||
ENGINE={engine}
|
||||
ROW_FORMAT=COMPRESSED
|
||||
CHARACTER SET=utf8mb4
|
||||
COLLATE=utf8mb4_unicode_ci""".format(varchar_len=frappe.db.VARCHAR_LEN,
|
||||
engine=self.meta.get("engine") or 'InnoDB') % (self.table_name, add_text))
|
||||
|
||||
def alter(self):
|
||||
for col in self.columns.values():
|
||||
col.build_for_alter_table(self.current_columns.get(col.fieldname.lower()))
|
||||
|
||||
add_column_query = []
|
||||
modify_column_query = []
|
||||
add_index_query = []
|
||||
drop_index_query = []
|
||||
|
||||
columns_to_modify = set(self.change_type + self.add_unique + self.set_default)
|
||||
|
||||
for col in self.add_column:
|
||||
add_column_query.append("ADD COLUMN `{}` {}".format(col.fieldname, col.get_definition()))
|
||||
|
||||
for col in columns_to_modify:
|
||||
modify_column_query.append("MODIFY `{}` {}".format(col.fieldname, col.get_definition()))
|
||||
|
||||
for col in self.add_index:
|
||||
# if index key not exists
|
||||
if not frappe.db.sql("SHOW INDEX FROM `%s` WHERE key_name = %s" %
|
||||
(self.table_name, '%s'), col.fieldname):
|
||||
add_index_query.append("ADD INDEX `{}`(`{}`)".format(col.fieldname, col.fieldname))
|
||||
|
||||
for col in self.drop_index:
|
||||
if col.fieldname != 'name': # primary key
|
||||
# if index key exists
|
||||
if frappe.db.sql("""SHOW INDEX FROM `{0}`
|
||||
WHERE key_name=%s
|
||||
AND Non_unique=%s""".format(self.table_name), (col.fieldname, col.unique)):
|
||||
drop_index_query.append("drop index `{}`".format(col.fieldname))
|
||||
|
||||
try:
|
||||
for query_parts in [add_column_query, modify_column_query, add_index_query, drop_index_query]:
|
||||
if query_parts:
|
||||
query_body = ", ".join(query_parts)
|
||||
query = "ALTER TABLE `{}` {}".format(self.table_name, query_body)
|
||||
frappe.db.sql(query)
|
||||
|
||||
except Exception as e:
|
||||
# sanitize
|
||||
if e.args[0]==1060:
|
||||
frappe.throw(str(e))
|
||||
elif e.args[0]==1062:
|
||||
fieldname = str(e).split("'")[-2]
|
||||
frappe.throw(_("{0} field cannot be set as unique in {1}, as there are non-unique existing values".format(
|
||||
fieldname, self.table_name)))
|
||||
else:
|
||||
raise e
|
||||
148
frappe/database/mariadb/setup_db.py
Normal file
148
frappe/database/mariadb/setup_db.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import os, sys
|
||||
from frappe.database.db_manager import DbManager
|
||||
|
||||
def setup_database(force, source_sql, verbose):
|
||||
frappe.local.session = frappe._dict({'user':'Administrator'})
|
||||
|
||||
db_name = frappe.local.conf.db_name
|
||||
root_conn = get_root_connection(frappe.flags.root_login, frappe.flags.root_password)
|
||||
dbman = DbManager(root_conn)
|
||||
if force or (db_name not in dbman.get_database_list()):
|
||||
dbman.delete_user(db_name)
|
||||
dbman.drop_database(db_name)
|
||||
else:
|
||||
raise Exception("Database %s already exists" % (db_name,))
|
||||
|
||||
dbman.create_user(db_name, frappe.conf.db_password)
|
||||
if verbose: print("Created user %s" % db_name)
|
||||
|
||||
dbman.create_database(db_name)
|
||||
if verbose: print("Created database %s" % db_name)
|
||||
|
||||
dbman.grant_all_privileges(db_name, db_name)
|
||||
dbman.flush_privileges()
|
||||
if verbose: print("Granted privileges to user %s and database %s" % (db_name, db_name))
|
||||
|
||||
# close root connection
|
||||
root_conn.close()
|
||||
|
||||
bootstrap_database(db_name, verbose, source_sql)
|
||||
|
||||
def setup_help_database(help_db_name):
|
||||
dbman = DbManager(get_root_connection(frappe.flags.root_login, frappe.flags.root_password))
|
||||
dbman.drop_database(help_db_name)
|
||||
|
||||
# make database
|
||||
if not help_db_name in dbman.get_database_list():
|
||||
try:
|
||||
dbman.create_user(help_db_name, help_db_name)
|
||||
except Exception as e:
|
||||
# user already exists
|
||||
if e.args[0] != 1396: raise
|
||||
dbman.create_database(help_db_name)
|
||||
dbman.grant_all_privileges(help_db_name, help_db_name)
|
||||
dbman.flush_privileges()
|
||||
|
||||
def drop_user_and_database(db_name, root_login, root_password):
|
||||
frappe.local.db = get_root_connection(root_login, root_password)
|
||||
dbman = DbManager(frappe.local.db)
|
||||
dbman.delete_user(db_name)
|
||||
dbman.drop_database(db_name)
|
||||
|
||||
def bootstrap_database(db_name, verbose, source_sql=None):
|
||||
frappe.connect(db_name=db_name)
|
||||
check_if_ready_for_barracuda()
|
||||
import_db_from_sql(source_sql, verbose)
|
||||
if not 'tabDefaultValue' in frappe.db.get_tables():
|
||||
print('''Database not installed, this can due to lack of permission, or that the database name exists.
|
||||
Check your mysql root password, or use --force to reinstall''')
|
||||
sys.exit(1)
|
||||
|
||||
def import_db_from_sql(source_sql=None, verbose=False):
|
||||
if verbose: print("Starting database import...")
|
||||
db_name = frappe.conf.db_name
|
||||
if not source_sql:
|
||||
source_sql = os.path.join(os.path.dirname(__file__), 'framework_mariadb.sql')
|
||||
DbManager(frappe.local.db).restore_database(db_name, source_sql, db_name, frappe.conf.db_password)
|
||||
if verbose: print("Imported from database %s" % source_sql)
|
||||
|
||||
def check_if_ready_for_barracuda():
|
||||
mariadb_variables = frappe._dict(frappe.db.sql("""show variables"""))
|
||||
mariadb_minor_version = int(mariadb_variables.get('version').split('-')[0].split('.')[1])
|
||||
if mariadb_minor_version < 3:
|
||||
check_database(mariadb_variables, {
|
||||
"innodb_file_format": "Barracuda",
|
||||
"innodb_file_per_table": "ON",
|
||||
"innodb_large_prefix": "ON"
|
||||
})
|
||||
check_database(mariadb_variables, {
|
||||
"character_set_server": "utf8mb4",
|
||||
"collation_server": "utf8mb4_unicode_ci"
|
||||
})
|
||||
|
||||
def check_database(mariadb_variables, variables_dict):
|
||||
mariadb_minor_version = int(mariadb_variables.get('version').split('-')[0].split('.')[1])
|
||||
for key, value in variables_dict.items():
|
||||
if mariadb_variables.get(key) != value:
|
||||
site = frappe.local.site
|
||||
msg = ("Creation of your site - {x} failed because MariaDB is not properly {sep}"
|
||||
"configured to use the Barracuda storage engine. {sep}"
|
||||
"Please add the settings below to MariaDB's my.cnf, restart MariaDB then {sep}"
|
||||
"run `bench new-site {x}` again.{sep2}"
|
||||
"").format(x=site, sep2="\n"*2, sep="\n")
|
||||
|
||||
if mariadb_minor_version < 3:
|
||||
print_db_config(msg, expected_config_for_barracuda_2)
|
||||
else:
|
||||
print_db_config(msg, expected_config_for_barracuda_3)
|
||||
raise frappe.exceptions.ImproperDBConfigurationError(
|
||||
reason="MariaDB default file format is not Barracuda"
|
||||
)
|
||||
|
||||
def get_root_connection(root_login, root_password):
|
||||
import getpass
|
||||
if not frappe.local.flags.root_connection:
|
||||
if not root_login:
|
||||
root_login = 'root'
|
||||
|
||||
if not root_password:
|
||||
root_password = frappe.conf.get("root_password") or None
|
||||
|
||||
if not root_password:
|
||||
root_password = getpass.getpass("MySQL root password: ")
|
||||
|
||||
frappe.local.flags.root_connection = frappe.database.get_db(user=root_login, password=root_password)
|
||||
|
||||
return frappe.local.flags.root_connection
|
||||
|
||||
def print_db_config(explanation, config_text):
|
||||
print("="*80)
|
||||
print(explanation)
|
||||
print(config_text)
|
||||
print("="*80)
|
||||
|
||||
expected_config_for_barracuda_2 = """
|
||||
[mysqld]
|
||||
innodb-file-format=barracuda
|
||||
innodb-file-per-table=1
|
||||
innodb-large-prefix=1
|
||||
character-set-client-handshake = FALSE
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
[mysql]
|
||||
default-character-set = utf8mb4
|
||||
"""
|
||||
|
||||
expected_config_for_barracuda_3 = """
|
||||
[mysqld]
|
||||
character-set-client-handshake = FALSE
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
[mysql]
|
||||
default-character-set = utf8mb4
|
||||
"""
|
||||
0
frappe/database/postgres/__init__.py
Normal file
0
frappe/database/postgres/__init__.py
Normal file
309
frappe/database/postgres/database.py
Normal file
309
frappe/database/postgres/database.py
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import frappe
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
from six import string_types
|
||||
from frappe.utils import cstr
|
||||
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
|
||||
|
||||
from frappe.database.database import Database
|
||||
from frappe.database.postgres.schema import PostgresTable
|
||||
|
||||
# cast decimals as floats
|
||||
DEC2FLOAT = psycopg2.extensions.new_type(
|
||||
psycopg2.extensions.DECIMAL.values,
|
||||
'DEC2FLOAT',
|
||||
lambda value, curs: float(value) if value is not None else None)
|
||||
|
||||
psycopg2.extensions.register_type(DEC2FLOAT)
|
||||
|
||||
class PostgresDatabase(Database):
|
||||
ProgrammingError = psycopg2.ProgrammingError
|
||||
OperationalError = psycopg2.OperationalError
|
||||
InternalError = psycopg2.InternalError
|
||||
SQLError = psycopg2.ProgrammingError
|
||||
DataError = psycopg2.DataError
|
||||
InterfaceError = psycopg2.InterfaceError
|
||||
REGEX_CHARACTER = '~'
|
||||
|
||||
def setup_type_map(self):
|
||||
self.type_map = {
|
||||
'Currency': ('decimal', '18,6'),
|
||||
'Int': ('bigint', None),
|
||||
'Long Int': ('bigint', None), # convert int to bigint if length is more than 11
|
||||
'Float': ('decimal', '18,6'),
|
||||
'Percent': ('decimal', '18,6'),
|
||||
'Check': ('smallint', None),
|
||||
'Small Text': ('text', ''),
|
||||
'Long Text': ('text', ''),
|
||||
'Code': ('text', ''),
|
||||
'Text Editor': ('text', ''),
|
||||
'Date': ('date', ''),
|
||||
'Datetime': ('timestamp', None),
|
||||
'Time': ('time', '6'),
|
||||
'Text': ('text', ''),
|
||||
'Data': ('varchar', self.VARCHAR_LEN),
|
||||
'Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Dynamic Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Password': ('varchar', self.VARCHAR_LEN),
|
||||
'Select': ('varchar', self.VARCHAR_LEN),
|
||||
'Read Only': ('varchar', self.VARCHAR_LEN),
|
||||
'Attach': ('text', ''),
|
||||
'Attach Image': ('text', ''),
|
||||
'Signature': ('text', ''),
|
||||
'Color': ('varchar', self.VARCHAR_LEN),
|
||||
'Barcode': ('text', ''),
|
||||
'Geolocation': ('text', '')
|
||||
}
|
||||
|
||||
def get_connection(self):
|
||||
# warnings.filterwarnings('ignore', category=psycopg2.Warning)
|
||||
conn = psycopg2.connect('host={} dbname={}'.format(self.host, self.user))
|
||||
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) # TODO: Remove this
|
||||
# conn = psycopg2.connect('host={} dbname={} user={} password={}'.format(self.host,
|
||||
# self.user, self.user, self.password))
|
||||
|
||||
return conn
|
||||
|
||||
def escape(self, s, percent=True):
|
||||
"""Excape quotes and percent in given string."""
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode('utf-8')
|
||||
|
||||
if percent:
|
||||
s = s.replace("%", "%%")
|
||||
|
||||
s = s.encode('utf-8')
|
||||
|
||||
return str(psycopg2.extensions.QuotedString(s))
|
||||
|
||||
def get_database_size(self):
|
||||
''''Returns database size in MB'''
|
||||
db_size = self.sql("SELECT (pg_database_size(%s) / 1024 / 1024) as database_size",
|
||||
self.db_name, as_dict=True)
|
||||
return db_size[0].get('database_size')
|
||||
|
||||
# pylint: disable=W0221
|
||||
def sql(self, *args, **kwargs):
|
||||
if len(args):
|
||||
# since tuple is immutable
|
||||
args = list(args)
|
||||
args[0] = modify_query(args[0])
|
||||
args = tuple(args)
|
||||
elif kwargs.get('query'):
|
||||
kwargs['query'] = modify_query(kwargs.get('query'))
|
||||
|
||||
return super(PostgresDatabase, self).sql(*args, **kwargs)
|
||||
|
||||
def get_tables(self):
|
||||
return [d[0] for d in self.sql("""select table_name
|
||||
from information_schema.tables
|
||||
where table_catalog='{0}'
|
||||
and table_type = 'BASE TABLE'
|
||||
and table_schema='public'""".format(frappe.conf.db_name))]
|
||||
|
||||
def format_date(self, date):
|
||||
if not date:
|
||||
return '0001-01-01::DATE'
|
||||
|
||||
if isinstance(date, frappe.string_types):
|
||||
if ':' not in date:
|
||||
date = date + '::DATE'
|
||||
else:
|
||||
date = date.strftime('%Y-%m-%d') + '::DATE'
|
||||
|
||||
return date
|
||||
|
||||
# column type
|
||||
@staticmethod
|
||||
def is_type_number(code):
|
||||
return code == psycopg2.NUMBER
|
||||
|
||||
@staticmethod
|
||||
def is_type_datetime(code):
|
||||
return code == psycopg2.DATETIME
|
||||
|
||||
# exception type
|
||||
@staticmethod
|
||||
def is_deadlocked(e):
|
||||
return e.pgcode == '40P01'
|
||||
|
||||
@staticmethod
|
||||
def is_timedout(e):
|
||||
# http://initd.org/psycopg/docs/extensions.html?highlight=datatype#psycopg2.extensions.QueryCanceledError
|
||||
return isinstance(e, psycopg2.extensions.QueryCanceledError)
|
||||
|
||||
@staticmethod
|
||||
def is_table_missing(e):
|
||||
return e.pgcode == '42P01'
|
||||
|
||||
@staticmethod
|
||||
def is_missing_column(e):
|
||||
return e.pgcode == '42703'
|
||||
|
||||
@staticmethod
|
||||
def is_access_denied(e):
|
||||
return e.pgcode == '42501'
|
||||
|
||||
@staticmethod
|
||||
def cant_drop_field_or_key(e):
|
||||
return e.pgcode.startswith('23')
|
||||
|
||||
@staticmethod
|
||||
def is_duplicate_entry(e):
|
||||
return e.pgcode == '23505'
|
||||
|
||||
@staticmethod
|
||||
def is_primary_key_violation(e):
|
||||
return e.pgcode == '23505' and '_pkey' in cstr(e.args[0])
|
||||
|
||||
@staticmethod
|
||||
def is_unique_key_violation(e):
|
||||
return e.pgcode == '23505' and '_key' in cstr(e.args[0])
|
||||
|
||||
@staticmethod
|
||||
def is_duplicate_fieldname(e):
|
||||
return e.pgcode == '42701'
|
||||
|
||||
def create_auth_table(self):
|
||||
self.sql_ddl("""create table if not exists "__Auth" (
|
||||
"doctype" VARCHAR(140) NOT NULL,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"fieldname" VARCHAR(140) NOT NULL,
|
||||
"password" VARCHAR(255) NOT NULL,
|
||||
"encrypted" INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY ("doctype", "name", "fieldname")
|
||||
)""")
|
||||
|
||||
def create_global_search_table(self):
|
||||
if not '__global_search' in self.get_tables():
|
||||
self.sql('''create table "__global_search"(
|
||||
doctype varchar(100),
|
||||
name varchar({0}),
|
||||
title varchar({0}),
|
||||
content text,
|
||||
route varchar({0}),
|
||||
published int not null default 0,
|
||||
unique (doctype, name))'''.format(self.VARCHAR_LEN))
|
||||
|
||||
def create_user_settings_table(self):
|
||||
self.sql_ddl("""create table if not exists "__UserSettings" (
|
||||
"user" VARCHAR(180) NOT NULL,
|
||||
"doctype" VARCHAR(180) NOT NULL,
|
||||
"data" TEXT,
|
||||
UNIQUE ("user", "doctype")
|
||||
)""")
|
||||
|
||||
def create_help_table(self):
|
||||
self.sql('''CREATE TABLE "help"(
|
||||
"path" varchar(255),
|
||||
"content" text,
|
||||
"title" text,
|
||||
"intro" text,
|
||||
"full_path" text)''')
|
||||
self.sql('''CREATE INDEX IF NOT EXISTS "help_index" ON "help" ("path")''')
|
||||
|
||||
def updatedb(self, doctype, meta=None):
|
||||
"""
|
||||
Syncs a `DocType` to the table
|
||||
* creates if required
|
||||
* updates columns
|
||||
* updates indices
|
||||
"""
|
||||
res = self.sql("select issingle from `tabDocType` where name='{}'".format(doctype))
|
||||
if not res:
|
||||
raise Exception('Wrong doctype {0} in updatedb'.format(doctype))
|
||||
|
||||
if not res[0][0]:
|
||||
db_table = PostgresTable(doctype, meta)
|
||||
db_table.validate()
|
||||
|
||||
self.commit()
|
||||
db_table.sync()
|
||||
self.begin()
|
||||
|
||||
@staticmethod
|
||||
def get_on_duplicate_update(key='name'):
|
||||
if isinstance(key, list):
|
||||
key = '", "'.join(key)
|
||||
return 'ON CONFLICT ("{key}") DO UPDATE SET '.format(
|
||||
key=key
|
||||
)
|
||||
|
||||
def check_transaction_status(self, query):
|
||||
pass
|
||||
|
||||
def has_index(self, table_name, index_name):
|
||||
return self.sql("""SELECT 1 FROM pg_indexes WHERE tablename='{table_name}'
|
||||
and indexname='{index_name}' limit 1""".format(table_name=table_name, index_name=index_name))
|
||||
|
||||
def add_index(self, doctype, fields, index_name=None):
|
||||
"""Creates an index with given fields if not already created.
|
||||
Index name will be `fieldname1_fieldname2_index`"""
|
||||
index_name = index_name or self.get_index_name(fields)
|
||||
table_name = 'tab' + doctype
|
||||
|
||||
self.commit()
|
||||
self.sql("""CREATE INDEX IF NOT EXISTS "{}" ON `{}`("{}")""".format(index_name, table_name, '", "'.join(fields)))
|
||||
|
||||
def add_unique(self, doctype, fields, constraint_name=None):
|
||||
if isinstance(fields, string_types):
|
||||
fields = [fields]
|
||||
if not constraint_name:
|
||||
constraint_name = "unique_" + "_".join(fields)
|
||||
|
||||
if not self.sql("""
|
||||
SELECT CONSTRAINT_NAME
|
||||
FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE table_name=%s
|
||||
AND constraint_type='UNIQUE'
|
||||
AND CONSTRAINT_NAME=%s""",
|
||||
('tab' + doctype, constraint_name)):
|
||||
self.commit()
|
||||
self.sql("""ALTER TABLE `tab%s`
|
||||
ADD CONSTRAINT %s UNIQUE (%s)""" % (doctype, constraint_name, ", ".join(fields)))
|
||||
|
||||
def get_table_columns_description(self, table_name):
|
||||
"""Returns list of column and its description"""
|
||||
# pylint: disable=W1401
|
||||
return self.sql('''
|
||||
SELECT a.column_name AS name,
|
||||
CASE a.data_type
|
||||
WHEN 'character varying' THEN CONCAT('varchar(', a.character_maximum_length ,')')
|
||||
WHEN 'timestamp without TIME zone' THEN 'timestamp'
|
||||
ELSE a.data_type
|
||||
END AS type,
|
||||
COUNT(b.indexdef) AS Index,
|
||||
COALESCE(a.column_default, NULL) AS default,
|
||||
BOOL_OR(b.unique) AS unique
|
||||
FROM information_schema.columns a
|
||||
LEFT JOIN
|
||||
(SELECT indexdef, tablename, indexdef LIKE '%UNIQUE INDEX%' AS unique
|
||||
FROM pg_indexes
|
||||
WHERE tablename='{table_name}') b
|
||||
ON SUBSTRING(b.indexdef, '\(.*\)') LIKE CONCAT('%', a.column_name, '%')
|
||||
WHERE a.table_name = '{table_name}'
|
||||
GROUP BY a.column_name, a.data_type, a.column_default, a.character_maximum_length;'''
|
||||
.format(table_name=table_name), as_dict=1)
|
||||
|
||||
def get_database_list(self, target):
|
||||
return [d[0] for d in self.sql("SELECT datname FROM pg_database;")]
|
||||
|
||||
def modify_query(query):
|
||||
""""Modifies query according to the requirements of postgres"""
|
||||
# replace ` with " for definitions
|
||||
query = query.replace('`', '"')
|
||||
query = replace_locate_with_strpos(query)
|
||||
# select from requires ""
|
||||
if re.search('from tab', query, flags=re.IGNORECASE):
|
||||
query = re.sub('from tab([a-zA-Z]*)', r'from "tab\1"', query, flags=re.IGNORECASE)
|
||||
|
||||
return query
|
||||
|
||||
def replace_locate_with_strpos(query):
|
||||
# strpos is the locate equivalent in postgres
|
||||
if re.search(r'locate\(', query, flags=re.IGNORECASE):
|
||||
query = re.sub(r'locate\(([^,]+),([^)]+)\)', r'strpos(\2, \1)', query, flags=re.IGNORECASE)
|
||||
return query
|
||||
279
frappe/database/postgres/framework_postgres.sql
Normal file
279
frappe/database/postgres/framework_postgres.sql
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
-- Core Elements to install WNFramework
|
||||
-- To be called from install.py
|
||||
|
||||
|
||||
--
|
||||
-- Table structure for table "tabDocField"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabDocField";
|
||||
CREATE TABLE "tabDocField" (
|
||||
"name" varchar(255) NOT NULL,
|
||||
"creation" timestamp(6) DEFAULT NULL,
|
||||
"modified" timestamp(6) DEFAULT NULL,
|
||||
"modified_by" varchar(255) DEFAULT NULL,
|
||||
"owner" varchar(255) DEFAULT NULL,
|
||||
"docstatus" smallint NOT NULL DEFAULT 0,
|
||||
"parent" varchar(255) DEFAULT NULL,
|
||||
"parentfield" varchar(255) DEFAULT NULL,
|
||||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"fieldname" varchar(255) DEFAULT NULL,
|
||||
"label" varchar(255) DEFAULT NULL,
|
||||
"oldfieldname" varchar(255) DEFAULT NULL,
|
||||
"fieldtype" varchar(255) DEFAULT NULL,
|
||||
"oldfieldtype" varchar(255) DEFAULT NULL,
|
||||
"options" text,
|
||||
"search_index" smallint NOT NULL DEFAULT 0,
|
||||
"hidden" smallint NOT NULL DEFAULT 0,
|
||||
"set_only_once" smallint NOT NULL DEFAULT 0,
|
||||
"allow_in_quick_entry" smallint NOT NULL DEFAULT 0,
|
||||
"print_hide" smallint NOT NULL DEFAULT 0,
|
||||
"report_hide" smallint NOT NULL DEFAULT 0,
|
||||
"reqd" smallint NOT NULL DEFAULT 0,
|
||||
"bold" smallint NOT NULL DEFAULT 0,
|
||||
"in_global_search" smallint NOT NULL DEFAULT 0,
|
||||
"collapsible" smallint NOT NULL DEFAULT 0,
|
||||
"unique" smallint NOT NULL DEFAULT 0,
|
||||
"no_copy" smallint NOT NULL DEFAULT 0,
|
||||
"allow_on_submit" smallint NOT NULL DEFAULT 0,
|
||||
"trigger" varchar(255) DEFAULT NULL,
|
||||
"collapsible_depends_on" text,
|
||||
"depends_on" text,
|
||||
"permlevel" bigint NOT NULL DEFAULT 0,
|
||||
"ignore_user_permissions" smallint NOT NULL DEFAULT 0,
|
||||
"width" varchar(255) DEFAULT NULL,
|
||||
"print_width" varchar(255) DEFAULT NULL,
|
||||
"columns" bigint NOT NULL DEFAULT 0,
|
||||
"default" text,
|
||||
"description" text,
|
||||
"in_list_view" smallint NOT NULL DEFAULT 0,
|
||||
"in_standard_filter" smallint NOT NULL DEFAULT 0,
|
||||
"read_only" smallint NOT NULL DEFAULT 0,
|
||||
"precision" varchar(255) DEFAULT NULL,
|
||||
"length" bigint NOT NULL DEFAULT 0,
|
||||
"translatable" smallint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY ("name")
|
||||
) ;
|
||||
|
||||
create index on "tabDocField" ("parent");
|
||||
create index on "tabDocField" ("label");
|
||||
create index on "tabDocField" ("fieldtype");
|
||||
create index on "tabDocField" ("fieldname");
|
||||
|
||||
--
|
||||
-- Table structure for table "tabDocPerm"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabDocPerm";
|
||||
CREATE TABLE "tabDocPerm" (
|
||||
"name" varchar(255) NOT NULL,
|
||||
"creation" timestamp(6) DEFAULT NULL,
|
||||
"modified" timestamp(6) DEFAULT NULL,
|
||||
"modified_by" varchar(255) DEFAULT NULL,
|
||||
"owner" varchar(255) DEFAULT NULL,
|
||||
"docstatus" smallint NOT NULL DEFAULT 0,
|
||||
"parent" varchar(255) DEFAULT NULL,
|
||||
"parentfield" varchar(255) DEFAULT NULL,
|
||||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"permlevel" bigint DEFAULT '0',
|
||||
"role" varchar(255) DEFAULT NULL,
|
||||
"match" varchar(255) DEFAULT NULL,
|
||||
"read" smallint NOT NULL DEFAULT 1,
|
||||
"write" smallint NOT NULL DEFAULT 1,
|
||||
"create" smallint NOT NULL DEFAULT 1,
|
||||
"submit" smallint NOT NULL DEFAULT 0,
|
||||
"cancel" smallint NOT NULL DEFAULT 0,
|
||||
"delete" smallint NOT NULL DEFAULT 1,
|
||||
"amend" smallint NOT NULL DEFAULT 0,
|
||||
"report" smallint NOT NULL DEFAULT 1,
|
||||
"export" smallint NOT NULL DEFAULT 1,
|
||||
"import" smallint NOT NULL DEFAULT 0,
|
||||
"share" smallint NOT NULL DEFAULT 1,
|
||||
"print" smallint NOT NULL DEFAULT 1,
|
||||
"email" smallint NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY ("name")
|
||||
) ;
|
||||
|
||||
create index on "tabDocPerm" ("parent");
|
||||
|
||||
--
|
||||
-- Table structure for table "tabDocType"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabDocType";
|
||||
CREATE TABLE "tabDocType" (
|
||||
"name" varchar(255) NOT NULL DEFAULT '',
|
||||
"creation" timestamp(6) DEFAULT NULL,
|
||||
"modified" timestamp(6) DEFAULT NULL,
|
||||
"modified_by" varchar(255) DEFAULT NULL,
|
||||
"owner" varchar(255) DEFAULT NULL,
|
||||
"docstatus" smallint NOT NULL DEFAULT 0,
|
||||
"parent" varchar(255) DEFAULT NULL,
|
||||
"parentfield" varchar(255) DEFAULT NULL,
|
||||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"search_fields" varchar(255) DEFAULT NULL,
|
||||
"issingle" smallint NOT NULL DEFAULT 0,
|
||||
"istable" smallint NOT NULL DEFAULT 0,
|
||||
"editable_grid" smallint NOT NULL DEFAULT 1,
|
||||
"track_changes" smallint NOT NULL DEFAULT 0,
|
||||
"module" varchar(255) DEFAULT NULL,
|
||||
"restrict_to_domain" varchar(255) DEFAULT NULL,
|
||||
"app" varchar(255) DEFAULT NULL,
|
||||
"autoname" varchar(255) DEFAULT NULL,
|
||||
"name_case" varchar(255) DEFAULT NULL,
|
||||
"title_field" varchar(255) DEFAULT NULL,
|
||||
"image_field" varchar(255) DEFAULT NULL,
|
||||
"timeline_field" varchar(255) DEFAULT NULL,
|
||||
"sort_field" varchar(255) DEFAULT NULL,
|
||||
"sort_order" varchar(255) DEFAULT NULL,
|
||||
"description" text,
|
||||
"colour" varchar(255) DEFAULT NULL,
|
||||
"read_only" smallint NOT NULL DEFAULT 0,
|
||||
"in_create" smallint NOT NULL DEFAULT 0,
|
||||
"menu_index" bigint DEFAULT NULL,
|
||||
"parent_node" varchar(255) DEFAULT NULL,
|
||||
"smallicon" varchar(255) DEFAULT NULL,
|
||||
"allow_copy" smallint NOT NULL DEFAULT 0,
|
||||
"allow_rename" smallint NOT NULL DEFAULT 0,
|
||||
"allow_import" smallint NOT NULL DEFAULT 0,
|
||||
"hide_toolbar" smallint NOT NULL DEFAULT 0,
|
||||
"hide_heading" smallint NOT NULL DEFAULT 0,
|
||||
"track_seen" smallint NOT NULL DEFAULT 0,
|
||||
"max_attachments" bigint NOT NULL DEFAULT 0,
|
||||
"print_outline" varchar(255) DEFAULT NULL,
|
||||
"read_only_onload" smallint NOT NULL DEFAULT 0,
|
||||
"document_type" varchar(255) DEFAULT NULL,
|
||||
"icon" varchar(255) DEFAULT NULL,
|
||||
"color" varchar(255) DEFAULT NULL,
|
||||
"tag_fields" varchar(255) DEFAULT NULL,
|
||||
"subject" varchar(255) DEFAULT NULL,
|
||||
"_last_update" varchar(32) DEFAULT NULL,
|
||||
"engine" varchar(20) DEFAULT 'InnoDB',
|
||||
"default_print_format" varchar(255) DEFAULT NULL,
|
||||
"is_submittable" smallint NOT NULL DEFAULT 0,
|
||||
"show_name_in_global_search" smallint NOT NULL DEFAULT 0,
|
||||
"_user_tags" varchar(255) DEFAULT NULL,
|
||||
"custom" smallint NOT NULL DEFAULT 0,
|
||||
"beta" smallint NOT NULL DEFAULT 0,
|
||||
"image_view" smallint NOT NULL DEFAULT 0,
|
||||
"has_web_view" smallint NOT NULL DEFAULT 0,
|
||||
"allow_guest_to_view" smallint NOT NULL DEFAULT 0,
|
||||
"route" varchar(255) DEFAULT NULL,
|
||||
"is_published_field" varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY ("name")
|
||||
) ;
|
||||
|
||||
--
|
||||
-- Table structure for table "tabSeries"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabSeries";
|
||||
CREATE TABLE "tabSeries" (
|
||||
"name" varchar(100) DEFAULT NULL,
|
||||
"current" bigint NOT NULL DEFAULT 0
|
||||
) ;
|
||||
|
||||
create index on "tabSeries" ("name");
|
||||
|
||||
--
|
||||
-- Table structure for table "tabSessions"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabSessions";
|
||||
CREATE TABLE "tabSessions" (
|
||||
"user" varchar(255) DEFAULT NULL,
|
||||
"sid" varchar(255) DEFAULT NULL,
|
||||
"sessiondata" text,
|
||||
"ipaddress" varchar(16) DEFAULT NULL,
|
||||
"lastupdate" timestamp(6) DEFAULT NULL,
|
||||
"device" varchar(255) DEFAULT 'desktop',
|
||||
"status" varchar(20) DEFAULT NULL
|
||||
);
|
||||
|
||||
create index on "tabSessions" ("sid");
|
||||
|
||||
--
|
||||
-- Table structure for table "tabSingles"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabSingles";
|
||||
CREATE TABLE "tabSingles" (
|
||||
"doctype" varchar(255) DEFAULT NULL,
|
||||
"field" varchar(255) DEFAULT NULL,
|
||||
"value" text
|
||||
);
|
||||
|
||||
create index on "tabSingles" ("doctype", "field");
|
||||
|
||||
--
|
||||
-- Table structure for table "__Auth"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "__Auth";
|
||||
CREATE TABLE "__Auth" (
|
||||
"doctype" VARCHAR(140) NOT NULL,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"fieldname" VARCHAR(140) NOT NULL,
|
||||
"password" VARCHAR(255) NOT NULL,
|
||||
"encrypted" int NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY ("doctype", "name", "fieldname")
|
||||
);
|
||||
|
||||
create index on "__Auth" ("doctype", "name", "fieldname");
|
||||
|
||||
--
|
||||
-- Table structure for table "tabFile"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabFile";
|
||||
CREATE TABLE "tabFile" (
|
||||
"name" varchar(255) NOT NULL,
|
||||
"creation" timestamp(6) DEFAULT NULL,
|
||||
"modified" timestamp(6) DEFAULT NULL,
|
||||
"modified_by" varchar(255) DEFAULT NULL,
|
||||
"owner" varchar(255) DEFAULT NULL,
|
||||
"docstatus" smallint NOT NULL DEFAULT 0,
|
||||
"parent" varchar(255) DEFAULT NULL,
|
||||
"parentfield" varchar(255) DEFAULT NULL,
|
||||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"file_name" varchar(255) DEFAULT NULL,
|
||||
"file_url" varchar(255) DEFAULT NULL,
|
||||
"module" varchar(255) DEFAULT NULL,
|
||||
"attached_to_name" varchar(255) DEFAULT NULL,
|
||||
"file_size" bigint NOT NULL DEFAULT 0,
|
||||
"attached_to_doctype" varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY ("name")
|
||||
);
|
||||
|
||||
create index on "tabFile" ("parent");
|
||||
create index on "tabFile" ("attached_to_name");
|
||||
create index on "tabFile" ("attached_to_doctype");
|
||||
|
||||
--
|
||||
-- Table structure for table "tabDefaultValue"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabDefaultValue";
|
||||
CREATE TABLE "tabDefaultValue" (
|
||||
"name" varchar(255) NOT NULL,
|
||||
"creation" timestamp(6) DEFAULT NULL,
|
||||
"modified" timestamp(6) DEFAULT NULL,
|
||||
"modified_by" varchar(255) DEFAULT NULL,
|
||||
"owner" varchar(255) DEFAULT NULL,
|
||||
"docstatus" smallint NOT NULL DEFAULT 0,
|
||||
"parent" varchar(255) DEFAULT NULL,
|
||||
"parentfield" varchar(255) DEFAULT NULL,
|
||||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"defvalue" text,
|
||||
"defkey" varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY ("name")
|
||||
);
|
||||
|
||||
create index on "tabDefaultValue" ("parent");
|
||||
create index on "tabDefaultValue" ("parent", "defkey");
|
||||
|
||||
96
frappe/database/postgres/schema.py
Normal file
96
frappe/database/postgres/schema.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt
|
||||
from frappe.database.schema import DBTable, get_definition
|
||||
|
||||
class PostgresTable(DBTable):
|
||||
def create(self):
|
||||
add_text = ''
|
||||
|
||||
# columns
|
||||
column_defs = self.get_column_definitions()
|
||||
if column_defs: add_text += ',\n'.join(column_defs)
|
||||
|
||||
# index
|
||||
# index_defs = self.get_index_definitions()
|
||||
# TODO: set docstatus length
|
||||
# create table
|
||||
frappe.db.sql("""create table `%s` (
|
||||
name varchar({varchar_len}) not null primary key,
|
||||
creation timestamp(6),
|
||||
modified timestamp(6),
|
||||
modified_by varchar({varchar_len}),
|
||||
owner varchar({varchar_len}),
|
||||
docstatus smallint not null default '0',
|
||||
parent varchar({varchar_len}),
|
||||
parentfield varchar({varchar_len}),
|
||||
parenttype varchar({varchar_len}),
|
||||
idx bigint not null default '0',
|
||||
%s)""".format(varchar_len=frappe.db.VARCHAR_LEN) % (self.table_name, add_text))
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
def alter(self):
|
||||
for col in self.columns.values():
|
||||
col.build_for_alter_table(self.current_columns.get(col.fieldname.lower()))
|
||||
|
||||
query = []
|
||||
|
||||
for col in self.add_column:
|
||||
query.append("ADD COLUMN `{}` {}".format(col.fieldname, col.get_definition()))
|
||||
|
||||
for col in self.change_type:
|
||||
query.append("ALTER COLUMN `{}` TYPE {}".format(col.fieldname, get_definition(col.fieldtype, precision=col.precision, length=col.length)))
|
||||
|
||||
for col in self.set_default:
|
||||
if col.fieldname=="name":
|
||||
continue
|
||||
|
||||
if col.fieldtype in ("Check", "Int"):
|
||||
col_default = cint(col.default)
|
||||
|
||||
elif col.fieldtype in ("Currency", "Float", "Percent"):
|
||||
col_default = flt(col.default)
|
||||
|
||||
elif not col.default:
|
||||
col_default = "NULL"
|
||||
|
||||
else:
|
||||
col_default = "{}".format(frappe.db.escape(col.default))
|
||||
|
||||
query.append("ALTER COLUMN `{}` SET DEFAULT {}".format(col.fieldname, col_default))
|
||||
|
||||
create_index_query = ""
|
||||
for col in self.add_index:
|
||||
# if index key not exists
|
||||
create_index_query += 'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format(
|
||||
index_name=col.fieldname,
|
||||
table_name=self.table_name,
|
||||
field=col.fieldname)
|
||||
|
||||
drop_index_query = ""
|
||||
for col in self.drop_index:
|
||||
# primary key
|
||||
if col.fieldname != 'name':
|
||||
# if index key exists
|
||||
if not frappe.db.has_index(self.table_name, col.fieldname):
|
||||
drop_index_query += 'DROP INDEX IF EXISTS "{}" ;'.format(col.fieldname)
|
||||
|
||||
if query:
|
||||
try:
|
||||
final_alter_query = "ALTER TABLE `{}` {}".format(self.table_name, ", ".join(query))
|
||||
if final_alter_query: frappe.db.sql(final_alter_query)
|
||||
if create_index_query: frappe.db.sql(create_index_query)
|
||||
if drop_index_query: frappe.db.sql(drop_index_query)
|
||||
except Exception as e:
|
||||
# sanitize
|
||||
if frappe.db.is_duplicate_fieldname(e):
|
||||
frappe.throw(str(e))
|
||||
elif frappe.db.is_duplicate_entry(e):
|
||||
fieldname = str(e).split("'")[-2]
|
||||
frappe.throw(_("""{0} field cannot be set as unique in {1},
|
||||
as there are non-unique existing values""".format(
|
||||
fieldname, self.table_name)))
|
||||
raise e
|
||||
else:
|
||||
raise e
|
||||
41
frappe/database/postgres/setup_db.py
Normal file
41
frappe/database/postgres/setup_db.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import frappe, subprocess, os
|
||||
|
||||
def setup_database(force, source_sql, verbose):
|
||||
root_conn = get_root_connection()
|
||||
root_conn.commit()
|
||||
root_conn.sql("DROP DATABASE IF EXISTS `{0}`".format(frappe.conf.db_name))
|
||||
root_conn.sql("DROP USER IF EXISTS {0}".format(frappe.conf.db_name))
|
||||
root_conn.sql("CREATE DATABASE `{0}`".format(frappe.conf.db_name))
|
||||
root_conn.sql("CREATE user {0} password '{1}'".format(frappe.conf.db_name,
|
||||
frappe.conf.db_password))
|
||||
root_conn.sql("GRANT ALL PRIVILEGES ON DATABASE `{0}` TO {0}".format(frappe.conf.db_name))
|
||||
|
||||
# bootstrap db
|
||||
subprocess.check_output(['psql', frappe.conf.db_name, '-qf',
|
||||
os.path.join(os.path.dirname(__file__), 'framework_postgres.sql')])
|
||||
|
||||
frappe.connect()
|
||||
|
||||
def setup_help_database(help_db_name):
|
||||
root_conn = get_root_connection()
|
||||
root_conn.sql("DROP DATABASE IF EXISTS `{0}`".format(help_db_name))
|
||||
root_conn.sql("DROP USER IF EXISTS {0}".format(help_db_name))
|
||||
root_conn.sql("CREATE DATABASE `{0}`".format(help_db_name))
|
||||
root_conn.sql("CREATE user {0} password '{1}'".format(help_db_name, help_db_name))
|
||||
root_conn.sql("GRANT ALL PRIVILEGES ON DATABASE `{0}` TO {0}".format(help_db_name))
|
||||
|
||||
def get_root_connection(root_login='postgres', root_password=None):
|
||||
import getpass
|
||||
if not frappe.local.flags.root_connection:
|
||||
if not root_login:
|
||||
root_login = 'root'
|
||||
|
||||
if not root_password:
|
||||
root_password = frappe.conf.get("root_password") or None
|
||||
|
||||
if not root_password:
|
||||
root_password = getpass.getpass("Postgres root password: ")
|
||||
|
||||
frappe.local.flags.root_connection = frappe.database.get_db(user=root_login, password=root_password)
|
||||
|
||||
return frappe.local.flags.root_connection
|
||||
339
frappe/database/schema.py
Normal file
339
frappe/database/schema.py
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import frappe
|
||||
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, cint, flt
|
||||
|
||||
|
||||
class InvalidColumnName(frappe.ValidationError): pass
|
||||
|
||||
class DBTable:
|
||||
def __init__(self, doctype, meta=None):
|
||||
self.doctype = doctype
|
||||
self.table_name = 'tab{}'.format(doctype)
|
||||
self.meta = meta or frappe.get_meta(doctype)
|
||||
self.columns = {}
|
||||
self.current_columns = {}
|
||||
|
||||
# lists for change
|
||||
self.add_column = []
|
||||
self.change_type = []
|
||||
self.change_name = []
|
||||
self.add_unique = []
|
||||
self.add_index = []
|
||||
self.drop_index = []
|
||||
self.set_default = []
|
||||
|
||||
# load
|
||||
self.get_columns_from_docfields()
|
||||
|
||||
def sync(self):
|
||||
if self.is_new():
|
||||
self.create()
|
||||
else:
|
||||
self.alter()
|
||||
|
||||
def create(self):
|
||||
pass
|
||||
|
||||
def get_column_definitions(self):
|
||||
column_list = [] + frappe.db.DEFAULT_COLUMNS
|
||||
ret = []
|
||||
for k in list(self.columns):
|
||||
if k not in column_list:
|
||||
d = self.columns[k].get_definition()
|
||||
if d:
|
||||
ret.append('`'+ k + '` ' + d)
|
||||
column_list.append(k)
|
||||
return ret
|
||||
|
||||
def get_index_definitions(self):
|
||||
ret = []
|
||||
for key, col in self.columns.items():
|
||||
if (col.set_index
|
||||
and not col.unique
|
||||
and col.fieldtype in frappe.db.type_map
|
||||
and frappe.db.type_map.get(col.fieldtype)[0]
|
||||
not in ('text', 'longtext')):
|
||||
ret.append('index `' + key + '`(`' + key + '`)')
|
||||
return ret
|
||||
|
||||
def get_columns_from_docfields(self):
|
||||
"""
|
||||
get columns from docfields and custom fields
|
||||
"""
|
||||
fl = frappe.db.sql("SELECT * FROM `tabDocField` WHERE parent = %s", self.doctype, as_dict = 1)
|
||||
lengths = {}
|
||||
precisions = {}
|
||||
uniques = {}
|
||||
|
||||
# optional fields like _comments
|
||||
if not self.meta.istable:
|
||||
for fieldname in frappe.db.OPTIONAL_COLUMNS:
|
||||
fl.append({
|
||||
"fieldname": fieldname,
|
||||
"fieldtype": "Text"
|
||||
})
|
||||
|
||||
# add _seen column if track_seen
|
||||
if getattr(self.meta, 'track_seen', False):
|
||||
fl.append({
|
||||
'fieldname': '_seen',
|
||||
'fieldtype': 'Text'
|
||||
})
|
||||
|
||||
if (not frappe.flags.in_install_db
|
||||
and (frappe.flags.in_install != "frappe"
|
||||
or frappe.flags.ignore_in_install)):
|
||||
custom_fl = frappe.db.sql("""
|
||||
SELECT * FROM `tabCustom Field`
|
||||
WHERE dt = %s AND docstatus < 2
|
||||
""", (self.doctype,), as_dict=1)
|
||||
if custom_fl: fl += custom_fl
|
||||
|
||||
# apply length, precision and unique from property setters
|
||||
for ps in frappe.get_all("Property Setter",
|
||||
fields=["field_name", "property", "value"],
|
||||
filters={
|
||||
"doc_type": self.doctype,
|
||||
"doctype_or_field": "DocField",
|
||||
"property": ["in", ["precision", "length", "unique"]]
|
||||
}):
|
||||
|
||||
if ps.property=="length":
|
||||
lengths[ps.field_name] = cint(ps.value)
|
||||
|
||||
elif ps.property=="precision":
|
||||
precisions[ps.field_name] = cint(ps.value)
|
||||
|
||||
elif ps.property=="unique":
|
||||
uniques[ps.field_name] = cint(ps.value)
|
||||
|
||||
for f in fl:
|
||||
self.columns[f['fieldname']] = DbColumn(self,
|
||||
f['fieldname'],
|
||||
f['fieldtype'],
|
||||
lengths.get(f["fieldname"]) or f.get('length'),
|
||||
f.get('default'),
|
||||
f.get('search_index'),
|
||||
f.get('options'),
|
||||
uniques.get(f["fieldname"],
|
||||
f.get('unique')),
|
||||
precisions.get(f['fieldname']) or f.get('precision'))
|
||||
|
||||
def validate(self):
|
||||
"""Check if change in varchar length isn't truncating the columns"""
|
||||
if self.is_new():
|
||||
return
|
||||
|
||||
self.setup_table_columns()
|
||||
|
||||
columns = [frappe._dict({"fieldname": f, "fieldtype": "Data"}) for f in
|
||||
frappe.db.STANDARD_VARCHAR_COLUMNS]
|
||||
columns += self.columns.values()
|
||||
|
||||
for col in columns:
|
||||
if len(col.fieldname) >= 64:
|
||||
frappe.throw(_("Fieldname is limited to 64 characters ({0})")
|
||||
.format(frappe.bold(col.fieldname)))
|
||||
|
||||
if 'varchar' in frappe.db.type_map.get(col.fieldtype, ()):
|
||||
|
||||
# validate length range
|
||||
new_length = cint(col.length) or cint(frappe.db.VARCHAR_LEN)
|
||||
if not (1 <= new_length <= 1000):
|
||||
frappe.throw(_("Length of {0} should be between 1 and 1000").format(col.fieldname))
|
||||
|
||||
current_col = self.current_columns.get(col.fieldname, {})
|
||||
if not current_col:
|
||||
continue
|
||||
current_type = self.current_columns[col.fieldname]["type"]
|
||||
current_length = re.findall(r'varchar\(([\d]+)\)', current_type)
|
||||
if not current_length:
|
||||
# case when the field is no longer a varchar
|
||||
continue
|
||||
current_length = current_length[0]
|
||||
if cint(current_length) != cint(new_length):
|
||||
try:
|
||||
# check for truncation
|
||||
max_length = frappe.db.sql("""SELECT MAX(CHAR_LENGTH(`{fieldname}`)) FROM `tab{doctype}`"""
|
||||
.format(fieldname=col.fieldname, doctype=self.doctype))
|
||||
|
||||
except frappe.db.InternalError as e:
|
||||
if frappe.db.is_missing_column(e):
|
||||
# Unknown column 'column_name' in 'field list'
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
if max_length and max_length[0][0] and max_length[0][0] > new_length:
|
||||
if col.fieldname in self.columns:
|
||||
self.columns[col.fieldname].length = current_length
|
||||
|
||||
frappe.msgprint(_("""Reverting length to {0} for '{1}' in '{2}';
|
||||
Setting the length as {3} will cause truncation of data.""")
|
||||
.format(current_length, col.fieldname, self.doctype, new_length))
|
||||
|
||||
def is_new(self):
|
||||
return self.table_name not in frappe.db.get_tables()
|
||||
|
||||
def setup_table_columns(self):
|
||||
# TODO: figure out a way to get key data
|
||||
for c in frappe.db.get_table_columns_description(self.table_name):
|
||||
self.current_columns[c.name.lower()] = c
|
||||
|
||||
def alter(self):
|
||||
pass
|
||||
|
||||
|
||||
class DbColumn:
|
||||
def __init__(self, table, fieldname, fieldtype, length, default,
|
||||
set_index, options, unique, precision):
|
||||
self.table = table
|
||||
self.fieldname = fieldname
|
||||
self.fieldtype = fieldtype
|
||||
self.length = length
|
||||
self.set_index = set_index
|
||||
self.default = default
|
||||
self.options = options
|
||||
self.unique = unique
|
||||
self.precision = precision
|
||||
|
||||
def get_definition(self, with_default=1):
|
||||
column_def = get_definition(self.fieldtype, precision=self.precision, length=self.length)
|
||||
|
||||
if not column_def:
|
||||
return column_def
|
||||
|
||||
if self.fieldtype in ("Check", "Int"):
|
||||
default_value = cint(self.default) or 0
|
||||
column_def += ' not null default {0}'.format(default_value)
|
||||
|
||||
elif self.fieldtype in ("Currency", "Float", "Percent"):
|
||||
default_value = flt(self.default) or 0
|
||||
column_def += ' not null default {0}'.format(default_value)
|
||||
|
||||
elif self.default and (self.default not in frappe.db.DEFAULT_SHORTCUTS) \
|
||||
and not self.default.startswith(":") and column_def not in ('text', 'longtext'):
|
||||
column_def += " default {}".format(frappe.db.escape(self.default))
|
||||
|
||||
if self.unique and (column_def not in ('text', 'longtext')):
|
||||
column_def += ' unique'
|
||||
|
||||
return column_def
|
||||
|
||||
def build_for_alter_table(self, current_def):
|
||||
column_type = get_definition(self.fieldtype, self.precision, self.length)
|
||||
|
||||
# no columns
|
||||
if not column_type:
|
||||
return
|
||||
|
||||
# to add?
|
||||
if not current_def:
|
||||
self.fieldname = validate_column_name(self.fieldname)
|
||||
self.table.add_column.append(self)
|
||||
return
|
||||
|
||||
# type
|
||||
if ((current_def['type']) != column_type):
|
||||
self.table.change_type.append(self)
|
||||
|
||||
# unique
|
||||
if((self.unique and not current_def['unique']) and column_type not in ('text', 'longtext')):
|
||||
self.table.add_unique.append(self)
|
||||
|
||||
# default
|
||||
if (self.default_changed(current_def)
|
||||
and (self.default not in frappe.db.DEFAULT_SHORTCUTS)
|
||||
and not cstr(self.default).startswith(":")
|
||||
and not (column_type in ['text','longtext'])):
|
||||
self.table.set_default.append(self)
|
||||
|
||||
# index should be applied or dropped irrespective of type change
|
||||
if ((current_def['index'] and not self.set_index and not self.unique)
|
||||
or (current_def['unique'] and not self.unique)):
|
||||
# to drop unique you have to drop index
|
||||
self.table.drop_index.append(self)
|
||||
|
||||
elif (not current_def['index'] and self.set_index) and not (column_type in ('text', 'longtext')):
|
||||
self.table.add_index.append(self)
|
||||
|
||||
def default_changed(self, current_def):
|
||||
if "decimal" in current_def['type']:
|
||||
return self.default_changed_for_decimal(current_def)
|
||||
else:
|
||||
return current_def['default'] != self.default
|
||||
|
||||
def default_changed_for_decimal(self, current_def):
|
||||
try:
|
||||
if current_def['default'] in ("", None) and self.default in ("", None):
|
||||
# both none, empty
|
||||
return False
|
||||
|
||||
elif current_def['default'] in ("", None):
|
||||
try:
|
||||
# check if new default value is valid
|
||||
float(self.default)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
elif self.default in ("", None):
|
||||
# new default value is empty
|
||||
return True
|
||||
|
||||
else:
|
||||
# NOTE float() raise ValueError when "" or None is passed
|
||||
return float(current_def['default'])!=float(self.default)
|
||||
except TypeError:
|
||||
return True
|
||||
|
||||
def validate_column_name(n):
|
||||
special_characters = re.findall(r"[\W]", n, re.UNICODE)
|
||||
if special_characters:
|
||||
special_characters = ", ".join('"{0}"'.format(c) for c in special_characters)
|
||||
frappe.throw(_("Fieldname {0} cannot have special characters like {1}").format(
|
||||
frappe.bold(cstr(n)), special_characters), frappe.db.InvalidColumnName)
|
||||
return n
|
||||
|
||||
def validate_column_length(fieldname):
|
||||
if len(fieldname) > frappe.db.MAX_COLUMN_LENGTH:
|
||||
frappe.throw(_("Fieldname is limited to 64 characters ({0})").format(fieldname))
|
||||
|
||||
def get_definition(fieldtype, precision=None, length=None):
|
||||
d = frappe.db.type_map.get(fieldtype)
|
||||
|
||||
# convert int to long int if the length of the int is greater than 11
|
||||
if fieldtype == "Int" and length and length > 11:
|
||||
d = frappe.db.type_map.get("Long Int")
|
||||
|
||||
if not d: return
|
||||
|
||||
coltype = d[0]
|
||||
size = d[1] if d[1] else None
|
||||
|
||||
if size:
|
||||
if fieldtype in ["Float", "Currency", "Percent"] and cint(precision) > 6:
|
||||
size = '21,9'
|
||||
|
||||
if coltype == "varchar" and length:
|
||||
size = length
|
||||
|
||||
if size is not None:
|
||||
coltype = "{coltype}({size})".format(coltype=coltype, size=size)
|
||||
|
||||
return coltype
|
||||
|
||||
def add_column(doctype, column_name, fieldtype, precision=None):
|
||||
if column_name in frappe.db.get_table_columns(doctype):
|
||||
# already exists
|
||||
return
|
||||
|
||||
frappe.db.commit()
|
||||
frappe.db.sql("alter table `tab%s` add column %s %s" % (doctype,
|
||||
column_name, get_definition(fieldtype, precision)))
|
||||
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ def set_default(key, value, parent, parenttype="__default"):
|
|||
select
|
||||
defkey
|
||||
from
|
||||
tabDefaultValue
|
||||
`tabDefaultValue`
|
||||
where
|
||||
defkey=%s and parent=%s
|
||||
for update''', (key, parent)):
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ def get_events(doctype, start, end, field_map, filters=None, fields=None):
|
|||
if field_map.color:
|
||||
fields.append(field_map.color)
|
||||
|
||||
start_date = "ifnull(%s, '0000-00-00 00:00:00')" % field_map.start
|
||||
start_date = "ifnull(%s, '0001-01-01 00:00:00')" % field_map.start
|
||||
end_date = "ifnull(%s, '2199-12-31 00:00:00')" % field_map.end
|
||||
|
||||
filters += [
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ frappe.ui.form.on('Auto Repeat', {
|
|||
|
||||
if(frm.doc.docstatus == 1) {
|
||||
|
||||
let label = __('View {0}', [frm.doc.reference_doctype]);
|
||||
let label = __('View {0}', [__(frm.doc.reference_doctype)]);
|
||||
frm.add_custom_button(__(label),
|
||||
function() {
|
||||
frappe.route_options = {
|
||||
|
|
@ -51,14 +51,14 @@ frappe.ui.form.on('Auto Repeat', {
|
|||
}
|
||||
|
||||
if(frm.doc.status == 'Stopped') {
|
||||
frm.add_custom_button(__("Resume"),
|
||||
frm.add_custom_button(__("Restart"),
|
||||
function() {
|
||||
frm.events.stop_resume_auto_repeat(frm, "Resumed");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(frm.doc.status!= 0){
|
||||
if(frm.doc.docstatus!= 0 && !frm.doc.status.includes('Stopped', 'Cancelled') && frm.doc.next_schedule_date >= frappe.datetime.get_today()){
|
||||
frappe.auto_repeat.render_schedule(frm);
|
||||
}
|
||||
}
|
||||
|
|
@ -107,10 +107,31 @@ frappe.ui.form.on('Auto Repeat', {
|
|||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
preview_message: function(frm) {
|
||||
if (frm.doc.message) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.auto_repeat.auto_repeat.generate_message_preview",
|
||||
args: {
|
||||
reference_dt: frm.doc.reference_doctype,
|
||||
reference_doc: frm.doc.reference_document,
|
||||
subject: frm.doc.subject,
|
||||
message: frm.doc.message
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frappe.msgprint(r.message.message, r.message.subject)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(__("Please setup a message first"), __("Message not setup"))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.auto_repeat.render_schedule = function(frm) {
|
||||
frappe.auto_repeat.render_schedule = function(frm) {
|
||||
frappe.call({
|
||||
method: "get_auto_repeat_schedule",
|
||||
doc: frm.doc
|
||||
|
|
|
|||
|
|
@ -143,6 +143,72 @@
|
|||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_party_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reference Party Doctype",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reference Party",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "reference_party_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
|
|
@ -563,7 +629,7 @@
|
|||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
|
|
@ -595,7 +661,73 @@
|
|||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "notify_by_email",
|
||||
"fieldname": "recipients",
|
||||
"fieldtype": "Small 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": "Recipients",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"fieldname": "get_contacts",
|
||||
"fieldtype": "Button",
|
||||
"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": "Get Contacts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
|
|
@ -629,7 +761,7 @@
|
|||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
|
|
@ -663,12 +795,14 @@
|
|||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break",
|
||||
"default": "Please find attached {{ doc.doctype }} #{{ doc.name }}",
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
|
|
@ -676,6 +810,7 @@
|
|||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
|
|
@ -694,45 +829,12 @@
|
|||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "notify_by_email",
|
||||
"fieldname": "recipients",
|
||||
"fieldtype": "Small 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": "Recipients",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"fieldname": "get_contacts",
|
||||
"fieldname": "preview_message",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
|
|
@ -741,7 +843,7 @@
|
|||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Get Contacts",
|
||||
"label": "Preview Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
|
|
@ -791,72 +893,6 @@
|
|||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.notify_by_email",
|
||||
"fieldname": "section_break_20",
|
||||
"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": "Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Please find attached {{ doc.doctype }} #{{ doc.name }}",
|
||||
"fieldname": "message",
|
||||
"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": "Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
|
|
@ -892,7 +928,7 @@
|
|||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
|
|
@ -965,8 +1001,8 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-07-13 15:14:34.524098",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2018-09-12 17:17:36.071411",
|
||||
"modified_by": "faris@erpnext.com",
|
||||
"module": "Desk",
|
||||
"name": "Auto Repeat",
|
||||
"name_case": "",
|
||||
|
|
@ -1039,5 +1075,6 @@
|
|||
"sort_order": "DESC",
|
||||
"title_field": "reference_document",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ class AutoRepeat(Document):
|
|||
self.validate_dates()
|
||||
self.validate_next_schedule_date()
|
||||
self.validate_email_id()
|
||||
self.link_party()
|
||||
|
||||
validate_template(self.subject or "")
|
||||
validate_template(self.message or "")
|
||||
|
|
@ -124,6 +125,14 @@ class AutoRepeat(Document):
|
|||
|
||||
return schedule_details
|
||||
|
||||
def link_party(self):
|
||||
reference = frappe.get_meta(self.reference_doctype)
|
||||
for field in reference.fields:
|
||||
if field.options in ['Customer', 'Supplier', 'Employee']:
|
||||
self.reference_party_doctype = field.options
|
||||
self.reference_party = frappe.db.get_value(self.reference_doctype, self.reference_document, field.fieldname)
|
||||
break
|
||||
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day):
|
||||
mcount = month_map.get(frequency)
|
||||
if mcount:
|
||||
|
|
@ -376,3 +385,13 @@ def update_reference(docname, reference):
|
|||
except Exception as e:
|
||||
raise e
|
||||
return "error"
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_message_preview(reference_dt, reference_doc, message=None, subject=None):
|
||||
doc = frappe.get_doc(reference_dt, reference_doc)
|
||||
subject_preview = _("Please add a subject to your email")
|
||||
msg_preview = frappe.render_template(message, {'doc': doc})
|
||||
if subject:
|
||||
subject_preview = frappe.render_template(subject, {'doc': doc})
|
||||
|
||||
return {'message': msg_preview, 'subject': subject_preview}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ def add_custom_fields():
|
|||
|
||||
class TestAutoRepeat(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not frappe.db.sql('select name from `tabCustom Field` where name="auto_repeat"'):
|
||||
if not frappe.db.sql("SELECT `name` FROM `tabCustom Field` WHERE `name`='auto_repeat'"):
|
||||
add_custom_fields()
|
||||
|
||||
def test_daily_auto_repeat(self):
|
||||
|
|
|
|||
|
|
@ -32,9 +32,8 @@ class Event(Document):
|
|||
|
||||
def get_permission_query_conditions(user):
|
||||
if not user: user = frappe.session.user
|
||||
return """(tabEvent.event_type='Public' or tabEvent.owner='%(user)s')""" % {
|
||||
return """(`tabEvent`.`event_type`='Public' or `tabEvent`.`owner`=%(user)s)""" % {
|
||||
"user": frappe.db.escape(user),
|
||||
"roles": "', '".join([frappe.db.escape(r) for r in frappe.get_roles(user)])
|
||||
}
|
||||
|
||||
def has_permission(doc, user):
|
||||
|
|
@ -72,28 +71,26 @@ def get_events(start, end, user=None, for_reminder=False, filters=None):
|
|||
user = frappe.session.user
|
||||
if isinstance(filters, string_types):
|
||||
filters = json.loads(filters)
|
||||
roles = frappe.get_roles(user)
|
||||
events = frappe.db.sql("""select name, subject, description, color,
|
||||
events = frappe.db.sql("""select `name`, subject, description, color,
|
||||
starts_on, ends_on, owner, all_day, event_type, repeat_this_event, repeat_on,repeat_till,
|
||||
monday, tuesday, wednesday, thursday, friday, saturday, sunday
|
||||
from tabEvent where ((
|
||||
from `tabEvent` where ((
|
||||
(date(starts_on) between date(%(start)s) and date(%(end)s))
|
||||
or (date(ends_on) between date(%(start)s) and date(%(end)s))
|
||||
or (date(starts_on) <= date(%(start)s) and date(ends_on) >= date(%(end)s))
|
||||
) or (
|
||||
date(starts_on) <= date(%(start)s) and repeat_this_event=1 and
|
||||
ifnull(repeat_till, "3000-01-01") > date(%(start)s)
|
||||
coalesce(repeat_till, '3000-01-01') > date(%(start)s)
|
||||
))
|
||||
{reminder_condition}
|
||||
{filter_condition}
|
||||
and (event_type='Public' or owner=%(user)s
|
||||
or exists(select name from `tabDocShare` where
|
||||
tabDocShare.share_doctype="Event" and `tabDocShare`.share_name=tabEvent.name
|
||||
and tabDocShare.user=%(user)s))
|
||||
`tabDocShare`.share_doctype='Event' and `tabDocShare`.share_name=`tabEvent`.`name`
|
||||
and `tabDocShare`.`user`=%(user)s))
|
||||
order by starts_on""".format(
|
||||
filter_condition=get_filters_cond('Event', filters, []),
|
||||
reminder_condition="and ifnull(send_reminder,0)=1" if for_reminder else "",
|
||||
roles=", ".join('"{}"'.format(frappe.db.escape(r)) for r in roles)
|
||||
reminder_condition="and coalesce(send_reminder, 0)=1" if for_reminder else ""
|
||||
), {
|
||||
"start": start,
|
||||
"end": end,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ def get_permission_query_conditions(user):
|
|||
if user == "Administrator":
|
||||
return ""
|
||||
|
||||
return """(`tabKanban Board`.private=0 or `tabKanban Board`.owner="{user}")""".format(user=user)
|
||||
return """(`tabKanban Board`.private=0 or `tabKanban Board`.owner='{user}')""".format(user=user)
|
||||
|
||||
def has_permission(doc, ptype, user):
|
||||
if doc.private == 0 or user == "Administrator":
|
||||
|
|
|
|||
|
|
@ -16,8 +16,14 @@ class ToDo(Document):
|
|||
def validate(self):
|
||||
self._assignment = None
|
||||
if self.is_new():
|
||||
|
||||
if self.assigned_by == self.owner:
|
||||
assignment_message = frappe._("{0} self assigned this task: {1}").format(get_fullname(self.assigned_by), self.description)
|
||||
else:
|
||||
assignment_message = frappe._("{0} assigned {1}: {2}").format(get_fullname(self.assigned_by), get_fullname(self.owner), self.description)
|
||||
|
||||
self._assignment = {
|
||||
"text": frappe._("{0} assigned {1}: {2}").format(get_fullname(self.assigned_by), get_fullname(self.owner), self.description),
|
||||
"text": assignment_message,
|
||||
"comment_type": "Assigned"
|
||||
}
|
||||
|
||||
|
|
@ -66,12 +72,12 @@ class ToDo(Document):
|
|||
"_assign", json.dumps(assignments), update_modified=False)
|
||||
|
||||
except Exception as e:
|
||||
if e.args[0] == 1146 and frappe.flags.in_install:
|
||||
if frappe.db.is_table_missing(e) and frappe.flags.in_install:
|
||||
# no table
|
||||
return
|
||||
|
||||
elif e.args[0]==1054:
|
||||
from frappe.model.db_schema import add_column
|
||||
elif frappe.db.is_column_missing(e):
|
||||
from frappe.database.schema import add_column
|
||||
add_column(self.reference_type, "_assign", "Text")
|
||||
self.update_in_reference()
|
||||
|
||||
|
|
@ -88,7 +94,7 @@ def get_permission_query_conditions(user):
|
|||
if "System Manager" in frappe.get_roles(user):
|
||||
return None
|
||||
else:
|
||||
return """(tabToDo.owner = '{user}' or tabToDo.assigned_by = '{user}')"""\
|
||||
return """(tabToDo.owner = {user} or tabToDo.assigned_by = {user})"""\
|
||||
.format(user=frappe.db.escape(user))
|
||||
|
||||
def has_permission(doc, user):
|
||||
|
|
|
|||
|
|
@ -18,9 +18,13 @@ def get(args=None):
|
|||
|
||||
get_docinfo(frappe.get_doc(args.get("doctype"), args.get("name")))
|
||||
|
||||
return frappe.db.sql("""select owner, description from `tabToDo`
|
||||
where reference_type=%(doctype)s and reference_name=%(name)s and status="Open"
|
||||
order by modified desc limit 5""", args, as_dict=True)
|
||||
return frappe.db.sql("""SELECT `owner`, `description`
|
||||
FROM `tabToDo`
|
||||
WHERE reference_type=%(doctype)s
|
||||
AND reference_name=%(name)s
|
||||
AND status='Open'
|
||||
ORDER BY modified DESC
|
||||
LIMIT 5""", args, as_dict=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def add(args=None):
|
||||
|
|
@ -36,9 +40,12 @@ def add(args=None):
|
|||
if not args:
|
||||
args = frappe.local.form_dict
|
||||
|
||||
if frappe.db.sql("""select owner from `tabToDo`
|
||||
where reference_type=%(doctype)s and reference_name=%(name)s and status="Open"
|
||||
and owner=%(assign_to)s""", args):
|
||||
if frappe.db.sql("""SELECT `owner`
|
||||
FROM `tabToDo`
|
||||
WHERE `reference_type`=%(doctype)s
|
||||
AND `reference_name`=%(name)s
|
||||
AND `status`='Open'
|
||||
AND `owner`=%(assign_to)s""", args):
|
||||
frappe.throw(_("Already in user's To Do list"), DuplicateToDoError)
|
||||
|
||||
else:
|
||||
|
|
@ -47,6 +54,9 @@ def add(args=None):
|
|||
# if args.get("re_assign"):
|
||||
# remove_from_todo_if_already_assigned(args['doctype'], args['name'])
|
||||
|
||||
if not args.get('description'):
|
||||
args['description'] = _('Assignment')
|
||||
|
||||
d = frappe.get_doc({
|
||||
"doctype":"ToDo",
|
||||
"owner": args['assign_to'],
|
||||
|
|
@ -160,4 +170,30 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
|
|||
'notify': notify
|
||||
}
|
||||
|
||||
arg["parenttype"] = "Assignment"
|
||||
if arg and arg.get("notify"):
|
||||
_notify(arg)
|
||||
|
||||
def _notify(args):
|
||||
from frappe.utils import get_fullname, get_url
|
||||
|
||||
args = frappe._dict(args)
|
||||
contact = args.contact
|
||||
txt = args.txt
|
||||
|
||||
try:
|
||||
if not isinstance(contact, list):
|
||||
contact = [frappe.db.get_value("User", contact, "email") or contact]
|
||||
|
||||
frappe.sendmail(\
|
||||
recipients=contact,
|
||||
sender= frappe.db.get_value("User", frappe.session.user, "email"),
|
||||
subject=_("New message from {0}").format(get_fullname(frappe.session.user)),
|
||||
template="new_message",
|
||||
args={
|
||||
"from": get_fullname(frappe.session.user),
|
||||
"message": txt,
|
||||
"link": get_url()
|
||||
},
|
||||
header=[_('New Message'), 'orange'])
|
||||
except frappe.OutgoingEmailError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -138,24 +138,22 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
|
|||
group_by=None, as_dict=True):
|
||||
'''Returns list of communications for a given document'''
|
||||
if not fields:
|
||||
fields = '''name, communication_type,
|
||||
communication_medium, comment_type, communication_date,
|
||||
content, sender, sender_full_name, creation, subject, delivery_status, _liked_by,
|
||||
timeline_doctype, timeline_name,
|
||||
reference_doctype, reference_name,
|
||||
link_doctype, link_name, read_by_recipient,
|
||||
rating, "Communication" as doctype'''
|
||||
fields = '''`name`, `communication_type`,`communication_medium`, `comment_type`,
|
||||
`communication_date`, `content`, `sender`, `sender_full_name`,
|
||||
`creation`, `subject`, `delivery_status`, `_liked_by`,
|
||||
`timeline_doctype`, `timeline_name`, `reference_doctype`, `reference_name`,
|
||||
`link_doctype`, `link_name`, `read_by_recipient`, `rating` '''
|
||||
|
||||
conditions = '''communication_type in ("Communication", "Comment", "Feedback")
|
||||
conditions = '''communication_type in ('Communication', 'Comment', 'Feedback')
|
||||
and (
|
||||
(reference_doctype=%(doctype)s and reference_name=%(name)s)
|
||||
or (
|
||||
(timeline_doctype=%(doctype)s and timeline_name=%(name)s)
|
||||
and (
|
||||
communication_type="Communication"
|
||||
communication_type='Communication'
|
||||
or (
|
||||
communication_type="Comment"
|
||||
and comment_type in ("Created", "Updated", "Submitted", "Cancelled", "Deleted")
|
||||
communication_type='Comment'
|
||||
and comment_type in ('Created', 'Updated', 'Submitted', 'Cancelled', 'Deleted')
|
||||
)))
|
||||
)'''
|
||||
|
||||
|
|
@ -165,12 +163,12 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
|
|||
conditions+= ' and creation > {0}'.format(after)
|
||||
|
||||
if doctype=='User':
|
||||
conditions+= ' and not (reference_doctype="User" and communication_type="Communication")'
|
||||
conditions+= " and not (reference_doctype='User' and communication_type='Communication')"
|
||||
|
||||
communications = frappe.db.sql("""select {fields}
|
||||
from tabCommunication
|
||||
from `tabCommunication`
|
||||
where {conditions} {group_by}
|
||||
order by creation desc limit %(start)s, %(limit)s""".format(
|
||||
order by creation desc LIMIT %(limit)s OFFSET %(start)s""".format(
|
||||
fields = fields, conditions=conditions, group_by=group_by or ""),
|
||||
{ "doctype": doctype, "name": name, "start": frappe.utils.cint(start), "limit": limit },
|
||||
as_dict=as_dict)
|
||||
|
|
@ -178,8 +176,8 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
|
|||
return communications
|
||||
|
||||
def get_assignments(dt, dn):
|
||||
cl = frappe.db.sql("""select name, owner, description from `tabToDo`
|
||||
where reference_type=%(doctype)s and reference_name=%(name)s and status="Open"
|
||||
cl = frappe.db.sql("""select `name`, owner, description from `tabToDo`
|
||||
where reference_type=%(doctype)s and reference_name=%(name)s and status='Open'
|
||||
order by modified desc limit 5""", {
|
||||
"doctype": dt,
|
||||
"name": dn
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ def validate_link():
|
|||
frappe.response['message'] = 'Ok'
|
||||
return
|
||||
|
||||
valid_value = frappe.db.sql("select name from `tab%s` where name=%s" % (frappe.db.escape(options),
|
||||
'%s'), (value,))
|
||||
valid_value = frappe.db.get_all(options, filters=dict(name=value), as_list=1, limit=1)
|
||||
|
||||
if valid_value:
|
||||
valid_value = valid_value[0][0]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||
"""Allow adding of likes to documents"""
|
||||
|
||||
import frappe, json
|
||||
from frappe.model.db_schema import add_column
|
||||
from frappe.database.schema import add_column
|
||||
from frappe import _
|
||||
from frappe.utils import get_link_to_form
|
||||
|
||||
|
|
@ -54,8 +54,8 @@ def _toggle_like(doctype, name, add, user=None):
|
|||
|
||||
frappe.db.set_value(doctype, name, "_liked_by", json.dumps(liked_by), update_modified=False)
|
||||
|
||||
except Exception as e:
|
||||
if isinstance(e.args, (tuple, list)) and e.args and e.args[0]==1054:
|
||||
except frappe.db.ProgrammingError as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
add_column(doctype, "_liked_by", "Text")
|
||||
_toggle_like(doctype, name, add, user)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ def get_last_modified(doctype):
|
|||
try:
|
||||
last_modified = frappe.get_all(doctype, fields=["max(modified)"], as_list=True, limit_page_length=1)[0][0]
|
||||
except Exception as e:
|
||||
if e.args[0]==1146:
|
||||
if frappe.db.is_table_missing(e):
|
||||
last_modified = None
|
||||
else:
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
|
@ -10,9 +10,6 @@ from frappe.utils import cint
|
|||
import frappe.defaults
|
||||
from six import text_type
|
||||
|
||||
# imports - third-party imports
|
||||
import pymysql
|
||||
|
||||
def get_sql_tables(q):
|
||||
if q.find('WHERE') != -1:
|
||||
tl = q.split('FROM')[1].split('WHERE')[0].split(',')
|
||||
|
|
@ -78,16 +75,16 @@ def add_match_conditions(q, tl):
|
|||
q = q[0] + condition_st + '(' + ' OR '.join(sl) + ') ' + condition_end + q[1]
|
||||
else:
|
||||
q = q + condition_st + '(' + ' OR '.join(sl) + ')'
|
||||
|
||||
|
||||
return q
|
||||
|
||||
def guess_type(m):
|
||||
"""
|
||||
Returns fieldtype depending on the MySQLdb Description
|
||||
"""
|
||||
if m in pymysql.NUMBER:
|
||||
if frappe.db.is_type_number(m):
|
||||
return 'Currency'
|
||||
elif m in pymysql.DATE:
|
||||
elif m in frappe.is_type_datetime(m):
|
||||
return 'Date'
|
||||
else:
|
||||
return 'Data'
|
||||
|
|
@ -97,7 +94,7 @@ def build_description_simple():
|
|||
|
||||
for m in frappe.db.get_description():
|
||||
colnames.append(m[0])
|
||||
coltypes.append(guess_type[m[0]])
|
||||
coltypes.append(guess_type[m[1]])
|
||||
coloptions.append('')
|
||||
colwidths.append('100')
|
||||
|
||||
|
|
@ -178,7 +175,7 @@ def runquery(q='', ret=0, from_export=0):
|
|||
meta = get_sql_meta(tl)
|
||||
|
||||
q = add_match_conditions(q, tl)
|
||||
|
||||
|
||||
# replace special variables
|
||||
q = q.replace('__user', frappe.session.user)
|
||||
q = q.replace('__today', frappe.utils.nowdate())
|
||||
|
|
@ -264,9 +261,9 @@ def add_limit_to_query(query, args):
|
|||
if args.get('limit_page_length'):
|
||||
query += """
|
||||
limit %(limit_start)s, %(limit_page_length)s"""
|
||||
|
||||
|
||||
import frappe.utils
|
||||
args['limit_start'] = frappe.utils.cint(args.get('limit_start'))
|
||||
args['limit_page_length'] = frappe.utils.cint(args.get('limit_page_length'))
|
||||
|
||||
|
||||
return query, args
|
||||
|
|
|
|||
|
|
@ -260,13 +260,14 @@ def add_total_row(result, columns, meta = None):
|
|||
total_row = [""]*len(columns)
|
||||
has_percent = []
|
||||
for i, col in enumerate(columns):
|
||||
fieldtype, options = None, None
|
||||
fieldtype, options, fieldname = None, None, None
|
||||
if isinstance(col, string_types):
|
||||
if meta:
|
||||
# get fieldtype from the meta
|
||||
field = meta.get_field(col)
|
||||
if field:
|
||||
fieldtype = meta.get_field(col).fieldtype
|
||||
fieldname = meta.get_field(col).fieldname
|
||||
else:
|
||||
col = col.split(":")
|
||||
if len(col) > 1:
|
||||
|
|
@ -278,19 +279,21 @@ def add_total_row(result, columns, meta = None):
|
|||
fieldtype = "Data"
|
||||
else:
|
||||
fieldtype = col.get("fieldtype")
|
||||
fieldname = col.get("fieldname")
|
||||
options = col.get("options")
|
||||
|
||||
for row in result:
|
||||
if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(row[i]):
|
||||
total_row[i] = flt(total_row[i]) + flt(row[i])
|
||||
cell = row.get(fieldname) if isinstance(row, dict) else row[i]
|
||||
if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(cell):
|
||||
total_row[i] = flt(total_row[i]) + flt(cell)
|
||||
|
||||
if fieldtype == "Percent" and i not in has_percent:
|
||||
has_percent.append(i)
|
||||
|
||||
if fieldtype == "Time" and row[i]:
|
||||
if fieldtype == "Time" and cell:
|
||||
if not total_row[i]:
|
||||
total_row[i]=timedelta(hours=0,minutes=0,seconds=0)
|
||||
total_row[i] = total_row[i] + row[i]
|
||||
total_row[i] = total_row[i] + cell
|
||||
|
||||
|
||||
if fieldtype=="Link" and options == "Currency":
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ from frappe.model.db_query import DatabaseQuery
|
|||
from frappe import _
|
||||
from six import text_type, string_types, StringIO
|
||||
|
||||
# imports - third-party imports
|
||||
import pymysql
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get():
|
||||
|
|
@ -235,11 +232,11 @@ def delete_items():
|
|||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_sidebar_stats(stats, doctype, filters=[]):
|
||||
cat_tags = frappe.db.sql("""select tag.parent as category, tag.tag_name as tag
|
||||
from `tabTag Doc Category` as docCat
|
||||
INNER JOIN tabTag as tag on tag.parent = docCat.parent
|
||||
where docCat.tagdoc=%s
|
||||
ORDER BY tag.parent asc,tag.idx""",doctype,as_dict=1)
|
||||
cat_tags = frappe.db.sql("""select `tag`.parent as `category`, `tag`.tag_name as `tag`
|
||||
from `tabTag Doc Category` as `docCat`
|
||||
INNER JOIN `tabTag` as `tag` on `tag`.parent = `docCat`.parent
|
||||
where `docCat`.tagdoc=%s
|
||||
ORDER BY `tag`.parent asc, `tag`.idx""", doctype, as_dict=1)
|
||||
|
||||
return {"defined_cat":cat_tags, "stats":get_stats(stats, doctype, filters)}
|
||||
|
||||
|
|
@ -254,7 +251,7 @@ def get_stats(stats, doctype, filters=[]):
|
|||
|
||||
try:
|
||||
columns = frappe.db.get_table_columns(doctype)
|
||||
except pymysql.InternalError:
|
||||
except frappe.db.InternalError:
|
||||
# raised when _user_tags column is added on the fly
|
||||
columns = []
|
||||
|
||||
|
|
@ -273,10 +270,10 @@ def get_stats(stats, doctype, filters=[]):
|
|||
else:
|
||||
stats[tag] = tagcount
|
||||
|
||||
except frappe.SQLError:
|
||||
except frappe.db.SQLError:
|
||||
# does not work for child tables
|
||||
pass
|
||||
except pymysql.InternalError:
|
||||
except frappe.db.InternalError:
|
||||
# raised when _user_tags column is added on the fly
|
||||
pass
|
||||
return stats
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ def sanitize_searchfield(searchfield):
|
|||
|
||||
if len(searchfield) == 1:
|
||||
# do not allow special characters to pass as searchfields
|
||||
regex = re.compile('^.*[=;*,\'"$\-+%#@()_].*')
|
||||
regex = re.compile(r'^.*[=;*,\'"$\-+%#@()_].*')
|
||||
if regex.match(searchfield):
|
||||
_raise_exception(searchfield)
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ def sanitize_searchfield(searchfield):
|
|||
_raise_exception(searchfield)
|
||||
|
||||
else:
|
||||
regex = re.compile('^.*[=;*,\'"$\-+%#@()].*')
|
||||
regex = re.compile(r'^.*[=;*,\'"$\-+%#@()].*')
|
||||
if any(regex.match(f) for f in searchfield.split()):
|
||||
_raise_exception(searchfield)
|
||||
|
||||
|
|
@ -126,14 +126,15 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
|
|||
formatted_fields = ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in fields]
|
||||
|
||||
# find relevance as location of search term from the beginning of string `name`. used for sorting results.
|
||||
formatted_fields.append("""locate("{_txt}", `tab{doctype}`.`name`) as `_relevance`""".format(
|
||||
_txt=frappe.db.escape((txt or "").replace("%", "")), doctype=frappe.db.escape(doctype)))
|
||||
formatted_fields.append("""locate({_txt}, `tab{doctype}`.`name`) as `_relevance`""".format(
|
||||
_txt=frappe.db.escape((txt or "").replace("%", "")), doctype=doctype))
|
||||
|
||||
|
||||
# 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), {0}, `tab{1}`.idx desc".format(order_by_based_on_meta, doctype)
|
||||
# 2 is the index of _relevance column
|
||||
order_by = "2 , {0}, `tab{1}`.idx desc".format(order_by_based_on_meta, doctype)
|
||||
|
||||
ignore_permissions = True if doctype == "DocType" else (cint(ignore_user_permissions) and has_permission(doctype))
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def check_user_tags(dt):
|
|||
try:
|
||||
frappe.db.sql("select `_user_tags` from `tab%s` limit 1" % dt)
|
||||
except Exception as e:
|
||||
if e.args[0] == 1054:
|
||||
if frappe.db.is_column_missing(e):
|
||||
DocTags(dt).setup()
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -62,11 +62,11 @@ def get_tags(doctype, txt, cat_tags):
|
|||
try:
|
||||
for _user_tags in frappe.db.sql_list("""select DISTINCT `_user_tags`
|
||||
from `tab{0}`
|
||||
where _user_tags like '%{1}%'
|
||||
limit 50""".format(frappe.db.escape(doctype), frappe.db.escape(txt))):
|
||||
where _user_tags like '{1}'
|
||||
limit 50""".format(doctype, frappe.db.escape('%' + txt + '%'))):
|
||||
tags.extend(_user_tags[1:].split(","))
|
||||
except Exception as e:
|
||||
if e.args[0]!=1054: raise
|
||||
if not frappe.db.is_column_missing(e): raise
|
||||
return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags))))
|
||||
|
||||
class DocTags:
|
||||
|
|
@ -112,7 +112,7 @@ class DocTags:
|
|||
doc= frappe.get_doc(self.dt, dn)
|
||||
update_global_search(doc)
|
||||
except Exception as e:
|
||||
if e.args[0]==1054:
|
||||
if frappe.db.is_column_missing(e):
|
||||
if not tags:
|
||||
# no tags, nothing to do
|
||||
return
|
||||
|
|
@ -123,5 +123,5 @@ class DocTags:
|
|||
|
||||
def setup(self):
|
||||
"""adds the _user_tags column if not exists"""
|
||||
from frappe.model.db_schema import add_column
|
||||
from frappe.database.schema import add_column
|
||||
add_column(self.dt, "_user_tags", "Data")
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ def get_contact_list(txt, page_length=20):
|
|||
where name like %(txt)s
|
||||
%(condition)s
|
||||
limit %(page_length)s
|
||||
""", {'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
""", {'txt': frappe.db.escape('%' + txt + '%'),
|
||||
'condition': match_conditions, 'page_length': page_length}, as_dict=True)
|
||||
out = filter(None, out)
|
||||
|
||||
|
|
|
|||
|
|
@ -342,9 +342,10 @@ class EmailAccount(Document):
|
|||
raise SentEmailInInbox
|
||||
|
||||
if email.message_id:
|
||||
names = frappe.db.sql("""select distinct name from tabCommunication
|
||||
where message_id='{message_id}'
|
||||
order by creation desc limit 1""".format(
|
||||
# https://stackoverflow.com/a/18367248
|
||||
names = frappe.db.sql("""SELECT DISTINCT `name`, `creation` FROM `tabCommunication`
|
||||
WHERE `message_id`='{message_id}'
|
||||
ORDER BY `creation` DESC LIMIT 1""".format(
|
||||
message_id=email.message_id
|
||||
), as_dict=True)
|
||||
|
||||
|
|
@ -462,7 +463,7 @@ class EmailAccount(Document):
|
|||
# try and match by subject and sender
|
||||
# if sent by same sender with same subject,
|
||||
# append it to old coversation
|
||||
subject = frappe.as_unicode(strip(re.sub("(^\s*(fw|fwd|wg)[^:]*:|\s*(re|aw)[^:]*:\s*)*",
|
||||
subject = frappe.as_unicode(strip(re.sub(r"(^\s*(fw|fwd|wg)[^:]*:|\s*(re|aw)[^:]*:\s*)*",
|
||||
"", email.subject, 0, flags=re.IGNORECASE)))
|
||||
|
||||
parent = frappe.db.get_all(self.append_to, filters={
|
||||
|
|
@ -604,7 +605,7 @@ class EmailAccount(Document):
|
|||
return
|
||||
|
||||
flags = frappe.db.sql("""select name, communication, uid, action from
|
||||
`tabEmail Flag Queue` where is_completed=0 and email_account='{email_account}'
|
||||
`tabEmail Flag Queue` where is_completed=0 and email_account={email_account}
|
||||
""".format(email_account=frappe.db.escape(self.name)), as_dict=True)
|
||||
|
||||
uid_list = { flag.get("uid", None): flag.get("action", "Read") for flag in flags }
|
||||
|
|
|
|||
|
|
@ -45,13 +45,13 @@ class TestEmailAccount(unittest.TestCase):
|
|||
comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"})
|
||||
comm.db_set("creation", datetime.now() - timedelta(seconds = 30 * 60))
|
||||
|
||||
frappe.db.sql("delete from `tabEmail Queue`")
|
||||
frappe.db.sql("DELETE FROM `tabEmail Queue`")
|
||||
notify_unreplied()
|
||||
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": comm.reference_doctype,
|
||||
"reference_name": comm.reference_name, "status":"Not Sent"}))
|
||||
|
||||
def test_incoming_with_attach(self):
|
||||
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
|
||||
frappe.db.sql("DELETE FROM `tabCommunication` WHERE sender='test_sender@example.com'")
|
||||
existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'})
|
||||
frappe.delete_doc("File", existing_file.name)
|
||||
existing_file.delete_file_from_filesystem()
|
||||
|
|
|
|||
|
|
@ -252,12 +252,21 @@ def get_list_context(context=None):
|
|||
|
||||
|
||||
def get_newsletter_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"):
|
||||
email_group_list = frappe.db.sql('''select eg.name from `tabEmail Group` eg, `tabEmail Group Member` egm
|
||||
where egm.unsubscribed=0 and eg.name=egm.email_group and egm.email = %s''', frappe.session.user)
|
||||
if email_group_list:
|
||||
return frappe.db.sql('''select n.name, n.subject, n.message, n.modified
|
||||
from `tabNewsletter` n, `tabNewsletter Email Group` neg
|
||||
where n.name = neg.parent and n.email_sent=1 and n.published=1 and neg.email_group in %s
|
||||
order by n.modified desc limit {0}, {1}
|
||||
'''.format(limit_start, limit_page_length), [email_group_list], as_dict=1)
|
||||
email_group_list = frappe.db.sql('''SELECT eg.name
|
||||
FROM `tabEmail Group` eg, `tabEmail Group Member` egm
|
||||
WHERE egm.unsubscribed=0
|
||||
AND eg.name=egm.email_group
|
||||
AND egm.email = %s''', frappe.session.user)
|
||||
email_group_list = [d[0] for d in email_group_list]
|
||||
|
||||
if email_group_list:
|
||||
return frappe.db.sql('''SELECT n.name, n.subject, n.message, n.modified
|
||||
FROM `tabNewsletter` n, `tabNewsletter Email Group` neg
|
||||
WHERE n.name = neg.parent
|
||||
AND n.email_sent=1
|
||||
AND n.published=1
|
||||
AND neg.email_group in ({0})
|
||||
ORDER BY n.modified DESC LIMIT {1} OFFSET {2}
|
||||
'''.format(','.join(['%s'] * len(email_group_list)),
|
||||
limit_page_length, limit_start), email_group_list, as_dict=1)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,16 +8,12 @@ import json, os
|
|||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.role.role import get_emails_from_role
|
||||
from frappe.utils import validate_email_add, nowdate, parse_val, is_html
|
||||
from frappe.utils import validate_email_add, nowdate, parse_val, is_html, add_to_date
|
||||
from frappe.utils.jinja import validate_template
|
||||
from frappe.modules.utils import export_module_json, get_doc_module
|
||||
from six import string_types
|
||||
from frappe.integrations.doctype.slack_webhook_url.slack_webhook_url import send_slack_message
|
||||
|
||||
# imports - third-party imports
|
||||
import pymysql
|
||||
from pymysql.constants import ER
|
||||
|
||||
class Notification(Document):
|
||||
def onload(self):
|
||||
'''load message'''
|
||||
|
|
@ -91,11 +87,19 @@ def get_context(context):
|
|||
if self.event=="Days After":
|
||||
diff_days = -diff_days
|
||||
|
||||
for name in frappe.db.sql_list("""select name from `tab{0}` where
|
||||
DATE(`{1}`) = ADDDATE(DATE(%s), INTERVAL %s DAY)""".format(self.document_type,
|
||||
self.date_changed), (nowdate(), diff_days or 0)):
|
||||
reference_date = add_to_date(nowdate(), days=diff_days)
|
||||
reference_date_start = reference_date + ' 00:00:00.000000'
|
||||
reference_date_end = reference_date + ' 23:59:59.000000'
|
||||
|
||||
doc = frappe.get_doc(self.document_type, name)
|
||||
doc_list = frappe.get_all(self.document_type,
|
||||
fields='name',
|
||||
filters=[
|
||||
{ self.date_changed: ('>=', reference_date_start) },
|
||||
{ self.date_changed: ('<=', reference_date_end) }
|
||||
])
|
||||
|
||||
for d in doc_list:
|
||||
doc = frappe.get_doc(self.document_type, d.name)
|
||||
|
||||
if self.condition and not frappe.safe_eval(self.condition, None, get_context(doc)):
|
||||
continue
|
||||
|
|
@ -246,9 +250,14 @@ def trigger_notifications(doc, method=None):
|
|||
return
|
||||
|
||||
if method == "daily":
|
||||
for alert in frappe.db.sql_list("""select name from `tabNotification`
|
||||
where event in ('Days Before', 'Days After') and enabled=1"""):
|
||||
alert = frappe.get_doc("Notification", alert)
|
||||
doc_list = frappe.get_all('Notification',
|
||||
filters={
|
||||
'event': ('in', ('Days Before', 'Days After')),
|
||||
'enabled': 1
|
||||
})
|
||||
for d in doc_list:
|
||||
alert = frappe.get_doc("Notification", d.name)
|
||||
|
||||
for doc in alert.get_documents_for_today():
|
||||
evaluate_alert(doc, alert, alert.event)
|
||||
frappe.db.commit()
|
||||
|
|
@ -268,12 +277,13 @@ def evaluate_alert(doc, alert, event):
|
|||
if event=="Value Change" and not doc.is_new():
|
||||
try:
|
||||
db_value = frappe.db.get_value(doc.doctype, doc.name, alert.value_changed)
|
||||
except pymysql.InternalError as e:
|
||||
if e.args[0]== ER.BAD_FIELD_ERROR:
|
||||
except Exception as e:
|
||||
if frappe.db.is_missing_column(e):
|
||||
alert.db_set('enabled', 0)
|
||||
frappe.log_error('Notification {0} has been disabled due to missing field'.format(alert.name))
|
||||
return
|
||||
|
||||
else:
|
||||
raise
|
||||
db_value = parse_val(db_value)
|
||||
if (doc.get(alert.value_changed) == db_value) or \
|
||||
(not db_value and not doc.get(alert.value_changed)):
|
||||
|
|
|
|||
|
|
@ -252,12 +252,12 @@ def check_email_limit(recipients):
|
|||
EmailLimitCrossedError)
|
||||
|
||||
def get_emails_sent_this_month():
|
||||
return frappe.db.sql("""select count(name) from `tabEmail Queue` where
|
||||
status='Sent' and MONTH(creation)=MONTH(CURDATE())""")[0][0]
|
||||
return frappe.db.sql("""SELECT COUNT(`name`) FROM `tabEmail Queue` WHERE
|
||||
`status`='Sent' AND EXTRACT(MONTH FROM `creation`) = EXTRACT(MONTH FROM NOW())""")[0][0]
|
||||
|
||||
def get_emails_sent_today():
|
||||
return frappe.db.sql("""select count(name) from `tabEmail Queue` where
|
||||
status='Sent' and creation>DATE_SUB(NOW(), INTERVAL 24 HOUR)""")[0][0]
|
||||
return frappe.db.sql("""SELECT COUNT(`name`) FROM `tabEmail Queue` WHERE
|
||||
`status`='Sent' AND `creation` > (NOW() - INTERVAL '24' HOUR)""")[0][0]
|
||||
|
||||
def get_unsubscribe_message(unsubscribe_message, expose_recipients):
|
||||
if unsubscribe_message:
|
||||
|
|
@ -483,8 +483,8 @@ def prepare_message(email, recipient, recipients_list):
|
|||
return ""
|
||||
|
||||
# Parse "Email Account" from "Email Sender"
|
||||
email_account = email.sender.rsplit('<',1)[0]
|
||||
if frappe.conf.use_ssl and frappe.db.get_value("Email Account", {"name": email_account}, "track_email_status"):
|
||||
email_account = get_outgoing_email_account(raise_exception_not_set=False, sender=email.sender)
|
||||
if frappe.conf.use_ssl and email_account.track_email_status:
|
||||
# Using SSL => Publically available domain => Email Read Reciept Possible
|
||||
message = message.replace("<!--email open check-->", quopri.encodestring('<img src="https://{}/api/method/frappe.core.doctype.communication.email.mark_email_as_seen?name={}"/>'.format(frappe.local.site, email.communication).encode()).decode())
|
||||
else:
|
||||
|
|
@ -554,17 +554,21 @@ def clear_outbox():
|
|||
Note: Used separate query to avoid deadlock
|
||||
"""
|
||||
|
||||
email_queues = frappe.db.sql_list("""select name from `tabEmail Queue`
|
||||
where priority=0 and datediff(now(), modified) > 31""")
|
||||
email_queues = frappe.db.sql_list("""SELECT `name` FROM `tabEmail Queue`
|
||||
WHERE `priority`=0 AND `modified` < (NOW() - INTERVAL '31' DAY)""")
|
||||
|
||||
if email_queues:
|
||||
frappe.db.sql("""delete from `tabEmail Queue` where name in (%s)"""
|
||||
% ','.join(['%s']*len(email_queues)), tuple(email_queues))
|
||||
frappe.db.sql("""DELETE FROM `tabEmail Queue` WHERE `name` IN ({0})""".format(
|
||||
','.join(['%s']*len(email_queues)
|
||||
)), tuple(email_queues))
|
||||
|
||||
frappe.db.sql("""delete from `tabEmail Queue Recipient` where parent in (%s)"""
|
||||
% ','.join(['%s']*len(email_queues)), tuple(email_queues))
|
||||
frappe.db.sql("""DELETE FROM `tabEmail Queue Recipient` WHERE `parent` IN ({0})""".format(
|
||||
','.join(['%s']*len(email_queues)
|
||||
)), tuple(email_queues))
|
||||
|
||||
frappe.db.sql("""
|
||||
update `tabEmail Queue`
|
||||
set status='Expired'
|
||||
where datediff(curdate(), modified) > 7 and status='Not Sent' and (send_after is null or send_after < %(now)s)""", { 'now': now_datetime() })
|
||||
UPDATE `tabEmail Queue`
|
||||
SET `status`='Expired'
|
||||
WHERE `modified` < (NOW() - INTERVAL '7' DAY)
|
||||
AND `status`='Not Sent'
|
||||
AND (`send_after` IS NULL OR `send_after` < %(now)s)""", { 'now': now_datetime() })
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@ from __future__ import unicode_literals
|
|||
# BEWARE don't put anything in this file except exceptions
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
# imports - third-party imports
|
||||
from pymysql import ProgrammingError as SQLError, Error
|
||||
# from pymysql import OperationalError as DatabaseOperationalError
|
||||
|
||||
class ValidationError(Exception):
|
||||
http_status_code = 417
|
||||
|
||||
|
|
@ -46,7 +42,7 @@ class Redirect(Exception):
|
|||
class CSRFTokenError(Exception):
|
||||
http_status_code = 400
|
||||
|
||||
class ImproperDBConfigurationError(Error):
|
||||
class ImproperDBConfigurationError(Exception):
|
||||
"""
|
||||
Used when frappe detects that database or tables are not properly
|
||||
configured
|
||||
|
|
@ -84,3 +80,4 @@ class RetryBackgroundJobError(Exception): pass
|
|||
class DocumentLockedError(ValidationError): pass
|
||||
class CircularLinkingError(ValidationError): pass
|
||||
class SecurityException(Exception): pass
|
||||
class InvalidColumnName(ValidationError): pass
|
||||
|
|
|
|||
|
|
@ -282,8 +282,6 @@ class FrappeClient(object):
|
|||
return params
|
||||
|
||||
def post_process(self, response):
|
||||
response.raise_for_status()
|
||||
|
||||
try:
|
||||
rjson = response.json()
|
||||
except ValueError:
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -176,6 +177,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -252,6 +254,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -860,6 +863,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -871,6 +875,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "# ###,##",
|
||||
"date_format": "dd/mm/yyyy",
|
||||
|
|
@ -937,6 +942,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -971,6 +977,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -1195,6 +1202,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -1228,6 +1236,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#.###,##",
|
||||
"date_format": "dd/mm/yyyy",
|
||||
|
|
@ -1463,6 +1472,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -1642,6 +1652,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -1668,6 +1679,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -1756,6 +1768,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -1978,6 +1991,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -2110,6 +2124,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -2203,6 +2218,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -2214,6 +2230,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
@ -2279,6 +2296,7 @@
|
|||
"currency": "EUR",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_symbol": "\u20ac",
|
||||
"number_format": "#,###.##",
|
||||
"timezones": [
|
||||
|
|
|
|||
|
|
@ -1,193 +1,208 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:country_name",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-19 10:23:30",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:country_name",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-19 10:23:30",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "country_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Country Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "country_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "country_name",
|
||||
"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": "Country Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "country_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"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": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "date_format",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Date Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "date_format",
|
||||
"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": "Date Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "time_zones",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Time Zones",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "time_zones",
|
||||
"fieldtype": "Text",
|
||||
"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": "Time Zones",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "code",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "code",
|
||||
"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": "Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-globe",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2016-12-29 14:40:34.951894",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Geo",
|
||||
"name": "Country",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-globe",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2018-08-29 06:37:32.303570",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Geo",
|
||||
"name": "Country",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "country_name",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "country_name",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -1,322 +1,345 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:currency_name",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-28 10:06:02",
|
||||
"custom": 0,
|
||||
"description": "**Currency** Master",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:currency_name",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-28 10:06:02",
|
||||
"custom": 0,
|
||||
"description": "**Currency** Master",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "currency_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Currency Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "currency_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "currency_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": "Currency Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "currency_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"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": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Enabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"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": "Enabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Sub-currency. For e.g. \"Cent\"",
|
||||
"fieldname": "fraction",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Fraction",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Sub-currency. For e.g. \"Cent\"",
|
||||
"fieldname": "fraction",
|
||||
"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": "Fraction",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "1 Currency = [?] Fraction\nFor e.g. 1 USD = 100 Cent",
|
||||
"fieldname": "fraction_units",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Fraction Units",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "1 Currency = [?] Fraction\nFor e.g. 1 USD = 100 Cent",
|
||||
"fieldname": "fraction_units",
|
||||
"fieldtype": "Int",
|
||||
"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": "Fraction Units",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Smallest circulating fraction unit (coin). For e.g. 1 cent for USD and it should be entered as 0.01",
|
||||
"fieldname": "smallest_currency_fraction_value",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Smallest Currency Fraction Value",
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Smallest circulating fraction unit (coin). For e.g. 1 cent for USD and it should be entered as 0.01",
|
||||
"fieldname": "smallest_currency_fraction_value",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Smallest Currency Fraction Value",
|
||||
"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_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "A symbol for this currency. For e.g. $",
|
||||
"fieldname": "symbol",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Symbol",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "A symbol for this currency. For e.g. $",
|
||||
"fieldname": "symbol",
|
||||
"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": "Symbol",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "How should this currency be formatted? If not set, will use system defaults",
|
||||
"fieldname": "number_format",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Number Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\n#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###",
|
||||
"permlevel": 0,
|
||||
"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_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "How should this currency be formatted? If not set, will use system defaults",
|
||||
"fieldname": "number_format",
|
||||
"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": "Number Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\n#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###",
|
||||
"permlevel": 0,
|
||||
"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
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-bitcoin",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-12-29 14:40:39.187557",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Geo",
|
||||
"name": "Currency",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-bitcoin",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-29 06:37:19.908254",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Geo",
|
||||
"name": "Currency",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Purchase User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Purchase User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
5
frappe/hooks.py
Executable file → Normal file
5
frappe/hooks.py
Executable file → Normal file
|
|
@ -12,7 +12,7 @@ source_link = "https://github.com/frappe/frappe"
|
|||
app_license = "MIT"
|
||||
|
||||
develop_version = '11.x.x-develop'
|
||||
staging_version = '11.0.0-beta'
|
||||
staging_version = '11.0.1'
|
||||
|
||||
app_email = "info@frappe.io"
|
||||
|
||||
|
|
@ -167,7 +167,8 @@ scheduler_events = {
|
|||
"frappe.utils.scheduler.restrict_scheduler_events_if_dormant",
|
||||
"frappe.email.doctype.auto_email_report.auto_email_report.send_daily",
|
||||
"frappe.core.doctype.feedback_request.feedback_request.delete_feedback_request",
|
||||
"frappe.core.doctype.activity_log.activity_log.clear_authentication_logs"
|
||||
"frappe.core.doctype.activity_log.activity_log.clear_authentication_logs",
|
||||
"frappe.utils.change_log.check_for_update"
|
||||
],
|
||||
"daily_long": [
|
||||
"frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily",
|
||||
|
|
|
|||
|
|
@ -8,104 +8,44 @@ from __future__ import unicode_literals, print_function
|
|||
|
||||
from six.moves import input
|
||||
|
||||
import os, json, sys, subprocess, shutil
|
||||
import os, json, subprocess, shutil
|
||||
import frappe
|
||||
import frappe.database
|
||||
import getpass
|
||||
import importlib
|
||||
from frappe import _
|
||||
from frappe.model.db_schema import DbManager
|
||||
from frappe.model.sync import sync_for
|
||||
from frappe.utils.fixtures import sync_fixtures
|
||||
from frappe.website import render
|
||||
from frappe.desk.doctype.desktop_icon.desktop_icon import sync_from_app
|
||||
from frappe.utils.password import create_auth_table
|
||||
from frappe.utils.global_search import setup_global_search_table
|
||||
from frappe.modules.utils import sync_customizations
|
||||
from frappe.database import setup_database
|
||||
|
||||
def install_db(root_login="root", root_password=None, db_name=None, source_sql=None,
|
||||
admin_password=None, verbose=True, force=0, site_config=None, reinstall=False):
|
||||
make_conf(db_name, site_config=site_config)
|
||||
frappe.flags.in_install_db = True
|
||||
if reinstall:
|
||||
frappe.connect(db_name=db_name)
|
||||
dbman = DbManager(frappe.local.db)
|
||||
dbman.create_database(db_name)
|
||||
admin_password=None, verbose=True, force=0, site_config=None, reinstall=False,
|
||||
db_type=None):
|
||||
|
||||
if not db_type:
|
||||
db_type = frappe.conf.db_type or 'mariadb'
|
||||
|
||||
else:
|
||||
frappe.local.db = get_root_connection(root_login, root_password)
|
||||
frappe.local.session = frappe._dict({'user':'Administrator'})
|
||||
create_database_and_user(force, verbose)
|
||||
|
||||
make_conf(db_name, site_config=site_config, db_type=db_type)
|
||||
frappe.flags.in_install_db = True
|
||||
|
||||
frappe.flags.root_login = root_login
|
||||
frappe.flags.root_password = root_password
|
||||
setup_database(force, source_sql, verbose)
|
||||
|
||||
frappe.conf.admin_password = frappe.conf.admin_password or admin_password
|
||||
|
||||
frappe.connect(db_name=db_name)
|
||||
check_if_ready_for_barracuda()
|
||||
import_db_from_sql(source_sql, verbose)
|
||||
if not 'tabDefaultValue' in frappe.db.get_tables():
|
||||
print('''Database not installed, this can due to lack of permission, or that the database name exists.
|
||||
Check your mysql root password, or use --force to reinstall''')
|
||||
sys.exit(1)
|
||||
|
||||
remove_missing_apps()
|
||||
|
||||
create_auth_table()
|
||||
setup_global_search_table()
|
||||
create_user_settings_table()
|
||||
frappe.db.create_auth_table()
|
||||
frappe.db.create_global_search_table()
|
||||
frappe.db.create_user_settings_table()
|
||||
|
||||
frappe.flags.in_install_db = False
|
||||
|
||||
|
||||
def create_database_and_user(force, verbose):
|
||||
db_name = frappe.local.conf.db_name
|
||||
dbman = DbManager(frappe.local.db)
|
||||
if force or (db_name not in dbman.get_database_list()):
|
||||
dbman.delete_user(db_name)
|
||||
dbman.drop_database(db_name)
|
||||
else:
|
||||
raise Exception("Database %s already exists" % (db_name,))
|
||||
|
||||
dbman.create_user(db_name, frappe.conf.db_password)
|
||||
if verbose: print("Created user %s" % db_name)
|
||||
|
||||
dbman.create_database(db_name)
|
||||
if verbose: print("Created database %s" % db_name)
|
||||
|
||||
dbman.grant_all_privileges(db_name, db_name)
|
||||
dbman.flush_privileges()
|
||||
if verbose: print("Granted privileges to user %s and database %s" % (db_name, db_name))
|
||||
|
||||
# close root connection
|
||||
frappe.db.close()
|
||||
|
||||
def create_user_settings_table():
|
||||
frappe.db.sql_ddl("""create table if not exists __UserSettings (
|
||||
`user` VARCHAR(180) NOT NULL,
|
||||
`doctype` VARCHAR(180) NOT NULL,
|
||||
`data` TEXT,
|
||||
UNIQUE(user, doctype)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8""")
|
||||
|
||||
def import_db_from_sql(source_sql, verbose):
|
||||
if verbose: print("Starting database import...")
|
||||
db_name = frappe.conf.db_name
|
||||
if not source_sql:
|
||||
source_sql = os.path.join(os.path.dirname(frappe.__file__), 'data', 'Framework.sql')
|
||||
DbManager(frappe.local.db).restore_database(db_name, source_sql, db_name, frappe.conf.db_password)
|
||||
if verbose: print("Imported from database %s" % source_sql)
|
||||
|
||||
def get_root_connection(root_login='root', root_password=None):
|
||||
if not frappe.local.flags.root_connection:
|
||||
if root_login:
|
||||
if not root_password:
|
||||
root_password = frappe.conf.get("root_password") or None
|
||||
|
||||
if not root_password:
|
||||
root_password = getpass.getpass("MySQL root password: ")
|
||||
frappe.local.flags.root_connection = frappe.database.Database(user=root_login, password=root_password)
|
||||
|
||||
return frappe.local.flags.root_connection
|
||||
|
||||
def install_app(name, verbose=False, set_as_patched=True):
|
||||
frappe.flags.in_install = name
|
||||
frappe.flags.ignore_in_install = False
|
||||
|
|
@ -117,7 +57,7 @@ def install_app(name, verbose=False, set_as_patched=True):
|
|||
# install pre-requisites
|
||||
if app_hooks.required_apps:
|
||||
for app in app_hooks.required_apps:
|
||||
install_app(app)
|
||||
install_app(app, verbose=verbose)
|
||||
|
||||
frappe.flags.in_install = name
|
||||
frappe.clear_cache()
|
||||
|
|
@ -257,14 +197,14 @@ def init_singles():
|
|||
doc.flags.ignore_validate=True
|
||||
doc.save()
|
||||
|
||||
def make_conf(db_name=None, db_password=None, site_config=None):
|
||||
def make_conf(db_name=None, db_password=None, site_config=None, db_type=None):
|
||||
site = frappe.local.site
|
||||
make_site_config(db_name, db_password, site_config)
|
||||
make_site_config(db_name, db_password, site_config, db_type=db_type)
|
||||
sites_path = frappe.local.sites_path
|
||||
frappe.destroy()
|
||||
frappe.init(site, sites_path=sites_path)
|
||||
|
||||
def make_site_config(db_name=None, db_password=None, site_config=None):
|
||||
def make_site_config(db_name=None, db_password=None, site_config=None, db_type=None):
|
||||
frappe.create_folder(os.path.join(frappe.local.site_path))
|
||||
site_file = get_site_config_path()
|
||||
|
||||
|
|
@ -272,6 +212,9 @@ def make_site_config(db_name=None, db_password=None, site_config=None):
|
|||
if not (site_config and isinstance(site_config, dict)):
|
||||
site_config = get_conf_params(db_name, db_password)
|
||||
|
||||
if db_type:
|
||||
site_config['db_type'] = db_type
|
||||
|
||||
with open(site_file, "w") as f:
|
||||
f.write(json.dumps(site_config, indent=1, sort_keys=True))
|
||||
|
||||
|
|
@ -300,7 +243,7 @@ def update_site_config(key, value, validate=True, site_config_path=None):
|
|||
|
||||
with open(site_config_path, "w") as f:
|
||||
f.write(json.dumps(site_config, indent=1, sort_keys=True))
|
||||
|
||||
|
||||
if hasattr(frappe.local, "conf"):
|
||||
frappe.local.conf[key] = value
|
||||
|
||||
|
|
@ -353,47 +296,6 @@ def remove_missing_apps():
|
|||
installed_apps.remove(app)
|
||||
frappe.db.set_global("installed_apps", json.dumps(installed_apps))
|
||||
|
||||
def check_if_ready_for_barracuda():
|
||||
mariadb_variables = frappe._dict(frappe.db.sql("""show variables"""))
|
||||
mariadb_minor_version = int(mariadb_variables.get('version').split('-')[0].split('.')[1])
|
||||
if mariadb_minor_version < 3:
|
||||
check_database(mariadb_variables, {
|
||||
"innodb_file_format": "Barracuda",
|
||||
"innodb_file_per_table": "ON",
|
||||
"innodb_large_prefix": "ON"
|
||||
})
|
||||
check_database(mariadb_variables, {
|
||||
"character_set_server": "utf8mb4",
|
||||
"collation_server": "utf8mb4_unicode_ci"
|
||||
})
|
||||
|
||||
def check_database(mariadb_variables, variables_dict):
|
||||
mariadb_minor_version = int(mariadb_variables.get('version').split('-')[0].split('.')[1])
|
||||
for key, value in variables_dict.items():
|
||||
if mariadb_variables.get(key) != value:
|
||||
site = frappe.local.site
|
||||
msg = ("Creation of your site - {x} failed because MariaDB is not properly {sep}"
|
||||
"configured to use the Barracuda storage engine. {sep}"
|
||||
"Please add the settings below to MariaDB's my.cnf, restart MariaDB then {sep}"
|
||||
"run `bench new-site {x}` again.{sep2}"
|
||||
"").format(x=site, sep2="\n"*2, sep="\n")
|
||||
|
||||
if mariadb_minor_version < 3:
|
||||
print_db_config(msg, expected_config_for_barracuda_2)
|
||||
else:
|
||||
print_db_config(msg, expected_config_for_barracuda_3)
|
||||
raise frappe.exceptions.ImproperDBConfigurationError(
|
||||
reason="MariaDB default file format is not Barracuda"
|
||||
)
|
||||
|
||||
|
||||
def print_db_config(explanation, config_text):
|
||||
print("="*80)
|
||||
print(explanation)
|
||||
print(config_text)
|
||||
print("="*80)
|
||||
|
||||
|
||||
def extract_sql_gzip(sql_gz_path):
|
||||
try:
|
||||
subprocess.check_call(['gzip', '-d', '-v', '-f', sql_gz_path])
|
||||
|
|
@ -421,25 +323,4 @@ def extract_tar_files(site_name, file_path, folder_name):
|
|||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
return tar_path
|
||||
|
||||
expected_config_for_barracuda_2 = """[mysqld]
|
||||
innodb-file-format=barracuda
|
||||
innodb-file-per-table=1
|
||||
innodb-large-prefix=1
|
||||
character-set-client-handshake = FALSE
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
[mysql]
|
||||
default-character-set = utf8mb4
|
||||
"""
|
||||
|
||||
expected_config_for_barracuda_3 = """[mysqld]
|
||||
character-set-client-handshake = FALSE
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
[mysql]
|
||||
default-character-set = utf8mb4
|
||||
"""
|
||||
return tar_path
|
||||
|
|
@ -106,7 +106,7 @@ def backup_to_dropbox(upload_db_backup=True):
|
|||
|
||||
dropbox_client = dropbox.Dropbox(dropbox_settings['access_token'])
|
||||
|
||||
if not upload_db_backup:
|
||||
if upload_db_backup:
|
||||
backup = new_backup(ignore_files=True)
|
||||
filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db))
|
||||
upload_file_to_dropbox(filename, "/database", dropbox_client)
|
||||
|
|
@ -133,10 +133,12 @@ def upload_from_folder(path, is_private, dropbox_folder, dropbox_client, did_not
|
|||
path = text_type(path)
|
||||
|
||||
for f in frappe.get_all("File", filters={"is_folder": 0, "is_private": is_private,
|
||||
"uploaded_to_dropbox": 0}, fields=['file_url', 'name']):
|
||||
"uploaded_to_dropbox": 0}, fields=['file_url', 'name', 'file_name']):
|
||||
if is_private:
|
||||
filename = f.file_url.replace('/private/files/', '')
|
||||
else:
|
||||
if not f.file_url:
|
||||
f.file_url = '/files/' + f.file_name;
|
||||
filename = f.file_url.replace('/files/', '')
|
||||
filepath = os.path.join(path, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
import googlemaps
|
||||
import datetime
|
||||
|
||||
|
||||
class GoogleMapsSettings(Document):
|
||||
def validate(self):
|
||||
|
|
@ -18,24 +18,14 @@ class GoogleMapsSettings(Document):
|
|||
frappe.throw(_("Home Address is required"))
|
||||
|
||||
def get_client(self):
|
||||
if not self.enabled:
|
||||
frappe.throw(_("Google Maps integration is not enabled"))
|
||||
|
||||
import googlemaps
|
||||
|
||||
try:
|
||||
client = googlemaps.Client(key=self.client_key)
|
||||
except Exception as e:
|
||||
frappe.throw(e.message)
|
||||
|
||||
return client
|
||||
|
||||
def round_timedelta(td, period):
|
||||
"""Round timedelta"""
|
||||
period_seconds = period.total_seconds()
|
||||
half_period_seconds = period_seconds / 2
|
||||
remainder = td.total_seconds() % period_seconds
|
||||
if remainder >= half_period_seconds:
|
||||
return datetime.timedelta(seconds=td.total_seconds() + (period_seconds - remainder))
|
||||
else:
|
||||
return datetime.timedelta(seconds=td.total_seconds() - remainder)
|
||||
|
||||
def format_address(address):
|
||||
"""Customer Address format """
|
||||
address = frappe.get_doc('Address', address)
|
||||
return '{}, {}, {}, {}'.format(address.address_line1, address.city, address.pincode, address.country)
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ def update_space_usage():
|
|||
files_size += get_folder_size(frappe.get_site_path("private", "files"))
|
||||
|
||||
backup_size = get_folder_size(frappe.get_site_path("private", "backups"))
|
||||
database_size = get_database_size()
|
||||
database_size = frappe.db.get_database_size()
|
||||
|
||||
usage = {
|
||||
'files_size': flt(files_size, 2),
|
||||
|
|
@ -225,13 +225,3 @@ def get_folder_size(path):
|
|||
if os.path.exists(path):
|
||||
return flt(subprocess.check_output(['du', '-ms', path]).split()[0], 2)
|
||||
|
||||
def get_database_size():
|
||||
'''Returns approximate database size in MB'''
|
||||
db_name = frappe.conf.db_name
|
||||
|
||||
# This query will get the database size in MB
|
||||
db_size = frappe.db.sql('''
|
||||
SELECT table_schema "database_name", sum( data_length + index_length ) / 1024 / 1024 "database_size"
|
||||
FROM information_schema.TABLES WHERE table_schema = %s GROUP BY table_schema''', db_name, as_dict=True)
|
||||
|
||||
return flt(db_size[0].get('database_size'), 2)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,34 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
import json
|
||||
|
||||
data_fieldtypes = (
|
||||
'Currency',
|
||||
'Int',
|
||||
'Long Int',
|
||||
'Float',
|
||||
'Percent',
|
||||
'Check',
|
||||
'Small Text',
|
||||
'Long Text',
|
||||
'Code',
|
||||
'Text Editor',
|
||||
'Date',
|
||||
'Datetime',
|
||||
'Time',
|
||||
'Text',
|
||||
'Data',
|
||||
'Link',
|
||||
'Dynamic Link',
|
||||
'Password',
|
||||
'Select',
|
||||
'Read Only',
|
||||
'Attach',
|
||||
'Attach Image',
|
||||
'Signature',
|
||||
'Color',
|
||||
'Barcode',
|
||||
'Geolocation'
|
||||
)
|
||||
|
||||
no_value_fields = ('Section Break', 'Column Break', 'HTML', 'Table', 'Button', 'Image',
|
||||
'Fold', 'Heading')
|
||||
|
|
@ -14,31 +42,12 @@ default_fields = ('doctype','name','owner','creation','modified','modified_by',
|
|||
'parent','parentfield','parenttype','idx','docstatus')
|
||||
optional_fields = ("_user_tags", "_comments", "_assign", "_liked_by", "_seen")
|
||||
|
||||
def copytables(srctype, src, srcfield, tartype, tar, tarfield, srcfields, tarfields=[]):
|
||||
if not tarfields:
|
||||
tarfields = srcfields
|
||||
l = []
|
||||
data = src.get(srcfield)
|
||||
for d in data:
|
||||
newrow = tar.append(tarfield)
|
||||
newrow.idx = d.idx
|
||||
|
||||
for i in range(len(srcfields)):
|
||||
newrow.set(tarfields[i], d.get(srcfields[i]))
|
||||
|
||||
l.append(newrow)
|
||||
return l
|
||||
|
||||
def db_exists(dt, dn):
|
||||
return frappe.db.exists(dt, dn)
|
||||
|
||||
def delete_fields(args_dict, delete=0):
|
||||
"""
|
||||
Delete a field.
|
||||
* Deletes record from `tabDocField`
|
||||
* If not single doctype: Drops column from table
|
||||
* If single, deletes record from `tabSingles`
|
||||
|
||||
args_dict = { dt: [field names] }
|
||||
"""
|
||||
import frappe.utils
|
||||
|
|
@ -65,4 +74,4 @@ def delete_fields(args_dict, delete=0):
|
|||
query = "ALTER TABLE `tab%s` " % dt + \
|
||||
", ".join(["DROP COLUMN `%s`" % f for f in fields if f in existing_fields])
|
||||
frappe.db.commit()
|
||||
frappe.db.sql(query)
|
||||
frappe.db.sql(query)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue