Sfoglia il codice sorgente

Merge branch 'v5.0' into develop

version-14
Pratik Vyas 10 anni fa
parent
commit
36c13b2cba
100 ha cambiato i file con 2834 aggiunte e 1649 eliminazioni
  1. +1
    -0
      .gitignore
  2. +16
    -23
      .travis.yml
  3. +51
    -0
      attributions.md
  4. +350
    -89
      frappe/__init__.py
  5. +1
    -1
      frappe/__version__.py
  6. +29
    -16
      frappe/api.py
  7. +6
    -2
      frappe/app.py
  8. +29
    -15
      frappe/auth.py
  9. +15
    -19
      frappe/boot.py
  10. +50
    -10
      frappe/build.py
  11. +1
    -1
      frappe/celery_app.py
  12. +96
    -0
      frappe/change_log/__init__.py
  13. +13
    -0
      frappe/change_log/v5/v5_0_0.md
  14. +57
    -21
      frappe/cli.py
  15. +39
    -27
      frappe/client.py
  16. +793
    -0
      frappe/commands.py
  17. +25
    -1
      frappe/config/desktop.py
  18. +32
    -7
      frappe/config/setup.py
  19. +2
    -12
      frappe/config/website.py
  20. +1
    -1
      frappe/core/__init__.py
  21. +1
    -1
      frappe/core/doctype/__init__.py
  22. +1
    -1
      frappe/core/doctype/comment/__init__.py
  23. +6
    -1
      frappe/core/doctype/comment/comment.json
  24. +41
    -1
      frappe/core/doctype/comment/comment.py
  25. +1
    -1
      frappe/core/doctype/comment/test_comment.py
  26. +1
    -1
      frappe/core/doctype/communication/__init__.py
  27. +30
    -7
      frappe/core/doctype/communication/communication.js
  28. +58
    -33
      frappe/core/doctype/communication/communication.json
  29. +168
    -134
      frappe/core/doctype/communication/communication.py
  30. +0
    -37
      frappe/core/doctype/communication/communication_list.html
  31. +1
    -1
      frappe/core/doctype/communication/test_communication.py
  32. +0
    -4
      frappe/core/doctype/customize_form_field/__init__.py
  33. +1
    -1
      frappe/core/doctype/defaultvalue/__init__.py
  34. +4
    -3
      frappe/core/doctype/defaultvalue/defaultvalue.json
  35. +8
    -7
      frappe/core/doctype/defaultvalue/defaultvalue.py
  36. +1
    -1
      frappe/core/doctype/docfield/__init__.py
  37. +258
    -258
      frappe/core/doctype/docfield/docfield.json
  38. +2
    -2
      frappe/core/doctype/docfield/docfield.py
  39. +1
    -1
      frappe/core/doctype/docperm/__init__.py
  40. +9
    -2
      frappe/core/doctype/docperm/docperm.json
  41. +3
    -2
      frappe/core/doctype/docperm/docperm.py
  42. +0
    -0
      frappe/core/doctype/docshare/__init__.py
  43. +172
    -0
      frappe/core/doctype/docshare/docshare.json
  44. +49
    -0
      frappe/core/doctype/docshare/docshare.py
  45. +80
    -0
      frappe/core/doctype/docshare/test_docshare.py
  46. +0
    -0
      frappe/core/doctype/docshare/test_records.json
  47. +1
    -1
      frappe/core/doctype/doctype/__init__.py
  48. +1
    -1
      frappe/core/doctype/doctype/boilerplate/controller.py
  49. +2
    -2
      frappe/core/doctype/doctype/boilerplate/test_controller.py
  50. +0
    -6
      frappe/core/doctype/doctype/boilerplate/test_records.json
  51. +6
    -10
      frappe/core/doctype/doctype/doctype.js
  52. +8
    -4
      frappe/core/doctype/doctype/doctype.json
  53. +54
    -25
      frappe/core/doctype/doctype/doctype.py
  54. +0
    -42
      frappe/core/doctype/email_alert_recipient/email_alert_recipient.json
  55. +0
    -4
      frappe/core/doctype/event/__init__.py
  56. +0
    -4
      frappe/core/doctype/event_role/__init__.py
  57. +0
    -1
      frappe/core/doctype/event_user/README.md
  58. +0
    -4
      frappe/core/doctype/event_user/__init__.py
  59. +0
    -31
      frappe/core/doctype/event_user/event_user.json
  60. +0
    -10
      frappe/core/doctype/event_user/event_user.py
  61. +1
    -1
      frappe/core/doctype/file_data/__init__.py
  62. +64
    -63
      frappe/core/doctype/file_data/file_data.json
  63. +9
    -3
      frappe/core/doctype/file_data/file_data.py
  64. +0
    -4
      frappe/core/doctype/letter_head/__init__.py
  65. +0
    -7
      frappe/core/doctype/letter_head/test_letter_head.py
  66. +1
    -1
      frappe/core/doctype/module_def/__init__.py
  67. +2
    -1
      frappe/core/doctype/module_def/module_def.json
  68. +18
    -9
      frappe/core/doctype/module_def/module_def.py
  69. +0
    -34
      frappe/core/doctype/notification_count/notification_count.json
  70. +0
    -155
      frappe/core/doctype/notification_count/notification_count.py
  71. +0
    -111
      frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json
  72. +0
    -32
      frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py
  73. +1
    -1
      frappe/core/doctype/page/__init__.py
  74. +3
    -1
      frappe/core/doctype/page/page.json
  75. +10
    -8
      frappe/core/doctype/page/page.py
  76. +1
    -1
      frappe/core/doctype/page/test_page.py
  77. +1
    -1
      frappe/core/doctype/page_role/__init__.py
  78. +4
    -3
      frappe/core/doctype/page_role/page_role.json
  79. +1
    -1
      frappe/core/doctype/page_role/page_role.py
  80. +1
    -1
      frappe/core/doctype/patch_log/__init__.py
  81. +1
    -1
      frappe/core/doctype/patch_log/patch_log.py
  82. +0
    -4
      frappe/core/doctype/print_format/__init__.py
  83. +0
    -21
      frappe/core/doctype/print_format/print_format.js
  84. +0
    -183
      frappe/core/doctype/print_format/print_format.json
  85. +0
    -4
      frappe/core/doctype/property_setter/__init__.py
  86. +1
    -1
      frappe/core/doctype/report/__init__.py
  87. +21
    -17
      frappe/core/doctype/report/report.js
  88. +13
    -9
      frappe/core/doctype/report/report.json
  89. +1
    -1
      frappe/core/doctype/report/report.py
  90. +10
    -0
      frappe/core/doctype/report/test_records.json
  91. +10
    -0
      frappe/core/doctype/report/test_report.py
  92. +1
    -1
      frappe/core/doctype/role/__init__.py
  93. +1
    -1
      frappe/core/doctype/role/role.js
  94. +3
    -1
      frappe/core/doctype/role/role.json
  95. +1
    -1
      frappe/core/doctype/role/role.py
  96. +1
    -1
      frappe/core/doctype/role/test_role.py
  97. +1
    -1
      frappe/core/doctype/scheduler_log/__init__.py
  98. +3
    -2
      frappe/core/doctype/scheduler_log/scheduler_log.json
  99. +1
    -1
      frappe/core/doctype/scheduler_log/scheduler_log.py
  100. +73
    -72
      frappe/core/doctype/system_settings/system_settings.json

