@@ -8,3 +8,4 @@ locale | |||
*.egg-info | |||
dist/ | |||
build/ | |||
docs/ |
@@ -7,33 +7,26 @@ services: | |||
- mysql | |||
install: | |||
- sudo service mysql stop | |||
- sudo apt-get install python-software-properties | |||
- sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db | |||
- sudo add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu precise main' | |||
- sudo apt-get update | |||
- sudo apt-get purge -y mysql-common | |||
- sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev | |||
- ./ci/fix-mariadb.sh | |||
- sudo apt-get install xfonts-75dpi xfonts-base -y | |||
- wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.2.1/wkhtmltox-0.12.2.1_linux-precise-amd64.deb | |||
- sudo dpkg -i wkhtmltox-0.12.2.1_linux-precise-amd64.deb | |||
- CFLAGS=-O0 pip install -r requirements.txt | |||
- pip install --editable . | |||
- wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh | |||
- sudo bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis | |||
- sudo service redis-server start | |||
- rm $TRAVIS_BUILD_DIR/.git/shallow | |||
- cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR | |||
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ | |||
script: | |||
- cd ./test_sites/ | |||
- frappe --use test_site | |||
- frappe --reinstall | |||
- frappe -b | |||
- frappe --build_website | |||
- frappe --serve_test & | |||
- frappe --verbose --run_tests | |||
- cd ~/frappe-bench | |||
- bench set-default-site test_site | |||
- bench frappe --use test_site | |||
- bench frappe --reinstall | |||
- bench frappe -b | |||
- bench frappe --build_website | |||
- bench frappe --serve_test & | |||
- bench frappe --verbose --run_tests | |||
before_script: | |||
- mysql -e 'create database test_frappe' | |||
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root | |||
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root | |||
- 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 | |||
@@ -0,0 +1,51 @@ | |||
## Frappe framework includes these public works | |||
### Javascript / CSS | |||
- Bootstrap: MIT License, (c) Twitter Inc, https://getbootstrap.com | |||
- JQuery: MIT License, (c) JQuery Foundation, http://jquery.org/license | |||
- JQuery UI: MIT License / GPL 2, (c) JQuery Foundation, https://jqueryui.com/about | |||
- JQuery UI Bootstrap Theme: MIT / GPL 2, (c) Addy Osmani, http://addyosmani.github.com/jquery-ui-bootstrap | |||
- QUnit: MIT License, (c) JQuery Foundation, http://jquery.org/license | |||
- jquery.event.drag, MIT License, (c) 2010 Three Dub Media - http://threedubmedia.com | |||
- JQuery Cookie Plugin, MIT / GPL 2, (c) 2011, Klaus Hartl | |||
- JQuery Time Picker, MIT License, (c) 2013 Trent Richardson, http://trentrichardson.com/examples/timepicker | |||
- JQuery Hotkeys Plugin, MIT License, (c) 2010, John Resig | |||
- prettydate.js, MIT License, (c) 2011, John Resig | |||
- jquery.flot.downsample, MIT License, (c) 2013, Sveinn Steinarsson | |||
- JQuery Resize Event, MIT License, (c) 2010 "Cowboy" Ben Alman | |||
- excanvas.js, Apache License Version 2.0, (c) 2006 Google Inc | |||
- showdown.js - Javascript Markdown, BSD-style Open Source License, (c) 2007 John Fraser | |||
- Beautify HTML - MIT License, (c) 2007-2013 Einar Lielmanis and contributors. | |||
- JQuery Gantt - MIT License, http://taitems.github.com/jQuery.Gantt/ | |||
- SlickGrid - MIT License, https://github.com/mleibman/SlickGrid | |||
- MomentJS - MIT License, https://github.com/moment/moment | |||
- JSColor - LGPL, (c) Jan Odvarko, http://jscolor.com | |||
- FullCalendar - MIT License, (c) 2013 Adam Shaw, http://fullcalendar.io/license/ | |||
- Sortable - MIT License (c) 2013-2015 Lebedev Konstantin http://rubaxa.github.io/Sortable/ | |||
### Python | |||
- minify.js - MIT License, (c) 2002 Douglas Crockford | |||
### Icon Fonts | |||
- Font Awesome - http://fontawesome.io/ | |||
- Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) | |||
- Code License: MIT (http://choosealicense.com/licenses/mit/) | |||
- Octicons (c) GitHub Inc, https://octicons.github.com/ | |||
- Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) | |||
- Code License: MIT (http://choosealicense.com/licenses/mit/) | |||
- Ionicons - MIT License, http://ionicons.com/ | |||
### IP Address Database | |||
- GeoIP: (c) 2014 MaxMind, http://dev.maxmind.com/geoip/geoip2/downloadable/ | |||
### Wallpaper | |||
- Version 5 Wallpaper: http://magdeleine.co/photo-nick-west-n-139/ (Public Domain) | |||
--- | |||
Last updated: 1st Jan 2015 |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
""" | |||
globals attached to frappe module | |||
@@ -37,7 +37,7 @@ class _dict(dict): | |||
return _dict(dict(self).copy()) | |||
def _(msg): | |||
"""translate object in current lang, if exists""" | |||
"""Returns translated string in current lang, if exists.""" | |||
if local.lang == "en": | |||
return msg | |||
@@ -45,12 +45,17 @@ def _(msg): | |||
return get_full_dict(local.lang).get(msg, msg) | |||
def get_lang_dict(fortype, name=None): | |||
"""Returns the translated language dict for the given type and name. | |||
:param fortype: must be one of `doctype`, `page`, `report`, `include`, `jsfile`, `boot` | |||
:param name: name of the document for which assets are to be returned.""" | |||
if local.lang=="en": | |||
return {} | |||
from frappe.translate import get_dict | |||
return get_dict(fortype, name) | |||
def set_user_lang(user, user_language=None): | |||
"""Guess and set user language for the session. `frappe.local.lang`""" | |||
from frappe.translate import get_user_lang | |||
local.lang = get_user_lang(user) | |||
@@ -72,6 +77,7 @@ message_log = local("message_log") | |||
lang = local("lang") | |||
def init(site, sites_path=None): | |||
"""Initialize frappe for the current site. Reset thread locals `frappe.local`""" | |||
if getattr(local, "initialised", None): | |||
return | |||
@@ -111,6 +117,10 @@ def init(site, sites_path=None): | |||
local.initialised = True | |||
def connect(site=None, db_name=None): | |||
"""Connect to site database instance. | |||
:param site: If site is given, calls `frappe.init`. | |||
:param db_name: Optional. Will use from `site_config.json`.""" | |||
from database import Database | |||
if site: | |||
init(site) | |||
@@ -120,6 +130,8 @@ def connect(site=None, db_name=None): | |||
set_user("Administrator") | |||
def get_site_config(sites_path=None, site_path=None): | |||
"""Returns `site_config.json` combined with `sites/common_site_config.json`. | |||
`site_config` is a set of site wide settings like database name, password, email etc.""" | |||
config = {} | |||
sites_path = sites_path or getattr(local, "sites_path", None) | |||
@@ -138,27 +150,31 @@ def get_site_config(sites_path=None, site_path=None): | |||
return _dict(config) | |||
def destroy(): | |||
"""closes connection and releases werkzeug local""" | |||
"""Closes connection and releases werkzeug local.""" | |||
if db: | |||
db.close() | |||
release_local(local) | |||
_memc = None | |||
# memcache | |||
redis_server = None | |||
def cache(): | |||
global _memc | |||
if not _memc: | |||
from frappe.memc import MClient | |||
_memc = MClient(['localhost:11211']) | |||
return _memc | |||
"""Returns memcache connection.""" | |||
global redis_server | |||
if not redis_server: | |||
from frappe.utils.redis_wrapper import RedisWrapper | |||
redis_server = RedisWrapper.from_url(conf.get("cache_redis_server") or "redis://localhost") | |||
return redis_server | |||
def get_traceback(): | |||
"""Returns error traceback.""" | |||
import utils | |||
return utils.get_traceback() | |||
def errprint(msg): | |||
"""Log error. This is sent back as `exc` in response. | |||
:param msg: Message.""" | |||
from utils import cstr | |||
if not request or (not "cmd" in local.form_dict): | |||
print cstr(msg) | |||
@@ -166,6 +182,9 @@ def errprint(msg): | |||
error_log.append(cstr(msg)) | |||
def log(msg): | |||
"""Add to `debug_log`. | |||
:param msg: Message.""" | |||
if not request: | |||
if conf.get("logging") or False: | |||
print repr(msg) | |||
@@ -174,21 +193,31 @@ def log(msg): | |||
debug_log.append(cstr(msg)) | |||
def msgprint(msg, small=0, raise_exception=0, as_table=False): | |||
"""Print a message to the user (via HTTP response). | |||
Messages are sent in the `__server_messages` property in the | |||
response JSON and shown in a pop-up / modal. | |||
:param msg: Message. | |||
:param small: [optional] Show as a floating message in the footer. | |||
:param raise_exception: [optional] Raise given exception and show message. | |||
:param as_table: [optional] If `msg` is a list of lists, render as HTML table. | |||
""" | |||
from utils import cstr, encode | |||
def _raise_exception(): | |||
if raise_exception: | |||
if flags.rollback_on_exception: | |||
db.rollback() | |||
import inspect | |||
if inspect.isclass(raise_exception) and issubclass(raise_exception, Exception): | |||
raise raise_exception, msg | |||
raise raise_exception, encode(msg) | |||
else: | |||
raise ValidationError, msg | |||
raise ValidationError, encode(msg) | |||
if flags.mute_messages: | |||
_raise_exception() | |||
return | |||
from utils import cstr | |||
if as_table and type(msg) in (list, tuple): | |||
msg = '<table border="1px" style="border-collapse: collapse" cellpadding="2px">' + ''.join(['<tr>'+''.join(['<td>%s</td>' % c for c in r])+'</tr>' for r in msg]) + '</table>' | |||
@@ -199,9 +228,17 @@ def msgprint(msg, small=0, raise_exception=0, as_table=False): | |||
_raise_exception() | |||
def throw(msg, exc=ValidationError): | |||
"""Throw execption and show message (`msgprint`). | |||
:param msg: Message. | |||
:param exc: Exception class. Default `frappe.ValidationError`""" | |||
msgprint(msg, raise_exception=exc) | |||
def create_folder(path, with_init=False): | |||
"""Create a folder in the given path and add an `__init__.py` file (optional). | |||
:param path: Folder path. | |||
:param with_init: Create `__init__.py` in the new folder.""" | |||
from frappe.utils import touch_file | |||
if not os.path.exists(path): | |||
os.makedirs(path) | |||
@@ -210,49 +247,76 @@ def create_folder(path, with_init=False): | |||
touch_file(os.path.join(path, "__init__.py")) | |||
def set_user(username): | |||
"""Set current user. | |||
:param username: **User** name to set as current user.""" | |||
from frappe.utils.user import User | |||
local.session.user = username | |||
local.session.sid = username | |||
local.cache = {} | |||
local.form_dict = _dict() | |||
local.jenv = None | |||
local.session.data = {} | |||
local.session.data = _dict() | |||
local.user = User(username) | |||
local.role_permissions = {} | |||
def get_request_header(key, default=None): | |||
"""Return HTTP request header. | |||
:param key: HTTP header key. | |||
:param default: Default value.""" | |||
return request.headers.get(key, default) | |||
def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", | |||
as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None, | |||
add_unsubscribe_link=False, attachments=None): | |||
add_unsubscribe_link=False, attachments=None, content=None, doctype=None, name=None, reply_to=None): | |||
"""Send email using user's default **Email Account** or global default **Email Account**. | |||
:param recipients: List of recipients. | |||
:param sender: Email sender. Default is current user. | |||
:param subject: Email Subject. | |||
:param message: (or `content`) Email Content. | |||
:param as_markdown: Convert content markdown to HTML. | |||
:param bulk: Send via scheduled email sender **Bulk Email**. Don't send immediately. | |||
:param ref_doctype: (or `doctype`) Append as communication to this DocType. | |||
:param ref_docname: (or `name`) Append as communication to this document name. | |||
:param add_unsubscribe_link: Allow user to unsubscribe from these emails. | |||
:param attachments: List of attachments. | |||
:param reply_to: Reply-To email id. | |||
""" | |||
if bulk: | |||
import frappe.utils.email_lib.bulk | |||
frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender, | |||
subject=subject, message=message, ref_doctype = ref_doctype, | |||
ref_docname = ref_docname, add_unsubscribe_link=add_unsubscribe_link, attachments=attachments) | |||
import frappe.email.bulk | |||
frappe.email.bulk.send(recipients=recipients, sender=sender, | |||
subject=subject, message=content or message, ref_doctype = doctype or ref_doctype, | |||
ref_docname = name or ref_docname, add_unsubscribe_link=add_unsubscribe_link, attachments=attachments, | |||
reply_to=reply_to) | |||
else: | |||
import frappe.utils.email_lib | |||
import frappe.email | |||
if as_markdown: | |||
frappe.utils.email_lib.sendmail_md(recipients, sender=sender, | |||
subject=subject, msg=message, attachments=attachments) | |||
frappe.email.sendmail_md(recipients, sender=sender, | |||
subject=subject, msg=content or message, attachments=attachments, reply_to=reply_to) | |||
else: | |||
frappe.utils.email_lib.sendmail(recipients, sender=sender, | |||
subject=subject, msg=message, attachments=attachments) | |||
frappe.email.sendmail(recipients, sender=sender, | |||
subject=subject, msg=content or message, attachments=attachments, reply_to=reply_to) | |||
logger = None | |||
whitelisted = [] | |||
guest_methods = [] | |||
def whitelist(allow_guest=False): | |||
""" | |||
decorator for whitelisting a function | |||
Decorator for whitelisting a function and making it accessible via HTTP. | |||
Standard request will be `/api/method/[path.to.method]` | |||
Note: if the function is allowed to be accessed by a guest user, | |||
it must explicitly be marked as allow_guest=True | |||
:param allow_guest: Allow non logged-in user to access this method. | |||
for specific roles, set allow_roles = ['Administrator'] etc. | |||
Use as: | |||
@frappe.whitelist() | |||
def myfunc(param1, param2): | |||
pass | |||
""" | |||
def innerfn(fn): | |||
global whitelisted, guest_methods | |||
@@ -266,6 +330,9 @@ def whitelist(allow_guest=False): | |||
return innerfn | |||
def only_for(roles): | |||
"""Raise `frappe.PermissionError` if the user does not have any of the given **Roles**. | |||
:param roles: List of roles to check.""" | |||
if not isinstance(roles, (tuple, list)): | |||
roles = (roles,) | |||
roles = set(roles) | |||
@@ -274,7 +341,10 @@ def only_for(roles): | |||
raise PermissionError | |||
def clear_cache(user=None, doctype=None): | |||
"""clear cache""" | |||
"""Clear **User**, **DocType** or global cache. | |||
:param user: If user is given, only user cache is cleared. | |||
:param doctype: If doctype is given, only DocType cache is cleared.""" | |||
import frappe.sessions | |||
if doctype: | |||
import frappe.model.meta | |||
@@ -287,6 +357,7 @@ def clear_cache(user=None, doctype=None): | |||
frappe.sessions.clear_cache() | |||
translate.clear_cache() | |||
reset_metadata_version() | |||
frappe.local.cache = {} | |||
for fn in frappe.get_hooks("clear_cache"): | |||
get_attr(fn)() | |||
@@ -294,114 +365,204 @@ def clear_cache(user=None, doctype=None): | |||
frappe.local.role_permissions = {} | |||
def get_roles(username=None): | |||
"""Returns roles of current user.""" | |||
if not local.session: | |||
return ["Guest"] | |||
return get_user(username).get_roles() | |||
def get_user(username): | |||
"""Returns `frappe.utils.user.User` instance of given user.""" | |||
from frappe.utils.user import User | |||
if not username or username == local.session.user: | |||
return local.user | |||
else: | |||
return User(username) | |||
def has_permission(doctype, ptype="read", doc=None, user=None): | |||
def has_permission(doctype, ptype="read", doc=None, user=None, verbose=False): | |||
"""Raises `frappe.PermissionError` if not permitted. | |||
:param doctype: DocType for which permission is to be check. | |||
:param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`. | |||
:param doc: [optional] Checks User permissions for given doc. | |||
:param user: [optional] Check for given user. Default: current user.""" | |||
import frappe.permissions | |||
return frappe.permissions.has_permission(doctype, ptype, doc, user=user) | |||
return frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user) | |||
def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=False): | |||
"""Raises `frappe.PermissionError` if not permitted. | |||
:param doctype: DocType for which permission is to be check. | |||
:param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`. | |||
:param doc: Checks User permissions for given doc. | |||
:param user: [optional] Check for given user. Default: current user.""" | |||
if not user: | |||
user = session.user | |||
for method in (get_hooks("has_website_permission") or {}).get(doctype, []): | |||
if not call(get_attr(method), doc=doc, ptype=ptype, user=user, verbose=verbose): | |||
return False | |||
return True | |||
def is_table(doctype): | |||
"""Returns True if `istable` property (indicating child Table) is set for given DocType.""" | |||
tables = cache().get_value("is_table") | |||
if tables==None: | |||
tables = db.sql_list("select name from tabDocType where ifnull(istable,0)=1") | |||
cache().set_value("is_table", tables) | |||
return doctype in tables | |||
def clear_perms(doctype): | |||
db.sql("""delete from tabDocPerm where parent=%s""", doctype) | |||
def reset_perms(doctype): | |||
from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for | |||
delete_notification_count_for(doctype) | |||
clear_perms(doctype) | |||
reload_doc(db.get_value("DocType", doctype, "module"), | |||
"DocType", doctype, force=True) | |||
def generate_hash(txt=None): | |||
"""Generates random hash for session id""" | |||
"""Generates random hash for given text + current timestamp + random string.""" | |||
import hashlib, time | |||
from .utils import random_string | |||
return hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest() | |||
def reset_metadata_version(): | |||
"""Reset `metadata_version` (Client (Javascript) build ID) hash.""" | |||
v = generate_hash() | |||
cache().set_value("metadata_version", v) | |||
return v | |||
def new_doc(doctype, parent_doc=None, parentfield=None): | |||
"""Returns a new document of the given DocType with defaults set. | |||
:param doctype: DocType of the new document. | |||
:param parent_doc: [optional] add to parent document. | |||
:param parentfield: [optional] add against this `parentfield`.""" | |||
from frappe.model.create_new import get_new_doc | |||
return get_new_doc(doctype, parent_doc, parentfield) | |||
def set_value(doctype, docname, fieldname, value): | |||
"""Set document value. Calls `frappe.client.set_value`""" | |||
import frappe.client | |||
return frappe.client.set_value(doctype, docname, fieldname, value) | |||
def get_doc(arg1, arg2=None): | |||
"""Return a `frappe.model.document.Document` object of the given type and name. | |||
:param arg1: DocType name as string **or** document JSON. | |||
:param arg2: [optional] Document name as string. | |||
Examples: | |||
# insert a new document | |||
todo = frappe.get_doc({"doctype":"ToDo", "description": "test"}) | |||
tood.insert() | |||
# open an existing document | |||
todo = frappe.get_doc("ToDo", "TD0001") | |||
""" | |||
import frappe.model.document | |||
return frappe.model.document.get_doc(arg1, arg2) | |||
def get_last_doc(doctype): | |||
"""Get last created document of this type.""" | |||
d = get_all(doctype, ["name"], order_by="creation desc", limit_page_length=1) | |||
if d: | |||
return get_doc(doctype, d[0].name) | |||
else: | |||
raise DoesNotExistError | |||
def get_single(doctype): | |||
"""Return a `frappe.model.document.Document` object of the given Single doctype.""" | |||
return get_doc(doctype, doctype) | |||
def get_meta(doctype, cached=True): | |||
"""Get `frappe.model.meta.Meta` instance of given doctype name.""" | |||
import frappe.model.meta | |||
return frappe.model.meta.get_meta(doctype, cached=cached) | |||
def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False): | |||
def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, | |||
ignore_permissions=False, flags=None): | |||
"""Delete a document. Calls `frappe.model.delete_doc.delete_doc`. | |||
:param doctype: DocType of document to be delete. | |||
:param name: Name of document to be delete. | |||
:param force: Allow even if document is linked. Warning: This may lead to data integrity errors. | |||
:param ignore_doctypes: Ignore if child table is one of these. | |||
:param for_reload: Call `before_reload` trigger before deleting. | |||
:param ignore_permissions: Ignore user permissions.""" | |||
import frappe.model.delete_doc | |||
frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions) | |||
frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, | |||
ignore_permissions, flags) | |||
def delete_doc_if_exists(doctype, name): | |||
"""Delete document if exists.""" | |||
if db.exists(doctype, name): | |||
delete_doc(doctype, name) | |||
def reload_doctype(doctype): | |||
"""Reload DocType from model (`[module]/[doctype]/[name]/[name].json`) files.""" | |||
reload_doc(scrub(db.get_value("DocType", doctype, "module")), "doctype", scrub(doctype)) | |||
def reload_doc(module, dt=None, dn=None, force=False): | |||
"""Reload Document from model (`[module]/[doctype]/[name]/[name].json`) files. | |||
:param module: Module name. | |||
:param dt: DocType name. | |||
:param dn: Document name. | |||
:param force: Reload even if `modified` timestamp matches. | |||
""" | |||
import frappe.modules | |||
return frappe.modules.reload_doc(module, dt, dn, force=force) | |||
def rename_doc(doctype, old, new, debug=0, force=False, merge=False, ignore_permissions=False): | |||
"""Rename a document. Calls `frappe.model.rename_doc.rename_doc`""" | |||
from frappe.model.rename_doc import rename_doc | |||
return rename_doc(doctype, old, new, force=force, merge=merge, ignore_permissions=ignore_permissions) | |||
def insert(doclist): | |||
import frappe.model | |||
return frappe.model.insert(doclist) | |||
def get_module(modulename): | |||
"""Returns a module object for given Python module name using `importlib.import_module`.""" | |||
return importlib.import_module(modulename) | |||
def scrub(txt): | |||
"""Returns sluggified string. e.g. `Sales Order` becomes `sales_order`.""" | |||
return txt.replace(' ','_').replace('-', '_').lower() | |||
def unscrub(txt): | |||
"""Returns titlified string. e.g. `sales_order` becomes `Sales Order`.""" | |||
return txt.replace('_',' ').replace('-', ' ').title() | |||
def get_module_path(module, *joins): | |||
"""Get the path of the given module name. | |||
:param module: Module name. | |||
:param *joins: Join additional path elements using `os.path.join`.""" | |||
module = scrub(module) | |||
return get_pymodule_path(local.module_app[module] + "." + module, *joins) | |||
def get_app_path(app_name, *joins): | |||
"""Return path of given app. | |||
:param app: App name. | |||
:param *joins: Join additional path elements using `os.path.join`.""" | |||
return get_pymodule_path(app_name, *joins) | |||
def get_site_path(*joins): | |||
"""Return path of current site. | |||
:param *joins: Join additional path elements using `os.path.join`.""" | |||
return os.path.join(local.site_path, *joins) | |||
def get_pymodule_path(modulename, *joins): | |||
"""Return path of given Python module name. | |||
:param modulename: Python module name. | |||
:param *joins: Join additional path elements using `os.path.join`.""" | |||
joins = [scrub(part) for part in joins] | |||
return os.path.join(os.path.dirname(get_module(scrub(modulename)).__file__), *joins) | |||
def get_module_list(app_name): | |||
"""Get list of modules for given all via `app/modules.txt`.""" | |||
return get_file_items(os.path.join(os.path.dirname(get_module(app_name).__file__), "modules.txt")) | |||
def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None): | |||
"""Get list of all apps via `sites/apps.txt`.""" | |||
if not sites_path: | |||
sites_path = local.sites_path | |||
@@ -409,36 +570,41 @@ def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None): | |||
if with_internal_apps: | |||
apps.extend(get_file_items(os.path.join(local.site_path, "apps.txt"))) | |||
if with_frappe: | |||
if "frappe" in apps: | |||
apps.remove("frappe") | |||
apps.insert(0, 'frappe') | |||
return apps | |||
def get_installed_apps(): | |||
def get_installed_apps(sort=False): | |||
"""Get list of installed apps in current site.""" | |||
if getattr(flags, "in_install_db", True): | |||
return [] | |||
installed = json.loads(db.get_global("installed_apps") or "[]") | |||
return installed | |||
@whitelist() | |||
def get_versions(): | |||
versions = {} | |||
for app in get_installed_apps(): | |||
versions[app] = { | |||
"title": get_hooks("app_title", app_name=app), | |||
"description": get_hooks("app_description", app_name=app) | |||
} | |||
try: | |||
versions[app]["version"] = get_attr(app + ".__version__") | |||
except AttributeError: | |||
versions[app]["version"] = '0.0.1' | |||
if sort: | |||
installed = [app for app in get_all_apps(True) if app in installed] | |||
return versions | |||
return installed | |||
def get_hooks(hook=None, default=None, app_name=None): | |||
"""Get hooks via `app/hooks.py` | |||
:param hook: Name of the hook. Will gather all hooks for this name and return as a list. | |||
:param default: Default if no hook found. | |||
:param app_name: Filter by app.""" | |||
def load_app_hooks(app_name=None): | |||
hooks = {} | |||
for app in [app_name] if app_name else get_installed_apps(): | |||
app = "frappe" if app=="webnotes" else app | |||
app_hooks = get_module(app + ".hooks") | |||
try: | |||
app_hooks = get_module(app + ".hooks") | |||
except ImportError: | |||
if local.flags.in_install_app: | |||
# if app is not installed while restoring | |||
# ignore it | |||
pass | |||
raise | |||
for key in dir(app_hooks): | |||
if not key.startswith("_"): | |||
append_hook(hooks, key, getattr(app_hooks, key)) | |||
@@ -469,6 +635,7 @@ def get_hooks(hook=None, default=None, app_name=None): | |||
return hooks | |||
def setup_module_map(): | |||
"""Rebuild map of all modules (internal).""" | |||
_cache = cache() | |||
if conf.db_name: | |||
@@ -490,6 +657,7 @@ def setup_module_map(): | |||
_cache.set_value("module_app", local.module_app) | |||
def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): | |||
"""Returns items from text file as a list. Ignores empty lines.""" | |||
import frappe.utils | |||
content = read_file(path, raise_not_found=raise_not_found) | |||
@@ -501,10 +669,12 @@ def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): | |||
return [] | |||
def get_file_json(path): | |||
"""Read a file and return parsed JSON object.""" | |||
with open(path, 'r') as f: | |||
return json.load(f) | |||
def read_file(path, raise_not_found=False): | |||
"""Open a file and return its content as Unicode.""" | |||
from frappe.utils import cstr | |||
if os.path.exists(path): | |||
with open(path, "r") as f: | |||
@@ -515,23 +685,30 @@ def read_file(path, raise_not_found=False): | |||
return None | |||
def get_attr(method_string): | |||
"""Get python method object from its name.""" | |||
modulename = '.'.join(method_string.split('.')[:-1]) | |||
methodname = method_string.split('.')[-1] | |||
return getattr(get_module(modulename), methodname) | |||
def call(fn, *args, **kwargs): | |||
"""Call a function and match arguments.""" | |||
if hasattr(fn, 'fnargs'): | |||
fnargs = fn.fnargs | |||
else: | |||
fnargs, varargs, varkw, defaults = inspect.getargspec(fn) | |||
newargs = {} | |||
for a in fnargs: | |||
if a in kwargs: | |||
for a in kwargs: | |||
if (a in fnargs) or varkw: | |||
newargs[a] = kwargs.get(a) | |||
if "flags" in newargs: | |||
del newargs["flags"] | |||
return fn(*args, **newargs) | |||
def make_property_setter(args, ignore_validate=False, validate_fields_for_doctype=True): | |||
"""Create a new **Property Setter** (for overriding DocType and DocField properties).""" | |||
args = _dict(args) | |||
ps = get_doc({ | |||
'doctype': "Property Setter", | |||
@@ -543,11 +720,12 @@ def make_property_setter(args, ignore_validate=False, validate_fields_for_doctyp | |||
'property_type': args.property_type or "Data", | |||
'__islocal': 1 | |||
}) | |||
ps.ignore_validate = ignore_validate | |||
ps.validate_fields_for_doctype = validate_fields_for_doctype | |||
ps.flags.ignore_validate = ignore_validate | |||
ps.flags.validate_fields_for_doctype = validate_fields_for_doctype | |||
ps.insert() | |||
def import_doc(path, ignore_links=False, ignore_insert=False, insert=False): | |||
"""Import a file using Data Import Tool.""" | |||
from frappe.core.page.data_import_tool import data_import_tool | |||
data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert) | |||
@@ -566,6 +744,7 @@ def copy_doc(doc, ignore_no_copy=True): | |||
d = doc | |||
newdoc = get_doc(copy.deepcopy(d)) | |||
newdoc.name = None | |||
newdoc.set("__islocal", 1) | |||
newdoc.owner = None | |||
@@ -587,10 +766,31 @@ def copy_doc(doc, ignore_no_copy=True): | |||
return newdoc | |||
def compare(val1, condition, val2): | |||
"""Compare two values using `frappe.utils.compare` | |||
`condition` could be: | |||
- "^" | |||
- "in" | |||
- "not in" | |||
- "=" | |||
- "!=" | |||
- ">" | |||
- "<" | |||
- ">=" | |||
- "<=" | |||
- "not None" | |||
- "None" | |||
""" | |||
import frappe.utils | |||
return frappe.utils.compare(val1, condition, val2) | |||
def respond_as_web_page(title, html, success=None, http_status_code=None): | |||
"""Send response as a web page with a message rather than JSON. Used to show permission errors etc. | |||
:param title: Page title and heading. | |||
:param message: Message to be shown. | |||
:param success: Alert message. | |||
:param http_status_code: HTTP status code.""" | |||
local.message_title = title | |||
local.message = html | |||
local.message_success = success | |||
@@ -600,26 +800,64 @@ def respond_as_web_page(title, html, success=None, http_status_code=None): | |||
local.response['http_status_code'] = http_status_code | |||
def build_match_conditions(doctype, as_condition=True): | |||
import frappe.widgets.reportview | |||
return frappe.widgets.reportview.build_match_conditions(doctype, as_condition) | |||
"""Return match (User permissions) for given doctype as list or SQL.""" | |||
import frappe.desk.reportview | |||
return frappe.desk.reportview.build_match_conditions(doctype, as_condition) | |||
def get_list(doctype, *args, **kwargs): | |||
"""List database query via `frappe.model.db_query`. Will also check for permissions. | |||
def get_list(doctype, filters=None, fields=None, or_filters=None, docstatus=None, | |||
group_by=None, order_by=None, limit_start=0, limit_page_length=None, | |||
as_list=False, debug=False, ignore_permissions=False, user=None): | |||
:param doctype: DocType on which query is to be made. | |||
:param fields: List of fields or `*`. | |||
:param filters: List of filters (see example). | |||
:param order_by: Order By e.g. `modified desc`. | |||
:param limit_page_start: Start results at record #. Default 0. | |||
:param limit_poge_length: No of records in the page. Default 20. | |||
Example usage: | |||
# simple dict filter | |||
frappe.get_list("ToDo", fields=["name", "description"], filters = {"owner":"test@example.com"}) | |||
# filter as a list of lists | |||
frappe.get_list("ToDo", fields="*", filters = [["modified", ">", "2014-01-01"]]) | |||
# filter as a list of dicts | |||
frappe.get_list("ToDo", fields="*", filters = {"description": ("like", "test%")}) | |||
""" | |||
import frappe.model.db_query | |||
return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters, | |||
fields=fields, docstatus=docstatus, or_filters=or_filters, | |||
group_by=group_by, order_by=order_by, limit_start=limit_start, | |||
limit_page_length=limit_page_length, as_list=as_list, debug=debug, | |||
ignore_permissions=ignore_permissions, user=user) | |||
return frappe.model.db_query.DatabaseQuery(doctype).execute(None, *args, **kwargs) | |||
def get_all(doctype, *args, **kwargs): | |||
"""List database query via `frappe.model.db_query`. Will **not** check for conditions. | |||
Parameters are same as `frappe.get_list` | |||
:param doctype: DocType on which query is to be made. | |||
:param fields: List of fields or `*`. Default is: `["name"]`. | |||
:param filters: List of filters (see example). | |||
:param order_by: Order By e.g. `modified desc`. | |||
:param limit_page_start: Start results at record #. Default 0. | |||
:param limit_poge_length: No of records in the page. Default 20. | |||
Example usage: | |||
# simple dict filter | |||
frappe.get_all("ToDo", fields=["name", "description"], filters = {"owner":"test@example.com"}) | |||
def get_all(doctype, **args): | |||
args["ignore_permissions"] = True | |||
return get_list(doctype, **args) | |||
# filter as a list of lists | |||
frappe.get_all("ToDo", fields=["*"], filters = [["modified", ">", "2014-01-01"]]) | |||
run_query = get_list | |||
# filter as a list of dicts | |||
frappe.get_all("ToDo", fields=["*"], filters = {"description": ("like", "test%")}) | |||
""" | |||
kwargs["ignore_permissions"] = True | |||
if not "limit_page_length" in kwargs: | |||
kwargs["limit_page_length"] = 0 | |||
return get_list(doctype, *args, **kwargs) | |||
def add_version(doc): | |||
"""Insert a new **Version** of the given document. | |||
A **Version** is a JSON dump of the current document state.""" | |||
get_doc({ | |||
"doctype": "Version", | |||
"ref_doctype": doc.doctype, | |||
@@ -628,6 +866,7 @@ def add_version(doc): | |||
}).insert(ignore_permissions=True) | |||
def get_test_records(doctype): | |||
"""Returns list of objects from `test_records.json` in the given doctype's folder.""" | |||
from frappe.modules import get_doctype_module, get_module_path | |||
path = os.path.join(get_module_path(get_doctype_module(doctype)), "doctype", scrub(doctype), "test_records.json") | |||
if os.path.exists(path): | |||
@@ -637,10 +876,21 @@ def get_test_records(doctype): | |||
return [] | |||
def format_value(value, df, doc=None, currency=None): | |||
"""Format value with given field properties. | |||
:param value: Value to be formatted. | |||
:param df: DocField object with properties `fieldtype`, `options` etc.""" | |||
import frappe.utils.formatters | |||
return frappe.utils.formatters.format_value(value, df, doc, currency=currency) | |||
def get_print_format(doctype, name, print_format=None, style=None, as_pdf=False): | |||
def get_print(doctype, name, print_format=None, style=None, as_pdf=False): | |||
"""Get Print Format for given document. | |||
:param doctype: DocType of document. | |||
:param name: Name of document. | |||
:param print_format: Print Format name. Default 'Standard', | |||
:param style: Print Format style. | |||
:param as_pdf: Return as PDF. Default False.""" | |||
from frappe.website.render import build_page | |||
from frappe.utils.pdf import get_pdf | |||
@@ -656,21 +906,32 @@ def get_print_format(doctype, name, print_format=None, style=None, as_pdf=False) | |||
else: | |||
return html | |||
def attach_print(doctype, name, file_name): | |||
def attach_print(doctype, name, file_name=None): | |||
from frappe.utils import scrub_urls | |||
if not file_name: file_name = name | |||
print_settings = db.get_singles_dict("Print Settings") | |||
local.flags.ignore_print_permissions = True | |||
if int(print_settings.send_print_as_pdf or 0): | |||
return { | |||
out = { | |||
"fname": file_name + ".pdf", | |||
"fcontent": get_print_format(doctype, name, as_pdf=True) | |||
"fcontent": get_print(doctype, name, as_pdf=True) | |||
} | |||
else: | |||
return { | |||
out = { | |||
"fname": file_name + ".html", | |||
"fcontent": scrub_urls(get_print_format(doctype, name)).encode("utf-8") | |||
"fcontent": scrub_urls(get_print(doctype, name)).encode("utf-8") | |||
} | |||
print print_settings, out | |||
local.flags.ignore_print_permissions = False | |||
return out | |||
logging_setup_complete = False | |||
def get_logger(module=None): | |||
from frappe.setup_logging import setup_logging | |||
@@ -1,2 +1,2 @@ | |||
from __future__ import unicode_literals | |||
__version__ = "4.13.2" | |||
__version__ = "5.0.0-alpha" |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -6,25 +6,32 @@ import json | |||
import frappe | |||
import frappe.handler | |||
import frappe.client | |||
import frappe.widgets.reportview | |||
import frappe.desk.reportview | |||
from frappe.utils.response import build_response | |||
from frappe import _ | |||
def handle(): | |||
""" | |||
/api/method/{methodname} will call a whitelisted method | |||
/api/resource/{doctype} will query a table | |||
Handler for `/api` methods | |||
### Examples: | |||
`/api/method/{methodname}` will call a whitelisted method | |||
`/api/resource/{doctype}` will query a table | |||
examples: | |||
?fields=["name", "owner"] | |||
?filters=[["Task", "name", "like", "%005"]] | |||
?limit_start=0 | |||
?limit_page_length=20 | |||
/api/resource/{doctype}/{name} will point to a resource | |||
GET will return doclist | |||
POST will insert | |||
PUT will update | |||
DELETE will delete | |||
/api/resource/{doctype}/{name}?run_method={method} will run a whitelisted controller method | |||
- `?fields=["name", "owner"]` | |||
- `?filters=[["Task", "name", "like", "%005"]]` | |||
- `?limit_start=0` | |||
- `?limit_page_length=20` | |||
`/api/resource/{doctype}/{name}` will point to a resource | |||
`GET` will return doclist | |||
`POST` will insert | |||
`PUT` will update | |||
`DELETE` will delete | |||
`/api/resource/{doctype}/{name}?run_method={method}` will run a whitelisted controller method | |||
""" | |||
parts = frappe.request.path[1:].split("/",3) | |||
call = doctype = name = None | |||
@@ -71,10 +78,15 @@ def handle(): | |||
if frappe.local.request.method=="PUT": | |||
data = json.loads(frappe.local.form_dict.data) | |||
doc = frappe.get_doc(doctype, name) | |||
if "flags" in data: | |||
del data["flags"] | |||
# Not checking permissions here because it's checked in doc.save | |||
doc.update(data) | |||
frappe.local.response.update({ | |||
"data": doc.save().as_dict() | |||
"data": doc.save().as_dict() | |||
}) | |||
frappe.db.commit() | |||
@@ -90,8 +102,9 @@ def handle(): | |||
if frappe.local.request.method=="GET": | |||
if frappe.local.form_dict.get('fields'): | |||
frappe.local.form_dict['fields'] = json.loads(frappe.local.form_dict['fields']) | |||
frappe.local.form_dict.setdefault('limit_page_length', 20) | |||
frappe.local.response.update({ | |||
"data": frappe.call(frappe.widgets.reportview.execute, | |||
"data": frappe.call(frappe.client.get_list, | |||
doctype, **frappe.local.form_dict)}) | |||
if frappe.local.request.method=="POST": | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -79,7 +79,7 @@ def application(request): | |||
# code 409 represents conflict | |||
http_status_code = 409 | |||
if frappe.local.is_ajax: | |||
if frappe.local.is_ajax or 'application/json' in request.headers.get('Accept', ''): | |||
response = frappe.utils.response.report_error(http_status_code) | |||
else: | |||
frappe.respond_as_web_page("Server Error", | |||
@@ -129,6 +129,10 @@ def make_form_dict(request): | |||
frappe.local.form_dict = frappe._dict({ k:v[0] if isinstance(v, (list, tuple)) else v \ | |||
for k, v in (request.form or request.args).iteritems() }) | |||
if "_" in frappe.local.form_dict: | |||
# _ is passed by $.ajax so that the request is not cached by the browser. So, remove _ from form_dict | |||
frappe.local.form_dict.pop("_") | |||
application = local_manager.make_middleware(application) | |||
def serve(port=8000, profile=False, site=None, sites_path='.'): | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -22,10 +22,11 @@ class HTTPRequest: | |||
if self.domain and self.domain.startswith('www.'): | |||
self.domain = self.domain[4:] | |||
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') \ | |||
or frappe.get_request_header('X-Forwarded-For') or '127.0.0.1' | |||
frappe.local.request_ip = (frappe.request.remote_addr | |||
or frappe.get_request_header('X-Forwarded-For') or '127.0.0.1') | |||
# language | |||
self.set_lang(frappe.get_request_header('HTTP_ACCEPT_LANGUAGE')) | |||
self.set_lang(frappe.request.accept_languages.values()) | |||
# load cookies | |||
frappe.local.cookie_manager = CookieManager() | |||
@@ -53,10 +54,19 @@ class HTTPRequest: | |||
# run login triggers | |||
if frappe.form_dict.get('cmd')=='login': | |||
frappe.local.login_manager.run_trigger('on_session_creation') | |||
self.clear_active_sessions() | |||
def clear_active_sessions(self): | |||
if not frappe.conf.get("deny_multiple_sessions"): | |||
return | |||
if frappe.session.user != "Guest": | |||
clear_sessions(frappe.session.user, keep_current=True) | |||
def set_lang(self, lang): | |||
from frappe.translate import guess_language_from_http_header | |||
frappe.local.lang = guess_language_from_http_header(lang) | |||
def set_lang(self, lang_codes): | |||
from frappe.translate import guess_language | |||
frappe.local.lang = guess_language(lang_codes) | |||
def setup_user(self): | |||
frappe.local.user = frappe.utils.user.User() | |||
@@ -73,6 +83,9 @@ class HTTPRequest: | |||
class LoginManager: | |||
def __init__(self): | |||
self.user = None | |||
self.info = None | |||
self.full_name = None | |||
if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login": | |||
self.login() | |||
else: | |||
@@ -85,6 +98,10 @@ class LoginManager: | |||
self.post_login() | |||
def post_login(self): | |||
self.info = frappe.db.get_value("User", self.user, | |||
["user_type", "first_name", "last_name", "user_image"], as_dict=1) | |||
self.full_name = " ".join(filter(None, [self.info.first_name, self.info.last_name])) | |||
self.run_trigger('on_login') | |||
self.validate_ip_address() | |||
self.validate_hour() | |||
@@ -95,24 +112,21 @@ class LoginManager: | |||
# set sid again | |||
frappe.local.cookie_manager.init_cookies() | |||
info = frappe.db.get_value("User", self.user, | |||
["user_type", "first_name", "last_name", "user_image"], as_dict=1) | |||
if info.user_type=="Website User": | |||
if self.info.user_type=="Website User": | |||
frappe.local.cookie_manager.set_cookie("system_user", "no") | |||
frappe.local.response["message"] = "No App" | |||
else: | |||
frappe.local.cookie_manager.set_cookie("system_user", "yes") | |||
frappe.local.response['message'] = 'Logged In' | |||
full_name = " ".join(filter(None, [info.first_name, info.last_name])) | |||
frappe.response["full_name"] = full_name | |||
frappe.local.cookie_manager.set_cookie("full_name", full_name) | |||
frappe.response["full_name"] = self.full_name | |||
frappe.local.cookie_manager.set_cookie("full_name", self.full_name) | |||
frappe.local.cookie_manager.set_cookie("user_id", self.user) | |||
frappe.local.cookie_manager.set_cookie("user_image", info.user_image or "") | |||
frappe.local.cookie_manager.set_cookie("user_image", self.info.user_image or "") | |||
def make_session(self, resume=False): | |||
# start session | |||
frappe.local.session_obj = Session(user=self.user, resume=resume) | |||
frappe.local.session_obj = Session(user=self.user, resume=resume, full_name=self.full_name) | |||
# reset user if changed to Guest | |||
self.user = frappe.local.session_obj.user | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -8,7 +8,7 @@ bootstrap client session | |||
import frappe | |||
import frappe.defaults | |||
import frappe.widgets.page | |||
import frappe.desk.desk_page | |||
from frappe.utils import get_gravatar | |||
def get_bootinfo(): | |||
@@ -43,11 +43,9 @@ def get_bootinfo(): | |||
bootinfo.hidden_modules = frappe.db.get_global("hidden_modules") | |||
bootinfo.doctype_icons = dict(frappe.db.sql("""select name, icon from | |||
tabDocType where ifnull(icon,'')!=''""")) | |||
bootinfo.doctype_icons.update(dict(frappe.db.sql("""select name, icon from | |||
tabPage where ifnull(icon,'')!=''"""))) | |||
bootinfo.single_types = frappe.db.sql_list("""select name from tabDocType where ifnull(issingle,0)=1""") | |||
add_home_page(bootinfo, doclist) | |||
add_allowed_pages(bootinfo) | |||
bootinfo.page_info = get_allowed_pages() | |||
load_translations(bootinfo) | |||
add_timezone_info(bootinfo) | |||
load_conf_settings(bootinfo) | |||
@@ -67,6 +65,7 @@ def get_bootinfo(): | |||
bootinfo.lang = unicode(bootinfo.lang) | |||
bootinfo.error_report_email = frappe.get_hooks("error_report_email") | |||
bootinfo.default_background_image = "/assets/frappe/images/ui/into-the-dawn.jpg" | |||
return bootinfo | |||
@@ -75,9 +74,10 @@ def load_conf_settings(bootinfo): | |||
for key in ['developer_mode']: | |||
if key in conf: bootinfo[key] = conf.get(key) | |||
def add_allowed_pages(bootinfo): | |||
def get_allowed_pages(): | |||
roles = frappe.get_roles() | |||
bootinfo.page_info = {} | |||
page_info = {} | |||
for p in frappe.db.sql("""select distinct | |||
tabPage.name, tabPage.modified, tabPage.title | |||
from `tabPage Role`, `tabPage` | |||
@@ -85,7 +85,7 @@ def add_allowed_pages(bootinfo): | |||
and `tabPage Role`.parent = `tabPage`.name""" % ', '.join(['%s']*len(roles)), | |||
roles, as_dict=True): | |||
bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title} | |||
page_info[p.name] = {"modified":p.modified, "title":p.title} | |||
# pages where role is not set are also allowed | |||
for p in frappe.db.sql("""select name, modified, title | |||
@@ -93,7 +93,9 @@ def add_allowed_pages(bootinfo): | |||
(select count(*) from `tabPage Role` | |||
where `tabPage Role`.parent=tabPage.name) = 0""", as_dict=1): | |||
bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title} | |||
page_info[p.name] = {"modified":p.modified, "title":p.title} | |||
return page_info | |||
def load_translations(bootinfo): | |||
if frappe.local.lang != 'en': | |||
@@ -106,7 +108,7 @@ def get_fullnames(): | |||
concat(ifnull(first_name, ''), | |||
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')) as fullname, | |||
user_image as image, gender, email | |||
from tabUser where ifnull(enabled, 0)=1""", as_dict=1) | |||
from tabUser where ifnull(enabled, 0)=1 and user_type!="Website User" """, as_dict=1) | |||
d = {} | |||
for r in ret: | |||
@@ -116,12 +118,6 @@ def get_fullnames(): | |||
return d | |||
def get_startup_js(): | |||
startup_js = [] | |||
for method in frappe.get_hooks().startup_js or []: | |||
startup_js.append(frappe.get_attr(method)() or "") | |||
return "\n".join(startup_js) | |||
def get_user(bootinfo): | |||
"""get user info""" | |||
bootinfo.user = frappe.user.load_user() | |||
@@ -132,10 +128,10 @@ def add_home_page(bootinfo, docs): | |||
return | |||
home_page = frappe.db.get_default("desktop:home_page") | |||
try: | |||
page = frappe.widgets.page.get(home_page) | |||
page = frappe.desk.desk_page.get(home_page) | |||
except (frappe.DoesNotExistError, frappe.PermissionError): | |||
frappe.message_log.pop() | |||
page = frappe.widgets.page.get('desktop') | |||
page = frappe.desk.desk_page.get('desktop') | |||
bootinfo['home_page'] = page.name | |||
docs.append(page) | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -8,21 +8,38 @@ from frappe.utils.minify import JavascriptMinify | |||
Build the `public` folders and setup languages | |||
""" | |||
import os, sys, frappe, json, shutil | |||
from cssmin import cssmin | |||
import os, frappe, json, shutil, re | |||
# from cssmin import cssmin | |||
app_paths = None | |||
def setup(): | |||
global app_paths | |||
pymodules = [] | |||
for app in frappe.get_all_apps(True): | |||
try: | |||
pymodules.append(frappe.get_module(app)) | |||
except ImportError: pass | |||
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules] | |||
def bundle(no_compress, make_copy=False, verbose=False): | |||
"""concat / minify js files""" | |||
# build js files | |||
setup() | |||
make_asset_dirs(make_copy=make_copy) | |||
build(no_compress, verbose) | |||
def watch(no_compress): | |||
"""watch and rebuild if necessary""" | |||
setup() | |||
import time | |||
compile_less() | |||
build(no_compress=True) | |||
while True: | |||
compile_less() | |||
if files_dirty(): | |||
build(no_compress=True) | |||
@@ -61,8 +78,6 @@ def build(no_compress=False, verbose=False): | |||
def get_build_maps(): | |||
"""get all build.jsons with absolute paths""" | |||
# framework js and css files | |||
pymodules = [frappe.get_module(app) for app in frappe.get_all_apps(True)] | |||
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules] | |||
build_maps = {} | |||
for app_path in app_paths: | |||
@@ -81,7 +96,7 @@ def get_build_maps(): | |||
source_paths.append(s) | |||
build_maps[target] = source_paths | |||
except Exception, e: | |||
except Exception: | |||
print path | |||
raise | |||
@@ -118,14 +133,12 @@ def pack(target, sources, no_compress, verbose): | |||
print "{0}: {1}k".format(f, int(len(minified) / 1024)) | |||
elif outtype=="js" and extn=="html": | |||
# add to frappe.templates | |||
content = data.replace("\n", " ").replace("'", "\'") | |||
outtxt += """frappe.templates["{key}"] = '{content}';\n""".format(\ | |||
key=f.rsplit("/", 1)[1][:-5], content=content) | |||
outtxt += html_to_js_template(f, data) | |||
else: | |||
outtxt += ('\n/*\n *\t%s\n */' % f) | |||
outtxt += '\n' + data + '\n' | |||
except Exception, e: | |||
except Exception: | |||
print "--Error in:" + f + "--" | |||
print frappe.get_traceback() | |||
@@ -138,6 +151,16 @@ def pack(target, sources, no_compress, verbose): | |||
print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))) | |||
def html_to_js_template(path, content): | |||
# remove whitespace to a single space | |||
content = re.sub("\s+", " ", content).replace("'", "\'") | |||
# strip comments | |||
content = re.sub("(<!--.*?-->)", "", content) | |||
return """frappe.templates["{key}"] = '{content}';\n""".format(\ | |||
key=path.rsplit("/", 1)[-1][:-5], content=content) | |||
def files_dirty(): | |||
for target, sources in get_build_maps().iteritems(): | |||
for f in sources: | |||
@@ -149,3 +172,20 @@ def files_dirty(): | |||
else: | |||
return False | |||
def compile_less(): | |||
for path in app_paths: | |||
less_path = os.path.join(path, "public", "less") | |||
if os.path.exists(less_path): | |||
for fname in os.listdir(less_path): | |||
if fname.endswith(".less") and fname != "variables.less": | |||
fpath = os.path.join(less_path, fname) | |||
mtime = os.path.getmtime(fpath) | |||
if fpath in timestamps and mtime == timestamps[fpath]: | |||
continue | |||
timestamps[fpath] = mtime | |||
print "compiling {0}".format(fpath) | |||
css_path = os.path.join(path, "public", "css", fname.rsplit(".", 1)[0] + ".css") | |||
os.system("lessc {0} > {1}".format(fpath, css_path)) |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals, absolute_import | |||
@@ -0,0 +1,96 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import os | |||
import json | |||
from semantic_version import Version | |||
import frappe | |||
from frappe.utils import cstr | |||
def get_change_log(user=None): | |||
if not user: user = frappe.session.user | |||
last_known_versions = frappe._dict(json.loads(frappe.db.get_value("User", user, "last_known_versions") or "{}")) | |||
current_versions = get_versions() | |||
if not last_known_versions: | |||
update_last_known_versions() | |||
return [] | |||
change_log = [] | |||
for app, opts in current_versions.items(): | |||
from_version = last_known_versions.get(app, {}).get("version") or "0.0.1" | |||
to_version = opts["version"] | |||
if from_version != to_version: | |||
app_change_log = get_change_log_for_app(app, from_version=from_version, to_version=to_version) | |||
if app_change_log: | |||
change_log.append({ | |||
"title": opts["title"], | |||
"description": opts["description"], | |||
"version": to_version, | |||
"change_log": app_change_log | |||
}) | |||
return change_log | |||
def get_change_log_for_app(app, from_version, to_version): | |||
change_log_folder = os.path.join(frappe.get_app_path(app), "change_log") | |||
if not os.path.exists(change_log_folder): | |||
return | |||
from_version = Version(from_version) | |||
to_version = Version(to_version) | |||
# remove pre-release part | |||
to_version.prerelease = None | |||
major_version_folders = ["v{0}".format(i) for i in xrange(from_version.major, to_version.major + 1)] | |||
app_change_log = [] | |||
for folder in os.listdir(change_log_folder): | |||
if folder in major_version_folders: | |||
for file in os.listdir(os.path.join(change_log_folder, folder)): | |||
version = Version(os.path.splitext(file)[0][1:].replace("_", ".")) | |||
if from_version < version <= to_version: | |||
file_path = os.path.join(change_log_folder, folder, file) | |||
content = frappe.read_file(file_path) | |||
app_change_log.append([version, content]) | |||
app_change_log = sorted(app_change_log, key=lambda d: d[0], reverse=True) | |||
# convert version to string and send | |||
return [[cstr(d[0]), d[1]] for d in app_change_log] | |||
@frappe.whitelist() | |||
def update_last_known_versions(): | |||
frappe.db.set_value("User", frappe.session.user, "last_known_versions", json.dumps(get_versions()), update_modified=False) | |||
@frappe.whitelist() | |||
def get_versions(): | |||
"""Get versions of all installed apps. | |||
Example: | |||
{ | |||
"frappe": { | |||
"title": "Frappe Framework", | |||
"version": "5.0.0" | |||
} | |||
}""" | |||
versions = {} | |||
for app in frappe.get_installed_apps(sort=True): | |||
versions[app] = { | |||
"title": frappe.get_hooks("app_title", app_name=app), | |||
"description": frappe.get_hooks("app_description", app_name=app) | |||
} | |||
try: | |||
versions[app]["version"] = frappe.get_attr(app + ".__version__") | |||
except AttributeError: | |||
versions[app]["version"] = '0.0.1' | |||
return versions | |||
@@ -0,0 +1,13 @@ | |||
### Version 5 | |||
Please see https://frappe.io/version-5 | |||
Changes include: | |||
1. New Visual Design | |||
1. Custom DocTypes | |||
1. Email Accounts | |||
1. Email Replies and Notifications | |||
1. Print Format Builder | |||
1. Document Sharing | |||
@@ -1,6 +1,6 @@ | |||
#!/usr/bin/env python2.7 | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -124,6 +124,8 @@ def setup_parser(): | |||
help="Show verbose output (where applicable)") | |||
parser.add_argument("--quiet", default=False, action="store_true", | |||
help="Do not show verbose output (where applicable)") | |||
parser.add_argument("--args", metavar="pass arguments", nargs="*", | |||
help="pass arguments to the method") | |||
return parser.parse_args() | |||
@@ -141,6 +143,8 @@ def setup_install(parser): | |||
help="Install a new app") | |||
parser.add_argument("--add_to_installed_apps", metavar="APP-NAME", nargs="*", | |||
help="Add these app(s) to Installed Apps") | |||
parser.add_argument("--remove_from_installed_apps", metavar="APP-NAME", nargs="*", | |||
help="Remove these app(s) from Installed Apps") | |||
parser.add_argument("--reinstall", default=False, action="store_true", | |||
help="Install a fresh app in db_name specified in conf.py") | |||
parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2, | |||
@@ -220,7 +224,6 @@ def setup_utilities(parser): | |||
parser.add_argument("--smtp", action="store_true", help="Run smtp debug server", | |||
dest="smtp_debug_server") | |||
parser.add_argument("--python", action="store_true", help="get python shell for a site") | |||
parser.add_argument("--flush_memcache", action="store_true", help="flush memcached") | |||
parser.add_argument("--ipython", action="store_true", help="get ipython shell for a site") | |||
parser.add_argument("--execute", help="execute a function", nargs=1, metavar="FUNCTION") | |||
parser.add_argument("--get_site_status", action="store_true", help="Get site details") | |||
@@ -235,6 +238,10 @@ def setup_utilities(parser): | |||
help="Clear website cache") | |||
parser.add_argument("--build_website", default=False, action="store_true", | |||
help="Sync statics and clear cache") | |||
parser.add_argument("--setup_docs", nargs=3, metavar = ("APP", "TARGET-APP", "PATH-IN-TARGET-APP"), | |||
help="Setup docs in target folder of target app") | |||
parser.add_argument("--build_docs", nargs=1, metavar = ("APP"), | |||
help="Build docs from /src to /www folder in app") | |||
parser.add_argument("--sync_statics", default=False, action="store_true", | |||
help="Sync files from templates/statics to Web Pages") | |||
parser.add_argument("--clear_cache", default=False, action="store_true", | |||
@@ -264,7 +271,7 @@ def setup_utilities(parser): | |||
# import/export | |||
parser.add_argument("--export_doc", nargs=2, metavar=('"DOCTYPE"', '"DOCNAME"')) | |||
parser.add_argument("--export_doclist", nargs=3, metavar=("DOCTYPE", "NAME", "PATH"), | |||
parser.add_argument("--export_json", nargs=3, metavar=("DOCTYPE", "NAME", "PATH"), | |||
help="""Export doclist as json to the given path, use '-' as name for Singles.""") | |||
parser.add_argument("--export_csv", nargs=2, metavar=("DOCTYPE", "PATH"), | |||
help="""Dump DocType as csv""") | |||
@@ -361,9 +368,17 @@ def add_to_installed_apps(*apps): | |||
from frappe.installer import add_to_installed_apps | |||
frappe.connect() | |||
all_apps = frappe.get_all_apps(with_frappe=True) | |||
for each in apps: | |||
if each in all_apps: | |||
add_to_installed_apps(each, rebuild_website=False) | |||
for app in apps: | |||
if app in all_apps: | |||
add_to_installed_apps(app, rebuild_website=False) | |||
frappe.destroy() | |||
@cmd | |||
def remove_from_installed_apps(*apps): | |||
from frappe.installer import remove_from_installed_apps | |||
frappe.connect() | |||
for app in apps: | |||
remove_from_installed_apps(app) | |||
frappe.destroy() | |||
@cmd | |||
@@ -420,13 +435,15 @@ def latest(rebuild_website=True, quiet=False): | |||
import frappe.model.sync | |||
from frappe.utils.fixtures import sync_fixtures | |||
import frappe.translate | |||
from frappe.core.doctype.notification_count.notification_count import clear_notifications | |||
from frappe.desk.notifications import clear_notifications | |||
verbose = not quiet | |||
frappe.connect() | |||
try: | |||
prepare_for_update() | |||
# run patches | |||
frappe.modules.patch_handler.run_all() | |||
# sync | |||
@@ -441,6 +458,10 @@ def latest(rebuild_website=True, quiet=False): | |||
finally: | |||
frappe.destroy() | |||
def prepare_for_update(): | |||
from frappe.sessions import clear_global_cache | |||
clear_global_cache() | |||
@cmd | |||
def sync_all(force=False, quiet=False): | |||
import frappe.model.sync | |||
@@ -540,7 +561,7 @@ def make_conf(db_name=None, db_password=None, site_config=None): | |||
@cmd | |||
def make_custom_server_script(doctype): | |||
from frappe.core.doctype.custom_script.custom_script import make_custom_server_script_file | |||
from frappe.custom.doctype.custom_script.custom_script import make_custom_server_script_file | |||
frappe.connect() | |||
make_custom_server_script_file(doctype) | |||
frappe.destroy() | |||
@@ -554,7 +575,7 @@ def init_list(doctype): | |||
@cmd | |||
def clear_cache(): | |||
import frappe.sessions | |||
from frappe.core.doctype.notification_count.notification_count import clear_notifications | |||
from frappe.desk.notifications import clear_notifications | |||
frappe.connect() | |||
frappe.clear_cache() | |||
clear_notifications() | |||
@@ -592,21 +613,41 @@ def sync_statics(force=False): | |||
frappe.db.commit() | |||
frappe.destroy() | |||
@cmd | |||
def setup_docs(app, docs_app, path): | |||
from frappe.utils.setup_docs import setup_docs | |||
frappe.connect() | |||
setup_docs(app, docs_app, path) | |||
frappe.destroy() | |||
@cmd | |||
def build_docs(app): | |||
from frappe.utils.autodoc import build | |||
frappe.connect() | |||
build(app) | |||
frappe.destroy() | |||
@cmd | |||
def reset_perms(): | |||
from frappe.permissions import reset_perms | |||
frappe.connect() | |||
for d in frappe.db.sql_list("""select name from `tabDocType` | |||
where ifnull(istable, 0)=0 and ifnull(custom, 0)=0"""): | |||
frappe.clear_cache(doctype=d) | |||
frappe.reset_perms(d) | |||
reset_perms(d) | |||
frappe.destroy() | |||
@cmd | |||
def execute(method): | |||
def execute(method, args=None): | |||
frappe.connect() | |||
ret = frappe.get_attr(method)() | |||
frappe.db.commit() | |||
frappe.destroy() | |||
if args: | |||
ret = frappe.get_attr(method)(*args) | |||
else: | |||
ret = frappe.get_attr(method)() | |||
if frappe.db: | |||
frappe.db.commit() | |||
frappe.destroy() | |||
if ret: | |||
print ret | |||
@@ -658,7 +699,7 @@ def export_doc(doctype, docname): | |||
frappe.destroy() | |||
@cmd | |||
def export_doclist(doctype, name, path): | |||
def export_json(doctype, name, path): | |||
from frappe.core.page.data_import_tool import data_import_tool | |||
frappe.connect() | |||
data_import_tool.export_json(doctype, name, path) | |||
@@ -850,11 +891,6 @@ def resize_images(path): | |||
import frappe.utils.image | |||
frappe.utils.image.resize_images(path) | |||
@cmd | |||
def flush_memcache(): | |||
frappe.cache().flush_all() | |||
def replace_code(start, txt1, txt2, extn, search=None, force=False): | |||
"""replace all txt1 by txt2 in files with extension (extn)""" | |||
import frappe.utils | |||
@@ -929,7 +965,7 @@ def get_site_status(verbose=False): | |||
# basic usage/progress analytics | |||
for doctype in ("Company", "Customer", "Item", "Quotation", "Sales Invoice", | |||
"Journal Voucher", "Stock Ledger Entry"): | |||
"Journal Entry", "Stock Ledger Entry"): | |||
key = doctype.lower().replace(" ", "_") + "_exists" | |||
ret[key] = 1 if frappe.db.count(doctype) else 0 | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -8,6 +8,12 @@ import frappe.model | |||
import frappe.utils | |||
import json, os | |||
@frappe.whitelist() | |||
def get_list(doctype, fields=None, filters=None, order_by=None, | |||
limit_start=None, limit_page_length=20): | |||
return frappe.get_list(doctype, fields=fields, filters=filters, order_by=order_by, | |||
limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=True) | |||
@frappe.whitelist() | |||
def get(doctype, name=None, filters=None): | |||
if filters and not name: | |||
@@ -26,9 +32,19 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False): | |||
if not frappe.has_permission(doctype): | |||
frappe.throw(_("Not permitted"), frappe.PermissionError) | |||
if fieldname and fieldname.startswith("["): | |||
try: | |||
filters = json.loads(filters) | |||
except ValueError: | |||
# name passed, not json | |||
pass | |||
try: | |||
fieldname = json.loads(fieldname) | |||
return frappe.db.get_value(doctype, json.loads(filters), fieldname, as_dict=as_dict, debug=debug) | |||
except ValueError: | |||
# name passed, not json | |||
pass | |||
return frappe.db.get_value(doctype, filters, fieldname, as_dict=as_dict, debug=debug) | |||
@frappe.whitelist() | |||
def set_value(doctype, name, fieldname, value): | |||
@@ -36,7 +52,7 @@ def set_value(doctype, name, fieldname, value): | |||
frappe.throw(_("Cannot edit standard fields")) | |||
doc = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True) | |||
if doc and doc.parent: | |||
if doc and doc.parent and doc.parenttype: | |||
doc = frappe.get_doc(doc.parenttype, doc.parent) | |||
child = doc.getone({"doctype": doctype, "name": name}) | |||
child.set(fieldname, value) | |||
@@ -53,30 +69,26 @@ def set_value(doctype, name, fieldname, value): | |||
return doc.as_dict() | |||
@frappe.whitelist() | |||
def insert(doclist): | |||
if isinstance(doclist, basestring): | |||
doclist = json.loads(doclist) | |||
if isinstance(doclist, dict): | |||
doclist = [doclist] | |||
def insert(doc=None): | |||
if isinstance(doc, basestring): | |||
doc = json.loads(doc) | |||
if doclist[0].get("parent") and doclist[0].get("parenttype"): | |||
if doc.get("parent") and doc.get("parenttype"): | |||
# inserting a child record | |||
d = doclist[0] | |||
doc = frappe.get_doc(d["parenttype"], d["parent"]) | |||
doc.append(d) | |||
doc.save() | |||
return [d] | |||
parent = frappe.get_doc(doc.parenttype, doc.parent) | |||
parent.append(doc) | |||
parent.save() | |||
return parent.as_dict() | |||
else: | |||
doc = frappe.get_doc(doclist).insert() | |||
doc = frappe.get_doc(doc).insert() | |||
return doc.as_dict() | |||
@frappe.whitelist() | |||
def save(doclist): | |||
if isinstance(doclist, basestring): | |||
doclist = json.loads(doclist) | |||
def save(doc): | |||
if isinstance(doc, basestring): | |||
doc = json.loads(doc) | |||
doc = frappe.get_doc(doclist).save() | |||
doc = frappe.get_doc(doc).save() | |||
return doc.as_dict() | |||
@frappe.whitelist() | |||
@@ -85,14 +97,14 @@ def rename_doc(doctype, old_name, new_name, merge=False): | |||
return new_name | |||
@frappe.whitelist() | |||
def submit(doclist): | |||
if isinstance(doclist, basestring): | |||
doclist = json.loads(doclist) | |||
def submit(doc): | |||
if isinstance(doc, basestring): | |||
doc = json.loads(doc) | |||
doclistobj = frappe.get_doc(doclist) | |||
doclistobj.submit() | |||
doc = frappe.get_doc(doc) | |||
doc.submit() | |||
return doclistobj.as_dict() | |||
return doc.as_dict() | |||
@frappe.whitelist() | |||
def cancel(doctype, name): | |||
@@ -0,0 +1,793 @@ | |||
# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import sys | |||
import os | |||
import subprocess | |||
import json | |||
import click | |||
import hashlib | |||
import cProfile | |||
import StringIO | |||
import pstats | |||
import frappe | |||
import frappe.utils | |||
from frappe.utils import cint | |||
from distutils.spawn import find_executable | |||
from functools import wraps | |||
def pass_context(f): | |||
@wraps(f) | |||
def _func(ctx, *args, **kwargs): | |||
profile = ctx.obj['profile'] | |||
if profile: | |||
pr = cProfile.Profile() | |||
pr.enable() | |||
ret = f(frappe._dict(ctx.obj), *args, **kwargs) | |||
if profile: | |||
pr.disable() | |||
s = StringIO.StringIO() | |||
ps = pstats.Stats(pr, stream=s).sort_stats('tottime', 'ncalls') | |||
ps.print_stats() | |||
print s.getvalue() | |||
return ret | |||
return click.pass_context(_func) | |||
def get_single_site(context): | |||
if not len(context.sites) == 1: | |||
print 'please select a site' | |||
sys.exit(1) | |||
site = context.sites[0] | |||
return site | |||
@click.command('new-site') | |||
@click.argument('site') | |||
@click.option('--db-name', help='Database name') | |||
@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) | |||
@click.option('--verbose', is_flag=True, default=False, help='Verbose') | |||
@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): | |||
"Install a new site" | |||
if not db_name: | |||
db_name = hashlib.sha1(site).hexdigest()[:10] | |||
frappe.init(site=site) | |||
_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) | |||
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): | |||
"Install a new Frappe site" | |||
from frappe.installer import install_db, make_site_dirs | |||
from frappe.installer import install_app as _install_app | |||
import frappe.utils.scheduler | |||
frappe.init(site=site) | |||
# enable scheduler post install? | |||
enable_scheduler = _is_scheduler_enabled() | |||
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) | |||
make_site_dirs() | |||
_install_app("frappe", verbose=verbose, set_as_patched=not source_sql) | |||
if frappe.conf.get("install_apps"): | |||
for app in frappe.conf.install_apps: | |||
install_app(app, verbose=verbose, set_as_patched=not source_sql) | |||
if install_apps: | |||
for app in install_apps: | |||
_install_app(app, verbose=verbose, set_as_patched=not source_sql) | |||
frappe.utils.scheduler.toggle_scheduler(enable_scheduler) | |||
scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled" | |||
print "*** Scheduler is", scheduler_status, "***" | |||
frappe.destroy() | |||
def _is_scheduler_enabled(): | |||
enable_scheduler = False | |||
try: | |||
frappe.connect() | |||
enable_scheduler = cint(frappe.db.get_default("enable_scheduler")) | |||
except: | |||
pass | |||
finally: | |||
frappe.db.close() | |||
return enable_scheduler | |||
@click.command('restore') | |||
@click.argument('sql-file-path') | |||
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB') | |||
@click.option('--mariadb-root-password', help='Root password for MariaDB') | |||
@click.option('--db-name', help='Database name for site in case it is a new one') | |||
@click.option('--admin-password', help='Administrator password for new site') | |||
@click.option('--install-app', multiple=True, help='Install app after installation') | |||
@pass_context | |||
def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_password=None, db_name=None, verbose=None, install_app=None, admin_password=None, force=None): | |||
"Restore site database from an sql file" | |||
site = get_single_site(context) | |||
frappe.init(site=site) | |||
if not db_name: | |||
db_name = frappe.conf.db_name | |||
_new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password, verbose=context.verbose, install_apps=install_app, source_sql=sql_file_path, force=context.force) | |||
@click.command('reinstall') | |||
@pass_context | |||
def reinstall(context): | |||
"Reinstall site ie. wipe all data and start over" | |||
site = get_single_site(context) | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
installed = frappe.get_installed_apps() | |||
frappe.clear_cache() | |||
except: | |||
installed = [] | |||
finally: | |||
frappe.db.close() | |||
_new_site(frappe.conf.db_name, site, verbose=context.verbose, force=True, reinstall=True, install_apps=installed) | |||
@click.command('install-app') | |||
@click.argument('app') | |||
@pass_context | |||
def install_app(context, app): | |||
"Install a new app to site" | |||
from frappe.installer import install_app | |||
for site in context.sites: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
try: | |||
install_app(app, verbose=context.verbose) | |||
finally: | |||
frappe.destroy() | |||
@click.command('add-system-manager') | |||
@click.argument('email') | |||
@click.option('--first-name') | |||
@click.option('--last-name') | |||
@pass_context | |||
def add_system_manager(context, email, first_name, last_name): | |||
"Add a new system manager to a site" | |||
import frappe.utils.user | |||
for site in context.sites: | |||
frappe.connect(site=site) | |||
try: | |||
frappe.utils.user.add_system_manager(email, first_name, last_name) | |||
frappe.db.commit() | |||
finally: | |||
frappe.destroy() | |||
@click.command('migrate') | |||
@click.option('--rebuild-website', help="Rebuild webpages after migration") | |||
@pass_context | |||
def migrate(context, rebuild_website=False): | |||
"Run patches, sync schema and rebuild files/translations" | |||
import frappe.modules.patch_handler | |||
import frappe.model.sync | |||
from frappe.utils.fixtures import sync_fixtures | |||
import frappe.translate | |||
from frappe.desk.notifications import clear_notifications | |||
verbose = context.verbose | |||
for site in context.sites: | |||
print 'Migrating', site | |||
frappe.init(site=site) | |||
frappe.connect() | |||
try: | |||
prepare_for_update() | |||
# run patches | |||
frappe.modules.patch_handler.run_all() | |||
# sync | |||
frappe.model.sync.sync_all(verbose=context.verbose) | |||
frappe.translate.clear_cache() | |||
sync_fixtures() | |||
clear_notifications() | |||
if rebuild_website: | |||
build_website() | |||
finally: | |||
frappe.destroy() | |||
def prepare_for_update(): | |||
from frappe.sessions import clear_global_cache | |||
clear_global_cache() | |||
@click.command('run-patch') | |||
@click.argument('module') | |||
@click.pass_context | |||
def run_patch(context, module): | |||
"Run a particular patch" | |||
import frappe.modules.patch_handler | |||
for site in context.sites: | |||
frappe.init(site=site) | |||
try: | |||
frappe.connect() | |||
frappe.modules.patch_handler.run_single(patch_module, force=context.force) | |||
finally: | |||
frappe.destroy() | |||
@click.command('reload-doc') | |||
@click.argument('module') | |||
@click.argument('doctype') | |||
@click.argument('docname') | |||
@pass_context | |||
def reload_doc(context, module, doctype, docname): | |||
"Reload schema for a DocType" | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.reload_doc(module, doctype, docname, force=context.force) | |||
frappe.db.commit() | |||
finally: | |||
frappe.destroy() | |||
@click.command('build') | |||
@click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking') | |||
@click.option('--verbose', is_flag=True, default=False, help='Verbose') | |||
def build(make_copy=False, verbose=False): | |||
"Minify + concatenate JS and CSS files, build translations" | |||
import frappe.build | |||
import frappe | |||
frappe.init('') | |||
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose) | |||
@click.command('watch') | |||
def watch(): | |||
"Watch and concatenate JS and CSS files as and when they change" | |||
import frappe.build | |||
frappe.init('') | |||
frappe.build.watch(True) | |||
@click.command('clear-cache') | |||
@pass_context | |||
def clear_cache(context): | |||
"Clear cache, doctype cache and defaults" | |||
import frappe.sessions | |||
import frappe.website.render | |||
from frappe.desk.notifications import clear_notifications | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.clear_cache() | |||
clear_notifications() | |||
frappe.website.render.clear_cache() | |||
finally: | |||
frappe.destroy() | |||
@click.command('clear-website-cache') | |||
@pass_context | |||
def clear_website_cache(context): | |||
"Clear website cache" | |||
import frappe.website.render | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.website.render.clear_cache() | |||
finally: | |||
frappe.destroy() | |||
@click.command('destroy-all-sessions') | |||
@pass_context | |||
def destroy_all_sessions(context): | |||
"Clear sessions of all users (logs them out)" | |||
import frappe.sessions | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.sessions.clear_all_sessions() | |||
frappe.db.commit() | |||
finally: | |||
frappe.destroy() | |||
@click.command('sync-www') | |||
@pass_context | |||
def sync_www(context): | |||
"Sync files from static pages from www directory to Web Pages" | |||
from frappe.website import statics | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
statics.sync_statics(rebuild=context.force) | |||
frappe.db.commit() | |||
finally: | |||
frappe.destroy() | |||
@click.command('build-website') | |||
@pass_context | |||
def build_website(context): | |||
"Sync statics and clear cache" | |||
from frappe.website import render, statics | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
render.clear_cache() | |||
statics.sync(verbose=context.verbose).start(True) | |||
frappe.db.commit() | |||
finally: | |||
frappe.destroy() | |||
@click.command('setup-docs') | |||
@click.argument('app') | |||
@click.argument('docs-app') | |||
@click.argument('path') | |||
@pass_context | |||
def setup_docs(context,app, docs_app, path): | |||
"Setup docs in target folder of target app" | |||
from frappe.utils.setup_docs import setup_docs | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
setup_docs(app, docs_app, path) | |||
finally: | |||
frappe.destroy() | |||
@click.command('build-docs') | |||
@pass_context | |||
def build_docs(context): | |||
"Build docs from /src to /www folder in app" | |||
from frappe.utils.autodoc import build | |||
frappe.destroy() | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
build(app) | |||
finally: | |||
frappe.destroy() | |||
@click.command('reset-perms') | |||
@pass_context | |||
def reset_perms(context): | |||
"Reset permissions for all doctypes" | |||
from frappe.permissions import reset_perms | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
for d in frappe.db.sql_list("""select name from `tabDocType` | |||
where ifnull(istable, 0)=0 and ifnull(custom, 0)=0"""): | |||
frappe.clear_cache(doctype=d) | |||
reset_perms(d) | |||
finally: | |||
frappe.destroy() | |||
@click.command('execute') | |||
@click.argument('method') | |||
@pass_context | |||
def execute(context, method): | |||
"execute a function" | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
ret = frappe.get_attr(method)() | |||
if frappe.db: | |||
frappe.db.commit() | |||
finally: | |||
frappe.destroy() | |||
if ret: | |||
print ret | |||
@click.command('celery') | |||
@click.argument('args') | |||
def celery(args): | |||
"Run a celery command" | |||
python = sys.executable | |||
os.execv(python, [python, "-m", "frappe.celery_app"] + args.split()) | |||
@click.command('trigger-scheduler-event') | |||
@click.argument('event') | |||
@pass_context | |||
def trigger_scheduler_event(context, event): | |||
"Trigger a scheduler event" | |||
import frappe.utils.scheduler | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.utils.scheduler.trigger(site, event, now=context.force) | |||
finally: | |||
frappe.destroy() | |||
@click.command('enable-scheduler') | |||
@pass_context | |||
def enable_scheduler(context): | |||
"Enable scheduler" | |||
import frappe.utils.scheduler | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.utils.scheduler.enable_scheduler() | |||
frappe.db.commit() | |||
print "Enabled for", site | |||
finally: | |||
frappe.destroy() | |||
@click.command('disable-scheduler') | |||
@pass_context | |||
def disable_scheduler(context): | |||
"Disable scheduler" | |||
import frappe.utils.scheduler | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.utils.scheduler.disable_scheduler() | |||
frappe.db.commit() | |||
print "Disabled for", site | |||
finally: | |||
frappe.destroy() | |||
@click.command('export-doc') | |||
@click.argument('doctype') | |||
@click.argument('docname') | |||
@pass_context | |||
def export_doc(context, doctype, docname): | |||
"Export a single document to csv" | |||
import frappe.modules | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.modules.export_doc(doctype, docname) | |||
finally: | |||
frappe.destroy() | |||
@click.command('export-json') | |||
@click.argument('doctype') | |||
@click.argument('name') | |||
@click.argument('path') | |||
@pass_context | |||
def export_json(context, doctype, name, path): | |||
"Export doclist as json to the given path, use '-' as name for Singles." | |||
from frappe.core.page.data_import_tool import data_import_tool | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
data_import_tool.export_json(doctype, name, path) | |||
finally: | |||
frappe.destroy() | |||
@click.command('export-csv') | |||
@click.argument('doctype') | |||
@click.argument('path') | |||
@pass_context | |||
def export_csv(context, doctype, path): | |||
"Dump DocType as csv" | |||
from frappe.core.page.data_import_tool import data_import_tool | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
data_import_tool.export_csv(doctype, path) | |||
finally: | |||
frappe.destroy() | |||
@click.command('export-fixtures') | |||
@pass_context | |||
def export_fixtures(context): | |||
"export fixtures" | |||
from frappe.utils.fixtures import export_fixtures | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
export_fixtures() | |||
finally: | |||
frappe.destroy() | |||
@click.command('import-doc') | |||
@click.argument('path') | |||
@pass_context | |||
def import_doc(context, path, force=False): | |||
"Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported" | |||
from frappe.core.page.data_import_tool import data_import_tool | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
data_import_tool.import_doc(path, overwrite=context.force) | |||
finally: | |||
frappe.destroy() | |||
# translation | |||
@click.command('build-message-files') | |||
@pass_context | |||
def build_message_files(context): | |||
"Build message files for translation" | |||
import frappe.translate | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.translate.rebuild_all_translation_files() | |||
finally: | |||
frappe.destroy() | |||
@click.command('get-untranslated') | |||
@click.argument('lang') | |||
@click.argument('untranslated_file') | |||
@click.option('--all', default=False, is_flag=True, help='Get all message strings') | |||
@pass_context | |||
def get_untranslated(context, lang, untranslated_file, all=None): | |||
"Get untranslated strings for language" | |||
import frappe.translate | |||
site = get_single_site(context) | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.translate.get_untranslated(lang, untranslated_file, get_all=all) | |||
finally: | |||
frappe.destroy() | |||
@click.command('update-translations') | |||
@click.argument('lang') | |||
@click.argument('untranslated_file') | |||
@click.argument('translated-file') | |||
@pass_context | |||
def update_translations(context, lang, untranslated_file, translated_file): | |||
"Update translated strings" | |||
import frappe.translate | |||
site = get_single_site(context) | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.translate.update_translations(lang, untranslated_file, translated_file) | |||
finally: | |||
frappe.destroy() | |||
@click.command('set-admin-password') | |||
@click.argument('admin-password') | |||
@pass_context | |||
def set_admin_password(context, admin_password): | |||
"Set Administrator password for a site" | |||
import getpass | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
while not admin_password: | |||
admin_password = getpass.getpass("Administrator's password for {0}: ".format(site)) | |||
frappe.connect() | |||
frappe.db.sql("""update __Auth set `password`=password(%s) | |||
where user='Administrator'""", (admin_password,)) | |||
frappe.db.commit() | |||
admin_password = None | |||
finally: | |||
frappe.destroy() | |||
@click.command('mysql') | |||
@pass_context | |||
def mysql(context): | |||
"Start Mariadb console for a site" | |||
site = get_single_site(context) | |||
frappe.init(site=site) | |||
msq = find_executable('mysql') | |||
os.execv(msq, [msq, '-u', frappe.conf.db_name, '-p'+frappe.conf.db_password, frappe.conf.db_name, '-h', frappe.conf.db_host or "localhost", "-A"]) | |||
@click.command('console') | |||
@pass_context | |||
def console(context): | |||
"Start ipython console for a site" | |||
site = get_single_site(context) | |||
import frappe | |||
frappe.init(site=site) | |||
frappe.connect() | |||
import IPython | |||
IPython.embed() | |||
@click.command('run-tests') | |||
@click.option('--app') | |||
@click.option('--doctype') | |||
@click.option('--test', multiple=True) | |||
@click.option('--driver') | |||
@click.option('--module') | |||
@pass_context | |||
def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None): | |||
"Run tests" | |||
import frappe.test_runner | |||
from frappe.utils import sel | |||
tests = test | |||
site = get_single_site(context) | |||
# sel.start(verbose, driver) | |||
try: | |||
frappe.init(site=site) | |||
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests, force=context.force) | |||
if len(ret.failures) == 0 and len(ret.errors) == 0: | |||
ret = 0 | |||
finally: | |||
pass | |||
# sel.close() | |||
return ret | |||
@click.command('serve') | |||
@click.option('--port', default=8000) | |||
@click.option('--profile', is_flag=True, default=False) | |||
@pass_context | |||
def serve(context, port=None, profile=False, sites_path='.', site=None): | |||
"Start development web server" | |||
if not context.sites: | |||
site = None | |||
else: | |||
site = context.sites[0] | |||
import frappe.app | |||
frappe.app.serve(port=port, profile=profile, site=site, sites_path='.') | |||
@click.command('request') | |||
@click.argument('args') | |||
@pass_context | |||
def request(context, args): | |||
"Run a request as an admin" | |||
import frappe.handler | |||
import frappe.api | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
if "?" in args: | |||
frappe.local.form_dict = frappe._dict([a.split("=") for a in args.split("?")[-1].split("&")]) | |||
else: | |||
frappe.local.form_dict = frappe._dict() | |||
if args.startswith("/api/method"): | |||
frappe.local.form_dict.cmd = args.split("?")[0].split("/")[-1] | |||
frappe.handler.execute_cmd(frappe.form_dict.cmd) | |||
print frappe.response | |||
finally: | |||
frappe.destroy() | |||
@click.command('doctor') | |||
def doctor(): | |||
"Get untranslated strings for lang." | |||
from frappe.utils.doctor import doctor as _doctor | |||
frappe.init('') | |||
return _doctor() | |||
@click.command('purge-all-tasks') | |||
def purge_all_tasks(): | |||
"Purge any pending periodic tasks of 'all' event. Doesn't purge hourly, daily and weekly" | |||
from frappe.utils.doctor import purge_pending_tasks | |||
count = purge_pending_tasks() | |||
print "Purged {} tasks".format(count) | |||
@click.command('dump-queue-status') | |||
def dump_queue_status(): | |||
"Dump detailed diagnostic infomation for task queues in JSON format" | |||
from frappe.utils.doctor import dump_queue_status as _dump_queue_status | |||
print json.dumps(_dump_queue_status(), indent=1) | |||
@click.command('make-app') | |||
@click.argument('destination') | |||
@click.argument('app_name') | |||
def make_app(destination, app_name): | |||
from frappe.utils.boilerplate import make_boilerplate | |||
make_boilerplate(destination, app_name) | |||
@click.command('use') | |||
@click.argument('site') | |||
def _use(site, sites_path='.'): | |||
use(site, sites_path=sites_path) | |||
def use(site, sites_path='.'): | |||
with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile: | |||
sitefile.write(site) | |||
@click.command('backup') | |||
@click.option('--with-files', default=False, is_flag=True, help="Take backup with files") | |||
@pass_context | |||
def backup(context, with_files=False, backup_path_db=None, backup_path_files=None, quiet=False): | |||
"Backup" | |||
from frappe.utils.backups import scheduled_backup | |||
verbose = context.verbose | |||
for site in context.sites: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, force=True) | |||
if verbose: | |||
from frappe.utils import now | |||
print "database backup taken -", odb.backup_path_db, "- on", now() | |||
if with_files: | |||
print "files backup taken -", odb.backup_path_files, "- on", now() | |||
frappe.destroy() | |||
@click.command('remove-from-installed-apps') | |||
@click.argument('app') | |||
@pass_context | |||
def remove_from_installed_apps(context, app): | |||
from frappe.installer import remove_from_installed_apps | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
remove_from_installed_apps(app) | |||
finally: | |||
frappe.destroy() | |||
# commands = [ | |||
# new_site, | |||
# restore, | |||
# install_app, | |||
# run_patch, | |||
# migrate, | |||
# add_system_manager, | |||
# celery | |||
# ] | |||
commands = [ | |||
new_site, | |||
restore, | |||
reinstall, | |||
install_app, | |||
add_system_manager, | |||
migrate, | |||
run_patch, | |||
reload_doc, | |||
build, | |||
watch, | |||
clear_cache, | |||
clear_website_cache, | |||
destroy_all_sessions, | |||
sync_www, | |||
build_website, | |||
setup_docs, | |||
build_docs, | |||
reset_perms, | |||
execute, | |||
celery, | |||
trigger_scheduler_event, | |||
enable_scheduler, | |||
disable_scheduler, | |||
export_doc, | |||
export_json, | |||
export_csv, | |||
export_fixtures, | |||
import_doc, | |||
build_message_files, | |||
get_untranslated, | |||
update_translations, | |||
set_admin_password, | |||
mysql, | |||
run_tests, | |||
serve, | |||
request, | |||
doctor, | |||
purge_all_tasks, | |||
dump_queue_status, | |||
console, | |||
make_app, | |||
_use, | |||
backup, | |||
remove_from_installed_apps, | |||
] |
@@ -3,9 +3,18 @@ from frappe import _ | |||
def get_data(): | |||
return { | |||
"Activity": { | |||
"color": "#e67e22", | |||
"icon": "icon-play", | |||
"icon": "octicon octicon-pulse", | |||
"label": _("Activity"), | |||
"link": "activity", | |||
"type": "page" | |||
}, | |||
"Calendar": { | |||
"color": "#2980b9", | |||
"icon": "icon-calendar", | |||
"icon": "octicon octicon-calendar", | |||
"label": _("Calendar"), | |||
"link": "Calendar/Event", | |||
"type": "view" | |||
@@ -13,6 +22,7 @@ def get_data(): | |||
"Messages": { | |||
"color": "#9b59b6", | |||
"icon": "icon-comments", | |||
"icon": "octicon octicon-comment-discussion", | |||
"label": _("Messages"), | |||
"link": "messages", | |||
"type": "page" | |||
@@ -20,19 +30,31 @@ def get_data(): | |||
"To Do": { | |||
"color": "#f1c40f", | |||
"icon": "icon-check", | |||
"icon": "octicon octicon-check", | |||
"label": _("To Do"), | |||
"link": "List/ToDo", | |||
"doctype": "ToDo", | |||
"type": "list" | |||
}, | |||
"Notes": { | |||
"color": "#95a5a6", | |||
"doctype": "Note", | |||
"icon": "icon-file-alt", | |||
"icon": "octicon octicon-file-text", | |||
"label": _("Notes"), | |||
"link": "List/Note", | |||
"type": "list" | |||
}, | |||
"Website": { | |||
"color": "#16a085", | |||
"icon": "icon-globe", | |||
"icon": "octicon octicon-globe", | |||
"type": "module" | |||
}, | |||
"Installer": { | |||
"color": "#888", | |||
"color": "#5ac8fb", | |||
"icon": "icon-download", | |||
"icon": "octicon octicon-cloud-download", | |||
"link": "applications", | |||
"type": "page", | |||
"label": _("Installer") | |||
@@ -40,11 +62,13 @@ def get_data(): | |||
"Setup": { | |||
"color": "#bdc3c7", | |||
"icon": "icon-wrench", | |||
"icon": "octicon octicon-settings", | |||
"type": "module" | |||
}, | |||
"Core": { | |||
"color": "#589494", | |||
"icon": "icon-cog", | |||
"icon": "octicon octicon-file-binary", | |||
"type": "module", | |||
"system_manager": 1 | |||
}, | |||
@@ -1,11 +1,11 @@ | |||
from __future__ import unicode_literals | |||
from frappe import _ | |||
from frappe.widgets.moduleview import add_setup_section | |||
from frappe.desk.moduleview import add_setup_section | |||
def get_data(): | |||
data = [ | |||
{ | |||
"label": _("Users and Permissions"), | |||
"label": _("Users"), | |||
"icon": "icon-group", | |||
"items": [ | |||
{ | |||
@@ -17,7 +17,13 @@ def get_data(): | |||
"type": "doctype", | |||
"name": "Role", | |||
"description": _("User Roles") | |||
}, | |||
} | |||
] | |||
}, | |||
{ | |||
"label": _("Permissions"), | |||
"icon": "icon-lock", | |||
"items": [ | |||
{ | |||
"type": "page", | |||
"name": "permission-manager", | |||
@@ -39,6 +45,13 @@ def get_data(): | |||
"icon": "icon-eye-open", | |||
"name": "Permitted Documents For User", | |||
"description": _("Check which Documents are readable by a User") | |||
}, | |||
{ | |||
"type": "report", | |||
"doctype": "DocShare", | |||
"icon": "icon-share", | |||
"name": "Document Share Report", | |||
"description": _("Report of all document shares") | |||
} | |||
] | |||
}, | |||
@@ -119,8 +132,8 @@ def get_data(): | |||
"items": [ | |||
{ | |||
"type": "doctype", | |||
"name": "Outgoing Email Settings", | |||
"description": _("Set outgoing mail server.") | |||
"name": "Email Account", | |||
"description": _("Add / Manage Email Accounts.") | |||
}, | |||
{ | |||
"type": "doctype", | |||
@@ -135,9 +148,15 @@ def get_data(): | |||
] | |||
}, | |||
{ | |||
"label": _("Printing and Branding"), | |||
"label": _("Printing"), | |||
"icon": "icon-print", | |||
"items": [ | |||
{ | |||
"type": "page", | |||
"label": "Print Format Builder", | |||
"name": "print-format-builder", | |||
"description": _("Drag and Drop tool to build and customize Print Formats.") | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Print Settings", | |||
@@ -146,7 +165,7 @@ def get_data(): | |||
{ | |||
"type": "doctype", | |||
"name": "Print Format", | |||
"description": _("Customized HTML Templates for printing transctions.") | |||
"description": _("Customized HTML Templates for printing transactions.") | |||
}, | |||
] | |||
}, | |||
@@ -169,7 +188,13 @@ def get_data(): | |||
"type": "doctype", | |||
"name": "Custom Script", | |||
"description": _("Add custom javascript to forms.") | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "DocType", | |||
"description": _("Add custom forms.") | |||
} | |||
] | |||
}, | |||
{ | |||
@@ -27,16 +27,6 @@ def get_data(): | |||
"name": "Blogger", | |||
"description": _("User ID of a blog writer."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Website Group", | |||
"description": _("Web Site Forum Page."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Post", | |||
"description": _("List of Web Site Forum's Posts."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Website Slideshow", | |||
@@ -85,8 +75,8 @@ def get_data(): | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Website Page Permission", | |||
"description": _("Define read, write, admin permissions for a Website Page."), | |||
"name": "Website Theme", | |||
"description": _("List of themes for Website."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,4 +1,5 @@ | |||
{ | |||
"allow_import": 1, | |||
"autoname": "hash", | |||
"creation": "2012-08-08 10:40:11", | |||
"docstatus": 0, | |||
@@ -20,6 +21,7 @@ | |||
"fieldname": "comment_type", | |||
"fieldtype": "Data", | |||
"label": "Comment Type", | |||
"options": "Email\nChat\nPhone\nSMS\nCreated\nSubmitted\nCancelled\nAssigned\nAssignment Completed\nComment\nWorkflow\nLabel\nAttachment\nAttachment Removed", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
@@ -106,7 +108,7 @@ | |||
"icon": "icon-comments", | |||
"idx": 1, | |||
"issingle": 0, | |||
"modified": "2014-08-22 05:24:28.072749", | |||
"modified": "2015-02-11 15:32:45.807458", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Comment", | |||
@@ -117,11 +119,14 @@ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": 1, | |||
"import": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -9,16 +9,48 @@ from frappe.model.document import Document | |||
from frappe.model.db_schema import add_column | |||
class Comment(Document): | |||
"""Comments are added to Documents via forms or views like blogs etc.""" | |||
__doclink__ = "https://frappe.io/docs/models/core/comment" | |||
def get_feed(self): | |||
"""Returns feed HTML from Comment.""" | |||
if self.comment_doctype == "Message": | |||
return | |||
if self.comment_type in ("Created", "Submitted", "Cancelled", "Label"): | |||
comment_type = "Label" | |||
elif self.comment_type == "Comment": | |||
comment_type = "Comment" | |||
else: | |||
comment_type = "Info" | |||
return { | |||
"subject": self.comment, | |||
"doctype": self.comment_doctype, | |||
"name": self.comment_docname, | |||
"feed_type": comment_type | |||
} | |||
def validate(self): | |||
"""Raise exception for more than 50 comments.""" | |||
if frappe.db.sql("""select count(*) from tabComment where comment_doctype=%s | |||
and comment_docname=%s""", (self.doctype, self.name))[0][0] >= 50: | |||
frappe.throw(_("Cannot add more than 50 comments")) | |||
def on_update(self): | |||
"""Updates `_comments` property in parent Document.""" | |||
self.update_comment_in_doc() | |||
def update_comment_in_doc(self): | |||
"""Updates `_comments` (JSON) property in parent Document. | |||
Creates a column `_comments` if property does not exist. | |||
`_comments` format | |||
{ | |||
"comment": [String], | |||
"by": [user], | |||
"name": [Comment Document name] | |||
}""" | |||
if self.comment_doctype and self.comment_docname and self.comment and self.comment_type=="Comment": | |||
_comments = self.get_comments_from_parent() | |||
updated = False | |||
@@ -60,6 +92,9 @@ class Comment(Document): | |||
raise | |||
def update_comments_in_parent(self, _comments): | |||
"""Updates `_comments` property in parent Document with given dict. | |||
:param _comments: Dict of comments.""" | |||
# use sql, so that we do not mess with the timestamp | |||
frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (self.comment_doctype, | |||
"%s", "%s"), (json.dumps(_comments), self.comment_docname)) | |||
@@ -69,6 +104,10 @@ class Comment(Document): | |||
clear_cache(comment_doc.get_route()) | |||
def on_trash(self): | |||
"""Removes from `_comments` in parent Document""" | |||
if self.comment_doctype == "Message": | |||
return | |||
if (self.comment_type or "Comment") != "Comment": | |||
frappe.only_for("System Manager") | |||
@@ -80,6 +119,7 @@ class Comment(Document): | |||
self.update_comments_in_parent(_comments) | |||
def on_doctype_update(): | |||
"""Add index to `tabComment` `(comment_doctype, comment_name)`""" | |||
if not frappe.db.sql("""show index from `tabComment` | |||
where Key_name="comment_doctype_docname_index" """): | |||
frappe.db.commit() | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -1,3 +1,3 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
@@ -1,10 +1,33 @@ | |||
cur_frm.cscript.onload = function(doc) { | |||
cur_frm.fields_dict.user.get_query = function() { | |||
return { | |||
query: "frappe.core.doctype.communication.communication.get_user" | |||
frappe.ui.form.on("setup", "Communication", function(frm) { | |||
frappe.call({ | |||
method:"frappe.core.doctype.doctype.communication.get_convert_to", | |||
callback: function(r) { | |||
frappe.communication_convert_to = r.message; | |||
frm.convert_to_click = []; | |||
$.each(r.message, function(i, v) { | |||
frm.convert_to_click.append({label:__(v), value:v, action:function() { | |||
frm.convert_to($(this).attr("data-value")); | |||
}}); | |||
}); | |||
frm.set_convert_button(); | |||
} | |||
}); | |||
frm.set_convert_button = function() { | |||
frm.add_custom_button(__("Add To"), frm.convert_to_click); | |||
}; | |||
if(doc.content) | |||
frm.convert_to = function(doctype) { | |||
}; | |||
}); | |||
frappe.ui.form.on("refresh", "Communication", function(frm) { | |||
frm.convert_to_click && frm.set_convert_button(); | |||
}); | |||
frappe.ui.form.on("onload", "Communication", function(frm) { | |||
if(doc.content) { | |||
doc.content = frappe.utils.remove_script_and_style(doc.content); | |||
} | |||
} | |||
}); |
@@ -1,5 +1,5 @@ | |||
{ | |||
"allow_import": 1, | |||
"allow_import": 1, | |||
"autoname": "naming_series:", | |||
"creation": "2013-01-29 10:47:14", | |||
"description": "Keep a track of all communications", | |||
@@ -19,59 +19,63 @@ | |||
{ | |||
"fieldname": "sent_or_received", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Sent or Received", | |||
"options": "Sent\nReceived", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"label": "Status", | |||
"options": "Open\nReplied\nArchived", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"in_list_view": 0, | |||
"label": "Subject", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"label": "Content", | |||
"fieldname": "column_break_5", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"width": "400" | |||
}, | |||
{ | |||
"fieldname": "section_break1", | |||
"fieldtype": "Section Break", | |||
"options": "simple", | |||
"permlevel": 0 | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "category", | |||
"fieldtype": "Select", | |||
"label": "Category", | |||
"options": "\nSales\nComplaint\nHelp\nSuggestion\nMiscellaneous\nSent Mail", | |||
"fieldname": "reference_doctype", | |||
"fieldtype": "Link", | |||
"label": "Reference DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"reqd": 0 | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "column_break2", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
"fieldname": "reference_name", | |||
"fieldtype": "Dynamic Link", | |||
"label": "Reference Name", | |||
"options": "reference_doctype", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "next_communication_date", | |||
"fieldtype": "Date", | |||
"label": "Next Communcation On", | |||
"permlevel": 0 | |||
"fieldname": "section_break_8", | |||
"fieldtype": "Section Break", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "action", | |||
"fieldtype": "Select", | |||
"label": "Action", | |||
"options": "\nCreated Opportunity\nSent Quotation\nCreated Support Ticket\nCreated Customer Issue\nNo Action\nSent Mail", | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"label": "Content", | |||
"permlevel": 0, | |||
"reqd": 0 | |||
"reqd": 0, | |||
"width": "400" | |||
}, | |||
{ | |||
"fieldname": "additional_info", | |||
@@ -91,6 +95,13 @@ | |||
"label": "Sender", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "sender_full_name", | |||
"fieldtype": "Data", | |||
"label": "Sender Full Name", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "communication_medium", | |||
"fieldtype": "Select", | |||
@@ -117,6 +128,14 @@ | |||
"label": "By", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "email_account", | |||
"fieldtype": "Link", | |||
"label": "Email Account", | |||
"options": "Email Account", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"default": "__user", | |||
"fieldname": "user", | |||
@@ -154,7 +173,7 @@ | |||
"idx": 1, | |||
"in_dialog": 0, | |||
"issingle": 0, | |||
"modified": "2014-08-14 09:39:23.219125", | |||
"modified": "2015-02-05 05:11:35.650325", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Communication", | |||
@@ -162,7 +181,7 @@ | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 0, | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -171,6 +190,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Support Team", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -184,12 +204,13 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Sales Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 0, | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -198,6 +219,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Sales User", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -210,6 +232,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Support Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -222,9 +245,11 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
], | |||
"search_fields": "subject", | |||
"title_field": "subject" | |||
} |
@@ -1,152 +1,196 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import json | |||
from email.utils import formataddr | |||
from frappe.website.utils import is_signup_enabled | |||
from frappe.utils import get_url, cstr | |||
from frappe.utils.email_lib.email_body import get_email | |||
from frappe.utils.email_lib.smtp import send | |||
from frappe.utils import scrub_urls, cint, quoted | |||
from frappe.utils import get_url, cint, scrub_urls, get_formatted_email | |||
from frappe.email.email_body import get_email | |||
import frappe.email.smtp | |||
from frappe import _ | |||
from frappe.model.document import Document | |||
class Communication(Document): | |||
def validate(self): | |||
if not self.parentfield: | |||
self.parentfield = "communications" | |||
"""Communication represents an external communication like Email.""" | |||
def get_parent_doc(self): | |||
return frappe.get_doc(self.parenttype, self.parent) | |||
def update_parent(self): | |||
"""update status of parent Lead or Contact based on who is replying""" | |||
if self.parenttype and self.parent: | |||
parent_doc = self.get_parent_doc() | |||
parent_doc.run_method("on_communication") | |||
"""Returns document of `reference_doctype`, `reference_doctype`""" | |||
if not hasattr(self, "parent_doc"): | |||
if self.reference_doctype and self.reference_name: | |||
self.parent_doc = frappe.get_doc(self.reference_doctype, self.reference_name) | |||
else: | |||
self.parent_doc = None | |||
return self.parent_doc | |||
def on_update(self): | |||
"""Update parent status as `Open` or `Replied`.""" | |||
self.update_parent() | |||
def update_parent(self): | |||
"""Update status of parent document based on who is replying.""" | |||
parent = self.get_parent_doc() | |||
if not parent: | |||
return | |||
status_field = parent.meta.get_field("status") | |||
if status_field and "Open" in (status_field.options or "").split("\n"): | |||
to_status = "Open" if self.sent_or_received=="Received" else "Replied" | |||
if to_status in status_field.options.splitlines(): | |||
frappe.db.set_value(parent.doctype, parent.name, "status", to_status) | |||
def send(self, print_html=None, print_format=None, | |||
attachments=None): | |||
"""Send communication via Email. | |||
:param print_html: Send given value as HTML attachment. | |||
:param print_format: Attach print format of parent document.""" | |||
self.notify(self.get_email(print_html, print_format, attachments)) | |||
def get_email(self, print_html=None, print_format=None, attachments=None): | |||
"""Make multipart MIME Email | |||
:param print_html: Send given value as HTML attachment. | |||
:param print_format: Attach print format of parent document.""" | |||
if print_format: | |||
self.content += self.get_attach_link(print_format) | |||
default_incoming = frappe.db.get_value("Email Account", {"default_incoming": 1}, "email_id") | |||
default_outgoing = frappe.db.get_value("Email Account", {"default_outgoing": 1}, "email_id") | |||
if not self.sender: | |||
self.sender = "{0} <{1}>".format(frappe.session.data.full_name or "Notification", default_outgoing) | |||
mail = get_email(self.recipients, sender=self.sender, subject=self.subject, | |||
content=self.content, reply_to=default_incoming) | |||
mail.set_message_id(self.name) | |||
if print_html or print_format: | |||
attach_print(mail, self.get_parent_doc(), print_html, print_format) | |||
if isinstance(attachments, basestring): | |||
attachments = json.loads(attachments) | |||
if attachments: | |||
for a in attachments: | |||
try: | |||
mail.attach_file(a) | |||
except IOError: | |||
frappe.throw(_("Unable to find attachment {0}").format(a)) | |||
return mail | |||
def add_to_mail_queue(self, mail): | |||
mail = frappe.get_doc({ | |||
"doctype": "Bulk Email", | |||
"sender": mail.sender, | |||
"recipient": mail.recipients[0], | |||
"message": mail.as_string(), | |||
"ref_doctype": self.reference_doctype, | |||
"ref_docname": self.reference_name | |||
}).insert(ignore_permissions=True) | |||
def notify(self, mail, except_sender=False): | |||
for recipient in self.get_recipients(): | |||
if except_sender and recipient == self.sender: | |||
continue | |||
mail.recipients = [recipient] | |||
self.add_to_mail_queue(mail) | |||
def get_recipients(self): | |||
# Earlier repliers | |||
recipients = frappe.db.sql_list(""" | |||
select distinct sender | |||
from tabCommunication where | |||
reference_doctype=%s and reference_name=%s""", | |||
(self.reference_doctype, self.reference_name)) | |||
# Commentors | |||
recipients += frappe.db.sql_list(""" | |||
select distinct comment_by | |||
from tabComment where | |||
comment_doctype=%s and comment_docname=%s and | |||
ifnull(unsubscribed, 0)=0 and comment_by!='Administrator'""", | |||
(self.reference_doctype, self.reference_name)) | |||
# Explicit recipients | |||
recipients += [s.strip() for s in self.recipients.split(",")] | |||
# Assigned | |||
assigned = frappe.db.get_value("ToDo", {"reference_type": self.reference_doctype, | |||
"reference_name": self.reference_name, "status": "Open"}, "owner") | |||
if assigned: | |||
recipients.append(assigned) | |||
recipients = filter(lambda e: e and e!="Administrator", list(set(recipients))) | |||
return recipients | |||
def get_attach_link(self, print_format): | |||
"""Returns public link for the attachment via `templates/emails/print_link.html`.""" | |||
return frappe.get_template("templates/emails/print_link.html").render({ | |||
"url": get_url(), | |||
"doctype": self.reference_doctype, | |||
"name": self.reference_name, | |||
"print_format": print_format, | |||
"key": self.get_parent_doc().get_signature() | |||
}) | |||
def on_doctype_update(): | |||
"""Add index in `tabCommunication` for `(reference_doctype, reference_name)`""" | |||
frappe.db.add_index("Communication", ["reference_doctype", "reference_name"]) | |||
@frappe.whitelist() | |||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | |||
sender=None, recipients=None, communication_medium="Email", send_email=False, | |||
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): | |||
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False): | |||
"""Make a new communication. | |||
:param doctype: Reference DocType. | |||
:param name: Reference Document name. | |||
:param content: Communication body. | |||
:param subject: Communication subject. | |||
:param sent_or_received: Sent or Received (default **Sent**). | |||
:param sender: Communcation sender (default current user). | |||
:param recipients: Communication recipients as list. | |||
:param communication_medium: Medium of communication (default **Email**). | |||
:param send_mail: Send via email (default **False**). | |||
:param print_html: HTML Print format to be sent as attachment. | |||
:param print_format: Print Format name of parent document to be sent as attachment. | |||
:param attachments: List of attachments as list of files or JSON string.""" | |||
is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report") | |||
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name): | |||
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not ignore_doctype_permissions: | |||
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format( | |||
doctype=doctype, name=name)) | |||
_make(doctype=doctype, name=name, content=content, subject=subject, sent_or_received=sent_or_received, | |||
sender=sender, recipients=recipients, communication_medium=communication_medium, send_email=send_email, | |||
print_html=print_html, print_format=print_format, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead, | |||
date=date) | |||
def _make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | |||
sender=None, recipients=None, communication_medium="Email", send_email=False, | |||
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): | |||
# add to Communication | |||
sent_via = None | |||
# since we are using fullname and email, | |||
# if the fullname has any incompatible characters,formataddr can deal with it | |||
try: | |||
sender = json.loads(sender) | |||
except ValueError: | |||
pass | |||
if isinstance(sender, (tuple, list)) and len(sender)==2: | |||
sender = formataddr(sender) | |||
comm = frappe.new_doc('Communication') | |||
d = comm | |||
d.subject = subject | |||
d.content = content | |||
d.sent_or_received = sent_or_received | |||
d.sender = sender or frappe.db.get_value("User", frappe.session.user, "email") | |||
d.recipients = recipients | |||
# add as child | |||
sent_via = frappe.get_doc(doctype, name) | |||
d.parent = name | |||
d.parenttype = doctype | |||
d.parentfield = "communications" | |||
if date: | |||
d.communication_date = date | |||
d.communication_medium = communication_medium | |||
d.idx = cint(frappe.db.sql("""select max(idx) from `tabCommunication` | |||
where parenttype=%s and parent=%s""", (doctype, name))[0][0]) + 1 | |||
comm.ignore_permissions = True | |||
comm.insert() | |||
comm = frappe.get_doc({ | |||
"doctype":"Communication", | |||
"subject": subject, | |||
"content": content, | |||
"sender": sender or get_formatted_email(frappe.session.user), | |||
"recipients": recipients, | |||
"communication_medium": "Email", | |||
"sent_or_received": sent_or_received, | |||
"reference_doctype": doctype, | |||
"reference_name": name | |||
}) | |||
comm.insert(ignore_permissions=True) | |||
if send_email: | |||
d = comm | |||
send_comm_email(d, name, sent_via, print_html, print_format, attachments, send_me_a_copy) | |||
comm.send(print_html, print_format, attachments) | |||
@frappe.whitelist() | |||
def get_customer_supplier(args=None): | |||
""" | |||
Get Customer/Supplier, given a contact, if a unique match exists | |||
""" | |||
if not args: args = frappe.local.form_dict | |||
if not args.get('contact'): | |||
raise Exception, "Please specify a contact to fetch Customer/Supplier" | |||
result = frappe.db.sql("""\ | |||
select customer, supplier | |||
from `tabContact` | |||
where name = %s""", args.get('contact'), as_dict=1) | |||
if result and len(result)==1 and (result[0]['customer'] or result[0]['supplier']): | |||
return { | |||
'fieldname': result[0]['customer'] and 'customer' or 'supplier', | |||
'value': result[0]['customer'] or result[0]['supplier'] | |||
} | |||
return {} | |||
def send_comm_email(d, name, sent_via=None, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False): | |||
footer = None | |||
if sent_via: | |||
if hasattr(sent_via, "get_sender"): | |||
d.sender = sent_via.get_sender(d) or d.sender | |||
if hasattr(sent_via, "get_subject"): | |||
d.subject = sent_via.get_subject(d) | |||
if hasattr(sent_via, "get_content"): | |||
d.content = sent_via.get_content(d) | |||
footer = "<hr>" + set_portal_link(sent_via, d) | |||
mail = get_email(d.recipients, sender=d.sender, subject=d.subject, | |||
msg=d.content, footer=footer) | |||
if send_me_a_copy: | |||
mail.cc.append(frappe.db.get_value("User", frappe.session.user, "email")) | |||
if print_html or print_format: | |||
attach_print(mail, sent_via, print_html, print_format) | |||
for a in json.loads(attachments): | |||
try: | |||
mail.attach_file(a) | |||
except IOError: | |||
frappe.throw(_("Unable to find attachment {0}").format(a)) | |||
send(mail) | |||
return comm.name | |||
def attach_print(mail, sent_via, print_html, print_format): | |||
name = sent_via.name | |||
if not print_html and print_format: | |||
print_html = frappe.get_print_format(sent_via.doctype, sent_via.name, print_format) | |||
def attach_print(mail, parent_doc, print_html, print_format): | |||
name = parent_doc.name if parent_doc else "attachment" | |||
if (not print_html) and parent_doc and print_format: | |||
print_html = frappe.get_print(parent_doc.doctype, parent_doc.name, print_format) | |||
print_settings = frappe.db.get_singles_dict("Print Settings") | |||
send_print_as_pdf = cint(print_settings.send_print_as_pdf) | |||
@@ -164,16 +208,6 @@ def attach_print(mail, sent_via, print_html, print_format): | |||
mail.add_attachment(name.replace(' ','').replace('/','-') + '.html', | |||
print_html, 'text/html') | |||
def set_portal_link(sent_via, comm): | |||
"""set portal link in footer""" | |||
footer = "" | |||
if is_signup_enabled(): | |||
is_valid_recipient = cstr(sent_via.get("email") or sent_via.get("email_id") or | |||
sent_via.get("contact_email")) in comm.recipients | |||
if is_valid_recipient: | |||
url = quoted("%s/%s/%s" % (get_url(), sent_via.doctype, sent_via.name)) | |||
footer = """<!-- Portal Link --> | |||
<p><a href="%s" target="_blank">View this on our website</a></p>""" % url | |||
return footer | |||
@frappe.whitelist() | |||
def get_convert_to(): | |||
return frappe.get_hooks("communication_convert_to") |
@@ -1,37 +0,0 @@ | |||
<div class="row" style="max-height: 30px;"> | |||
<div class="col-xs-10"> | |||
<div class="text-ellipsis"> | |||
{%= list.get_avatar_and_id(doc) %} | |||
<!-- sample icon --> | |||
<span style="margin-right: 8px;" | |||
title="{%= __(doc.sent_or_received) %}" class="filterable" | |||
data-filter="sent_or_received,=,{%= doc.sent_or_received %}"> | |||
<i class="icon-{%= doc.sent_or_received=="Sent" ? | |||
"arrow-right" : "arrow-left" %} text-muted"></i> | |||
</span> | |||
<span style="margin-right: 8px;" class="filterable" | |||
title="{%= doc.communication_medium %}" | |||
data-filter="communication_medium,=,{%= doc.communication_medium %}"> | |||
<i class="icon-{%= { | |||
"Chat": "comments", | |||
"Phone": "phone", | |||
"Email": "envelope", | |||
"SMS": "comment", | |||
"Visit": "male", | |||
"Other": "comments" | |||
}[doc.communication_medium] || "comments" %}"></i> | |||
</span> | |||
</div> | |||
</div> | |||
<div class="col-xs-2"> | |||
<div class="text-ellipsis"> | |||
<span style="margin-right: 8px;" class="filterable small" | |||
data-filter="recipients,=,{%= doc.recipients %}" title="{%= doc.recipients %}"> | |||
{%= doc.recipients %}</span> | |||
</div> | |||
</div> | |||
</div> |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
from __future__ import unicode_literals | |||
@@ -1,4 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,7 +1,7 @@ | |||
{ | |||
"allow_copy": 0, | |||
"autoname": "DEF.######", | |||
"creation": "2013-02-22 01:27:32.000000", | |||
"autoname": "hash", | |||
"creation": "2013-02-22 01:27:32", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
@@ -39,10 +39,11 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2013-12-20 19:23:05.000000", | |||
"modified": "2015-02-19 01:06:59.622792", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DefaultValue", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"read_only": 0 | |||
} |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -8,16 +8,17 @@ from frappe.model.document import Document | |||
class DefaultValue(Document): | |||
pass | |||
def on_doctype_update(): | |||
if not frappe.db.sql("""show index from `tabDefaultValue` | |||
"""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` | |||
frappe.db.sql("""alter table `tabDefaultValue` | |||
add index defaultvalue_parent_defkey_index(parent, defkey)""") | |||
if not frappe.db.sql("""show index from `tabDefaultValue` | |||
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.sql("""alter table `tabDefaultValue` | |||
add index defaultvalue_parent_parenttype_index(parent, parenttype)""") |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,324 +1,324 @@ | |||
{ | |||
"allow_copy": 0, | |||
"autoname": "FL.#####", | |||
"creation": "2013-02-22 01:27:33", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"allow_copy": 0, | |||
"autoname": "hash", | |||
"creation": "2013-02-22 01:27:33", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"fieldname": "label_and_type", | |||
"fieldtype": "Section Break", | |||
"label": "Label and Type", | |||
"fieldname": "label_and_type", | |||
"fieldtype": "Section Break", | |||
"label": "", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "label", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Label", | |||
"oldfieldname": "label", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_width": "163", | |||
"reqd": 0, | |||
"search_index": 1, | |||
"fieldname": "label", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Label", | |||
"oldfieldname": "label", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_width": "163", | |||
"reqd": 0, | |||
"search_index": 1, | |||
"width": "163" | |||
}, | |||
}, | |||
{ | |||
"default": "Data", | |||
"fieldname": "fieldtype", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"default": "Data", | |||
"fieldname": "fieldtype", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nAttach Image\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "fieldname", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Name", | |||
"oldfieldname": "fieldname", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"fieldname": "fieldname", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Name", | |||
"oldfieldname": "fieldname", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"search_index": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "reqd", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Mandatory", | |||
"oldfieldname": "reqd", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"fieldname": "reqd", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Mandatory", | |||
"oldfieldname": "reqd", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "search_index", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Index", | |||
"oldfieldname": "search_index", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"fieldname": "search_index", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Index", | |||
"oldfieldname": "search_index", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "in_list_view", | |||
"fieldtype": "Check", | |||
"label": "In List View", | |||
"permlevel": 0, | |||
"print_width": "70px", | |||
"fieldname": "in_list_view", | |||
"fieldtype": "Check", | |||
"label": "In List View", | |||
"permlevel": 0, | |||
"print_width": "70px", | |||
"width": "70px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "column_break_6", | |||
"fieldtype": "Column Break", | |||
"fieldname": "column_break_6", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", | |||
"description": "Set non-standard precision for a Float or Currency field", | |||
"fieldname": "precision", | |||
"fieldtype": "Select", | |||
"label": "Precision", | |||
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", | |||
"permlevel": 0, | |||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", | |||
"description": "Set non-standard precision for a Float or Currency field", | |||
"fieldname": "precision", | |||
"fieldtype": "Select", | |||
"label": "Precision", | |||
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", | |||
"permlevel": 0, | |||
"print_hide": 1 | |||
}, | |||
}, | |||
{ | |||
"description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", | |||
"fieldname": "options", | |||
"fieldtype": "Text", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Options", | |||
"oldfieldname": "options", | |||
"oldfieldtype": "Text", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", | |||
"fieldname": "options", | |||
"fieldtype": "Text", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Options", | |||
"oldfieldname": "options", | |||
"oldfieldtype": "Text", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "permissions", | |||
"fieldtype": "Section Break", | |||
"label": "Permissions", | |||
"fieldname": "permissions", | |||
"fieldtype": "Section Break", | |||
"label": "Permissions", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "depends_on", | |||
"fieldtype": "Data", | |||
"label": "Depends On", | |||
"oldfieldname": "depends_on", | |||
"oldfieldtype": "Data", | |||
"fieldname": "depends_on", | |||
"fieldtype": "Data", | |||
"label": "Depends On", | |||
"oldfieldname": "depends_on", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "permlevel", | |||
"fieldtype": "Int", | |||
"hidden": 0, | |||
"label": "Perm Level", | |||
"oldfieldname": "permlevel", | |||
"oldfieldtype": "Int", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"default": "0", | |||
"fieldname": "permlevel", | |||
"fieldtype": "Int", | |||
"hidden": 0, | |||
"label": "Perm Level", | |||
"oldfieldname": "permlevel", | |||
"oldfieldtype": "Int", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "hidden", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Hidden", | |||
"oldfieldname": "hidden", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"fieldname": "hidden", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Hidden", | |||
"oldfieldname": "hidden", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "read_only", | |||
"fieldtype": "Check", | |||
"label": "Read Only", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"fieldname": "read_only", | |||
"fieldtype": "Check", | |||
"label": "Read Only", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"description": "Do not allow user to change after set the first time", | |||
"fieldname": "set_only_once", | |||
"fieldtype": "Check", | |||
"label": "Set Only Once", | |||
"description": "Do not allow user to change after set the first time", | |||
"fieldname": "set_only_once", | |||
"fieldtype": "Check", | |||
"label": "Set Only Once", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "column_break_13", | |||
"fieldtype": "Column Break", | |||
"fieldname": "column_break_13", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"description": "User permissions should not apply for this Link", | |||
"fieldname": "ignore_user_permissions", | |||
"fieldtype": "Check", | |||
"label": "Ignore User Permissions", | |||
"description": "User permissions should not apply for this Link", | |||
"fieldname": "ignore_user_permissions", | |||
"fieldtype": "Check", | |||
"label": "Ignore User Permissions", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "allow_on_submit", | |||
"fieldtype": "Check", | |||
"label": "Allow on Submit", | |||
"oldfieldname": "allow_on_submit", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"fieldname": "allow_on_submit", | |||
"fieldtype": "Check", | |||
"label": "Allow on Submit", | |||
"oldfieldname": "allow_on_submit", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "report_hide", | |||
"fieldtype": "Check", | |||
"label": "Report Hide", | |||
"oldfieldname": "report_hide", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"fieldname": "report_hide", | |||
"fieldtype": "Check", | |||
"label": "Report Hide", | |||
"oldfieldname": "report_hide", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "display", | |||
"fieldtype": "Section Break", | |||
"label": "Display", | |||
"fieldname": "display", | |||
"fieldtype": "Section Break", | |||
"label": "Display", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "default", | |||
"fieldtype": "Text", | |||
"hidden": 0, | |||
"label": "Default", | |||
"oldfieldname": "default", | |||
"oldfieldtype": "Text", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"fieldname": "default", | |||
"fieldtype": "Text", | |||
"hidden": 0, | |||
"label": "Default", | |||
"oldfieldname": "default", | |||
"oldfieldtype": "Text", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "in_filter", | |||
"fieldtype": "Check", | |||
"label": "In Filter", | |||
"oldfieldname": "in_filter", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"fieldname": "in_filter", | |||
"fieldtype": "Check", | |||
"label": "In Filter", | |||
"oldfieldname": "in_filter", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "no_copy", | |||
"fieldtype": "Check", | |||
"label": "No Copy", | |||
"oldfieldname": "no_copy", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"fieldname": "no_copy", | |||
"fieldtype": "Check", | |||
"label": "No Copy", | |||
"oldfieldname": "no_copy", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "column_break_22", | |||
"fieldtype": "Column Break", | |||
"fieldname": "column_break_22", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "description", | |||
"fieldtype": "Text", | |||
"in_list_view": 1, | |||
"label": "Description", | |||
"oldfieldname": "description", | |||
"oldfieldtype": "Text", | |||
"permlevel": 0, | |||
"print_width": "300px", | |||
"fieldname": "description", | |||
"fieldtype": "Text", | |||
"in_list_view": 1, | |||
"label": "Description", | |||
"oldfieldname": "description", | |||
"oldfieldtype": "Text", | |||
"permlevel": 0, | |||
"print_width": "300px", | |||
"width": "300px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "print_hide", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Print Hide", | |||
"oldfieldname": "print_hide", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"fieldname": "print_hide", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Print Hide", | |||
"oldfieldname": "print_hide", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "print_width", | |||
"fieldtype": "Data", | |||
"label": "Print Width", | |||
"fieldname": "print_width", | |||
"fieldtype": "Data", | |||
"label": "Print Width", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "width", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "Width", | |||
"oldfieldname": "width", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"fieldname": "width", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "Width", | |||
"oldfieldname": "width", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "oldfieldname", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"oldfieldname": "oldfieldname", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"fieldname": "oldfieldname", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"oldfieldname": "oldfieldname", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "oldfieldtype", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"oldfieldname": "oldfieldtype", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"fieldname": "oldfieldtype", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"oldfieldname": "oldfieldtype", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"idx": 1, | |||
"in_dialog": 1, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2014-11-07 11:40:55.281141", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocField", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"idx": 1, | |||
"in_dialog": 1, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2015-02-23 02:06:59.836515", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocField", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"read_only": 0 | |||
} | |||
} |
@@ -1,10 +1,10 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.model.document import Document | |||
class DocField(Document): | |||
__doclink__ = "https://frappe.io/docs/models/core/docfield" | |||
pass |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,6 +1,6 @@ | |||
{ | |||
"allow_copy": 0, | |||
"autoname": "PERM.#####", | |||
"autoname": "hash", | |||
"creation": "2013-02-22 01:27:33", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
@@ -198,6 +198,13 @@ | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "share", | |||
"fieldtype": "Check", | |||
"label": "Share", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "print", | |||
"fieldtype": "Check", | |||
@@ -216,7 +223,7 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2014-08-26 01:43:31.499363", | |||
"modified": "2015-02-19 01:06:59.983050", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocPerm", | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -7,4 +7,5 @@ import frappe | |||
from frappe.model.document import Document | |||
class DocPerm(Document): | |||
pass | |||
__doclink__ = "https://frappe.io/docs/models/v5.x/core/docperm" | |||
pass |
@@ -0,0 +1,172 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_import": 1, | |||
"allow_rename": 0, | |||
"autoname": "hash", | |||
"creation": "2015-02-04 04:33:36.330477", | |||
"custom": 0, | |||
"description": "Internal record of document shares", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "User", | |||
"no_copy": 0, | |||
"options": "User", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 1, | |||
"set_only_once": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "share_doctype", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Document Type", | |||
"no_copy": 0, | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 1, | |||
"set_only_once": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "share_name", | |||
"fieldtype": "Dynamic Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Document Name", | |||
"no_copy": 0, | |||
"options": "share_doctype", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 1, | |||
"set_only_once": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"default": "0", | |||
"fieldname": "read", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Read", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"default": "0", | |||
"fieldname": "write", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Write", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"default": "0", | |||
"fieldname": "share", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Share", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"in_create": 1, | |||
"in_dialog": 0, | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-02-12 11:30:52.968078", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocShare", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 0, | |||
"export": 1, | |||
"import": 1, | |||
"permlevel": 0, | |||
"print": 0, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
], | |||
"read_only": 1, | |||
"read_only_onload": 0, | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
} |
@@ -0,0 +1,49 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.model.document import Document | |||
from frappe import _ | |||
from frappe.utils import get_fullname | |||
class DocShare(Document): | |||
no_feed_on_delete = True | |||
def validate(self): | |||
self.check_share_permission() | |||
self.cascade_permissions_downwards() | |||
self.get_doc().run_method("validate_share", self) | |||
def cascade_permissions_downwards(self): | |||
if self.share: | |||
self.write = 1 | |||
if self.write: | |||
self.read = 1 | |||
def get_doc(self): | |||
if not getattr(self, "_doc", None): | |||
self._doc = frappe.get_doc(self.share_doctype, self.share_name) | |||
return self._doc | |||
def check_share_permission(self): | |||
if (not self.flags.ignore_share_permission and | |||
not frappe.has_permission(self.share_doctype, "share", self.get_doc())): | |||
frappe.throw(_('You need to have "Share" permission'), frappe.PermissionError) | |||
def after_insert(self): | |||
self.get_doc().add_comment("Shared", | |||
_("{0} shared this document with {1}").format(get_fullname(self.owner), get_fullname(self.user))) | |||
def on_trash(self): | |||
if not self.flags.ignore_share_permission: | |||
self.check_share_permission() | |||
self.get_doc().add_comment("Unshared", | |||
_("{0} un-shared this document with {1}").format(get_fullname(self.owner), get_fullname(self.user))) | |||
def on_doctype_update(): | |||
"""Add index in `tabDocShare` for `(user, share_doctype)`""" | |||
frappe.db.add_index("DocShare", ["user", "share_doctype"]) | |||
frappe.db.add_index("DocShare", ["share_doctype", "share_name"]) |
@@ -0,0 +1,80 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
import frappe | |||
import frappe.share | |||
import unittest | |||
class TestDocShare(unittest.TestCase): | |||
def setUp(self): | |||
self.user = "test@example.com" | |||
self.event = frappe.get_doc({"doctype": "Event", | |||
"subject": "test share event", | |||
"starts_on": "2015-01-01 10:00:00", | |||
"event_type": "Private"}).insert() | |||
def tearDown(self): | |||
frappe.set_user("Administrator") | |||
self.event.delete() | |||
def test_add(self): | |||
# user not shared | |||
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", self.user)) | |||
frappe.share.add("Event", self.event.name, self.user) | |||
self.assertTrue(self.event.name in frappe.share.get_shared("Event", self.user)) | |||
def test_doc_permission(self): | |||
frappe.set_user(self.user) | |||
self.assertFalse(self.event.has_permission()) | |||
frappe.set_user("Administrator") | |||
frappe.share.add("Event", self.event.name, self.user) | |||
frappe.set_user(self.user) | |||
self.assertTrue(self.event.has_permission()) | |||
def test_share_permission(self): | |||
frappe.share.add("Event", self.event.name, self.user, share=1) | |||
frappe.set_user(self.user) | |||
self.assertTrue(self.event.has_permission("share")) | |||
# test cascade | |||
self.assertTrue(self.event.has_permission("read")) | |||
self.assertTrue(self.event.has_permission("write")) | |||
def test_set_permission(self): | |||
frappe.share.add("Event", self.event.name, self.user) | |||
frappe.set_user(self.user) | |||
self.assertFalse(self.event.has_permission("share")) | |||
frappe.set_user("Administrator") | |||
frappe.share.set_permission("Event", self.event.name, self.user, "share") | |||
frappe.set_user(self.user) | |||
self.assertTrue(self.event.has_permission("share")) | |||
def test_permission_to_share(self): | |||
frappe.set_user(self.user) | |||
self.assertRaises(frappe.PermissionError, frappe.share.add, "Event", self.event.name, self.user) | |||
frappe.set_user("Administrator") | |||
frappe.share.add("Event", self.event.name, self.user, share=1) | |||
# test not raises | |||
frappe.set_user(self.user) | |||
frappe.share.add("Event", self.event.name, "test1@example.com", share=1) | |||
def test_remove_share(self): | |||
frappe.share.add("Event", self.event.name, self.user, share=1) | |||
frappe.set_user(self.user) | |||
self.assertTrue(self.event.has_permission("share")) | |||
frappe.set_user("Administrator") | |||
frappe.share.remove("Event", self.event.name, self.user) | |||
frappe.set_user(self.user) | |||
self.assertFalse(self.event.has_permission("share")) | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, {app_publisher} and contributors | |||
# Copyright (c) 2015, {app_publisher} and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
@@ -1,11 +1,11 @@ | |||
# Copyright (c) 2013, {app_publisher} and Contributors | |||
# Copyright (c) 2015, {app_publisher} and Contributors | |||
# See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import unittest | |||
test_records = frappe.get_test_records('{doctype}') | |||
# test_records = frappe.get_test_records('{doctype}') | |||
class Test{classname}(unittest.TestCase): | |||
pass |
@@ -1,6 +0,0 @@ | |||
[ | |||
{{ | |||
"doctype": "{doctype}", | |||
"name": "_Test {doctype} 1" | |||
}} | |||
] |
@@ -1,4 +1,4 @@ | |||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt | |||
// ------------- | |||
@@ -12,18 +12,14 @@ $(cur_frm.wrapper).on("grid-row-render", function(e, grid_row) { | |||
}) | |||
cur_frm.cscript.refresh = function(doc, cdt, cdn) { | |||
if(in_list(user_roles, 'System Manager') && !in_list(user_roles, 'Administrator')) { | |||
if(!frappe.boot.developer_mode && !doc.custom) { | |||
// make the document read-only | |||
cur_frm.set_read_only(); | |||
} | |||
// make help heading | |||
msgprint($.format()) | |||
msgprint(__('Cannot Edit {0} directly: To edit {0} properties, create / update {1}, {2} and {3}', [ | |||
'DocType', | |||
'<a href="#!List/Custom%20Field">'+ __('Custom Field')+'</a>', | |||
'<a href="#!List/Custom%20Script">'+ __('Custom Script')+'</a>', | |||
'<a href="#!List/Property%20Setter">'+ __('Property Setter')+'</a>', | |||
])); | |||
if(doc.__islocal && (user !== "Administrator" || !frappe.boot.developer_mode)) { | |||
cur_frm.set_value("custom", 1); | |||
cur_frm.toggle_enable("custom", 0); | |||
} | |||
} | |||
@@ -1,5 +1,6 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_copy": 0, | |||
"allow_rename": 1, | |||
"autoname": "Prompt", | |||
"creation": "2013-02-18 13:36:19", | |||
"custom": 0, | |||
@@ -11,7 +12,7 @@ | |||
"fieldname": "sb0", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"label": "DocType Details", | |||
"label": "", | |||
"oldfieldtype": "Section Break", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
@@ -336,7 +337,7 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2014-08-22 05:33:03.067964", | |||
"modified": "2015-03-03 10:40:45.768116", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocType", | |||
@@ -344,6 +345,7 @@ | |||
"permissions": [ | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 0, | |||
"email": 1, | |||
"permlevel": 0, | |||
@@ -351,7 +353,8 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"submit": 0 | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"cancel": 0, | |||
@@ -363,6 +366,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Administrator", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -9,9 +9,8 @@ from frappe import _ | |||
from frappe.utils import now, cint | |||
from frappe.model import no_value_fields | |||
from frappe.model.document import Document | |||
from frappe.model.db_schema import type_map | |||
from frappe.core.doctype.property_setter.property_setter import make_property_setter | |||
from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for | |||
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 | |||
form_grid_templates = { | |||
@@ -19,9 +18,20 @@ form_grid_templates = { | |||
} | |||
class DocType(Document): | |||
__doclink__ = "https://frappe.io/docs/models/core/doctype" | |||
def get_feed(self): | |||
return self.name | |||
def validate(self): | |||
if not frappe.conf.get("developer_mode"): | |||
frappe.throw(_("Not in Developer Mode! Set in site_config.json")) | |||
"""Validate DocType before saving. | |||
- Check if developer mode is set. | |||
- Validate series | |||
- Check fieldnames (duplication etc) | |||
- Clear permission table for child tables | |||
- Add `amended_from` and `ameneded_by` if Amendable""" | |||
if not frappe.conf.get("developer_mode") and not self.custom: | |||
frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType.")) | |||
for c in [".", "/", "#", "&", "=", ":", "'", '"']: | |||
if c in self.name: | |||
frappe.throw(_("{0} not allowed in name").format(c)) | |||
@@ -39,6 +49,7 @@ class DocType(Document): | |||
self.make_amendable() | |||
def change_modified_of_parent(self): | |||
"""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 | |||
@@ -47,6 +58,7 @@ class DocType(Document): | |||
frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0])) | |||
def scrub_field_names(self): | |||
"""Sluggify fieldnames if not set from Label.""" | |||
restricted = ('name','parent','creation','modified','modified_by', | |||
'parentfield','parenttype',"file_list") | |||
for d in self.get("fields"): | |||
@@ -61,11 +73,13 @@ class DocType(Document): | |||
def validate_title_field(self): | |||
"""Throw exception if `title_field` is not a valid field.""" | |||
if self.title_field and \ | |||
self.title_field not in [d.fieldname for d in self.get("fields")]: | |||
frappe.throw(_("Title field must be a valid fieldname")) | |||
def validate_series(self, autoname=None, name=None): | |||
"""Validate if `autoname` property is correctly set.""" | |||
if not autoname: autoname = self.autoname | |||
if not name: name = self.name | |||
@@ -83,6 +97,7 @@ class DocType(Document): | |||
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 | |||
updatedb(self.name) | |||
@@ -90,7 +105,7 @@ class DocType(Document): | |||
make_module_and_roles(self) | |||
from frappe import conf | |||
if not (frappe.flags.in_import or frappe.flags.in_test) and conf.get('developer_mode') or 0: | |||
if not self.custom and not (frappe.flags.in_import or frappe.flags.in_test) and conf.get('developer_mode'): | |||
self.export_doc() | |||
self.make_controller_template() | |||
@@ -105,21 +120,25 @@ class DocType(Document): | |||
frappe.clear_cache(doctype=self.name) | |||
def before_rename(self, old, new, merge=False): | |||
"""Throw exception if merge. DocTypes cannot be merged.""" | |||
if merge: | |||
frappe.throw(_("DocType can not be merged")) | |||
def after_rename(self, old, new, merge=False): | |||
"""Change table name using `RENAME TABLE` if table exists. Or update | |||
`doctype` property for Single type.""" | |||
if self.issingle: | |||
frappe.db.sql("""update tabSingles set doctype=%s where doctype=%s""", (new, old)) | |||
else: | |||
frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new)) | |||
def before_reload(self): | |||
"""Preserve naming series changes in Property Setter.""" | |||
if not (self.issingle and self.istable): | |||
self.preserve_naming_series_options_in_property_setter() | |||
def preserve_naming_series_options_in_property_setter(self): | |||
"""preserve naming_series as property setter if it does not exist""" | |||
"""Preserve naming_series as property setter if it does not exist""" | |||
naming_series = self.get("fields", {"fieldname": "naming_series"}) | |||
if not naming_series: | |||
@@ -138,24 +157,24 @@ class DocType(Document): | |||
make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text", validate_fields_for_doctype=False) | |||
def export_doc(self): | |||
"""Export to standard folder `[module]/doctype/[name]/[name].json`.""" | |||
from frappe.modules.export_file import export_to_files | |||
export_to_files(record_list=[['DocType', self.name]]) | |||
def import_doc(self): | |||
"""Import from standard folder `[module]/doctype/[name]/[name].json`.""" | |||
from frappe.modules.import_module import import_from_files | |||
import_from_files(record_list=[[self.module, 'doctype', self.name]]) | |||
def make_controller_template(self): | |||
"""Make boilderplate controller template.""" | |||
make_boilerplate("controller.py", self) | |||
if not (self.istable or self.issingle): | |||
make_boilerplate("test_controller.py", self) | |||
make_boilerplate("test_records.json", self) | |||
def make_amendable(self): | |||
""" | |||
if is_submittable is set, add amended_from docfields | |||
""" | |||
"""If is_submittable is set, add amended_from docfields.""" | |||
if self.is_submittable: | |||
if not frappe.db.sql("""select name from tabDocField | |||
where fieldname = 'amended_from' and parent = %s""", self.name): | |||
@@ -170,6 +189,7 @@ class DocType(Document): | |||
}) | |||
def get_max_idx(self): | |||
"""Returns the highest `idx`""" | |||
max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""", | |||
self.name) | |||
return max_idx and max_idx[0][0] or 0 | |||
@@ -179,6 +199,20 @@ def validate_fields_for_doctype(doctype): | |||
# this is separate because it is also called via custom field | |||
def validate_fields(meta): | |||
"""Validate doctype fields. Checks | |||
1. There are no illegal characters in fieldnames | |||
2. If fieldnames are unique. | |||
3. Fields that do have database columns are not mandatory. | |||
4. `Link` and `Table` options are valid. | |||
5. **Hidden** and **Mandatory** are not set simultaneously. | |||
7. `Check` type field has default as 0 or 1. | |||
8. `Dynamic Links` are correctly defined. | |||
9. Precision is set in numeric fields and is between 1 & 6. | |||
10. Fold is not at the end (if set). | |||
11. `search_fields` are valid. | |||
:param meta: `frappe.model.meta.Meta` object to check.""" | |||
def check_illegal_characters(fieldname): | |||
for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$', | |||
'(', ')', '[', ']', '/']: | |||
@@ -207,12 +241,6 @@ def validate_fields(meta): | |||
if d.hidden and d.reqd and not d.default: | |||
frappe.throw(_("Field {0} in row {1} cannot be hidden and mandatory without default").format(d.label, d.idx)) | |||
def check_min_items_in_list(fields): | |||
if len(filter(lambda d: d.in_list_view, fields))==0: | |||
for d in fields[:5]: | |||
if d.fieldtype in type_map: | |||
d.in_list_view = 1 | |||
def check_width(d): | |||
if d.fieldtype == "Currency" and cint(d.width) < 100: | |||
frappe.throw(_("Max width for type Currency is 100px in row {0}").format(d.idx)) | |||
@@ -243,11 +271,10 @@ def validate_fields(meta): | |||
if fold_exists: | |||
frappe.throw(_("There can be only one Fold in a form")) | |||
fold_exists = True | |||
if i < len(fields)-2: | |||
if i < len(fields)-1: | |||
nxt = fields[i+1] | |||
if nxt.fieldtype != "Section Break" \ | |||
or (nxt.fieldtype=="Section Break" and not nxt.label): | |||
frappe.throw(_("Fold must come before a labelled Section Break")) | |||
if nxt.fieldtype != "Section Break": | |||
frappe.throw(_("Fold must come before a Section Break")) | |||
else: | |||
frappe.throw(_("Fold can not be at the end of the form")) | |||
@@ -275,11 +302,11 @@ def validate_fields(meta): | |||
check_in_list_view(d) | |||
check_illegal_default(d) | |||
check_min_items_in_list(fields) | |||
check_fold(fields) | |||
check_search_fields(meta) | |||
def validate_permissions_for_doctype(doctype, for_remove=False): | |||
"""Validates if permissions are set correctly.""" | |||
doctype = frappe.get_doc("DocType", doctype) | |||
if frappe.conf.developer_mode and not frappe.flags.in_test: | |||
@@ -386,11 +413,12 @@ def validate_permissions(doctype, for_remove=False): | |||
remove_rights_for_single(d) | |||
def make_module_and_roles(doc, perm_fieldname="permissions"): | |||
"""Make `Module Def` and `Role` records if already not made. Called while installing.""" | |||
try: | |||
if 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.ignore_mandatory = m.ignore_permissions = True | |||
m.flags.ignore_mandatory = m.flags.ignore_permissions = True | |||
m.insert() | |||
default_roles = ["Administrator", "Guest", "All"] | |||
@@ -400,7 +428,7 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): | |||
if not frappe.db.exists("Role", role): | |||
r = frappe.get_doc({"doctype": "Role", "role_name": role}) | |||
r.role_name = role | |||
r.ignore_mandatory = r.ignore_permissions = True | |||
r.flags.ignore_mandatory = r.flags.ignore_permissions = True | |||
r.insert() | |||
except frappe.DoesNotExistError, e: | |||
pass | |||
@@ -411,6 +439,7 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): | |||
raise | |||
def init_list(doctype): | |||
"""Make boilerplate list views.""" | |||
doc = frappe.get_meta(doctype) | |||
make_boilerplate("controller_list.js", doc) | |||
make_boilerplate("controller_list.html", doc) | |||
@@ -1,42 +0,0 @@ | |||
{ | |||
"creation": "2014-07-11 17:19:37.037109", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "", | |||
"fields": [ | |||
{ | |||
"description": "Optional: Alert will only be sent if value is a valid email id.", | |||
"fieldname": "email_by_document_field", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Email By Document Field", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "Optional: Always send to these ids. Each email id on a new row", | |||
"fieldname": "cc", | |||
"fieldtype": "Text", | |||
"in_list_view": 1, | |||
"label": "CC", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "Expression, Optional", | |||
"fieldname": "condition", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Condition", | |||
"permlevel": 0 | |||
} | |||
], | |||
"istable": 1, | |||
"modified": "2014-07-11 17:54:53.298526", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Email Alert Recipient", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
} |
@@ -1,4 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,4 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1 +0,0 @@ | |||
User (user) with whom the parent Event is shared. |
@@ -1,4 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,31 +0,0 @@ | |||
{ | |||
"autoname": "EVP.#####", | |||
"creation": "2013-02-22 01:27:33", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"fieldname": "person", | |||
"fieldtype": "Link", | |||
"in_list_view": 1, | |||
"label": "Person", | |||
"oldfieldname": "person", | |||
"oldfieldtype": "Select", | |||
"options": "User", | |||
"permlevel": 0, | |||
"print_width": "240px", | |||
"search_index": 1, | |||
"width": "240px" | |||
} | |||
], | |||
"idx": 1, | |||
"istable": 1, | |||
"modified": "2014-05-09 02:12:32.374008", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Event User", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
} |
@@ -1,10 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.model.document import Document | |||
class EventUser(Document): | |||
pass |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,83 +1,84 @@ | |||
{ | |||
"allow_import": 1, | |||
"autoname": "File.######", | |||
"creation": "2012-12-12 11:19:22", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"allow_import": 1, | |||
"autoname": "File.######", | |||
"creation": "2012-12-12 11:19:22", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"fieldname": "file_name", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "File Name", | |||
"oldfieldname": "file_name", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"fieldname": "file_name", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "File Name", | |||
"oldfieldname": "file_name", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "file_url", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "File URL", | |||
"permlevel": 0, | |||
"fieldname": "file_url", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "File URL", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "attached_to_doctype", | |||
"fieldtype": "Link", | |||
"in_list_view": 1, | |||
"label": "Attached To DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"read_only": 1, | |||
"fieldname": "attached_to_doctype", | |||
"fieldtype": "Link", | |||
"in_list_view": 1, | |||
"label": "Attached To DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"read_only": 1, | |||
"search_index": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "attached_to_name", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Attached To Name", | |||
"permlevel": 0, | |||
"read_only": 1, | |||
"fieldname": "attached_to_name", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Attached To Name", | |||
"permlevel": 0, | |||
"read_only": 1, | |||
"search_index": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "file_size", | |||
"fieldtype": "Int", | |||
"in_list_view": 1, | |||
"label": "File Size", | |||
"permlevel": 0, | |||
"fieldname": "file_size", | |||
"fieldtype": "Int", | |||
"in_list_view": 1, | |||
"label": "File Size", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "content_hash", | |||
"fieldtype": "Data", | |||
"label": "Content Hash", | |||
"permlevel": 0, | |||
"fieldname": "content_hash", | |||
"fieldtype": "Data", | |||
"label": "Content Hash", | |||
"permlevel": 0, | |||
"search_index": 1 | |||
} | |||
], | |||
"icon": "icon-file", | |||
"idx": 1, | |||
"in_create": 1, | |||
"modified": "2014-05-26 03:35:40.362967", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "File Data", | |||
"owner": "Administrator", | |||
], | |||
"icon": "icon-file", | |||
"idx": 1, | |||
"in_create": 1, | |||
"modified": "2015-02-05 05:11:38.944926", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "File Data", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"role": "System Manager", | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"write": 1 | |||
} | |||
], | |||
], | |||
"read_only": 0 | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -14,11 +14,13 @@ from frappe.model.document import Document | |||
from frappe.utils.file_manager import delete_file_data_content | |||
class FileData(Document): | |||
no_feed_on_delete = True | |||
def before_insert(self): | |||
frappe.local.rollback_observers.append(self) | |||
def validate(self): | |||
if not getattr(self, "ignore_duplicate_entry_error", False): | |||
if not self.flags.ignore_duplicate_entry_error: | |||
# check duplicate assignement | |||
n_records = frappe.db.sql("""select name from `tabFile Data` | |||
where content_hash=%s | |||
@@ -34,7 +36,7 @@ class FileData(Document): | |||
if self.attached_to_name: | |||
# check persmission | |||
try: | |||
if not getattr(self, 'ignore_permissions', False) and \ | |||
if not self.flags.ignore_permissions and \ | |||
not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name): | |||
frappe.msgprint(frappe._("No permission to write / remove."), raise_exception=True) | |||
@@ -49,3 +51,7 @@ class FileData(Document): | |||
def on_rollback(self): | |||
self.on_trash() | |||
def on_doctype_update(): | |||
frappe.db.add_index("File Data", ["attached_to_doctype", "attached_to_name"]) | |||
@@ -1,4 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,7 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
test_records = frappe.get_test_records('Letter Head') |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -25,7 +25,7 @@ | |||
], | |||
"icon": "icon-sitemap", | |||
"idx": 1, | |||
"modified": "2014-06-12 01:00:52.304755", | |||
"modified": "2015-02-05 05:11:41.388856", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Module Def", | |||
@@ -42,6 +42,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Administrator", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -7,10 +7,24 @@ import frappe, os | |||
from frappe.model.document import Document | |||
class ModuleDef(Document): | |||
def validate(self): | |||
if not frappe.conf.get("developer_mode"): | |||
return | |||
__doclink__ = "https://frappe.io/docs/models/core/module_def" | |||
def on_update(self): | |||
"""If in `developer_mode`, create folder for module and | |||
add in `modules.txt` of app if missing.""" | |||
if frappe.conf.get("developer_mode"): | |||
self.create_modules_folder() | |||
self.add_to_modules_txt() | |||
def create_modules_folder(self): | |||
"""Creates a folder `[app]/[module]` and adds `__init__.py`""" | |||
module_path = frappe.get_app_path(self.app_name, self.name) | |||
if not os.path.exists(module_path): | |||
os.mkdir(module_path) | |||
with open(os.path.join(module_path, "__init__.py"), "w") as f: | |||
f.write("") | |||
def add_to_modules_txt(self): | |||
"""Adds to `[app]/modules.txt`""" | |||
modules = None | |||
if not frappe.local.module_app.get(frappe.scrub(self.name)): | |||
with open(frappe.get_app_path(self.app_name, "modules.txt"), "r") as f: | |||
@@ -26,11 +40,6 @@ class ModuleDef(Document): | |||
frappe.clear_cache() | |||
frappe.setup_module_map() | |||
module_path = frappe.get_app_path(self.app_name, self.name) | |||
if not os.path.exists(module_path): | |||
os.mkdir(module_path) | |||
with open(os.path.join(module_path, "__init__.py"), "w") as f: | |||
f.write("") | |||
@@ -1,34 +0,0 @@ | |||
{ | |||
"autoname": "hash", | |||
"creation": "2013-11-18 05:31:03.000000", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Other", | |||
"fields": [ | |||
{ | |||
"fieldname": "for_doctype", | |||
"fieldtype": "Data", | |||
"label": "For DocType", | |||
"permlevel": 0, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"fieldname": "count", | |||
"fieldtype": "Int", | |||
"label": "Count", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "open_count", | |||
"fieldtype": "Int", | |||
"label": "Open Count", | |||
"permlevel": 0 | |||
} | |||
], | |||
"idx": 1, | |||
"modified": "2014-05-12 19:24:14.000000", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Notification Count", | |||
"owner": "Administrator" | |||
} |
@@ -1,155 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import MySQLdb | |||
from frappe.model.document import Document | |||
logger = frappe.get_logger() | |||
class NotificationCount(Document): | |||
pass | |||
@frappe.whitelist() | |||
def get_notifications(): | |||
if frappe.flags.in_install_app: | |||
return | |||
config = get_notification_config() | |||
can_read = frappe.user.get_can_read() | |||
open_count_doctype = {} | |||
open_count_module = {} | |||
notification_count = dict(frappe.db.sql("""select for_doctype, open_count | |||
from `tabNotification Count` where owner=%s""", (frappe.session.user,))) | |||
for d in config.for_doctype: | |||
if d in can_read: | |||
condition = config.for_doctype[d] | |||
key = condition.keys()[0] | |||
if d in notification_count: | |||
open_count_doctype[d] = notification_count[d] | |||
else: | |||
result = frappe.get_list(d, fields=["count(*)"], | |||
filters=[[d, key, "=", condition[key]]], as_list=True)[0][0] | |||
open_count_doctype[d] = result | |||
try: | |||
frappe.get_doc({"doctype":"Notification Count", "for_doctype":d, | |||
"open_count":result}).insert(ignore_permissions=True) | |||
except MySQLdb.OperationalError, e: | |||
if e.args[0] not in (1213, 1205): | |||
raise | |||
logger.error("Deadlock") | |||
for m in config.for_module: | |||
if m in notification_count: | |||
open_count_module[m] = notification_count[m] | |||
else: | |||
open_count_module[m] = frappe.get_attr(config.for_module[m])() | |||
try: | |||
frappe.get_doc({"doctype":"Notification Count", "for_doctype":m, | |||
"open_count":open_count_module[m]}).insert(ignore_permissions=True) | |||
except MySQLdb.OperationalError, e: | |||
if e.args[0] not in (1213, 1205): | |||
raise | |||
logger.error("Deadlock") | |||
return { | |||
"open_count_doctype": open_count_doctype, | |||
"open_count_module": open_count_module | |||
} | |||
def clear_notifications(user=None): | |||
if frappe.flags.in_install_app=="frappe": | |||
return | |||
try: | |||
if user: | |||
frappe.db.sql("""delete from `tabNotification Count` where owner=%s""", (user,)) | |||
else: | |||
frappe.db.sql("""delete from `tabNotification Count`""") | |||
except MySQLdb.OperationalError, e: | |||
if e.args[0] not in (1213, 1205): | |||
raise | |||
logger.error("Deadlock") | |||
def delete_notification_count_for(doctype): | |||
if frappe.flags.in_import: return | |||
try: | |||
frappe.db.sql("""delete from `tabNotification Count` where for_doctype = %s""", (doctype,)) | |||
except MySQLdb.OperationalError, e: | |||
if e.args[0] not in (1213, 1205): | |||
raise | |||
logger.error("Deadlock") | |||
def clear_doctype_notifications(doc, method=None, *args, **kwargs): | |||
if frappe.flags.in_import: | |||
return | |||
config = get_notification_config() | |||
doctype = doc.doctype | |||
if doctype in config.for_doctype: | |||
delete_notification_count_for(doctype) | |||
return | |||
if doctype in config.for_module_doctypes: | |||
delete_notification_count_for(config.for_module_doctypes[doctype]) | |||
def get_notification_info_for_boot(): | |||
out = get_notifications() | |||
config = get_notification_config() | |||
can_read = frappe.user.get_can_read() | |||
conditions = {} | |||
module_doctypes = {} | |||
doctype_info = dict(frappe.db.sql("""select name, module from tabDocType""")) | |||
for d in list(set(can_read + config.for_doctype.keys())): | |||
if d in config.for_doctype: | |||
conditions[d] = config.for_doctype[d] | |||
if d in doctype_info: | |||
module_doctypes.setdefault(doctype_info[d], []).append(d) | |||
out.update({ | |||
"conditions": conditions, | |||
"module_doctypes": module_doctypes, | |||
}) | |||
return out | |||
def get_notification_config(): | |||
config = frappe._dict() | |||
for notification_config in frappe.get_hooks().notification_config: | |||
nc = frappe.get_attr(notification_config)() | |||
for key in ("for_doctype", "for_module", "for_module_doctypes"): | |||
config.setdefault(key, {}) | |||
config[key].update(nc.get(key, {})) | |||
return config | |||
def on_doctype_update(): | |||
if not frappe.db.sql("""show index from `tabNotification Count` | |||
where Key_name="notification_count_owner_index" """): | |||
frappe.db.commit() | |||
frappe.db.sql("""alter table `tabNotification Count` | |||
add index notification_count_owner_index(owner)""") | |||
@@ -1,111 +0,0 @@ | |||
{ | |||
"allow_copy": 1, | |||
"creation": "2014-03-03 19:48:01", | |||
"description": "Email Settings for Outgoing and Incoming Emails.", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"fieldname": "enabled", | |||
"fieldtype": "Check", | |||
"label": "Enabled", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"depends_on": "eval:doc.enabled", | |||
"fieldname": "section_break_2", | |||
"fieldtype": "Section Break", | |||
"label": "Server & Credentials", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "SMTP Server (e.g. smtp.gmail.com)", | |||
"fieldname": "mail_server", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Outgoing Mail Server", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "<a href=\"https://en.wikipedia.org/wiki/Transport_Layer_Security\" target=\"_blank\">[?]</a>", | |||
"fieldname": "use_ssl", | |||
"fieldtype": "Check", | |||
"in_list_view": 1, | |||
"label": "Use TLS", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "If non standard port (e.g. 587)", | |||
"fieldname": "mail_port", | |||
"fieldtype": "Int", | |||
"in_list_view": 1, | |||
"label": "Port", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "cb0", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "Set Login and Password if authentication is required.", | |||
"fieldname": "mail_login", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Login Id", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "Check this if you want to send emails as this id only (in case of restriction by your email provider).", | |||
"fieldname": "always_use_login_id_as_sender", | |||
"fieldtype": "Check", | |||
"label": "Always use above Login Id as sender", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "mail_password", | |||
"fieldtype": "Password", | |||
"label": "Mail Password", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "System generated mails will be sent from this email id.", | |||
"fieldname": "auto_email_id", | |||
"fieldtype": "Data", | |||
"label": "Auto Email Id", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "section_break_15", | |||
"fieldtype": "Section Break", | |||
"label": "Email Footer", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"default": "<div style=\"padding: 7px; text-align: right; color: #888\"><small>Sent via \n\t<a style=\"color: #888\" href=\"http://frappe.io\">Frappe</a></div>", | |||
"fieldname": "footer", | |||
"fieldtype": "Text Editor", | |||
"label": "", | |||
"permlevel": 0, | |||
"reqd": 0 | |||
} | |||
], | |||
"icon": "icon-cog", | |||
"idx": 1, | |||
"in_create": 1, | |||
"issingle": 1, | |||
"modified": "2014-07-17 08:08:00.483391", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Outgoing Email Settings", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"write": 1 | |||
} | |||
] | |||
} |
@@ -1,32 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe import _, throw | |||
from frappe.utils import validate_email_add | |||
from frappe.model.document import Document | |||
class OutgoingEmailSettings(Document): | |||
def validate(self): | |||
if self.auto_email_id and not validate_email_add(self.auto_email_id): | |||
throw(_("{0} is not a valid email id").format(self.auto_email_id), frappe.InvalidEmailAddressError) | |||
if self.mail_server and not frappe.local.flags.in_patch: | |||
from frappe.utils import cint | |||
from frappe.utils.email_lib.smtp import SMTPServer | |||
smtpserver = SMTPServer(login = self.mail_login, | |||
password = self.mail_password, | |||
server = self.mail_server, | |||
port = cint(self.mail_port), | |||
use_ssl = cint(self.use_ssl) | |||
) | |||
# exceptions are handled in session connect | |||
sess = smtpserver.sess | |||
def get_mail_footer(): | |||
return frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "footer") or "" |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -87,7 +87,7 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2014-09-18 02:25:57.031810", | |||
"modified": "2015-02-05 05:11:41.982758", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Page", | |||
@@ -100,6 +100,7 @@ | |||
"print": 1, | |||
"read": 1, | |||
"role": "Administrator", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -118,6 +119,7 @@ | |||
"report": 0, | |||
"role": "System Manager", | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -1,9 +1,10 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.model.document import Document | |||
from frappe.build import html_to_js_template | |||
class Page(Document): | |||
def autoname(self): | |||
@@ -47,8 +48,8 @@ class Page(Document): | |||
# js | |||
if not os.path.exists(path + '.js'): | |||
with open(path + '.js', 'w') as f: | |||
f.write("""frappe.pages['%s'].onload = function(wrapper) { | |||
frappe.ui.make_app_page({ | |||
f.write("""frappe.pages['%s'].on_page_load = function(wrapper) { | |||
var page = frappe.ui.make_app_page({ | |||
parent: wrapper, | |||
title: '%s', | |||
single_column: true | |||
@@ -79,11 +80,12 @@ class Page(Document): | |||
with open(fpath, 'r') as f: | |||
self.style = unicode(f.read(), "utf-8") | |||
# html | |||
fpath = os.path.join(path, scrub(self.name) + '.html') | |||
if os.path.exists(fpath): | |||
with open(fpath, 'r') as f: | |||
self.content = unicode(f.read(), "utf-8") | |||
# html as js template | |||
for fname in os.listdir(path): | |||
if fname.endswith(".html"): | |||
with open(os.path.join(path, fname), 'r') as f: | |||
template = unicode(f.read(), "utf-8") | |||
self.script = html_to_js_template(fname, template) + self.script | |||
if frappe.lang != 'en': | |||
from frappe.translate import get_lang_js | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
from __future__ import unicode_literals | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,7 +1,7 @@ | |||
{ | |||
"allow_copy": 0, | |||
"autoname": "PR.######", | |||
"creation": "2013-02-22 01:27:34.000000", | |||
"autoname": "hash", | |||
"creation": "2013-02-22 01:27:34", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
@@ -24,10 +24,11 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2013-12-20 19:23:24.000000", | |||
"modified": "2015-02-19 01:07:00.897854", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Page Role", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"read_only": 0 | |||
} |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -1,3 +1,3 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# For license information, please see license.txt | |||
@@ -1,4 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,21 +0,0 @@ | |||
frappe.ui.form.on("Print Format", "onload", function(frm) { | |||
frm.add_fetch("doc_type", "module", "module"); | |||
}); | |||
frappe.ui.form.on("Print Format", "refresh", function(frm) { | |||
frm.set_intro(""); | |||
if (user!="Administrator") { | |||
if (frm.doc.standard == 'Yes') { | |||
frm.toggle_enable(["html", "doc_type", "module"], false); | |||
frm.disable_save(); | |||
} else { | |||
frm.toggle_enable(["html", "doc_type", "module"], true); | |||
frm.enable_save(); | |||
} | |||
frm.toggle_enable("standard", false); | |||
} else { | |||
if(frm.doc.standard==="Yes") { | |||
frm.set_intro(__("This is a standard format. To make changes, please copy it make make a new format.")) | |||
} | |||
} | |||
}) |
@@ -1,183 +0,0 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_rename": 0, | |||
"autoname": "Prompt", | |||
"creation": "2013-01-23 19:54:43", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"description": "Belongs to", | |||
"fieldname": "doc_type", | |||
"fieldtype": "Link", | |||
"in_filter": 1, | |||
"in_list_view": 1, | |||
"label": "DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "module", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"in_filter": 1, | |||
"in_list_view": 1, | |||
"label": "Module", | |||
"no_copy": 0, | |||
"oldfieldname": "module", | |||
"oldfieldtype": "Select", | |||
"options": "Module Def", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"fieldname": "column_break_3", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"default": "No", | |||
"fieldname": "standard", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"in_filter": 1, | |||
"in_list_view": 1, | |||
"label": "Standard", | |||
"no_copy": 1, | |||
"oldfieldname": "standard", | |||
"oldfieldtype": "Select", | |||
"options": "No\nYes", | |||
"permlevel": 1, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"default": "Server", | |||
"description": "Client-side formats are now deprecated", | |||
"fieldname": "print_format_type", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Print Format Type", | |||
"options": "Server\nClient", | |||
"permlevel": 0, | |||
"read_only": 0 | |||
}, | |||
{ | |||
"fieldname": "disabled", | |||
"fieldtype": "Check", | |||
"label": "Disabled", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "section_break_6", | |||
"fieldtype": "Section Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"depends_on": "", | |||
"fieldname": "html", | |||
"fieldtype": "Code", | |||
"hidden": 0, | |||
"in_filter": 0, | |||
"label": "HTML", | |||
"no_copy": 0, | |||
"oldfieldname": "html", | |||
"oldfieldtype": "Text Editor", | |||
"options": "HTML", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "print_format_help", | |||
"fieldtype": "HTML", | |||
"label": "Print Format Help", | |||
"options": "<h3>Print Format Help</h3>\n<hr>\n<h4>Introduction</h4>\n<p>Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the <code>doc</code> object which contains information about the document that is being formatted. You can also access common utilities via the <code>frappe</code> module.</p>\n<p>For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.</p>\n<hr>\n<h4>References</h4>\n<ol>\n\t<li><a href=\"http://jinja.pocoo.org/docs/templates/\" target=\"_blank\">Jinja Tempalting Language: Reference</a></li>\n\t<li><a href=\"http://getbootstrap.com\" target=\"_blank\">Bootstrap CSS Framework</a></li>\n</ol>\n<hr>\n<h4>Example</h4>\n<pre><code><h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-xs-3 text-right\">Customer Name</div>\n\t<div class=\"col-xs-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-xs-3 text-right\">Date</div>\n\t<div class=\"col-xs-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.entries -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table></code></pre>\n<hr>\n<h4>Common Functions</h4>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%\"><code>doc.get_formatted(\"[fieldname]\", [parent_doc])</code></td>\n\t\t\t<td>Get document value formatted as Date, Currency etc. Pass parent <code>doc</code> for curreny type fields.</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%\"><code>frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")</code></td>\n\t\t\t<td>Get value from another document.</td>\n\t\t</tr>\n\t</tbody>\n</table>\n", | |||
"permlevel": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"icon": "icon-print", | |||
"idx": 1, | |||
"in_create": 0, | |||
"in_dialog": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2015-01-23 03:39:35.898711", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Print Format", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Administrator", | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 0, | |||
"delete": 0, | |||
"permlevel": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"submit": 0 | |||
}, | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 0, | |||
"delete": 0, | |||
"permlevel": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Administrator", | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
], | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
} |
@@ -1,4 +0,0 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,3 +1,23 @@ | |||
cur_frm.cscript.report_type = function(doc) { | |||
cur_frm.set_intro(""); | |||
switch(doc.report_type) { | |||
case "Report Builder": | |||
cur_frm.set_intro(__("Report Builder reports are managed directly by the report builder. Nothing to do.")); | |||
break; | |||
case "Query Report": | |||
cur_frm.set_intro(__("Write a SELECT query. Note result is not paged (all data is sent in one go).") | |||
+ __("To format columns, give column labels in the query.") + "<br>" | |||
+ __("[Label]:[Field Type]/[Options]:[Width]") + "<br><br>" | |||
+ __("Example:") + "<br>" | |||
+ "Employee:Link/Employee:200" + "<br>" | |||
+ "Rate:Currency:120" + "<br>") | |||
break; | |||
case "Script Report": | |||
cur_frm.set_intro(__("Write a Python file in the same folder where this is saved and return column and result.")) | |||
break; | |||
} | |||
} | |||
cur_frm.cscript.refresh = function(doc) { | |||
cur_frm.add_custom_button("Show Report", function() { | |||
switch(doc.report_type) { | |||
@@ -28,21 +48,5 @@ cur_frm.cscript.refresh = function(doc) { | |||
}, doc.disabled ? "icon-ok" : "icon-off"); | |||
} | |||
cur_frm.set_intro(""); | |||
switch(doc.report_type) { | |||
case "Report Builder": | |||
cur_frm.set_intro(__("Report Builder reports are managed directly by the report builder. Nothing to do.")); | |||
break; | |||
case "Query Report": | |||
cur_frm.set_intro(__("Write a SELECT query. Note result is not paged (all data is sent in one go).") | |||
+ __("To format columns, give column labels in the query.") + "<br>" | |||
+ __("[Label]:[Field Type]/[Options]:[Width]") + "<br><br>" | |||
+ __("Example:") + "<br>" | |||
+ "Employee:Link/Employee:200" + "<br>" | |||
+ "Rate:Currency:120" + "<br>") | |||
break; | |||
case "Script Report": | |||
cur_frm.set_intro(__("Write a Python file in the same folder where this is saved and return column and result.")) | |||
break; | |||
} | |||
cur_frm.cscript.report_type(doc); | |||
} |
@@ -33,6 +33,13 @@ | |||
"read_only": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "module", | |||
"fieldtype": "Link", | |||
"label": "Module", | |||
"options": "Module Def", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "add_total_row", | |||
"fieldtype": "Check", | |||
@@ -85,10 +92,11 @@ | |||
"read_only": 0 | |||
}, | |||
{ | |||
"depends_on": "eval:doc.report_type==\"Query Report\"", | |||
"depends_on": "", | |||
"description": "JavaScript Format: frappe.query_reports['REPORTNAME'] = {}", | |||
"fieldname": "javascript", | |||
"fieldtype": "Code", | |||
"hidden": 1, | |||
"label": "Javascript", | |||
"permlevel": 0 | |||
}, | |||
@@ -99,18 +107,11 @@ | |||
"label": "JSON", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
{ | |||
"fieldname": "module", | |||
"fieldtype": "Link", | |||
"label": "Module", | |||
"options": "Module Def", | |||
"permlevel": 0 | |||
} | |||
], | |||
"icon": "icon-table", | |||
"idx": 1, | |||
"modified": "2014-06-03 07:25:41.509885", | |||
"modified": "2015-02-05 05:11:44.753200", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Report", | |||
@@ -125,6 +126,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Administrator", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -137,6 +139,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -149,6 +152,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Report Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -0,0 +1,10 @@ | |||
[ | |||
{ | |||
"doctype": "Report", | |||
"name": "_Test Report 1", | |||
"report_name": "_Test Report 1", | |||
"report_type": "Query Report", | |||
"is_standard": "No", | |||
"ref_doctype": "Event" | |||
} | |||
] |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
import frappe | |||
import unittest | |||
test_records = frappe.get_test_records('Report') | |||
class TestReport(unittest.TestCase): | |||
pass |
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals |
@@ -1,4 +1,4 @@ | |||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt | |||
cur_frm.cscript.refresh = function(doc) { | |||
@@ -23,7 +23,7 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2014-08-05 05:24:42.185395", | |||
"modified": "2015-02-05 05:11:44.831475", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Role", | |||
@@ -38,6 +38,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -50,6 +51,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "Administrator", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -1,3 +1,3 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"autoname": "SCHLOG.#####", | |||
"creation": "2013-01-16 13:09:40.000000", | |||
"creation": "2013-01-16 13:09:40", | |||
"description": "Log of Scheduler Errors", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
@@ -22,7 +22,7 @@ | |||
], | |||
"icon": "icon-warning-sign", | |||
"idx": 1, | |||
"modified": "2014-01-20 17:49:26.000000", | |||
"modified": "2015-02-05 05:11:46.339879", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Scheduler Log", | |||
@@ -38,6 +38,7 @@ | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# For license information, please see license.txt | |||
@@ -1,100 +1,101 @@ | |||
{ | |||
"creation": "2014-04-17 16:53:52.640856", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"creation": "2014-04-17 16:53:52.640856", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"fields": [ | |||
{ | |||
"fieldname": "localization", | |||
"fieldtype": "Section Break", | |||
"label": "Localization", | |||
"fieldname": "localization", | |||
"fieldtype": "Section Break", | |||
"label": "Localization", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "language", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Language", | |||
"options": "Loading...", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"fieldname": "language", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Language", | |||
"options": "Loading...", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "time_zone", | |||
"fieldtype": "Select", | |||
"label": "Time Zone", | |||
"permlevel": 0, | |||
"fieldname": "time_zone", | |||
"fieldtype": "Select", | |||
"label": "Time Zone", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "date_and_number_format", | |||
"fieldtype": "Section Break", | |||
"label": "Date and Number Format", | |||
"fieldname": "date_and_number_format", | |||
"fieldtype": "Section Break", | |||
"label": "Date and Number Format", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "date_format", | |||
"fieldtype": "Select", | |||
"label": "Date Format", | |||
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\ndd.mm.yyyy\nmm/dd/yyyy\nmm-dd-yyyy", | |||
"permlevel": 0, | |||
"fieldname": "date_format", | |||
"fieldtype": "Select", | |||
"label": "Date Format", | |||
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\ndd.mm.yyyy\nmm/dd/yyyy\nmm-dd-yyyy", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "number_format", | |||
"fieldtype": "Select", | |||
"label": "Number Format", | |||
"options": "#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###", | |||
"permlevel": 0, | |||
"fieldname": "number_format", | |||
"fieldtype": "Select", | |||
"label": "Number Format", | |||
"options": "#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "float_precision", | |||
"fieldtype": "Select", | |||
"label": "Float Precision", | |||
"options": "\n2\n3\n4\n5\n6", | |||
"fieldname": "float_precision", | |||
"fieldtype": "Select", | |||
"label": "Float Precision", | |||
"options": "\n2\n3\n4\n5\n6", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "security", | |||
"fieldtype": "Section Break", | |||
"label": "Security", | |||
"fieldname": "security", | |||
"fieldtype": "Section Break", | |||
"label": "Security", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"default": "06:00", | |||
"description": "Session Expiry in Hours e.g. 06:00", | |||
"fieldname": "session_expiry", | |||
"fieldtype": "Data", | |||
"label": "Session Expiry", | |||
"options": "", | |||
"default": "06:00", | |||
"description": "Session Expiry in Hours e.g. 06:00", | |||
"fieldname": "session_expiry", | |||
"fieldtype": "Data", | |||
"label": "Session Expiry", | |||
"options": "", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"description": "Run scheduled jobs only if checked", | |||
"fieldname": "enable_scheduler", | |||
"fieldtype": "Check", | |||
"in_list_view": 0, | |||
"label": "Enable Scheduled Jobs", | |||
"description": "Run scheduled jobs only if checked", | |||
"fieldname": "enable_scheduler", | |||
"fieldtype": "Check", | |||
"in_list_view": 0, | |||
"label": "Enable Scheduled Jobs", | |||
"permlevel": 0 | |||
} | |||
], | |||
"icon": "icon-cog", | |||
"issingle": 1, | |||
"modified": "2014-06-18 02:09:03.623094", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "System Settings", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
], | |||
"icon": "icon-cog", | |||
"issingle": 1, | |||
"modified": "2015-02-05 05:11:47.880614", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "System Settings", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"write": 1 | |||
} | |||
] | |||
} | |||
} |