+ 1
- 0
.gitignore Vedi File

@@ -8,3 +8,4 @@ locale
*.egg-info
dist/
build/
docs/

+ 16
- 23
.travis.yml Vedi File

@@ -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


+ 51
- 0
attributions.md Vedi File

@@ -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

+ 350
- 89
frappe/__init__.py Vedi File

@@ -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
- 1
frappe/__version__.py Vedi File

@@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = "4.13.2"
__version__ = "5.0.0-alpha"

+ 29
- 16
frappe/api.py Vedi File

@@ -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":


+ 6
- 2
frappe/app.py Vedi File

@@ -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='.'):


+ 29
- 15
frappe/auth.py Vedi File

@@ -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


+ 15
- 19
frappe/boot.py Vedi File

@@ -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)


+ 50
- 10
frappe/build.py Vedi File

@@ -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
- 1
frappe/celery_app.py Vedi File

@@ -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



+ 96
- 0
frappe/change_log/__init__.py Vedi File

@@ -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



+ 13
- 0
frappe/change_log/v5/v5_0_0.md Vedi File

@@ -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


+ 57
- 21
frappe/cli.py Vedi File

@@ -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



+ 39
- 27
frappe/client.py Vedi File

@@ -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):


+ 793
- 0
frappe/commands.py Vedi File

@@ -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,
]

+ 25
- 1
frappe/config/desktop.py Vedi File

@@ -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
},


+ 32
- 7
frappe/config/setup.py Vedi File

@@ -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.")
}

]
},
{


+ 2
- 12
frappe/config/website.py Vedi File

@@ -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
- 1
frappe/core/__init__.py Vedi File

@@ -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
- 1
frappe/core/doctype/__init__.py Vedi File

@@ -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
- 1
frappe/core/doctype/comment/__init__.py Vedi File

@@ -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
- 1
frappe/core/doctype/comment/comment.json Vedi File

@@ -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
}


+ 41
- 1
frappe/core/doctype/comment/comment.py Vedi File

@@ -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
- 1
frappe/core/doctype/comment/test_comment.py Vedi File

@@ -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
- 1
frappe/core/doctype/communication/__init__.py Vedi File

@@ -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


+ 30
- 7
frappe/core/doctype/communication/communication.js Vedi File

@@ -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);
}
}
});

+ 58
- 33
frappe/core/doctype/communication/communication.json Vedi File

@@ -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"
}

+ 168
- 134
frappe/core/doctype/communication/communication.py Vedi File

@@ -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")

+ 0
- 37
frappe/core/doctype/communication/communication_list.html Vedi File

@@ -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
- 1
frappe/core/doctype/communication/test_communication.py Vedi File

@@ -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



+ 0
- 4
frappe/core/doctype/customize_form_field/__init__.py Vedi File

@@ -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
- 1
frappe/core/doctype/defaultvalue/__init__.py Vedi File

@@ -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

+ 4
- 3
frappe/core/doctype/defaultvalue/defaultvalue.json Vedi File

@@ -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
}

+ 8
- 7
frappe/core/doctype/defaultvalue/defaultvalue.py Vedi File

@@ -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
- 1
frappe/core/doctype/docfield/__init__.py Vedi File

@@ -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

+ 258
- 258
frappe/core/doctype/docfield/docfield.json Vedi File

@@ -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
}
}

+ 2
- 2
frappe/core/doctype/docfield/docfield.py Vedi File

@@ -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
- 1
frappe/core/doctype/docperm/__init__.py Vedi File

@@ -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
- 2
frappe/core/doctype/docperm/docperm.json Vedi File

@@ -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",


+ 3
- 2
frappe/core/doctype/docperm/docperm.py Vedi File

@@ -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

frappe/core/doctype/email_alert/__init__.py → frappe/core/doctype/docshare/__init__.py Vedi File


+ 172
- 0
frappe/core/doctype/docshare/docshare.json Vedi File

@@ -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"
}

+ 49
- 0
frappe/core/doctype/docshare/docshare.py Vedi File

@@ -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"])

+ 80
- 0
frappe/core/doctype/docshare/test_docshare.py Vedi File

@@ -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"))


frappe/core/doctype/custom_field/test_records.json → frappe/core/doctype/docshare/test_records.json Vedi File


+ 1
- 1
frappe/core/doctype/doctype/__init__.py Vedi File

@@ -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
- 1
frappe/core/doctype/doctype/boilerplate/controller.py Vedi File

@@ -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


+ 2
- 2
frappe/core/doctype/doctype/boilerplate/test_controller.py Vedi File

@@ -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

+ 0
- 6
frappe/core/doctype/doctype/boilerplate/test_records.json Vedi File

@@ -1,6 +0,0 @@
[
{{
"doctype": "{doctype}",
"name": "_Test {doctype} 1"
}}
]

+ 6
- 10
frappe/core/doctype/doctype/doctype.js Vedi File

@@ -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);
}
}



+ 8
- 4
frappe/core/doctype/doctype/doctype.json Vedi File

@@ -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
}


+ 54
- 25
frappe/core/doctype/doctype/doctype.py Vedi File

@@ -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)


+ 0
- 42
frappe/core/doctype/email_alert_recipient/email_alert_recipient.json Vedi File

@@ -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"
}

+ 0
- 4
frappe/core/doctype/event/__init__.py Vedi File

@@ -1,4 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals

+ 0
- 4
frappe/core/doctype/event_role/__init__.py Vedi File

@@ -1,4 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals

+ 0
- 1
frappe/core/doctype/event_user/README.md Vedi File

@@ -1 +0,0 @@
User (user) with whom the parent Event is shared.

+ 0
- 4
frappe/core/doctype/event_user/__init__.py Vedi File

@@ -1,4 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals

+ 0
- 31
frappe/core/doctype/event_user/event_user.json Vedi File

@@ -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"
}

+ 0
- 10
frappe/core/doctype/event_user/event_user.py Vedi File

@@ -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
- 1
frappe/core/doctype/file_data/__init__.py Vedi File

@@ -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

+ 64
- 63
frappe/core/doctype/file_data/file_data.json Vedi File

@@ -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
}
}

+ 9
- 3
frappe/core/doctype/file_data/file_data.py Vedi File

@@ -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"])


+ 0
- 4
frappe/core/doctype/letter_head/__init__.py Vedi File

@@ -1,4 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals

+ 0
- 7
frappe/core/doctype/letter_head/test_letter_head.py Vedi File

@@ -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
- 1
frappe/core/doctype/module_def/__init__.py Vedi File

@@ -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

+ 2
- 1
frappe/core/doctype/module_def/module_def.json Vedi File

@@ -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
},


+ 18
- 9
frappe/core/doctype/module_def/module_def.py Vedi File

@@ -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("")




+ 0
- 34
frappe/core/doctype/notification_count/notification_count.json Vedi File

@@ -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"
}

+ 0
- 155
frappe/core/doctype/notification_count/notification_count.py Vedi File

@@ -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)""")


+ 0
- 111
frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json Vedi File

@@ -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
}
]
}

+ 0
- 32
frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py Vedi File

@@ -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
- 1
frappe/core/doctype/page/__init__.py Vedi File

@@ -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

+ 3
- 1
frappe/core/doctype/page/page.json Vedi File

@@ -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
},


+ 10
- 8
frappe/core/doctype/page/page.py Vedi File

@@ -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
- 1
frappe/core/doctype/page/test_page.py Vedi File

@@ -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
- 1
frappe/core/doctype/page_role/__init__.py Vedi File

@@ -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

+ 4
- 3
frappe/core/doctype/page_role/page_role.json Vedi File

@@ -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
- 1
frappe/core/doctype/page_role/page_role.py Vedi File

@@ -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
- 1
frappe/core/doctype/patch_log/__init__.py Vedi File

@@ -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
- 1
frappe/core/doctype/patch_log/patch_log.py Vedi File

@@ -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


+ 0
- 4
frappe/core/doctype/print_format/__init__.py Vedi File

@@ -1,4 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals

+ 0
- 21
frappe/core/doctype/print_format/print_format.js Vedi File

@@ -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."))
}
}
})

+ 0
- 183
frappe/core/doctype/print_format/print_format.json Vedi File

@@ -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>&lt;h3&gt;{{ doc.select_print_heading or \"Invoice\" }}&lt;/h3&gt;\n&lt;div class=\"row\"&gt;\n\t&lt;div class=\"col-xs-3 text-right\"&gt;Customer Name&lt;/div&gt;\n\t&lt;div class=\"col-xs-9\"&gt;{{ doc.customer_name }}&lt;/div&gt;\n&lt;/div&gt;\n&lt;div class=\"row\"&gt;\n\t&lt;div class=\"col-xs-3 text-right\"&gt;Date&lt;/div&gt;\n\t&lt;div class=\"col-xs-9\"&gt;{{ doc.get_formatted(\"invoice_date\") }}&lt;/div&gt;\n&lt;/div&gt;\n&lt;table class=\"table table-bordered\"&gt;\n\t&lt;tbody&gt;\n\t\t&lt;tr&gt;\n\t\t\t&lt;th&gt;Sr&lt;/th&gt;\n\t\t\t&lt;th&gt;Item Name&lt;/th&gt;\n\t\t\t&lt;th&gt;Description&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Qty&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Rate&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Amount&lt;/th&gt;\n\t\t&lt;/tr&gt;\n\t\t{%- for row in doc.entries -%}\n\t\t&lt;tr&gt;\n\t\t\t&lt;td style=\"width: 3%;\"&gt;{{ row.idx }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 20%;\"&gt;\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&lt;br&gt;Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 37%;\"&gt;\n\t\t\t\t&lt;div style=\"border: 0px;\"&gt;{{ row.description }}&lt;/div&gt;&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 10%; text-align: right;\"&gt;{{ row.qty }} {{ row.uom or row.stock_uom }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 15%; text-align: right;\"&gt;{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 15%; text-align: right;\"&gt;{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}&lt;/td&gt;\n\t\t&lt;/tr&gt;\n\t\t{%- endfor -%}\n\t&lt;/tbody&gt;\n&lt;/table&gt;</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"
}

+ 0
- 4
frappe/core/doctype/property_setter/__init__.py Vedi File

@@ -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
- 1
frappe/core/doctype/report/__init__.py Vedi File

@@ -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

+ 21
- 17
frappe/core/doctype/report/report.js Vedi File

@@ -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);
}

+ 13
- 9
frappe/core/doctype/report/report.json Vedi File

@@ -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
- 1
frappe/core/doctype/report/report.py Vedi File

@@ -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


+ 10
- 0
frappe/core/doctype/report/test_records.json Vedi File

@@ -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"
}
]

+ 10
- 0
frappe/core/doctype/report/test_report.py Vedi File

@@ -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
- 1
frappe/core/doctype/role/__init__.py Vedi File

@@ -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
- 1
frappe/core/doctype/role/role.js Vedi File

@@ -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) {


+ 3
- 1
frappe/core/doctype/role/role.json Vedi File

@@ -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
- 1
frappe/core/doctype/role/role.py Vedi File

@@ -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
- 1
frappe/core/doctype/role/test_role.py Vedi File

@@ -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
- 1
frappe/core/doctype/scheduler_log/__init__.py Vedi File

@@ -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


+ 3
- 2
frappe/core/doctype/scheduler_log/scheduler_log.json Vedi File

@@ -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
- 1
frappe/core/doctype/scheduler_log/scheduler_log.py Vedi File

@@ -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


+ 73
- 72
frappe/core/doctype/system_settings/system_settings.json Vedi File

@@ -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
}
]
}
}

Dato che sono stati cambiati molti file in questo diff, alcuni di essi non verranno mostrati

Caricamento…
Annulla
Salva