Merge branch 'develop' of http://github.com/frappe/frappe into develop

This commit is contained in:
mbauskar 2015-12-31 16:39:50 +05:30
commit 629221c8bd
981 changed files with 157508 additions and 26827 deletions

1
.gitignore vendored
View file

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

View file

@ -14,8 +14,6 @@ install:
- sudo apt-get purge -y mysql-common
- 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 pip install --upgrade pip
- 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/
@ -23,10 +21,13 @@ install:
script:
- cd ~/frappe-bench
- bench use test_site
- bench setup redis-cache
- bench setup redis-async-broker
- bench setup procfile --with-celery-broker
- bench reinstall
- bench build
- bench build-website
- bench serve &
- bench start &
- sleep 10
- bench --verbose run-tests --driver Firefox

View file

@ -1,4 +1,6 @@
## frappe [![Build Status](https://travis-ci.org/frappe/frappe.png)](https://travis-ci.org/frappe/frappe)
## Frappe Framework
[![Build Status](https://travis-ci.org/frappe/frappe.png)](https://travis-ci.org/frappe/frappe)
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. [Built for ERPNext](https://erpnext.com)

View file

@ -7,6 +7,7 @@ globals attached to frappe module
from __future__ import unicode_literals
from werkzeug.local import Local, release_local
from functools import wraps
import os, importlib, inspect, logging, json
# public
@ -14,6 +15,7 @@ from frappe.__version__ import __version__
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template
local = Local()
class _dict(dict):
@ -36,13 +38,16 @@ class _dict(dict):
def copy(self):
return _dict(dict(self).copy())
def _(msg):
def _(msg, lang=None):
"""Returns translated string in current lang, if exists."""
if local.lang == "en":
if not lang:
lang = local.lang
if lang == "en":
return msg
from frappe.translate import get_full_dict
return get_full_dict(local.lang).get(msg, msg)
return get_full_dict(local.lang).get(msg) or msg
def get_lang_dict(fortype, name=None):
"""Returns the translated language dict for the given type and name.
@ -64,7 +69,6 @@ db = local("db")
conf = local("conf")
form = form_dict = local("form_dict")
request = local("request")
request_method = local("request_method")
response = local("response")
session = local("session")
user = local("user")
@ -87,6 +91,7 @@ def init(site, sites_path=None):
local.error_log = []
local.message_log = []
local.debug_log = []
local.realtime_log = []
local.flags = _dict({
"ran_schedulers": [],
"redirect_location": "",
@ -106,9 +111,9 @@ def init(site, sites_path=None):
local.sites_path = sites_path
local.site_path = os.path.join(sites_path, site)
local.request_method = request.method if request else None
local.request_ip = None
local.response = _dict({"docs":[]})
local.task_id = None
local.conf = _dict(get_site_config())
local.lang = local.conf.lang or "en"
@ -239,7 +244,7 @@ def msgprint(msg, small=0, raise_exception=0, as_table=False):
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>'
if flags.print_messages:
print "Message: " + repr(msg)
print "Message: " + repr(msg).encode("utf-8")
message_log.append((small and '__small:' or '')+cstr(msg or ''))
_raise_exception()
@ -305,7 +310,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
as_markdown=False, bulk=False, reference_doctype=None, reference_name=None,
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, content=None, doctype=None, name=None, reply_to=None,
cc=(), message_id=None, as_bulk=False, send_after=None):
cc=(), show_as_cc=(), message_id=None, as_bulk=False, send_after=None, expose_recipients=False,
bulk_priority=1):
"""Send email using user's default **Email Account** or global default **Email Account**.
@ -315,6 +321,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
: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 bulk_priority: Priority for bulk email, default 1.
:param reference_doctype: (or `doctype`) Append as communication to this DocType.
:param reference_name: (or `name`) Append as communication to this document name.
:param unsubscribe_method: Unsubscribe url with options email, doctype, name. e.g. `/api/method/unsubscribe`
@ -323,6 +330,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
:param reply_to: Reply-To email id.
:param message_id: Used for threading. If a reply is received to this email, Message-Id is sent back as In-Reply-To in received email.
:param send_after: Send after the given datetime.
:param expose_recipients: Display all recipients in the footer message - "This email was sent to"
"""
if bulk or as_bulk:
@ -331,7 +339,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
subject=subject, message=content or message,
reference_doctype = doctype or reference_doctype, reference_name = name or reference_name,
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, send_after=send_after)
attachments=attachments, reply_to=reply_to, cc=cc, show_as_cc=show_as_cc, message_id=message_id, send_after=send_after,
expose_recipients=expose_recipients, bulk_priority=bulk_priority)
else:
import frappe.email
if as_markdown:
@ -346,7 +355,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
logger = None
whitelisted = []
guest_methods = []
def whitelist(allow_guest=False):
xss_safe_methods = []
def whitelist(allow_guest=False, xss_safe=False):
"""
Decorator for whitelisting a function and making it accessible via HTTP.
Standard request will be `/api/method/[path.to.method]`
@ -360,12 +370,15 @@ def whitelist(allow_guest=False):
pass
"""
def innerfn(fn):
global whitelisted, guest_methods
global whitelisted, guest_methods, xss_safe_methods
whitelisted.append(fn)
if allow_guest:
guest_methods.append(fn)
if xss_safe:
xss_safe_methods.append(fn)
return fn
return innerfn
@ -405,7 +418,7 @@ def clear_cache(user=None, doctype=None):
frappe.local.role_permissions = {}
def has_permission(doctype, ptype="read", doc=None, user=None, verbose=False):
def has_permission(doctype, ptype="read", doc=None, user=None, verbose=False, throw=False):
"""Raises `frappe.PermissionError` if not permitted.
:param doctype: DocType for which permission is to be check.
@ -413,7 +426,14 @@ def has_permission(doctype, ptype="read", doc=None, user=None, verbose=False):
: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=doc, verbose=verbose, user=user)
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user)
if throw and not out:
if doc:
frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name))
else:
frappe.throw(_("No permission for {0}").format(doctype))
return out
def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=False):
"""Raises `frappe.PermissionError` if not permitted.
@ -428,6 +448,9 @@ def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=F
hooks = (get_hooks("has_website_permission") or {}).get(doctype, [])
if hooks:
if isinstance(doc, basestring):
doc = get_doc(doctype, doc)
for method in hooks:
result = call(get_attr(method), doc=doc, ptype=ptype, user=user, verbose=verbose)
# if even a single permission check is Falsy
@ -443,7 +466,7 @@ def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=F
def is_table(doctype):
"""Returns True if `istable` property (indicating child Table) is set for given DocType."""
def get_tables():
return db.sql_list("select name from tabDocType where ifnull(istable,0)=1")
return db.sql_list("select name from tabDocType where istable=1")
tables = cache().get_value("is_table", get_tables)
return doctype in tables
@ -453,11 +476,14 @@ def get_precision(doctype, fieldname, currency=None, doc=None):
from frappe.model.meta import get_field_precision
return get_field_precision(get_meta(doctype).get_field(fieldname), doc, currency)
def generate_hash(txt=None):
def generate_hash(txt=None, length=None):
"""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()
digest = hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest()
if length:
digest = digest[:length]
return digest
def reset_metadata_version():
"""Reset `metadata_version` (Client (Javascript) build ID) hash."""
@ -538,9 +564,9 @@ def delete_doc_if_exists(doctype, name):
if db.exists(doctype, name):
delete_doc(doctype, name)
def reload_doctype(doctype):
def reload_doctype(doctype, force=False):
"""Reload DocType from model (`[module]/[doctype]/[name]/[name].json`) files."""
reload_doc(scrub(db.get_value("DocType", doctype, "module")), "doctype", scrub(doctype))
reload_doc(scrub(db.get_value("DocType", doctype, "module")), "doctype", scrub(doctype), force=force)
def reload_doc(module, dt=None, dn=None, force=False):
"""Reload Document from model (`[module]/[doctype]/[name]/[name].json`) files.
@ -597,7 +623,8 @@ def get_pymodule_path(modulename, *joins):
:param modulename: Python module name.
:param *joins: Join additional path elements using `os.path.join`."""
joins = [scrub(part) for part in joins]
if not "public" in joins:
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):
@ -623,6 +650,9 @@ def get_installed_apps(sort=False):
if getattr(flags, "in_install_db", True):
return []
if not db:
connect()
installed = json.loads(db.get_global("installed_apps") or "[]")
if sort:
@ -719,6 +749,9 @@ def get_file_json(path):
def read_file(path, raise_not_found=False):
"""Open a file and return its content as Unicode."""
from frappe.utils import cstr
if isinstance(path, unicode):
path = path.encode("utf-8")
if os.path.exists(path):
with open(path, "r") as f:
return cstr(f.read())
@ -729,6 +762,10 @@ def read_file(path, raise_not_found=False):
def get_attr(method_string):
"""Get python method object from its name."""
app_name = method_string.split(".")[0]
if not local.flags.in_install and app_name not in get_installed_apps():
throw(_("App {0} is not installed").format(app_name), AppNotInstalledError)
modulename = '.'.join(method_string.split('.')[:-1])
methodname = method_string.split('.')[-1]
return getattr(get_module(modulename), methodname)
@ -898,6 +935,20 @@ def get_all(doctype, *args, **kwargs):
kwargs["limit_page_length"] = 0
return get_list(doctype, *args, **kwargs)
def get_value(*args, **kwargs):
"""Returns a document property or list of properties.
Alias for `frappe.db.get_value`
:param doctype: DocType name.
:param filters: Filters like `{"x":"y"}` or name of the document. `None` if Single DocType.
:param fieldname: Column name.
:param ignore: Don't raise exception if table, column is missing.
:param as_dict: Return values as dict.
:param debug: Print query in error log.
"""
return db.get_value(*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."""
@ -912,6 +963,9 @@ def as_json(obj, indent=1):
from frappe.utils.response import json_handler
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler)
def are_emails_muted():
return flags.mute_emails or conf.get("mute_emails") or False
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
@ -989,3 +1043,43 @@ def get_logger(module=None):
logging_setup_complete = True
return logging.getLogger(module or "frappe")
def publish_realtime(*args, **kwargs):
"""Publish real-time updates
:param event: Event name, like `task_progress` etc.
:param message: JSON message object. For async must contain `task_id`
:param room: Room in which to publish update (default entire site)
:param user: Transmit to user
:param doctype: Transmit to doctype, docname
:param docname: Transmit to doctype, docname"""
import frappe.async
return frappe.async.publish_realtime(*args, **kwargs)
def local_cache(namespace, key, generator, regenerate_if_none=False):
"""A key value store for caching within a request
:param namespace: frappe.local.cache[namespace]
:param key: frappe.local.cache[namespace][key] used to retrieve value
:param generator: method to generate a value if not found in store
"""
if namespace not in local.cache:
local.cache[namespace] = {}
if key not in local.cache[namespace]:
local.cache[namespace][key] = generator()
elif local.cache[namespace][key]==None and regenerate_if_none:
# if key exists but the previous result was None
local.cache[namespace][key] = generator()
return local.cache[namespace][key]
def get_doctype_app(doctype):
def _get_doctype_app():
doctype_module = local.db.get_value("DocType", doctype, "module")
return local.module_app[scrub(doctype_module)]
return local_cache("doctype_app", doctype, generator=_get_doctype_app)

View file

@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = "5.0.29"
__version__ = "6.16.4"

View file

@ -58,13 +58,13 @@ def handle():
if frappe.local.request.method=="GET":
if not doc.has_permission("read"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
doc.run_method(method, **frappe.local.form_dict)
frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
if frappe.local.request.method=="POST":
if not doc.has_permission("write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
doc.run_method(method, **frappe.local.form_dict)
frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
frappe.db.commit()
else:

View file

@ -1,28 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import sys, os
import json
import logging
import os
import MySQLdb
from werkzeug.wrappers import Request, Response
from werkzeug.wrappers import Request
from werkzeug.local import LocalManager
from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.contrib.profiler import ProfilerMiddleware
from werkzeug.wsgi import SharedDataMiddleware
from werkzeug.serving import run_with_reloader
import mimetypes
import frappe
import frappe.handler
import frappe.auth
import frappe.api
import frappe.async
import frappe.utils.response
import frappe.website.render
from frappe.utils import get_site_name
from frappe.utils import get_site_name, get_site_path
from frappe.middlewares import StaticDataMiddleware
from frappe.utils.error import make_error_snapshot
local_manager = LocalManager([frappe.local])
_site = None
@ -30,6 +33,21 @@ _sites_path = os.environ.get("SITES_PATH", ".")
logger = frappe.get_logger()
class RequestContext(object):
def __init__(self, environ):
self.request = Request(environ)
def __enter__(self):
frappe.local.request = self.request
init_site(self.request)
make_form_dict(self.request)
frappe.local.http_request = frappe.auth.HTTPRequest()
def __exit__(self, type, value, traceback):
frappe.destroy()
@Request.application
def application(request):
frappe.local.request = request
@ -56,6 +74,9 @@ def application(request):
elif frappe.request.path.startswith('/backups'):
response = frappe.utils.response.download_backup(request.path)
elif frappe.request.path.startswith('/private/files/'):
response = frappe.utils.response.download_private_file(request.path)
elif frappe.local.request.method in ('GET', 'HEAD'):
response = frappe.website.render.render(request.path)
@ -82,8 +103,12 @@ def application(request):
if frappe.local.is_ajax or 'application/json' in request.headers.get('Accept', ''):
response = frappe.utils.response.report_error(http_status_code)
else:
traceback = "<pre>"+frappe.get_traceback()+"</pre>"
if frappe.local.flags.disable_traceback:
traceback = ""
frappe.respond_as_web_page("Server Error",
"<pre>"+frappe.get_traceback()+"</pre>",
traceback,
http_status_code=http_status_code)
response = frappe.website.render.render("message", http_status_code=http_status_code)
@ -94,10 +119,13 @@ def application(request):
if http_status_code==500:
logger.error('Request Error')
make_error_snapshot(e)
else:
if frappe.local.request.method in ("POST", "PUT") and frappe.db:
frappe.db.commit()
rollback = False
if (frappe.local.request.method in ("POST", "PUT") or frappe.local.flags.commit) and frappe.db:
if frappe.db.transaction_writes:
frappe.db.commit()
rollback = False
# update session
if getattr(frappe.local, "session_obj", None):
@ -105,6 +133,10 @@ def application(request):
if updated_in_db:
frappe.db.commit()
# publish realtime
for args in frappe.local.realtime_log:
frappe.async.emit_via_redis(*args)
finally:
if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback:
frappe.db.rollback()
@ -134,6 +166,8 @@ def make_form_dict(request):
frappe.local.form_dict.pop("_")
application = local_manager.make_middleware(application)
application.debug = True
def serve(port=8000, profile=False, site=None, sites_path='.'):
global application, _site, _sites_path
@ -154,5 +188,10 @@ def serve(port=8000, profile=False, site=None, sites_path='.'):
b'/files': os.path.abspath(sites_path).encode("utf-8")
})
application.debug = True
application.config = {
'SERVER_NAME': 'localhost:8000'
}
run_simple('0.0.0.0', int(port), application, use_reloader=True,
use_debugger=True, use_evalex=True, threaded=True)

228
frappe/async.py Normal file
View file

@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import os
import time
import redis
from functools import wraps
from frappe.utils import get_site_path
from frappe import conf
END_LINE = '<!-- frappe: end-file -->'
TASK_LOG_MAX_AGE = 86400 # 1 day in seconds
redis_server = None
def handler(f):
cmd = f.__module__ + '.' + f.__name__
def run(args, set_in_response=True, hijack_std=False):
from frappe.tasks import run_async_task
from frappe.handler import execute_cmd
if frappe.conf.disable_async:
return execute_cmd(cmd, from_async=True)
args = frappe._dict(args)
task = run_async_task.delay(site=frappe.local.site,
user=(frappe.session and frappe.session.user) or 'Administrator', cmd=cmd,
form_dict=args, hijack_std=hijack_std)
if set_in_response:
frappe.local.response['task_id'] = task.id
return task.id
@wraps(f)
def queue(*args, **kwargs):
task_id = run(frappe.local.form_dict, set_in_response=True)
return {
"status": "queued",
"task_id": task_id
}
queue.async = True
queue.queue = f
queue.run = run
frappe.whitelisted.append(f)
frappe.whitelisted.append(queue)
return queue
@frappe.whitelist()
def get_pending_tasks_for_doc(doctype, docname):
return frappe.db.sql_list("select name from `tabAsync Task` where status in ('Queued', 'Running') and reference_doctype=%s and reference_name=%s", (doctype, docname))
@handler
def ping():
from time import sleep
sleep(1)
return "pong"
@frappe.whitelist()
def get_task_status(task_id):
from frappe.celery_app import get_celery
c = get_celery()
a = c.AsyncResult(task_id)
frappe.local.response['response'] = a.result
return {
"state": a.state,
"progress": 0
}
def set_task_status(task_id, status, response=None):
if not response:
response = {}
response.update({
"status": status,
"task_id": task_id
})
emit_via_redis("task_status_change", response, room="task:" + task_id)
def remove_old_task_logs():
logs_path = get_site_path('task-logs')
def full_path(_file):
return os.path.join(logs_path, _file)
files_to_remove = [full_path(_file) for _file in os.listdir(logs_path)]
files_to_remove = [_file for _file in files_to_remove if is_file_old(_file) and os.path.isfile(_file)]
for _file in files_to_remove:
os.remove(_file)
def is_file_old(file_path):
return ((time.time() - os.stat(file_path).st_mtime) > TASK_LOG_MAX_AGE)
def publish_realtime(event=None, message=None, room=None, user=None, doctype=None, docname=None, now=False):
"""Publish real-time updates
:param event: Event name, like `task_progress` etc. that will be handled by the client (default is `task_progress` if within task or `global`)
:param message: JSON message object. For async must contain `task_id`
:param room: Room in which to publish update (default entire site)
:param user: Transmit to user
:param doctype: Transmit to doctype, docname
:param docname: Transmit to doctype, docname"""
if message is None:
message = {}
if event is None:
if frappe.local.task_id:
event = "task_progress"
else:
event = "global"
if not room:
if frappe.local.task_id:
room = get_task_progress_room()
if not "task_id" in message:
message["task_id"] = frappe.local.task_id
now = True
elif user:
room = get_user_room(user)
elif doctype and docname:
room = get_doc_room(doctype, docname)
else:
room = get_site_room()
if now:
emit_via_redis(event, message, room)
else:
frappe.local.realtime_log.append([event, message, room])
def emit_via_redis(event, message, room):
"""Publish real-time updates via redis
:param event: Event name, like `task_progress` etc.
:param message: JSON message object. For async must contain `task_id`
:param room: name of the room"""
r = get_redis_server()
try:
r.publish('events', frappe.as_json({'event': event, 'message': message, 'room': room}))
except redis.exceptions.ConnectionError:
# print frappe.get_traceback()
pass
def put_log(line_no, line, task_id=None):
r = get_redis_server()
if not task_id:
task_id = frappe.local.task_id
task_progress_room = get_task_progress_room()
task_log_key = "task_log:" + task_id
publish_realtime('task_progress', {
"message": {
"lines": {line_no: line}
},
"task_id": task_id
}, room=task_progress_room, now=True)
r.hset(task_log_key, line_no, line)
r.expire(task_log_key, 3600)
def get_redis_server():
"""Returns memcache connection."""
global redis_server
if not redis_server:
from redis import Redis
redis_server = Redis.from_url(conf.get("async_redis_server") or "redis://localhost:12311")
return redis_server
class FileAndRedisStream(file):
def __init__(self, *args, **kwargs):
ret = super(FileAndRedisStream, self).__init__(*args, **kwargs)
self.count = 0
return ret
def write(self, data):
ret = super(FileAndRedisStream, self).write(data)
if frappe.local.task_id:
put_log(self.count, data, task_id=frappe.local.task_id)
self.count += 1
return ret
def get_std_streams(task_id):
stdout = FileAndRedisStream(get_task_log_file_path(task_id, 'stdout'), 'w')
# stderr = FileAndRedisStream(get_task_log_file_path(task_id, 'stderr'), 'w')
return stdout, stdout
def get_task_log_file_path(task_id, stream_type):
logs_dir = frappe.utils.get_site_path('task-logs')
return os.path.join(logs_dir, task_id + '.' + stream_type)
@frappe.whitelist(allow_guest=True)
def can_subscribe_doc(doctype, docname, sid):
from frappe.sessions import Session
from frappe.exceptions import PermissionError
session = Session(None, resume=True).get_session_data()
if not frappe.has_permission(user=session.user, doctype=doctype, doc=docname, ptype='read'):
raise PermissionError()
return True
@frappe.whitelist(allow_guest=True)
def get_user_info(sid):
from frappe.sessions import Session
session = Session(None, resume=True).get_session_data()
return {
'user': session.user,
}
def get_doc_room(doctype, docname):
return ''.join([frappe.local.site, ':doc:', doctype, '/', docname])
def get_user_room(user):
return ''.join([frappe.local.site, ':user:', user])
def get_site_room():
return ''.join([frappe.local.site, ':all'])
def get_task_progress_room():
return "task_progress:" + frappe.local.task_id

View file

@ -23,7 +23,7 @@ class HTTPRequest:
self.domain = self.domain[4:]
if frappe.get_request_header('X-Forwarded-For'):
frappe.local.request_ip = frappe.get_request_header('X-Forwarded-For')
frappe.local.request_ip = (frappe.get_request_header('X-Forwarded-For').split(",")[0]).strip()
elif frappe.get_request_header('REMOTE_ADDR'):
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR')
@ -32,22 +32,19 @@ class HTTPRequest:
frappe.local.request_ip = '127.0.0.1'
# language
self.set_lang(frappe.request.accept_languages.values())
self.set_lang()
# load cookies
frappe.local.cookie_manager = CookieManager()
# override request method. All request to be of type POST, but if _type == "POST" then commit
if frappe.form_dict.get("_type"):
frappe.local.request_method = frappe.form_dict.get("_type")
del frappe.form_dict["_type"]
# set db
self.connect()
# login
frappe.local.login_manager = LoginManager()
self.validate_csrf_token()
# write out latest cookies
frappe.local.cookie_manager.init_cookies()
@ -57,19 +54,25 @@ 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
def validate_csrf_token(self):
if frappe.local.request and frappe.local.request.method=="POST":
if not frappe.local.session.data.csrf_token or frappe.local.session.data.device=="mobile":
# not via boot
return
if frappe.session.user != "Guest":
clear_sessions(frappe.session.user, keep_current=True)
csrf_token = frappe.get_request_header("X-Frappe-CSRF-Token")
if not csrf_token and "csrf_token" in frappe.local.form_dict:
csrf_token = frappe.local.form_dict.csrf_token
del frappe.local.form_dict["csrf_token"]
if frappe.local.session.data.csrf_token != csrf_token:
frappe.local.flags.disable_traceback = True
frappe.throw(_("Invalid Request"), frappe.CSRFTokenError)
def set_lang(self, lang_codes):
def set_lang(self):
from frappe.translate import guess_language
frappe.local.lang = guess_language(lang_codes)
frappe.local.lang = guess_language()
def get_db_name(self):
"""get database name from conf"""
@ -89,8 +92,16 @@ class LoginManager:
if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login":
self.login()
self.resume = False
else:
self.make_session(resume=True)
try:
self.resume = True
self.make_session(resume=True)
self.set_user_info(resume=True)
except AttributeError:
self.user = "Guest"
self.make_session()
self.set_user_info()
def login(self):
# clear cache
@ -99,29 +110,34 @@ 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.user_type = self.info.user_type
self.run_trigger('on_login')
self.validate_ip_address()
self.validate_hour()
self.make_session()
self.set_user_info()
def set_user_info(self):
def set_user_info(self, resume=False):
# set sid again
frappe.local.cookie_manager.init_cookies()
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.user_type = self.info.user_type
if self.info.user_type=="Website User":
frappe.local.cookie_manager.set_cookie("system_user", "no")
frappe.local.response["message"] = "No App"
if not resume:
frappe.local.response["message"] = "No App"
else:
frappe.local.cookie_manager.set_cookie("system_user", "yes")
frappe.local.response['message'] = 'Logged In'
if not resume:
frappe.local.response['message'] = 'Logged In'
if not resume:
frappe.response["full_name"] = self.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", self.info.user_image or "")
@ -134,6 +150,14 @@ class LoginManager:
# reset user if changed to Guest
self.user = frappe.local.session_obj.user
frappe.local.session = frappe.local.session_obj.data
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 authenticate(self, user=None, pwd=None):
if not (user and pwd):

View file

@ -31,7 +31,6 @@ def get_bootinfo():
bootinfo['user_info'] = get_fullnames()
bootinfo['sid'] = frappe.session['sid'];
# home page
bootinfo.modules = {}
for app in frappe.get_installed_apps():
try:
@ -45,7 +44,7 @@ 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.single_types = frappe.db.sql_list("""select name from tabDocType where ifnull(issingle,0)=1""")
bootinfo.single_types = frappe.db.sql_list("""select name from tabDocType where issingle=1""")
add_home_page(bootinfo, doclist)
bootinfo.page_info = get_allowed_pages()
load_translations(bootinfo)
@ -53,6 +52,7 @@ def get_bootinfo():
load_conf_settings(bootinfo)
load_print(bootinfo, doclist)
doclist.extend(get_meta_bundle("Page"))
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1})
# ipinfo
if frappe.session['data'].get('ipinfo'):
@ -69,13 +69,13 @@ def get_bootinfo():
bootinfo['versions'] = {k: v['version'] for k, v in get_versions().items()}
bootinfo.error_report_email = frappe.get_hooks("error_report_email")
bootinfo.default_background_image = get_url("/assets/frappe/images/ui/into-the-dawn.jpg")
bootinfo.calendars = sorted(frappe.get_hooks("calendars"))
return bootinfo
def load_conf_settings(bootinfo):
from frappe import conf
bootinfo.max_file_size = conf.get('max_file_size') or 5242880
bootinfo.max_file_size = conf.get('max_file_size') or 10485760
for key in ['developer_mode']:
if key in conf: bootinfo[key] = conf.get(key)
@ -104,16 +104,23 @@ def get_allowed_pages():
def load_translations(bootinfo):
if frappe.local.lang != 'en':
bootinfo["__messages"] = frappe.get_lang_dict("boot")
messages = frappe.get_lang_dict("boot")
bootinfo["lang"] = frappe.lang
# load translated report names
for name in bootinfo.user.all_reports:
messages[name] = frappe._(name)
bootinfo["__messages"] = messages
def get_fullnames():
"""map of user fullnames"""
ret = frappe.db.sql("""select name,
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 and user_type!="Website User" """, as_dict=1)
user_image as image, gender, email, username
from tabUser where enabled=1 and user_type!="Website User" """, as_dict=1)
d = {}
for r in ret:
@ -132,10 +139,15 @@ def add_home_page(bootinfo, docs):
if frappe.session.user=="Guest":
return
home_page = frappe.db.get_default("desktop:home_page")
if home_page == "setup-wizard":
bootinfo.setup_wizard_requires = frappe.get_hooks("setup_wizard_requires")
try:
page = frappe.desk.desk_page.get(home_page)
except (frappe.DoesNotExistError, frappe.PermissionError):
frappe.message_log.pop()
if frappe.message_log:
frappe.message_log.pop()
page = frappe.desk.desk_page.get('desktop')
bootinfo['home_page'] = page.name
@ -154,4 +166,4 @@ def load_print(bootinfo, doclist):
load_print_css(bootinfo, print_settings)
def load_print_css(bootinfo, print_settings):
bootinfo.print_css = frappe.get_attr("frappe.templates.pages.print.get_print_style")(print_settings.print_style or "Modern")
bootinfo.print_css = frappe.get_attr("frappe.templates.pages.print.get_print_style")(print_settings.print_style or "Modern", for_legacy=True)

View file

@ -127,7 +127,8 @@ def pack(target, sources, no_compress, verbose):
tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO()
jsm.minify(tmpin, tmpout)
minified = tmpout.getvalue()
outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';'
if minified:
outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';'
if verbose:
print "{0}: {1}k".format(f, int(len(minified) / 1024))
@ -173,6 +174,10 @@ def files_dirty():
return False
def compile_less():
from distutils.spawn import find_executable
if not find_executable("lessc"):
return
for path in app_paths:
less_path = os.path.join(path, "public", "less")
if os.path.exists(less_path):
@ -188,4 +193,4 @@ def compile_less():
print "compiling {0}".format(fpath)
css_path = os.path.join(path, "public", "css", fname.rsplit(".", 1)[0] + ".css")
os.system("which lessc && lessc {0} > {1}".format(fpath, css_path))
os.system("lessc {0} > {1}".format(fpath, css_path))

View file

@ -10,48 +10,59 @@ task_logger = get_task_logger(__name__)
from datetime import timedelta
import frappe
import json
import os
import threading
import time
SITES_PATH = os.environ.get('SITES_PATH', '.')
# defaults
DEFAULT_CELERY_BROKER = "redis://localhost"
DEFAULT_CELERY_BACKEND = None
DEFAULT_CELERY_BACKEND = "redis://localhost"
DEFAULT_SCHEDULER_INTERVAL = 300
LONGJOBS_PREFIX = "longjobs@"
ASYNC_TASKS_PREFIX = "async@"
_app = None
def get_celery():
global _app
if not _app:
conf = frappe.get_site_config(sites_path=SITES_PATH)
_app = Celery('frappe',
broker=conf.celery_broker or DEFAULT_CELERY_BROKER,
backend=conf.celery_result_backend or DEFAULT_CELERY_BACKEND)
setup_celery(_app, conf)
_app = get_celery_app()
return _app
def setup_celery(app, conf):
def get_celery_app():
conf = get_site_config()
app = Celery('frappe',
broker=conf.celery_broker or DEFAULT_CELERY_BROKER,
backend=conf.async_redis_server or DEFAULT_CELERY_BACKEND)
app.autodiscover_tasks(frappe.get_all_apps(with_frappe=True, with_internal_apps=False,
sites_path=SITES_PATH))
app.conf.CELERY_TASK_SERIALIZER = 'json'
app.conf.CELERY_ACCEPT_CONTENT = ['json']
app.conf.CELERY_TIMEZONE = 'UTC'
if conf.celery_queue_per_site:
app.conf.CELERY_ROUTES = (SiteRouter(),)
app.conf.CELERY_RESULT_SERIALIZER = 'json'
app.conf.CELERY_TASK_RESULT_EXPIRES = timedelta(0, 3600)
if conf.monitory_celery:
app.conf.CELERY_SEND_EVENTS = True
app.conf.CELERY_SEND_TASK_SENT_EVENT = True
app.conf.CELERY_ROUTES = (SiteRouter(), AsyncTaskRouter())
app.conf.CELERYBEAT_SCHEDULE = get_beat_schedule(conf)
if conf.celery_error_emails:
app.conf.CELERY_SEND_TASK_ERROR_EMAILS = True
for k, v in conf.celery_error_emails.iteritems():
setattr(app.conf, k, v)
return app
def get_site_config():
return frappe.get_site_config(sites_path=SITES_PATH)
class SiteRouter(object):
def route_for_task(self, task, args=None, kwargs=None):
if hasattr(frappe.local, 'site'):
@ -59,12 +70,17 @@ class SiteRouter(object):
return get_queue(frappe.local.site, LONGJOBS_PREFIX)
else:
return get_queue(frappe.local.site)
return None
class AsyncTaskRouter(object):
def route_for_task(self, task, args=None, kwargs=None):
if task == "frappe.tasks.run_async_task" and hasattr(frappe.local, 'site'):
return get_queue(frappe.local.site, ASYNC_TASKS_PREFIX)
def get_queue(site, prefix=None):
return {'queue': "{}{}".format(prefix or "", site)}
def get_beat_schedule(conf):
schedule = {
'scheduler': {
@ -72,17 +88,135 @@ def get_beat_schedule(conf):
'schedule': timedelta(seconds=conf.scheduler_interval or DEFAULT_SCHEDULER_INTERVAL)
},
}
if conf.celery_queue_per_site:
schedule['sync_queues'] = {
'task': 'frappe.tasks.sync_queues',
'schedule': timedelta(seconds=conf.scheduler_interval or DEFAULT_SCHEDULER_INTERVAL)
}
schedule['sync_queues'] = {
'task': 'frappe.tasks.sync_queues',
'schedule': timedelta(seconds=conf.scheduler_interval or DEFAULT_SCHEDULER_INTERVAL)
}
return schedule
def celery_task(*args, **kwargs):
return get_celery().task(*args, **kwargs)
def make_async_task(args):
task = frappe.new_doc("Async Task")
task.update(args)
task.status = "Queued"
task.set_docstatus_user_and_timestamp()
task.db_insert()
task.notify_update()
def run_test():
for i in xrange(30):
test.delay(site=frappe.local.site)
@celery_task()
def test(site=None):
time.sleep(1)
print "task"
class MonitorThread(object):
"""Thread manager for monitoring celery events"""
def __init__(self, celery_app, interval=1):
self.celery_app = celery_app
self.interval = interval
self.state = self.celery_app.events.State()
self.thread = threading.Thread(target=self.run, args=())
self.thread.daemon = True
self.thread.start()
def catchall(self, event):
if event['type'] != 'worker-heartbeat':
self.state.event(event)
if not 'uuid' in event:
return
task = self.state.tasks.get(event['uuid'])
info = task.info()
if 'name' in event and 'enqueue_events_for_site' in event['name']:
return
try:
kwargs = eval(info.get('kwargs'))
if 'site' in kwargs:
frappe.connect(kwargs['site'])
if event['type']=='task-sent':
make_async_task({
'name': event['uuid'],
'task_name': kwargs.get("cmd") or event['name']
})
elif event['type']=='task-received':
try:
task = frappe.get_doc("Async Task", event['uuid'])
task.status = 'Started'
task.set_docstatus_user_and_timestamp()
task.db_update()
task.notify_update()
except frappe.DoesNotExistError:
pass
elif event['type']=='task-succeeded':
try:
task = frappe.get_doc("Async Task", event['uuid'])
task.status = 'Succeeded'
task.result = info.get('result')
task.runtime = info.get('runtime')
task.set_docstatus_user_and_timestamp()
task.db_update()
task.notify_update()
except frappe.DoesNotExistError:
pass
elif event['type']=='task-failed':
try:
task = frappe.get_doc("Async Task", event['uuid'])
task.status = 'Failed'
task.traceback = event.get('traceback') or event.get('exception')
task.traceback = frappe.as_json(info) + "\n\n" + task.traceback
task.runtime = info.get('runtime')
task.set_docstatus_user_and_timestamp()
task.db_update()
task.notify_update()
except frappe.DoesNotExistError:
pass
frappe.db.commit()
except Exception:
print frappe.get_traceback()
finally:
frappe.destroy()
def run(self):
while True:
try:
with self.celery_app.connection() as connection:
recv = self.celery_app.events.Receiver(connection, handlers={
'*': self.catchall
})
recv.capture(limit=None, timeout=None, wakeup=True)
except (KeyboardInterrupt, SystemExit):
raise
except Exception:
# unable to capture
print "unable to capture:"
print frappe.get_traceback()
time.sleep(self.interval)
if __name__ == '__main__':
get_celery().start()
app = get_celery()
if get_site_config().get("monitor_celery"):
MonitorThread(app)
app.start()

View file

@ -0,0 +1,3 @@
Leave change log files in this folder for user release notes.
(this file is just a place holder, don't delete it)

View file

@ -0,0 +1,5 @@
- Reports are now searchable from awesome bar
- Show currect label for title in list views
- Datepicker now sets default value as Today
- Map child table as per meta, if not mentioned in table_map via mapper
- Re-enable save button on error

View file

@ -0,0 +1,3 @@
- Change print font from Setup > Print Settings or set it for each Print Format. Font options are "Default", "Arial", "Helvetica", "Verdana", "Monospace".
- Print and full-page print preview in user's language
- Fixed inconsistent visibility of a logged-in user's image in website

View file

@ -0,0 +1 @@
- Ability to **Share with Everyone** (except Guest) using **Share With**

View file

@ -0,0 +1,40 @@
- Added Language Support for Following languages
<table class="table table-bordered">
<tr>
<td style="width: 30%">bo*</td>
<td>ལྷ་སའི་སྐད་</td>
</tr>
<tr>
<td>fi</td>
<td>suomalainen</td>
</tr>
<tr>
<td>km</td>
<td>ភាសាខ្មែរ</td>
</tr>
<tr>
<td>mk</td>
<td>македонски</td>
</tr>
<tr>
<td>my</td>
<td>Melayu</td>
</tr>
<tr>
<td>no</td>
<td>norsk</td>
</tr>
<tr>
<td>sv</td>
<td>Svenska</td>
</tr>
<tr>
<td>sq</td>
<td>shqiptar</td>
</tr>
</table>
* Unable to find translations for Tibetian via Google
- To contribute to translations, please login to [https://translate.erpnext.com](https://translate.erpnext.com)

View file

@ -0,0 +1 @@
- Moved Backup Manager and Social Login Keys to the new **Integrations** module

View file

@ -0,0 +1,3 @@
- **Realtime updates** for new comments and list view
- Get warned if someone else modified the document that you are working on
- You can now quickly assign a document to yourself by clicking on "Assign to me"

View file

@ -0,0 +1 @@
- Set <head> HTML in Website Settings. This is usually used for website verification and SEO.

View file

@ -0,0 +1 @@
- Extract emails using IMAP. Contributed by Gangadhar Kadam ([New Indictrans](http://indictranstech.com/))

View file

@ -0,0 +1,4 @@
- Attachments can now be marked as **Private**
- Private files cannot be accessed unless you are logged in
- To access a private file, you need to have read permission on the file or read permission on the document to which the file is attached
- All attachments in a new incoming email are private

View file

@ -0,0 +1 @@
- Added language support for Malayalam: **ml - മലയാളം**

View file

@ -0,0 +1,4 @@
- **For Developers:** Automatic logging of request errors and its context in **Error Snapshot**
- Thank you [Maxwell Morais](https://discuss.erpnext.com/users/max_morais_dmm/activity) for this useful feature
- You can access it from *Developer > Logs > Error Snapshot*
- Added language support for [Gujarati](https://translate.erpnext.com/view?lang=gu): **gu - ગુજરાતી**

View file

@ -0,0 +1 @@
- Mention users in comments using `@username`. Mentioned users will receive an email with the comment.

View file

@ -0,0 +1,2 @@
- Developer Tutorial [Videos](http://frappe.github.io/frappe/user/videos/)
- Increased uploaded file size limit upto 10MB

View file

@ -0,0 +1,7 @@
- Sections can now be set as **Collapsible**.
- Collapsible sections can be shown as collapsed based on certain rules defined in the **Collapsible Depends On** property of the document field (DocField).
- Title is now editable from the form if the `fieldname` of the title field is **title**.
- Document can now be renamed by clicking the page heading.
- Fields can be set as **Bold** so that they can be easily identified in long forms.
- Fixed mobile views
- See Data Import progress in realtime

View file

@ -0,0 +1,3 @@
- **Permissions:**
- If User Permissions are missing for a DocType, don't show non-matching records.
- If **Ignore User Permissions If Missing** is checked in System Settings, show records even if User Permissions are not defined.

View file

@ -0,0 +1,2 @@
- You can now add **CC** in Email
- Show checkboxes in Print

View file

@ -0,0 +1 @@
- **File Manager:** A Document Management System for your organisation. Add files, organize them in folders and share it with a few users or everyone in the company.

View file

@ -0,0 +1,20 @@
- Added Language Support for Following languages
<table class="table table-bordered">
<tr>
<td style="width: 30%">bn</td>
<td>বাংলা </td>
</tr>
<tr>
<td>da-DK</td>
<td>dansk (Danmark)</td>
</tr>
<tr>
<td>es-PE</td>
<td>Español (Perú)</td>
</tr>
<tr>
<td>si</td>
<td>slovenščina</td>
</tr>
</table>

View file

@ -0,0 +1,2 @@
- **Linked With** will now show links from Dynamic Links
- **Data** field-type size truncated to 140 characters from 255 (by default). Can be changed by setting the **length** property from **Customize Form View**

View file

@ -0,0 +1 @@
- Added language support for Ukranian: **uk - українська**

View file

@ -0,0 +1,3 @@
- See who is currently viewing the document
- Sounds for various actions
- Added language support for Slovene: **sl - slovenščina (Slovene)**

View file

@ -0,0 +1,2 @@
- Pre-configured Email Account for Yandex.Mail
- Fixed inline images in received emails

View file

@ -156,9 +156,12 @@ def has_permission(doctype, docname, perm_type="read"):
@frappe.whitelist()
def get_js(src):
if src[0]=="/":
src = src[1:]
contentpath = os.path.join(frappe.local.sites_path, src)
src = src.strip("/").split("/")
if ".." in src:
frappe.throw(_("Invalid file path: {0}").format("/".join(src)))
contentpath = os.path.join(frappe.local.sites_path, *src)
with open(contentpath, "r") as srcfile:
code = frappe.utils.cstr(srcfile.read())

View file

@ -4,7 +4,6 @@
from __future__ import unicode_literals, absolute_import
import sys
import os
import subprocess
import json
import click
import hashlib
@ -17,6 +16,8 @@ from frappe.utils import cint
from distutils.spawn import find_executable
from functools import wraps
click.disable_unicode_literals_warning = True
def pass_context(f):
@wraps(f)
def _func(ctx, *args, **kwargs):
@ -104,7 +105,7 @@ def _is_scheduler_enabled():
enable_scheduler = False
try:
frappe.connect()
enable_scheduler = cint(frappe.db.get_default("enable_scheduler"))
enable_scheduler = cint(frappe.db.get_single_value("System Settings", "enable_scheduler")) and True or False
except:
pass
finally:
@ -125,9 +126,8 @@ def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_pas
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)
db_name = db_name or frappe.conf.db_name or hashlib.sha1(site).hexdigest()[:10]
_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
@ -140,7 +140,7 @@ def reinstall(context):
frappe.clear_cache()
installed = frappe.get_installed_apps()
frappe.clear_cache()
except Exception, e:
except Exception:
installed = []
finally:
if frappe.db:
@ -164,6 +164,16 @@ def install_app(context, app):
finally:
frappe.destroy()
@click.command('list-apps')
@pass_context
def list_apps(context):
"Reinstall site ie. wipe all data and start over"
site = get_single_site(context)
frappe.init(site=site)
frappe.connect()
print "\n".join(frappe.get_installed_apps())
frappe.destroy()
@click.command('add-system-manager')
@click.argument('email')
@click.option('--first-name')
@ -191,8 +201,6 @@ def migrate(context, rebuild_website=False):
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)
@ -312,15 +320,16 @@ def destroy_all_sessions(context):
frappe.destroy()
@click.command('sync-www')
@click.option('--force', help='Rebuild all pages', is_flag=True, default=False)
@pass_context
def sync_www(context):
def sync_www(context, force=False):
"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)
statics.sync_statics(rebuild=force)
frappe.db.commit()
finally:
frappe.destroy()
@ -340,35 +349,102 @@ def build_website(context):
finally:
frappe.destroy()
@click.command('setup-docs')
@click.argument('app')
@click.argument('docs-app')
@click.argument('path')
@click.command('make-docs')
@pass_context
def setup_docs(context,app, docs_app, path):
@click.argument('app')
@click.argument('docs_version')
def make_docs(context, app, docs_version):
"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)
make = setup_docs(app)
make.build(docs_version)
finally:
frappe.destroy()
@click.command('sync-docs')
@pass_context
@click.argument('app')
def sync_docs(context, app):
"Sync docs from /docs folder into the database (Web Page)"
from frappe.utils.setup_docs import setup_docs
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
make = setup_docs(app)
make.sync_docs()
finally:
frappe.destroy()
@click.command('write-docs')
@pass_context
@click.argument('app')
@click.argument('target')
@click.option('--local', default=False, is_flag=True, help='Run app locally')
def write_docs(context, app, target, local=False):
"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()
make = setup_docs(app)
make.make_docs(target, local)
finally:
frappe.destroy()
@click.command('build-docs')
@click.argument('app')
@pass_context
def build_docs(context, app):
"Build docs from /src to /www folder in app"
from frappe.utils.autodoc import build
frappe.destroy()
@click.argument('app')
@click.option('--docs-version', default='current')
@click.option('--target', default=None)
@click.option('--local', default=False, is_flag=True, help='Run app locally')
@click.option('--watch', default=False, is_flag=True, help='Watch for changes and rewrite')
def build_docs(context, app, docs_version="current", target=None, local=False, watch=False):
"Setup docs in target folder of target app"
from frappe.utils import watch as start_watch
if not target:
target = os.path.abspath(os.path.join("..", "docs", app))
for site in context.sites:
try:
frappe.init(site=site)
build(app)
finally:
frappe.destroy()
_build_docs_once(site, app, docs_version, target, local)
if watch:
def trigger_make(source_path, event_type):
if "/templates/autodoc/" in source_path:
_build_docs_once(site, app, docs_version, target, local)
elif ("/docs.css" in source_path
or "/docs/" in source_path
or "docs.py" in source_path):
_build_docs_once(site, app, docs_version, target, local, only_content_updated=True)
apps_path = frappe.get_app_path("frappe", "..", "..")
start_watch(apps_path, handler=trigger_make)
def _build_docs_once(site, app, docs_version, target, local, only_content_updated=False):
from frappe.utils.setup_docs import setup_docs
try:
frappe.init(site=site)
frappe.connect()
make = setup_docs(app)
if not only_content_updated:
make.build(docs_version)
make.sync_docs()
make.make_docs(target, local)
finally:
frappe.destroy()
@click.command('reset-perms')
@pass_context
@ -380,7 +456,7 @@ def reset_perms(context):
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"""):
where istable=0 and custom=0"""):
frappe.clear_cache(doctype=d)
reset_perms(d)
finally:
@ -388,22 +464,34 @@ def reset_perms(context):
@click.command('execute')
@click.argument('method')
@click.option('--args')
@click.option('--kwargs')
@pass_context
def execute(context, method):
def execute(context, method, args=None, kwargs=None):
"execute a function"
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
print frappe.local.site
ret = frappe.get_attr(method)()
if args:
args = eval(args)
else:
args = ()
if kwargs:
kwargs = eval(args)
else:
kwargs = {}
ret = frappe.get_attr(method)(*args, **kwargs)
if frappe.db:
frappe.db.commit()
finally:
frappe.destroy()
if ret:
print ret
print json.dumps(ret)
@click.command('celery')
@click.argument('args')
@ -529,6 +617,55 @@ def import_doc(context, path, force=False):
finally:
frappe.destroy()
@click.command('import-csv')
@click.argument('path')
@click.option('--only-insert', default=False, is_flag=True, help='Do not overwrite existing records')
@click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it')
@click.option('--ignore-encoding-errors', default=False, is_flag=True, help='Ignore encoding errors while coverting to unicode')
@pass_context
def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False):
"Import CSV using data import tool"
from frappe.core.page.data_import_tool import importer
from frappe.utils.csvutils import read_csv_content
site = get_single_site(context)
with open(path, 'r') as csvfile:
content = read_csv_content(csvfile.read())
frappe.init(site=site)
frappe.connect()
try:
importer.upload(content, submit_after_import=submit_after_import,
ignore_encoding_errors=ignore_encoding_errors, overwrite=not only_insert,
via_console=True)
frappe.db.commit()
except Exception:
print frappe.get_traceback()
frappe.destroy()
@click.command('bulk-rename')
@click.argument('doctype')
@click.argument('path')
@pass_context
def _bulk_rename(context, doctype, path):
"Rename multiple records via CSV file"
from frappe.model.rename_doc import bulk_rename
from frappe.utils.csvutils import read_csv_content
site = get_single_site(context)
with open(path, 'r') as csvfile:
rows = read_csv_content(csvfile.read())
frappe.init(site=site)
frappe.connect()
bulk_rename(doctype, rows, via_console = True)
frappe.destroy()
# translation
@click.command('build-message-files')
@pass_context
@ -613,6 +750,7 @@ def console(context):
site = get_single_site(context)
frappe.init(site=site)
frappe.connect()
frappe.local.lang = frappe.db.get_default("lang")
import IPython
IPython.embed()
@ -632,7 +770,7 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None
site = get_single_site(context)
frappe.init(site=site)
if frappe.conf.run_selenium_tests:
if frappe.conf.run_selenium_tests and False:
sel.start(context.verbose, driver)
try:
@ -686,11 +824,19 @@ def request(context, args):
@click.command('doctor')
def doctor():
"Get untranslated strings for lang."
"Get diagnostic info about background workers"
from frappe.utils.doctor import doctor as _doctor
frappe.init('')
return _doctor()
@click.command('celery-doctor')
@click.option('--site', help='site name')
def celery_doctor(site=None):
"Get diagnostic info about background workers"
from frappe.utils.doctor import celery_doctor as _celery_doctor
frappe.init('')
return _celery_doctor(site=site)
@click.command('purge-all-tasks')
def purge_all_tasks():
"Purge any pending periodic tasks of 'all' event. Doesn't purge hourly, daily and weekly"
@ -725,21 +871,23 @@ def use(site, sites_path='.'):
@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):
def backup(context, with_files=False, backup_path_db=None, backup_path_files=None,
backup_path_private_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)
odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_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()
print "private files backup taken -", odb.backup_path_private_files, "- on", now()
frappe.destroy()
@click.command('remove-from-installed-apps')
@click.argument('app')
@ -754,6 +902,20 @@ def remove_from_installed_apps(context, app):
finally:
frappe.destroy()
@click.command('uninstall-app')
@click.argument('app')
@click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False)
@pass_context
def uninstall(context, app, dry_run=False):
from frappe.installer import remove_app
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
remove_app(app, dry_run)
finally:
frappe.destroy()
def move(dest_dir, site):
import os
if not os.path.isdir(dest_dir):
@ -775,6 +937,18 @@ def move(dest_dir, site):
frappe.destroy()
return final_new_path
@click.command('set-config')
@click.argument('key')
@click.argument('value')
@pass_context
def set_config(context, key, value):
from frappe.installer import update_site_config
for site in context.sites:
frappe.init(site=site)
update_site_config(key, value)
frappe.destroy()
@click.command('drop-site')
@click.argument('site')
@click.option('--root-login', default='root')
@ -799,6 +973,15 @@ def drop_site(site, root_login='root', root_password=None):
os.mkdir(archived_sites_dir)
move(archived_sites_dir, site)
@click.command('version')
@pass_context
def get_version(context):
frappe.init(site=context.sites[0])
for m in sorted(frappe.local.app_modules.keys()):
module = frappe.get_module(m)
if hasattr(module, "__version__"):
print "{0} {1}".format(m, module.__version__)
# commands = [
# new_site,
# restore,
@ -813,6 +996,7 @@ commands = [
restore,
reinstall,
install_app,
list_apps,
add_system_manager,
migrate,
run_patch,
@ -824,7 +1008,9 @@ commands = [
destroy_all_sessions,
sync_www,
build_website,
setup_docs,
make_docs,
sync_docs,
write_docs,
build_docs,
reset_perms,
execute,
@ -837,6 +1023,8 @@ commands = [
export_csv,
export_fixtures,
import_doc,
import_csv,
_bulk_rename,
build_message_files,
get_untranslated,
update_translations,
@ -846,6 +1034,7 @@ commands = [
serve,
request,
doctor,
celery_doctor,
purge_all_tasks,
dump_queue_status,
console,
@ -853,5 +1042,8 @@ commands = [
_use,
backup,
remove_from_installed_apps,
uninstall,
drop_site,
set_config,
get_version,
]

61
frappe/config/core.py Normal file
View file

@ -0,0 +1,61 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return [
{
"label": _("Documents"),
"items": [
{
"type": "doctype",
"name": "DocType",
"description": _("Models (building blocks) of the Application"),
},
{
"type": "doctype",
"name": "Module Def",
"description": _("Groups of DocTypes"),
},
{
"type": "doctype",
"name": "Page",
"description": _("Pages in Desk (place holders)"),
},
{
"type": "doctype",
"name": "Report",
"description": _("Script or Query reports"),
},
{
"type": "doctype",
"name": "Print Format",
"description": _("Customized Formats for Printing, Email"),
},
{
"type": "doctype",
"name": "Custom Script",
"description": _("Client side script extensions in Javascript"),
}
]
},
{
"label": _("Logs"),
"items": [
{
"type": "doctype",
"name": "Scheduler Log",
"description": _("Errors in Background Events"),
},
{
"type": "doctype",
"name": "Bulk Email",
"description": _("Background Email Queue"),
},
{
"type": "doctype",
"name": "Error Snapshot",
"description": _("A log of request errors"),
},
]
}
]

43
frappe/config/desk.py Normal file
View file

@ -0,0 +1,43 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return [
{
"label": _("Tools"),
"icon": "octicon octicon-briefcase",
"items": [
{
"type": "doctype",
"name": "ToDo",
"label": _("To Do"),
"description": _("Documents assigned to you and by you."),
},
{
"type": "doctype",
"name": "Event",
"label": _("Calendar"),
"view": "Calendar",
"description": _("Event and other calendars."),
},
{
"type": "page",
"label": _("Messages"),
"name": "messages",
"description": _("Chat messages and other notifications."),
"data_doctype": "Comment"
},
{
"type": "doctype",
"name": "Note",
"description": _("Private and public Notes."),
},
{
"type": "page",
"label": _("Activity"),
"name": "activity",
"description": _("Activity log of all users."),
},
]
}
]

View file

@ -3,73 +3,38 @@ 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"
},
"Messages": {
"color": "#9b59b6",
"icon": "icon-comments",
"icon": "octicon octicon-comment-discussion",
"label": _("Messages"),
"link": "messages",
"type": "page"
},
"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",
"File Manager": {
"color": "#AA784D",
"doctype": "File",
"icon": "octicon octicon-file-directory",
"label": _("File Manager"),
"link": "List/File",
"type": "list"
},
"Website": {
"color": "#16a085",
"icon": "icon-globe",
"icon": "octicon octicon-globe",
"type": "module"
},
"Installer": {
"color": "#5ac8fb",
"icon": "icon-download",
"icon": "octicon octicon-cloud-download",
"link": "applications",
"type": "page",
"label": _("Installer")
},
"Setup": {
"color": "#bdc3c7",
"icon": "icon-wrench",
"reverse": 1,
"icon": "octicon octicon-settings",
"type": "module"
},
"Core": {
"label": _("Developer"),
"color": "#589494",
"icon": "icon-cog",
"icon": "octicon octicon-file-binary",
"icon": "octicon octicon-circuit-board",
"type": "module",
"system_manager": 1
},
"Desk": {
"label": _("Tools"),
"color": "#FFF5A7",
"reverse": 1,
"icon": "octicon octicon-calendar",
"type": "module"
}
}

31
frappe/config/docs.py Normal file
View file

@ -0,0 +1,31 @@
source_link = "https://github.com/frappe/frappe"
docs_base_url = "https://frappe.github.io/frappe"
headline = "Superhero Web Framework"
sub_heading = "Build extensions to ERPNext or make your own app"
hide_install = True
long_description = """Frappe is a full stack web application framework written in Python,
Javascript, HTML/CSS with MySQL as the backend. It was built for ERPNext
but is pretty generic and can be used to build database driven apps.
The key differece in Frappe compared to other frameworks is that Frappe
is that meta-data is also treated as data and is used to build front-ends
very easily. Frappe comes with a full blown admin UI called the **Desk**
that handles forms, navigation, lists, menus, permissions, file attachment
and much more out of the box.
Frappe also has a plug-in architecture that can be used to build plugins
to ERPNext.
Frappe Framework was designed to build [ERPNext](https://erpnext.com), open source
ERP for managing small and medium sized businesses.
[Get started with the Tutorial](https://frappe.github.io/frappe/user/tutorial)
"""
docs_version = "6.x.x"
def get_context(context):
context.top_bar_items = [
{"label": "Developer Tutorials", "url": context.docs_base_url + "/user", "right": 1},
{"label": "Documentation", "url": context.docs_base_url + "/current", "right": 1}
]

View file

@ -38,6 +38,13 @@ def get_data():
"icon": "icon-shield",
"description": _("Set Permissions per User")
},
{
"type": "page",
"name": "modules_setup",
"label": _("Show / Hide Modules"),
"icon": "icon-upload",
"description": _("Show or hide modules globally.")
},
{
"type": "report",
"is_query_report": True,
@ -67,17 +74,14 @@ def get_data():
"hide_count": True
},
{
"type": "page",
"name": "modules_setup",
"label": _("Show / Hide Modules"),
"icon": "icon-upload",
"description": _("Show or hide modules globally.")
"type": "doctype",
"name": "Scheduler Log",
"description": _("Log of error on automated events (scheduler).")
},
{
"type": "doctype",
"name": "Naming Series",
"description": _("Set numbering series for transactions."),
"hide_count": True
"name": "Error Snapshot",
"description": _("Log of error during requests.")
},
]
},
@ -94,15 +98,24 @@ def get_data():
},
{
"type": "doctype",
"name": "Rename Tool",
"description": _("Rename many items by uploading a .csv file."),
"name": "Naming Series",
"description": _("Set numbering series for transactions."),
"hide_count": True
},
{
"type": "doctype",
"name": "File Data",
"description": _("Manage uploaded files.")
}
"name": "Rename Tool",
"label": _("Bulk Rename"),
"description": _("Rename many items by uploading a .csv file."),
"hide_count": True
},
{
"type": "page",
"name": "backups",
"label": _("Download Backups"),
"description": _("List of backups available for download"),
"icon": "icon-download"
},
]
},
{
@ -169,6 +182,30 @@ def get_data():
},
]
},
{
"label": _("Integrations"),
"icon": "icon-star",
"items": [
{
"type": "page",
"name": "applications",
"label": _("Application Installer"),
"description": _("Install Applications."),
"icon": "icon-download"
},
{
"type": "doctype",
"name": "Social Login Keys",
"description": _("Enter keys to enable login via Facebook, Google, GitHub."),
},
{
"type": "doctype",
"name": "Dropbox Backup",
"description": _("Manage cloud backups on Dropbox"),
"hide_count": True
}
]
},
{
"label": _("Customize"),
"icon": "icon-glass",
@ -197,30 +234,6 @@ def get_data():
]
},
{
"label": _("System"),
"icon": "icon-cog",
"items": [
{
"type": "page",
"name": "applications",
"label": _("Application Installer"),
"description": _("Install Applications."),
"icon": "icon-download"
},
{
"type": "doctype",
"name": "Backup Manager",
"description": _("Manage cloud backups on Dropbox"),
"hide_count": True
},
{
"type": "doctype",
"name": "Scheduler Log",
"description": _("Log of error on automated events (scheduler).")
},
]
}
]
add_setup_section(data, "frappe", "website", _("Website"), "icon-globe")
return data

View file

@ -77,11 +77,6 @@ def get_data():
"type": "doctype",
"name": "Website Theme",
"description": _("List of themes for Website."),
},
{
"type": "doctype",
"name": "Social Login Keys",
"description": _("Enter keys to enable login via Facebook, Google, GitHub."),
}
]
},

View file

@ -0,0 +1,240 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
"creation": "2015-07-03 11:28:03.496346",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "\nQueued\nRunning\nSucceeded\nFailed\n",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "task_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Task Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "runtime",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Runtime",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "result",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Result",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "traceback",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Traceback",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference Doc",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:42.038458",
"modified_by": "Administrator",
"module": "Core",
"name": "Async Task",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "task_name"
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# 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
class AsyncTask(Document):
pass

View file

@ -0,0 +1,10 @@
frappe.listview_settings['Async Task'] = {
add_fields: ["status"],
get_indicator: function(doc) {
if(doc.status==="Succeeded") {
return [__("Succeeded"), "green", "status,=,Succeeded"];
} else if(doc.status==="Failed") {
return [__("Failed"), "red", "status,=,Failed"];
}
}
};

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Async Task')
class TestAsyncTask(unittest.TestCase):
pass

View file

@ -10,6 +10,8 @@
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "module",
"fieldtype": "Data",
"hidden": 0,
@ -17,6 +19,7 @@
"in_filter": 0,
"in_list_view": 0,
"label": "Module",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
@ -26,7 +29,8 @@
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@ -36,7 +40,8 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-03-24 16:03:52.359042",
"max_attachments": 0,
"modified": "2015-11-16 06:29:42.426522",
"modified_by": "Administrator",
"module": "Core",
"name": "Block Module",

View file

@ -1,154 +1,338 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "hash",
"creation": "2012-08-08 10:40:11",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "comment",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Comment",
"length": 0,
"no_copy": 0,
"oldfieldname": "comment",
"oldfieldtype": "Text",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "comment_type",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Comment Type",
"length": 0,
"no_copy": 0,
"options": "Email\nChat\nPhone\nSMS\nCreated\nSubmitted\nCancelled\nAssigned\nAssignment Completed\nComment\nWorkflow\nLabel\nAttachment\nAttachment Removed",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "comment_by",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Comment By",
"length": 0,
"no_copy": 0,
"oldfieldname": "comment_by",
"oldfieldtype": "Data",
"permlevel": 0,
"search_index": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "comment_by_fullname",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Comment By Fullname",
"length": 0,
"no_copy": 0,
"oldfieldname": "comment_by_fullname",
"oldfieldtype": "Data",
"permlevel": 0,
"search_index": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "comment_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Comment Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "comment_date",
"oldfieldtype": "Date",
"permlevel": 0,
"search_index": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "comment_time",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Comment Time",
"length": 0,
"no_copy": 0,
"oldfieldname": "comment_time",
"oldfieldtype": "Data",
"permlevel": 0,
"search_index": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "comment_doctype",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Comment Doctype",
"length": 0,
"no_copy": 0,
"oldfieldname": "comment_doctype",
"oldfieldtype": "Data",
"permlevel": 0,
"search_index": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "comment_docname",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Comment Docname",
"length": 0,
"no_copy": 0,
"oldfieldname": "comment_docname",
"oldfieldtype": "Data",
"permlevel": 0,
"search_index": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "post_topic",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Post Topic",
"length": 0,
"no_copy": 0,
"oldfieldname": "post_topic",
"oldfieldtype": "Data",
"permlevel": 0,
"search_index": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "unsubscribed",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Unsubscribed",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"read_only": 1
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Reference DocType and Reference Name are used to render a comment as a link (href) to a Doc.",
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"read_only": 1
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-comments",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"modified": "2015-06-08 12:31:15.122312",
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:43.314568",
"modified_by": "Administrator",
"module": "Core",
"name": "Comment",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"title_field": "comment"
}

View file

@ -7,11 +7,13 @@ from frappe import _
from frappe.website.render import clear_cache
from frappe.model.document import Document
from frappe.model.db_schema import add_column
from frappe.utils import get_fullname
from frappe.utils import get_fullname, get_link_to_form
from frappe.core.doctype.user.user import extract_mentions
exclude_from_linked_with = True
class Comment(Document):
"""Comments are added to Documents via forms or views like blogs etc."""
__doclink__ = "https://frappe.io/docs/models/core/comment"
no_feed_on_delete = True
def get_feed(self):
@ -33,6 +35,25 @@ class Comment(Document):
"feed_type": comment_type
}
def after_insert(self):
"""Send realtime updates"""
if not self.comment_doctype:
return
if self.comment_doctype == 'Message':
if self.comment_docname == frappe.session.user:
message = self.as_dict()
message['broadcast'] = True
frappe.publish_realtime('new_message', message)
else:
# comment_docname contains the user who is addressed in the messages' page comment
frappe.publish_realtime('new_message', self.as_dict(), user=self.comment_docname)
else:
frappe.publish_realtime('new_comment', self.as_dict(), doctype= self.comment_doctype,
docname = self.comment_docname)
self.notify_mentions()
def validate(self):
"""Raise exception for more than 50 comments."""
if frappe.db.sql("""select count(*) from tabComment where comment_doctype=%s
@ -127,6 +148,33 @@ class Comment(Document):
self.update_comments_in_parent(_comments)
def notify_mentions(self):
if self.comment_doctype and self.comment_docname and self.comment and self.comment_type=="Comment":
mentions = extract_mentions(self.comment)
if not mentions:
return
sender_fullname = get_fullname(frappe.session.user)
parent_doc_label = "{0} {1}".format(_(self.comment_doctype), self.comment_docname)
subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label)
message = frappe.get_template("templates/emails/mentioned_in_comment.html").render({
"sender_fullname": sender_fullname,
"comment": self,
"link": get_link_to_form(self.comment_doctype, self.comment_docname, label=parent_doc_label)
})
recipients = [frappe.db.get_value("User", {"enabled": 1, "username": username, "user_type": "System User"})
for username in mentions]
frappe.sendmail(
recipients=recipients,
sender=frappe.session.user,
subject=subject,
message=message,
bulk=True
)
def on_doctype_update():
"""Add index to `tabComment` `(comment_doctype, comment_name)`"""
if not frappe.db.sql("""show index from `tabComment`
@ -134,4 +182,3 @@ def on_doctype_update():
frappe.db.commit()
frappe.db.sql("""alter table `tabComment`
add index comment_doctype_docname_index(comment_doctype, comment_docname)""")

View file

@ -1,29 +1,31 @@
frappe.ui.form.on("Communication", "setup", 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);
};
frm.convert_to = function(doctype) {
};
});
frappe.ui.form.on("Communication", "refresh", function(frm) {
frm.convert_to_click && frm.set_convert_button();
frm.subject_field = "subject";
if(frm.doc.reference_doctype && frm.doc.reference_name) {
frm.add_custom_button(__(frm.doc.reference_name), function() {
frappe.set_route("Form", frm.doc.reference_doctype, frm.doc.reference_name);
}, frappe.boot.doctype_icons[frm.doc.reference_doctype]);
} else {
// if an unlinked communication, set email field
if (frm.doc.sent_or_received==="Received") {
frm.email_field = "sender";
} else {
frm.email_field = "recipients";
}
}
if(frm.doc.status==="Open") {
frm.add_custom_button("Close", function() {
frm.set_value("status", "Closed");
frm.save();
});
} else if (frm.doc.status !== "Linked") {
frm.add_custom_button("Reopen", function() {
frm.set_value("status", "Open");
frm.save();
});
}
});
frappe.ui.form.on("Communication", "onload", function(frm) {

View file

@ -1,188 +1,605 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:",
"creation": "2013-01-29 10:47:14",
"custom": 0,
"description": "Keep a track of all communications",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "COMM-",
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 1,
"label": "Series",
"options": "COMM-",
"permlevel": 0
},
{
"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",
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Subject",
"label": "Series",
"length": 0,
"no_copy": 0,
"options": "COMM-",
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "",
"fieldname": "communication_medium",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Communication Medium",
"length": 0,
"no_copy": 0,
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "recipients",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Recipients",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:doc.communication_medium===\"Email\"",
"fieldname": "cc",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "CC",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:doc.communication_medium!==\"Email\"",
"fieldname": "phone_no",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Phone No.",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"label": "Reference DocType",
"options": "DocType",
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "Open\nReplied\nClosed\nLinked",
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"label": "Reference Name",
"options": "reference_doctype",
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "sent_or_received",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Sent or Received",
"length": 0,
"no_copy": 0,
"options": "Sent\nReceived",
"permlevel": 0,
"precision": ""
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Integrations can use this field to set email delivery status",
"fieldname": "delivery_status",
"fieldtype": "Select",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Delivery Status",
"length": 0,
"no_copy": 0,
"options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "subject",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Subject",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "content",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Content",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "400"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"fieldname": "additional_info",
"fieldtype": "Section Break",
"label": "Additional Info",
"permlevel": 0
},
{
"fieldname": "recipients",
"fieldtype": "Data",
"label": "Recipients",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "More Information",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "sender",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sender",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "sender_full_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sender Full Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
},
{
"fieldname": "communication_medium",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Communication Medium",
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther",
"permlevel": 0
},
{
"fieldname": "phone_no",
"fieldtype": "Data",
"label": "Phone No.",
"permlevel": 0
},
{
"fieldname": "section_break2",
"fieldtype": "Section Break",
"options": "simple",
"permlevel": 0
},
{
"fieldname": "column_break4",
"fieldtype": "Column Break",
"label": "By",
"permlevel": 0
},
{
"fieldname": "email_account",
"fieldtype": "Link",
"label": "Email Account",
"options": "Email Account",
"permlevel": 0,
"precision": ""
},
{
"default": "__user",
"fieldname": "user",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "User",
"options": "User",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "column_break5",
"fieldtype": "Column Break",
"label": "On",
"permlevel": 0
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "Today",
"fieldname": "communication_date",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Date",
"permlevel": 0
},
{
"fieldname": "_user_tags",
"fieldtype": "Data",
"hidden": 1,
"label": "User Tags",
"no_copy": 1,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_14",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "in_reply_to",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "In Reply To",
"length": 0,
"no_copy": 0,
"options": "Communication",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "email_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Email Account",
"length": 0,
"no_copy": 0,
"options": "Email Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "__user",
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"in_filter": 0,
"in_list_view": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "0",
"fieldname": "unread_notification_sent",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Unread Notification Sent",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"read_only": 1
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "_user_tags",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "User Tags",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-comment",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"modified": "2015-03-23 02:33:55.289739",
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:43.407180",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",
@ -190,62 +607,27 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 1,
"create": 1,
"delete": 1,
"email": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Support Team",
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"create": 1,
"delete": 1,
"email": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 1,
"create": 1,
"delete": 1,
"email": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"share": 1,
"submit": 0,
"write": 1
},
{
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"search_fields": "subject",
"title_field": "subject"
}

View file

@ -5,8 +5,9 @@ from __future__ import unicode_literals, absolute_import
import frappe
import json
from email.utils import formataddr, parseaddr
from frappe.utils import get_url, get_formatted_email, cstr, cint
from frappe.utils import get_url, get_formatted_email, cint, validate_email_add, split_emails
from frappe.utils.file_manager import get_file
from frappe.email.bulk import check_bulk_limit
import frappe.email.smtp
from frappe import _
@ -16,6 +17,7 @@ class Communication(Document):
no_feed_on_delete = True
"""Communication represents an external communication like Email."""
def get_parent_doc(self):
"""Returns document of `reference_doctype`, `reference_doctype`"""
if not hasattr(self, "parent_doc"):
@ -25,6 +27,31 @@ class Communication(Document):
self.parent_doc = None
return self.parent_doc
def validate(self):
if self.get("__islocal"):
if self.reference_doctype and self.reference_name:
self.status = "Linked"
else:
self.status = "Open"
# validate recipients
for email in split_emails(self.recipients):
validate_email_add(email, throw=True)
# validate CC
for email in split_emails(self.cc):
validate_email_add(email, throw=True)
def after_insert(self):
# send new comment to listening clients
comment = self.as_dict()
comment["comment"] = comment["content"]
comment["comment_by"] = comment["sender"]
comment["comment_type"] = comment["communication_medium"]
frappe.publish_realtime('new_comment', comment, doctype = self.reference_doctype,
docname = self.reference_name)
def on_update(self):
"""Update parent status as `Open` or `Replied`."""
self.update_parent()
@ -41,7 +68,9 @@ class Communication(Document):
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)
parent.db_set("status", to_status)
parent.notify_update()
def send(self, print_html=None, print_format=None, attachments=None,
send_me_a_copy=False, recipients=None):
@ -53,31 +82,44 @@ class Communication(Document):
self.send_me_a_copy = send_me_a_copy
self.notify(print_html, print_format, attachments, recipients)
def set_incoming_outgoing_accounts(self):
self.incoming_email_account = self.outgoing_email_account = None
def notify(self, print_html=None, print_format=None, attachments=None,
recipients=None, cc=None, fetched_from_email_account=False):
"""Calls a delayed celery task 'sendmail' that enqueus email in Bulk Email queue
if self.reference_doctype:
self.incoming_email_account = frappe.db.get_value("Email Account",
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id")
:param print_html: Send given value as HTML attachment
:param print_format: Attach print format of parent document
:param attachments: A list of filenames that should be attached when sending this email
:param recipients: Email recipients
:param cc: Send email as CC to
:param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient
self.outgoing_email_account = frappe.db.get_value("Email Account",
{"append_to": self.reference_doctype, "enable_outgoing": 1},
["email_id", "always_use_account_email_id_as_sender"], as_dict=True)
"""
recipients, cc = self.get_recipients_and_cc(recipients, cc,
fetched_from_email_account=fetched_from_email_account)
if not self.incoming_email_account:
self.incoming_email_account = frappe.db.get_value("Email Account", {"default_incoming": 1}, "email_id")
self.emails_not_sent_to = set(self.all_email_addresses) - set(self.sent_email_addresses)
if not self.outgoing_email_account:
self.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1},
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict()
if frappe.flags.in_test:
# for test cases, run synchronously
self._notify(print_html=print_html, print_format=print_format, attachments=attachments,
recipients=recipients, cc=cc)
else:
check_bulk_limit(list(set(self.sent_email_addresses)))
from frappe.tasks import sendmail
sendmail.delay(frappe.local.site, self.name,
print_html=print_html, print_format=print_format, attachments=attachments,
recipients=recipients, cc=cc, lang=frappe.local.lang, session=frappe.local.session)
def _notify(self, print_html=None, print_format=None, attachments=None,
recipients=None, cc=None):
def notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False):
self.prepare_to_notify(print_html, print_format, attachments)
if not recipients:
recipients = self.get_recipients(except_recipient=except_recipient)
frappe.sendmail(
recipients=recipients,
recipients=(recipients or []) + (cc or []),
show_as_cc=(cc or []),
expose_recipients=True,
sender=self.sender,
reply_to=self.incoming_email_account,
subject=self.subject,
@ -90,6 +132,33 @@ class Communication(Document):
bulk=True
)
def get_recipients_and_cc(self, recipients, cc, fetched_from_email_account=False):
self.all_email_addresses = []
self.sent_email_addresses = []
self.previous_email_sender = None
if not recipients:
recipients = self.get_recipients(fetched_from_email_account=fetched_from_email_account)
if not cc:
cc = self.get_cc(recipients, fetched_from_email_account=fetched_from_email_account)
if fetched_from_email_account:
# email was already sent to the original recipient by the sender's email service
original_recipients, recipients = recipients, []
# send email to the sender of the previous email in the thread which this email is a reply to
if self.previous_email_sender:
recipients.append(self.previous_email_sender)
# cc that was received in the email
original_cc = split_emails(self.cc)
# don't cc to people who already received the mail from sender's email service
cc = list(set(cc) - set(original_cc) - set(original_recipients))
return recipients, cc
def prepare_to_notify(self, print_html=None, print_format=None, attachments=None):
"""Prepare to make multipart MIME Email
@ -125,70 +194,134 @@ class Communication(Document):
else:
self.attachments.append(a)
def get_recipients(self, except_recipient=False):
"""Build a list of users to which this email should go to"""
def set_incoming_outgoing_accounts(self):
self.incoming_email_account = self.outgoing_email_account = None
if self.reference_doctype:
self.incoming_email_account = frappe.db.get_value("Email Account",
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id")
self.outgoing_email_account = frappe.db.get_value("Email Account",
{"append_to": self.reference_doctype, "enable_outgoing": 1},
["email_id", "always_use_account_email_id_as_sender"], as_dict=True)
if not self.incoming_email_account:
self.incoming_email_account = frappe.db.get_value("Email Account",
{"default_incoming": 1, "enable_incoming": 1}, "email_id")
if not self.outgoing_email_account:
self.outgoing_email_account = frappe.db.get_value("Email Account",
{"default_outgoing": 1, "enable_outgoing": 1},
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict()
def get_recipients(self, fetched_from_email_account=False):
"""Build a list of email addresses for To"""
# [EDGE CASE] self.recipients can be None when an email is sent as BCC
original_recipients = [s.strip() for s in cstr(self.recipients).split(",")]
recipients = original_recipients[:]
recipients = split_emails(self.recipients)
if fetched_from_email_account and self.in_reply_to:
# add sender of previous reply
self.previous_email_sender = frappe.db.get_value("Communication", self.in_reply_to, "sender")
recipients.append(self.previous_email_sender)
if recipients:
# exclude email accounts
exclude = [d[0] for d in
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
exclude += [d[0] for d in
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True)
if d[0]]
recipients = self.filter_email_list(recipients, exclude)
return recipients
def get_cc(self, recipients=None, fetched_from_email_account=False):
"""Build a list of email addresses for CC"""
# get a copy of CC list
cc = split_emails(self.cc)
if self.reference_doctype and self.reference_name:
recipients += self.get_earlier_participants()
recipients += self.get_commentors()
recipients += self.get_assignees()
recipients += self.get_starrers()
if fetched_from_email_account:
# if it is a fetched email, add follows to CC
cc.append(self.get_owner_email())
cc += self.get_assignees()
cc += self.get_starrers()
# remove unsubscribed recipients
unsubscribed = [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)]
email_accounts = [d[0] for d in frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
sender = parseaddr(self.sender)[1]
if cc:
# exclude email accounts, unfollows, recipients and unsubscribes
exclude = [d[0] for d in
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
exclude += [d[0] for d in
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True)
if d[0]]
exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)]
exclude += [(parseaddr(email)[1] or "").lower() for email in recipients]
if fetched_from_email_account:
# exclude sender when pulling email
exclude += [parseaddr(self.sender)[1]]
if self.reference_doctype and self.reference_name:
exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"],
{"reference_doctype": self.reference_doctype, "reference_name": self.reference_name}, as_list=True)]
cc = self.filter_email_list(cc, exclude, is_cc=True)
if getattr(self, "send_me_a_copy", False) and self.sender not in cc:
self.all_email_addresses.append((parseaddr(self.sender)[1] or "").lower())
cc.append(self.sender)
return cc
def filter_email_list(self, email_list, exclude, is_cc=False):
# temp variables
filtered = []
for e in list(set(recipients)):
if (e=="Administrator") or ((e==self.sender) and (e not in original_recipients)) or \
(e in unsubscribed) or (e in email_accounts):
email_address_list = []
for email in list(set(email_list)):
email_address = (parseaddr(email)[1] or "").lower()
if not email_address:
continue
email_id = parseaddr(e)[1]
if email_id==sender or email_id in unsubscribed or email_id in email_accounts:
# this will be used to eventually find email addresses that aren't sent to
self.all_email_addresses.append(email_address)
if (email in exclude) or (email_address in exclude):
continue
if except_recipient and (e==self.recipients or email_id==self.recipients):
# while pulling email, don't send email to current recipient
continue
if is_cc:
is_user_enabled = frappe.db.get_value("User", email_address, "enabled")
if is_user_enabled==0:
# don't send to disabled users
continue
if e not in filtered and email_id not in filtered:
filtered.append(e)
# make sure of case-insensitive uniqueness of email address
if email_address not in email_address_list:
# append the full email i.e. "Human <human@example.com>"
filtered.append(email)
email_address_list.append(email_address)
if getattr(self, "send_me_a_copy", False):
filtered.append(self.sender)
self.sent_email_addresses.extend(email_address_list)
return filtered
def get_starrers(self):
"""Return list of users who have starred this document."""
if self.reference_doctype and self.reference_name:
return self.get_parent_doc().get_starred_by()
else:
return []
return [( get_formatted_email(user) or user ) for user in self.get_parent_doc().get_starred_by()]
def get_earlier_participants(self):
return frappe.db.sql_list("""
select distinct sender
from tabCommunication where
reference_doctype=%s and reference_name=%s""",
(self.reference_doctype, self.reference_name))
def get_commentors(self):
return 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))
def get_owner_email(self):
owner = self.get_parent_doc().owner
return get_formatted_email(owner) or owner
def get_assignees(self):
return [d.owner for d in frappe.db.get_all("ToDo", filters={"reference_type": self.reference_doctype,
"reference_name": self.reference_name, "status": "Open"}, fields=["owner"])]
return [( get_formatted_email(d.owner) or d.owner ) for d in
frappe.db.get_all("ToDo", filters={
"reference_type": self.reference_doctype,
"reference_name": self.reference_name,
"status": "Open"
}, fields=["owner"])
]
def get_attach_link(self, print_format):
"""Returns public link for the attachment via `templates/emails/print_link.html`."""
@ -208,7 +341,7 @@ def on_doctype_update():
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='[]', ignore_doctype_permissions=False,
send_me_a_copy=False):
send_me_a_copy=False, cc=None):
"""Make a new communication.
:param doctype: Reference DocType.
@ -232,7 +365,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
doctype=doctype, name=name))
if not sender and frappe.session.user != "Administrator":
if not sender:
sender = get_formatted_email(frappe.session.user)
comm = frappe.get_doc({
@ -241,24 +374,23 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
"content": content,
"sender": sender,
"recipients": recipients,
"communication_medium": "Email",
"cc": cc or None,
"communication_medium": communication_medium,
"sent_or_received": sent_or_received,
"reference_doctype": doctype,
"reference_name": name
})
comm.insert(ignore_permissions=True)
recipients = None
# needed for communication.notify which uses celery delay
# if not committed, delayed task doesn't find the communication
frappe.db.commit()
if send_email:
comm.send_me_a_copy = send_me_a_copy
recipients = comm.get_recipients()
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy, recipients=recipients)
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy)
return {
"name": comm.name,
"recipients": ", ".join(recipients) if recipients else None
"emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None
}
@frappe.whitelist()
def get_convert_to():
return frappe.get_hooks("communication_convert_to")

View file

@ -1,49 +1,81 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
"creation": "2013-02-22 01:27:32",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "defkey",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Key",
"length": 0,
"no_copy": 0,
"oldfieldname": "defkey",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_width": "200px",
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "200px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "defvalue",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Value",
"length": 0,
"no_copy": 0,
"oldfieldname": "defvalue",
"oldfieldtype": "Text",
"permlevel": 0,
"print_hide": 0,
"print_width": "200px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "200px"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-19 01:06:59.622792",
"max_attachments": 0,
"modified": "2015-11-16 06:29:44.604889",
"modified_by": "Administrator",
"module": "Core",
"name": "DefaultValue",
"owner": "Administrator",
"permissions": [],
"read_only": 0
"read_only": 0,
"read_only_onload": 0
}

File diff suppressed because it is too large Load diff

View file

@ -6,5 +6,4 @@ from __future__ import unicode_literals
from frappe.model.document import Document
class DocField(Document):
__doclink__ = "https://frappe.io/docs/models/core/docfield"
pass

View file

@ -1,242 +1,628 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
"creation": "2013-02-22 01:27:33",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "role_and_level",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Role and Level",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "role",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Role",
"length": 0,
"no_copy": 0,
"oldfieldname": "role",
"oldfieldtype": "Link",
"options": "Role",
"permlevel": 0,
"print_hide": 0,
"print_width": "150px",
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Filter records based on User Permissions defined for a user",
"fieldname": "apply_user_permissions",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Apply User Permissions",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Apply this rule if the User is the Owner",
"fieldname": "if_owner",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "If user is the owner",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Level",
"length": 0,
"no_copy": 0,
"oldfieldname": "permlevel",
"oldfieldtype": "Int",
"permlevel": 0,
"print_hide": 0,
"print_width": "40px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "40px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "",
"description": "JSON list of DocTypes used to apply User Permissions. If empty, all linked DocTypes will be used to apply User Permissions.",
"fieldname": "user_permission_doctypes",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "User Permission DocTypes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"read_only": 1
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Permissions",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "read",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Read",
"length": 0,
"no_copy": 0,
"oldfieldname": "read",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "32px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "32px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "write",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Write",
"length": 0,
"no_copy": 0,
"oldfieldname": "write",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "32px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "32px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "create",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Create",
"length": 0,
"no_copy": 0,
"oldfieldname": "create",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "32px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "32px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "delete",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Delete",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_8",
"fieldtype": "Column Break",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "submit",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Submit",
"length": 0,
"no_copy": 0,
"oldfieldname": "submit",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "32px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "32px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "cancel",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Cancel",
"length": 0,
"no_copy": 0,
"oldfieldname": "cancel",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "32px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "32px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "amend",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Amend",
"length": 0,
"no_copy": 0,
"oldfieldname": "amend",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "32px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "32px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "additional_permissions",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Additional Permissions",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "report",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Report",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_width": "32px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "32px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "export",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Export",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "import",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Import",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "This role update User Permissions for a user",
"fieldname": "set_user_permissions",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Set User Permissions",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_19",
"fieldtype": "Column Break",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "share",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Share",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "print",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Print",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Email",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-03-18 06:09:58.928014",
"max_attachments": 0,
"modified": "2015-11-16 06:29:45.429411",
"modified_by": "Administrator",
"module": "Core",
"name": "DocPerm",
"owner": "Administrator",
"permissions": [],
"read_only": 0
"read_only": 0,
"read_only_onload": 0
}

View file

@ -7,5 +7,4 @@ import frappe
from frappe.model.document import Document
class DocPerm(Document):
__doclink__ = "https://frappe.io/docs/models/v5.x/core/docperm"
pass

View file

@ -12,6 +12,8 @@
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
@ -19,6 +21,7 @@
"in_filter": 0,
"in_list_view": 1,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
@ -26,12 +29,15 @@
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 1,
"set_only_once": 0
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "share_doctype",
"fieldtype": "Link",
"hidden": 0,
@ -39,6 +45,7 @@
"in_filter": 0,
"in_list_view": 1,
"label": "Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
@ -48,10 +55,13 @@
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "share_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
@ -59,6 +69,7 @@
"in_filter": 0,
"in_list_view": 1,
"label": "Document Name",
"length": 0,
"no_copy": 0,
"options": "share_doctype",
"permlevel": 0,
@ -68,10 +79,13 @@
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "0",
"fieldname": "read",
"fieldtype": "Check",
@ -80,6 +94,7 @@
"in_filter": 0,
"in_list_view": 0,
"label": "Read",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
@ -88,10 +103,13 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "0",
"fieldname": "write",
"fieldtype": "Check",
@ -100,6 +118,7 @@
"in_filter": 0,
"in_list_view": 0,
"label": "Write",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
@ -108,10 +127,13 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "0",
"fieldname": "share",
"fieldtype": "Check",
@ -120,6 +142,7 @@
"in_filter": 0,
"in_list_view": 0,
"label": "Share",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
@ -128,7 +151,31 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "everyone",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Everyone",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@ -138,7 +185,8 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-02-12 11:30:52.968078",
"max_attachments": 0,
"modified": "2015-11-16 06:29:45.569747",
"modified_by": "Administrator",
"module": "Core",
"name": "DocShare",
@ -153,6 +201,7 @@
"delete": 1,
"email": 0,
"export": 1,
"if_owner": 0,
"import": 1,
"permlevel": 0,
"print": 0,

View file

@ -7,10 +7,13 @@ from frappe.model.document import Document
from frappe import _
from frappe.utils import get_fullname
exclude_from_linked_with = True
class DocShare(Document):
no_feed_on_delete = True
def validate(self):
self.validate_user()
self.check_share_permission()
self.cascade_permissions_downwards()
self.get_doc().run_method("validate_share", self)
@ -26,6 +29,12 @@ class DocShare(Document):
self._doc = frappe.get_doc(self.share_doctype, self.share_name)
return self._doc
def validate_user(self):
if self.everyone:
self.user = None
elif not self.user:
frappe.throw(_("User is mandatory for Share"), frappe.MandatoryError)
def check_share_permission(self):
if (not self.flags.ignore_share_permission and
not frappe.has_permission(self.share_doctype, "share", self.get_doc())):

View file

@ -78,3 +78,15 @@ class TestDocShare(unittest.TestCase):
frappe.set_user(self.user)
self.assertFalse(self.event.has_permission("share"))
def test_share_with_everyone(self):
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", self.user))
frappe.share.set_permission("Event", self.event.name, None, "read", everyone=1)
self.assertTrue(self.event.name in frappe.share.get_shared("Event", self.user))
self.assertTrue(self.event.name in frappe.share.get_shared("Event", "test1@example.com"))
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", "Guest"))
frappe.share.set_permission("Event", self.event.name, None, "read", value=0, everyone=1)
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", self.user))
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", "test1@example.com"))
self.assertTrue(self.event.name not in frappe.share.get_shared("Event", "Guest"))

View file

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "Prompt",
"creation": "2013-02-18 13:36:19",
@ -7,371 +8,936 @@
"description": "DocType is a Table / Form in the application.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "sb0",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Section Break",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "module",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Module",
"length": 0,
"no_copy": 0,
"oldfieldname": "module",
"oldfieldtype": "Link",
"options": "Module Def",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Child Tables are shown as a Grid in other DocTypes.",
"fieldname": "istable",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Child Table",
"length": 0,
"no_copy": 0,
"oldfieldname": "istable",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Single Types have only one record no tables associated. Values are stored in tabSingles",
"fieldname": "issingle",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Single",
"length": 0,
"no_copy": 0,
"oldfieldname": "issingle",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "cb01",
"fieldtype": "Column Break",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "document_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Document Type",
"length": 0,
"no_copy": 0,
"oldfieldname": "document_type",
"oldfieldtype": "Select",
"options": "\nMaster\nTransaction\nSystem\nOther",
"permlevel": 0
"options": "\nDocument\nSetup\nSystem\nOther",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "icon",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Icon",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "custom",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Custom?",
"length": 0,
"no_copy": 0,
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"fieldname": "plugin",
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "app",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Plugin",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"read_only": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "fields_section_break",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Fields",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Section Break",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "fields",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Fields",
"length": 0,
"no_copy": 0,
"oldfieldname": "fields",
"oldfieldtype": "Table",
"options": "DocField",
"permlevel": 0,
"reqd": 1,
"search_index": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "sb1",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Naming",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "<a onclick=\"msgprint('<ol>\\\n<li><b>field:[fieldname]</b> - By Field\\\n<li><b>naming_series:</b> - By Naming Series (field called naming_series must be present\\\n<li><b>Prompt</b> - Prompt user for a name\\\n<li><b>[series]</b> - Series by prefix (separated by a dot); for example PRE.#####\\\n</ol>')\">Naming Options</a>",
"fieldname": "autoname",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Auto Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "autoname",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "name_case",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Name Case",
"length": 0,
"no_copy": 0,
"oldfieldname": "name_case",
"oldfieldtype": "Select",
"options": "\nTitle Case\nUPPER CASE",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Text",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_15",
"fieldtype": "Column Break",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Show this field as title",
"fieldname": "title_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Title Field",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "search_fields",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Search Fields",
"length": 0,
"no_copy": 0,
"oldfieldname": "search_fields",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "modified",
"description": "",
"fieldname": "sort_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sort Field",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "DESC",
"fieldname": "sort_order",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sort Order",
"length": 0,
"no_copy": 0,
"options": "ASC\nDESC",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.istable",
"fieldname": "sb2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Permission Rules",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "",
"fieldname": "permissions",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Permissions",
"length": 0,
"no_copy": 0,
"oldfieldname": "permissions",
"oldfieldtype": "Table",
"options": "DocPerm",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.istable",
"fieldname": "sb3",
"fieldtype": "Section Break",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "cb30",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Permissions Settings",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "in_create",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "User Cannot Create",
"length": 0,
"no_copy": 0,
"oldfieldname": "in_create",
"oldfieldtype": "Check",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "read_only",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "User Cannot Search",
"length": 0,
"no_copy": 0,
"oldfieldname": "read_only",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "is_submittable",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Submittable",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Allow Import via Data Import Tool",
"fieldname": "allow_import",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Allow Import",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "allow_rename",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Allow Rename",
"length": 0,
"no_copy": 0,
"oldfieldname": "allow_rename",
"oldfieldtype": "Check",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "in_dialog",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "In Dialog",
"length": 0,
"no_copy": 0,
"oldfieldname": "in_dialog",
"oldfieldtype": "Check",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "read_only_onload",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Show Print First",
"length": 0,
"no_copy": 0,
"oldfieldname": "read_only_onload",
"oldfieldtype": "Check",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "max_attachments",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Max Attachments",
"length": 0,
"no_copy": 0,
"oldfieldname": "max_attachments",
"oldfieldtype": "Int",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "cb31",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Hide Actions",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "hide_heading",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Hide Heading",
"length": 0,
"no_copy": 0,
"oldfieldname": "hide_heading",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "hide_toolbar",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Hide Toolbar",
"length": 0,
"no_copy": 0,
"oldfieldname": "hide_toolbar",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "allow_copy",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Hide Copy",
"length": 0,
"no_copy": 0,
"oldfieldname": "allow_copy",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "default_print_format",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Default Print Format",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-bolt",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-03-03 10:40:45.768116",
"max_attachments": 0,
"modified": "2015-11-16 06:29:45.648699",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"search_fields": "module",
"sort_field": "name",
"sort_order": "ASC"

View file

@ -3,6 +3,7 @@
from __future__ import unicode_literals
import re
import frappe
from frappe import _
@ -12,13 +13,15 @@ from frappe.model.document import Document
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.desk.notifications import delete_notification_count_for
from frappe.modules import make_boilerplate
from frappe.model.db_schema import validate_column_name
class InvalidFieldNameError(frappe.ValidationError): pass
form_grid_templates = {
"fields": "templates/form_grid/fields.html"
}
class DocType(Document):
__doclink__ = "https://frappe.io/docs/models/core/doctype"
def get_feed(self):
return self.name
@ -30,14 +33,13 @@ class DocType(Document):
- 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."))
self.check_developer_mode()
for c in [".", "/", "#", "&", "=", ":", "'", '"']:
if c in self.name:
frappe.throw(_("{0} not allowed in name").format(c))
self.validate_series()
self.scrub_field_names()
self.validate_title_field()
self.validate_document_type()
validate_fields(self)
if self.istable:
@ -48,6 +50,20 @@ class DocType(Document):
self.make_amendable()
def check_developer_mode(self):
"""Throw exception if not developer mode or via patch"""
if frappe.flags.in_patch:
return
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."))
def validate_document_type(self):
if self.document_type=="Transaction":
self.document_type = "Document"
if self.document_type=="Master":
self.document_type = "Setup"
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:
@ -71,12 +87,8 @@ class DocType(Document):
else:
d.fieldname = d.fieldtype.lower().replace(" ","_") + "_" + str(d.idx)
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"))
# fieldnames should be lowercase
d.fieldname = d.fieldname.lower()
def validate_series(self, autoname=None, name=None):
"""Validate if `autoname` property is correctly set."""
@ -88,7 +100,7 @@ class DocType(Document):
if autoname and (not autoname.startswith('field:')) \
and (not autoname.startswith('eval:')) \
and (not autoname in ('Prompt', 'hash')) \
and (not autoname.lower() in ('prompt', 'hash')) \
and (not autoname.startswith('naming_series:')):
prefix = autoname.split('.')[0]
@ -127,6 +139,11 @@ class DocType(Document):
def before_rename(self, old, new, merge=False):
"""Throw exception if merge. DocTypes cannot be merged."""
if not self.custom and frappe.session.user != "Administrator":
frappe.throw(_("DocType can only be renamed by Administrator"))
self.check_developer_mode()
if merge:
frappe.throw(_("DocType can not be merged"))
@ -201,7 +218,7 @@ class DocType(Document):
return max_idx and max_idx[0][0] or 0
def validate_fields_for_doctype(doctype):
validate_fields(frappe.get_meta(doctype))
validate_fields(frappe.get_meta(doctype, cached=False))
# this is separate because it is also called via custom field
def validate_fields(meta):
@ -217,13 +234,13 @@ def validate_fields(meta):
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.
12. `title_field` and title field pattern are valid.
13. `unique` check is only valid for Data, Link and Read Only fieldtypes.
14. `unique` cannot be checked if there exist non-unique values.
:param meta: `frappe.model.meta.Meta` object to check."""
def check_illegal_characters(fieldname):
for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$',
'(', ')', '[', ']', '/']:
if c in fieldname:
frappe.throw(_("{0} not allowed in fieldname {1}").format(c, fieldname))
validate_column_name(fieldname)
def check_unique_fieldname(fieldname):
duplicates = filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))
@ -263,8 +280,8 @@ def validate_fields(meta):
def check_dynamic_link_options(d):
if d.fieldtype=="Dynamic Link":
doctype_pointer = filter(lambda df: df.fieldname==d.options, fields)
if not doctype_pointer or (doctype_pointer[0].fieldtype!="Link") \
or (doctype_pointer[0].options!="DocType"):
if not doctype_pointer or (doctype_pointer[0].fieldtype not in ("Link", "Select")) \
or (doctype_pointer[0].fieldtype=="Link" and doctype_pointer[0].options!="DocType"):
frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'"))
def check_illegal_default(d):
@ -278,8 +295,17 @@ def validate_fields(meta):
frappe.throw(_("Precision should be between 1 and 6"))
def check_unique_and_text(d):
if getattr(d, "unique", False) and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"):
frappe.throw(_("Fieldtype {0} for {1} cannot be unique").format(d.fieldtype, d.label))
if getattr(d, "unique", False):
if d.fieldtype not in ("Data", "Link", "Read Only"):
frappe.throw(_("Fieldtype {0} for {1} cannot be unique").format(d.fieldtype, d.label))
if not d.get("__islocal"):
has_non_unique_values = frappe.db.sql("""select `{fieldname}`, count(*)
from `tab{doctype}` group by `{fieldname}` having count(*) > 1 limit 1""".format(
doctype=d.parent, fieldname=d.fieldname))
if has_non_unique_values and has_non_unique_values[0][0]:
frappe.throw(_("Field '{0}' cannot be set as Unique as it has non-unique values").format(d.label))
if d.search_index and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"):
frappe.throw(_("Fieldtype {0} for {1} cannot be indexed").format(d.fieldtype, d.label))
@ -299,6 +325,7 @@ def validate_fields(meta):
frappe.throw(_("Fold can not be at the end of the form"))
def check_search_fields(meta):
"""Throw exception if `search_fields` don't contain valid fields."""
if not meta.search_fields:
return
@ -308,13 +335,40 @@ def validate_fields(meta):
if fieldname not in fieldname_list:
frappe.throw(_("Search Fields should contain valid fieldnames"))
def check_title_field(meta):
"""Throw exception if `title_field` isn't a valid fieldname."""
if not meta.title_field:
return
fieldname_list = [d.fieldname for d in fields]
if meta.title_field not in fieldname_list:
frappe.throw(_("Title field must be a valid fieldname"), InvalidFieldNameError)
def _validate_title_field_pattern(pattern):
if not pattern:
return
for fieldname in re.findall("{(.*?)}", pattern, re.UNICODE):
if fieldname.startswith("{"):
# edge case when double curlies are used for escape
continue
if fieldname not in fieldname_list:
frappe.throw(_("{{{0}}} is not a valid fieldname pattern. It should be {{field_name}}.").format(fieldname),
InvalidFieldNameError)
df = meta.get("fields", filters={"fieldname": meta.title_field})[0]
if df:
_validate_title_field_pattern(df.options)
_validate_title_field_pattern(df.default)
fields = meta.get("fields")
for d in fields:
if not d.permlevel: d.permlevel = 0
if not d.fieldname:
frappe.throw(_("Fieldname is required in row {0}").format(d.idx))
d.fieldname = d.fieldname.lower()
check_illegal_characters(d.fieldname)
check_unique_fieldname(d.fieldname)
check_illegal_mandatory(d)
@ -327,6 +381,7 @@ def validate_fields(meta):
check_fold(fields)
check_search_fields(meta)
check_title_field(meta)
def validate_permissions_for_doctype(doctype, for_remove=False):
"""Validates if permissions are set correctly."""
@ -362,14 +417,21 @@ def validate_permissions(doctype, for_remove=False):
def check_double(d):
has_similar = False
similar_because_of = ""
for p in permissions:
if (p.role==d.role and p.permlevel==d.permlevel
and p.apply_user_permissions==d.apply_user_permissions and p!=d):
has_similar = True
break
if p.role==d.role and p.permlevel==d.permlevel and p!=d:
if p.apply_user_permissions==d.apply_user_permissions:
has_similar = True
similar_because_of = _("Apply User Permissions")
break
elif p.if_owner==d.if_owner:
similar_because_of = _("If Owner")
has_similar = True
break
if has_similar:
frappe.throw(_("{0}: Only one rule allowed with the same Role, Level and Apply User Permissions").format(get_txt(d)))
frappe.throw(_("{0}: Only one rule allowed with the same Role, Level and {1}")\
.format(get_txt(d), similar_because_of))
def check_level_zero_is_set(d):
if cint(d.permlevel) > 0 and d.role != 'All':
@ -466,4 +528,3 @@ def init_list(doctype):
doc = frappe.get_meta(doctype)
make_boilerplate("controller_list.js", doc)
make_boilerplate("controller_list.html", doc)

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('DocType')
class TestDocType(unittest.TestCase):
pass

View file

@ -0,0 +1,16 @@
frappe.ui.form.on("Error Snapshot", "load", function(frm){
frm.set_read_only(true);
});
frappe.ui.form.on("Error Snapshot", "refresh", function(frm){
frm.set_df_property("view", "options", frappe.render_template("error_snapshot", {"doc": frm.doc}));
if (frm.doc.relapses) {
frm.add_custom_button(__('Show Relapses'), function() {
frappe.route_options = {
parent_error_snapshot: frm.doc.name
};
frappe.set_route("List", "Error Snapshot");
});
}
});

View file

@ -0,0 +1,344 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2015-11-28 00:57:39.766888",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "view",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Snapshot View",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "seen",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 1,
"in_list_view": 0,
"label": "Seen",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "evalue",
"fieldtype": "Small Text",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Friendly Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "timestamp",
"fieldtype": "Datetime",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Timestamp",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"fieldname": "relapses",
"fieldtype": "Int",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Relapses",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "etype",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Exception Type",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "traceback",
"fieldtype": "Small Text",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Traceback",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "parent_error_snapshot",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Parent Error Snapshot",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "pyver",
"fieldtype": "Small Text",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Pyver",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "exception",
"fieldtype": "Small Text",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Exception",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "locals",
"fieldtype": "Text",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Locals",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "frames",
"fieldtype": "Text",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Frames",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"in_create": 1,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-12-28 00:44:30.751680",
"modified_by": "Administrator",
"module": "Core",
"name": "Error Snapshot",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"sort_field": "timestamp",
"sort_order": "DESC",
"title_field": "evalue"
}

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# 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
class ErrorSnapshot(Document):
def onload(self):
if not self.parent_error_snapshot:
self.db_set('seen', True, update_modified=False)
for relapsed in frappe.get_all("Error Snapshot", filters={"parent_error_snapshot": self.name}):
frappe.db.set_value("Error Snapshot", relapsed.name, "seen", True, update_modified=False)
frappe.local.flags.commit = True
def validate(self):
parent = frappe.get_all("Error Snapshot",
filters={"evalue": self.evalue, "parent_error_snapshot": ""},
fields=["name", "relapses", "seen"], limit_page_length=1)
if parent:
parent = parent[0]
self.update({"parent_error_snapshot": parent['name']})
frappe.db.set_value('Error Snapshot', parent['name'], 'relapses', parent["relapses"] + 1)
if parent["seen"]:
frappe.db.set_value("Error Snapshot", parent["name"], "seen", False)

View file

@ -0,0 +1,14 @@
frappe.listview_settings["Error Snapshot"] = {
add_fields: ["parent_error_snapshot", "relapses", "seen"],
filters:[
["parent_error_snapshot","=",null],
["seen", "=", false]
],
get_indicator: function(doc){
if (doc.parent_error_snapshot && doc.parent_error_snapshot.length){
return [__("Relapsed"), !doc.seen ? "orange" : "blue", "parent_error_snapshot,!=,"];
} else {
return [__("First Level"), !doc.seen ? "red" : "green", "parent_error_snapshot,=,"];
}
}
}

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Error Snapshot')
class TestErrorSnapshot(unittest.TestCase):
pass

View file

View file

@ -0,0 +1,25 @@
frappe.ui.form.on("File", "refresh", function(frm) {
if(!frm.doc.is_folder) {
frm.add_custom_button(__('Download'), function() {
var file_url = frm.doc.file_url;
if (frm.doc.file_name) {
file_url = file_url.replace(/#/g, '%23');
}
window.open(file_url);
}, "icon-download");
}
var wrapper = frm.get_field("preview_html").$wrapper;
var is_viewable = frappe.utils.is_image_file(frm.doc.file_url);
frm.toggle_display("preview", is_viewable);
frm.toggle_display("preview_html", is_viewable);
if(is_viewable){
wrapper.html('<div class="img_preview">\
<img class="img-responsive" src="'+frm.doc.file_url+'"></img>\
</div>');
} else {
wrapper.empty();
}
});

View file

@ -0,0 +1,575 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "",
"creation": "2012-12-12 11:19:22",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "file_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "File Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "file_name",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "is_private",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Private",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "preview",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Preview",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "preview_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Preview HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "",
"fieldname": "is_home_folder",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Home Folder",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "is_attachments_folder",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Attachments Folder",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "file_size",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "File Size",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "file_url",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "File URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "thumbnail_url",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Thumbnail URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "folder",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Folder",
"length": 0,
"no_copy": 0,
"options": "File",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "is_folder",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Folder",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "attached_to_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Attached To DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "attached_to_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Attached To Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "content_hash",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Content Hash",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "lft",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "rgt",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "old_parent",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "old_parent",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-file",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2015-12-08 05:03:48.767257",
"modified_by": "Administrator",
"module": "Core",
"name": "File",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"title_field": "file_name"
}

View file

@ -0,0 +1,351 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
"""
record of files
naming for same name files: file.gif, file-1.gif, file-2.gif etc
"""
import frappe, frappe.utils
from frappe.utils.file_manager import delete_file_data_content, get_content_hash, get_random_filename
from frappe import _
from frappe.utils.nestedset import NestedSet
from frappe.utils import strip
import json
import urllib
from PIL import Image, ImageOps
import os
import requests
import requests.exceptions
import StringIO
import mimetypes, imghdr
from frappe.utils import get_files_path
class FolderNotEmpty(frappe.ValidationError): pass
exclude_from_linked_with = True
class File(NestedSet):
nsm_parent_field = 'folder'
no_feed_on_delete = True
def before_insert(self):
frappe.local.rollback_observers.append(self)
self.set_folder_name()
self.set_name()
def get_name_based_on_parent_folder(self):
path = get_breadcrumbs(self.folder)
folder_name = frappe.get_value("File", self.folder, "file_name")
return "/".join([d.file_name for d in path] + [folder_name, self.file_name])
def set_name(self):
"""Set name for folder"""
if self.is_folder:
if self.folder:
self.name = self.get_name_based_on_parent_folder()
else:
# home
self.name = self.file_name
else:
self.name = frappe.generate_hash("", 10)
def after_insert(self):
self.update_parent_folder_size()
def after_rename(self, olddn, newdn, merge=False):
for successor in self.get_successor():
setup_folder_path(successor, self.name)
def get_successor(self):
return frappe.db.sql_list("select name from tabFile where folder='%s'"%self.name) or []
def validate(self):
if self.is_new():
self.validate_duplicate_entry()
self.validate_folder()
if not self.flags.ignore_file_validate:
self.validate_file()
self.generate_content_hash()
self.set_folder_size()
def set_folder_size(self):
"""Set folder size if folder"""
if self.is_folder and not self.is_new():
self.file_size = self.get_folder_size()
frappe.db.set_value("File", self.name, "file_size", self.file_size)
for folder in self.get_ancestors():
frappe.db.set_value("File", folder, "file_size", self.get_folder_size(folder))
def get_folder_size(self, folder=None):
"""Returns folder size for current folder"""
if not folder:
folder = self.name
file_size = frappe.db.sql("""select sum(ifnull(file_size,0))
from tabFile where folder=%s """, (folder))[0][0]
return file_size
def update_parent_folder_size(self):
"""Update size of parent folder"""
if self.folder and not self.is_folder: # it not home
frappe.get_doc("File", self.folder).save(ignore_permissions=True)
def set_folder_name(self):
"""Make parent folders if not exists based on reference doctype and name"""
if self.attached_to_doctype and not self.folder:
self.folder = frappe.db.get_value("File", {"is_attachments_folder": 1})
def validate_folder(self):
if not self.is_home_folder and not self.folder and \
not self.flags.ignore_folder_validate:
frappe.throw(_("Folder is mandatory"))
def validate_file(self):
"""Validates existence of public file
TODO: validate for private file
"""
if (self.file_url or "").startswith("/files/"):
if not self.file_name:
self.file_name = self.file_url.split("/files/")[-1]
if not os.path.exists(get_files_path(self.file_name.lstrip("/"))):
frappe.throw(_("File {0} does not exist").format(self.file_url), IOError)
def validate_duplicate_entry(self):
if not self.flags.ignore_duplicate_entry_error and not self.is_folder:
# check duplicate name
# check duplicate assignement
n_records = frappe.db.sql("""select name from `tabFile`
where content_hash=%s
and name!=%s
and attached_to_doctype=%s
and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype,
self.attached_to_name))
if len(n_records) > 0:
self.duplicate_entry = n_records[0][0]
frappe.throw(frappe._("Same file has already been attached to the record"), frappe.DuplicateEntryError)
def generate_content_hash(self):
if self.content_hash or not self.file_url:
return
if self.file_url.startswith("/files/"):
try:
with open(get_files_path(self.file_name.lstrip("/")), "r") as f:
self.content_hash = get_content_hash(f.read())
except IOError:
frappe.msgprint(_("File {0} does not exist").format(self.file_url))
raise
def on_trash(self):
if self.is_home_folder or self.is_attachments_folder:
frappe.throw(_("Cannot delete Home and Attachments folders"))
self.check_folder_is_empty()
self.check_reference_doc_permission()
super(File, self).on_trash()
self.delete_file()
def make_thumbnail(self):
if self.file_url:
if self.file_url.startswith("/files"):
try:
image, filename, extn = get_local_image(self.file_url)
except IOError:
return
else:
try:
image, filename, extn = get_web_image(self.file_url)
except (requests.exceptions.HTTPError, requests.exceptions.SSLError, IOError):
return
thumbnail = ImageOps.fit(
image,
(300, 300),
Image.ANTIALIAS
)
thumbnail_url = filename + "_small." + extn
path = os.path.abspath(frappe.get_site_path("public", thumbnail_url.lstrip("/")))
try:
thumbnail.save(path)
self.db_set("thumbnail_url", thumbnail_url)
except IOError:
frappe.msgprint("Unable to write file format for {0}".format(path))
return
return thumbnail_url
def after_delete(self):
self.update_parent_folder_size()
def check_folder_is_empty(self):
"""Throw exception if folder is not empty"""
files = frappe.get_all("File", filters={"folder": self.name}, fields=("name", "file_name"))
if self.is_folder and files:
frappe.throw(_("Folder {0} is not empty").format(self.name), FolderNotEmpty)
def check_reference_doc_permission(self):
"""Check if permission exists for reference document"""
if self.attached_to_name:
# check persmission
try:
if not self.flags.ignore_permissions and \
not frappe.has_permission(self.attached_to_doctype,
"write", self.attached_to_name):
frappe.throw(frappe._("No permission to write / remove."),
frappe.PermissionError)
except frappe.DoesNotExistError:
pass
def delete_file(self):
"""If file not attached to any other record, delete it"""
if self.file_name and self.content_hash and (not frappe.db.count("File",
{"content_hash": self.content_hash, "name": ["!=", self.name]})):
delete_file_data_content(self)
elif self.file_url:
delete_file_data_content(self, only_thumbnail=True)
def on_rollback(self):
self.flags.on_rollback = True
self.on_trash()
def on_doctype_update():
frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"])
def make_home_folder():
home = frappe.get_doc({
"doctype": "File",
"is_folder": 1,
"is_home_folder": 1,
"file_name": _("Home")
}).insert()
frappe.get_doc({
"doctype": "File",
"folder": home.name,
"is_folder": 1,
"is_attachments_folder": 1,
"file_name": _("Attachments")
}).insert()
@frappe.whitelist()
def get_breadcrumbs(folder):
"""returns name, file_name of parent folder"""
lft, rgt = frappe.db.get_value("File", folder, ["lft", "rgt"])
return frappe.db.sql("""select name, file_name from tabFile
where lft < %s and rgt > %s order by lft asc""", (lft, rgt), as_dict=1)
@frappe.whitelist()
def create_new_folder(file_name, folder):
""" create new folder under current parent folder """
file = frappe.new_doc("File")
file.file_name = file_name
file.is_folder = 1
file.folder = folder
file.insert()
@frappe.whitelist()
def move_file(file_list, new_parent, old_parent):
if isinstance(file_list, basestring):
file_list = json.loads(file_list)
for file_obj in file_list:
setup_folder_path(file_obj.get("name"), new_parent)
# recalculate sizes
frappe.get_doc("File", old_parent).save()
frappe.get_doc("File", new_parent).save()
def setup_folder_path(filename, new_parent):
file = frappe.get_doc("File", filename)
file.folder = new_parent
file.save()
if file.is_folder:
frappe.rename_doc("File", file.name, file.get_name_based_on_parent_folder(), ignore_permissions=True)
def get_extension(filename, extn, content):
mimetype = None
if extn:
mimetype = mimetypes.guess_type(filename + "." + extn)[0]
if mimetype is None or not mimetype.startswith("image/") and content:
# detect file extension by reading image header properties
extn = imghdr.what(filename + "." + (extn or ""), h=content)
return extn
def get_local_image(file_url):
file_path = frappe.get_site_path("public", file_url.lstrip("/"))
try:
image = Image.open(file_path)
except IOError:
frappe.msgprint("Unable to read file format for {0}".format(file_url))
raise
content = None
try:
filename, extn = file_url.rsplit(".", 1)
except ValueError:
# no extn
with open(file_path, "r") as f:
content = f.read()
filename = file_url
extn = None
extn = get_extension(filename, extn, content)
return image, filename, extn
def get_web_image(file_url):
# downlaod
file_url = frappe.utils.get_url(file_url)
r = requests.get(file_url, stream=True)
try:
r.raise_for_status()
except requests.exceptions.HTTPError, e:
if "404" in e.args[0]:
frappe.msgprint(_("File '{0}' not found").format(file_url))
else:
frappe.msgprint("Unable to read file format for {0}".format(file_url))
raise
image = Image.open(StringIO.StringIO(r.content))
try:
filename, extn = file_url.rsplit("/", 1)[1].rsplit(".", 1)
except ValueError:
# the case when the file url doesn't have filename or extension
# but is fetched due to a query string. example: https://encrypted-tbn3.gstatic.com/images?q=something
filename = get_random_filename()
extn = None
extn = get_extension(filename, extn, r.content)
filename = "/files/" + strip(urllib.unquote(filename))
return image, filename, extn
def check_file_permission(file_url):
for file in frappe.get_all("File", filters={"file_url": file_url, "is_private": 1}, fields=["name", "attached_to_doctype", "attached_to_name"]):
if (frappe.has_permission("File", ptype="read", doc=file.name)
or frappe.has_permission(file.attached_to_doctype, ptype="read", doc=file.attached_to_name)):
return True
raise frappe.PermissionError

View file

@ -0,0 +1,216 @@
frappe.provide("frappe.ui");
frappe.listview_settings['File'] = {
hide_name_column: true,
use_route: true,
add_fields: ["is_folder", "file_name", "file_url", "folder", "is_private"],
formatters: {
file_size: function(value) {
// formatter for file size
if(value > 1048576) {
value = flt(flt(value) / 1048576, 1) + "M";
} else if (value > 1024) {
value = flt(flt(value) / 1024, 1) + "K";
}
return value;
}
},
prepare_data: function(data) {
// set image icons
var icon = ""
if(data.is_folder) {
icon += '<i class="icon-folder-close-alt icon-fixed-width"></i> ';
} else if(frappe.utils.is_image_file(data.file_name)) {
icon += '<i class="icon-picture icon-fixed-width"></i> ';
} else {
icon += '<i class="icon-file-alt icon-fixed-width"></i> '
}
data._title = icon + (data.file_name ? data.file_name : data.file_url)
if (data.is_private) {
data._title += ' <i class="icon-lock icon-fixed-width text-warning"></i>'
}
},
onload: function(doclist) {
doclist.filter_area = doclist.wrapper.find(".show_filters");
doclist.breadcrumb = $('<ol class="breadcrumb for-file-list"></ol>')
.insertBefore(doclist.filter_area);
doclist.listview.settings.setup_menu(doclist);
doclist.listview.settings.setup_dragdrop(doclist);
doclist.$page.on("click", ".list-delete", function(event) {
doclist.listview.settings.add_menu_item_copy(doclist);
})
},
list_view_doc:function(doclist){
$(doclist.wrapper).on("click", 'button[list_view_doc="'+doclist.doctype+'"]', function(){
dialog = frappe.ui.get_upload_dialog({
"args": {
"folder": doclist.current_folder,
"from_form": 1
},
callback: function() {
doclist.refresh();
}
});
});
},
setup_menu: function(doclist) {
doclist.page.add_menu_item(__("New Folder"), function() {
var d = frappe.prompt(__("Name"), function(values) {
if((values.value.indexOf("/") > -1)){
frappe.throw("Folder name should not include / !!!");
return;
}
var data = {
"file_name": values.value,
"folder": doclist.current_folder
};
frappe.call({
method: "frappe.core.doctype.file.file.create_new_folder",
args: data,
callback: function(r) { }
})
}, __('Enter folder name'), __("Create"));
});
doclist.page.add_menu_item(__("Edit Folder"), function() {
frappe.set_route("Form", "File", doclist.current_folder);
});
},
setup_dragdrop: function(doclist) {
$(doclist.$page).on('dragenter dragover', false)
.on('drop', function (e) {
var dataTransfer = e.originalEvent.dataTransfer;
if (!(dataTransfer && dataTransfer.files && dataTransfer.files.length > 0)) {
return;
}
e.stopPropagation();
e.preventDefault();
frappe.upload.upload_file(dataTransfer.files[0], {
"folder": doclist.current_folder,
"from_form": 1
}, {
confirm_is_private: 1
});
});
},
add_menu_item_copy: function(doclist){
if (!doclist.copy) {
var copy_menu = doclist.page.add_menu_item(__("Copy"), function() {
if(doclist.$page.find(".list-delete:checked").length){
doclist.selected_files = doclist.get_checked_items();
doclist.old_parent = doclist.current_folder;
doclist.listview.settings.add_menu_item_paste(doclist);
}
else{
frappe.throw("Please select file to copy");
}
})
doclist.copy = true;
}
},
add_menu_item_paste:function(doclist){
var paste_menu = doclist.page.add_menu_item(__("Paste"), function(){
frappe.call({
method:"frappe.core.doctype.file.file.move_file",
args: {
"file_list": doclist.selected_files,
"new_parent": doclist.current_folder,
"old_parent": doclist.old_parent
},
callback:function(r){
doclist.paste = false;
frappe.msgprint(__(r.message));
doclist.selected_files = [];
$(paste_menu).remove();
}
})
})
},
before_run: function(doclist) {
var name_filter = doclist.filter_list.get_filter("file_name");
if(name_filter) {
doclist.filter_area.removeClass("hide");
doclist.breadcrumb.addClass("hide");
} else {
doclist.filter_area.addClass("hide");
doclist.breadcrumb.removeClass("hide");
}
},
refresh: function(doclist) {
// set folder before querying
var name_filter = doclist.filter_list.get_filter("file_name");
var folder_filter = doclist.filter_list.get_filter("folder");
if(folder_filter) {
folder_filter.remove(true);
}
if(name_filter) return;
var route = frappe.get_route();
if(route[2]) {
doclist.current_folder = route.slice(2).join("/");
doclist.current_folder_name = route.slice(-1)[0];
}
if(!doclist.current_folder) {
doclist.current_folder = frappe.boot.home_folder;
doclist.current_folder_name = __("Home");
}
doclist.filter_list.add_filter("File", "folder", "=", doclist.current_folder, true);
doclist.dirty = true;
doclist.fresh = false;
doclist.page.set_title(doclist.current_folder_name);
frappe.utils.set_title(doclist.current_folder_name);
},
set_primary_action:function(doclist){
doclist.page.clear_primary_action();
doclist.page.set_primary_action(__("New"), function() {
dialog = frappe.ui.get_upload_dialog({
"args": {
"folder": doclist.current_folder,
"from_form": 1
},
callback: function() {
doclist.refresh();
}
});
}, "octicon octicon-plus");
},
post_render_item: function(list, row, data) {
if(data.is_folder) {
$(row).find(".list-id").attr("href", "#List/File/" + data.name);
}
},
set_file_route: function(name) {
frappe.set_route(["List", "File"].concat(decodeURIComponent(name).split("/")));
},
post_render: function(doclist) {
frappe.call({
method: "frappe.core.doctype.file.file.get_breadcrumbs",
args: {
folder: doclist.current_folder
},
callback: function(r) {
doclist.breadcrumb.empty();
if(r.message && r.message.length) {
$.each(r.message, function(i, folder) {
$('<li><a href="#List/File/'+folder.name+'">'
+ folder.file_name+'</a></li>')
.appendTo(doclist.breadcrumb);
});
}
$('<li class="active">'+ doclist.current_folder_name+'</li>')
.appendTo(doclist.breadcrumb);
}
});
}
}

View file

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils.file_manager import save_file, get_files_path
from frappe import _
from frappe.core.doctype.file.file import move_file
# test_records = frappe.get_test_records('File')
class TestFile(unittest.TestCase):
def setUp(self):
self.delete_test_data()
self.upload_file()
def tearDown(self):
try:
frappe.get_doc("File", {"file_name": "file_copy.txt"}).delete()
except frappe.DoesNotExistError:
pass
def delete_test_data(self):
for f in frappe.db.sql('''select name, file_name from tabFile where
is_home_folder = 0 and is_attachments_folder = 0 order by rgt-lft asc'''):
frappe.delete_doc("File", f[0])
def upload_file(self):
self.saved_file = save_file('file_copy.txt', "Testing file copy example.",\
"", "", self.get_folder("Test Folder 1", "Home").name)
self.saved_filename = get_files_path(self.saved_file.file_name)
def get_folder(self, folder_name, parent_folder="Home"):
return frappe.get_doc({
"doctype": "File",
"file_name": _(folder_name),
"is_folder": 1,
"folder": _(parent_folder)
}).insert()
def tests_after_upload(self):
self.assertEqual(self.saved_file.folder, _("Home/Test Folder 1"))
folder_size = frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size")
saved_file_size = frappe.db.get_value("File", self.saved_file.name, "file_size")
self.assertEqual(folder_size, saved_file_size)
def test_file_copy(self):
folder = self.get_folder("Test Folder 2", "Home")
file = frappe.get_doc("File", {"file_name":"file_copy.txt"})
move_file([{"name": file.name}], folder.name, file.folder)
file = frappe.get_doc("File", {"file_name":"file_copy.txt"})
self.assertEqual(_("Home/Test Folder 2"), file.folder)
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), file.file_size)
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), 0)
def test_folder_copy(self):
folder = self.get_folder("Test Folder 2", "Home")
folder = self.get_folder("Test Folder 3", "Home/Test Folder 2")
self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name)
move_file([{"name": folder.name}], 'Home/Test Folder 1', folder.folder)
file = frappe.get_doc("File", {"file_name":"folder_copy.txt"})
file_copy_txt = frappe.get_value("File", {"file_name":"file_copy.txt"})
if file_copy_txt:
frappe.get_doc("File", file_copy_txt).delete()
self.assertEqual(_("Home/Test Folder 1/Test Folder 3"), file.folder)
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), file.file_size)
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), 0)
def test_non_parent_folder(self):
d = frappe.get_doc({
"doctype": "File",
"file_name": _("Test_Folder"),
"is_folder": 1
})
self.assertRaises(frappe.ValidationError, d.save)
def test_on_delete(self):
file = frappe.get_doc("File", {"file_name":"file_copy.txt"})
file.delete()
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), 0)
folder = self.get_folder("Test Folder 3", "Home/Test Folder 1")
self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name)
folder = frappe.get_doc("File", "Home/Test Folder 1/Test Folder 3")
self.assertRaises(frappe.ValidationError, folder.delete)

View file

@ -1,3 +0,0 @@
File record and its relation to document to which the file is attached.
This is appened to the form as `docinfo` when a document is loaded.

View file

@ -1,4 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals

View file

@ -1,84 +0,0 @@
{
"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,
"read_only": 1
},
{
"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,
"search_index": 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,
"read_only": 1
},
{
"fieldname": "content_hash",
"fieldtype": "Data",
"label": "Content Hash",
"permlevel": 0,
"search_index": 1
}
],
"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",
"share": 1,
"write": 1
}
],
"read_only": 0
}

View file

@ -1,57 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
"""
record of files
naming for same name files: file.gif, file-1.gif, file-2.gif etc
"""
import frappe, frappe.utils, os
from frappe import conf
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 self.flags.ignore_duplicate_entry_error:
# check duplicate assignement
n_records = frappe.db.sql("""select name from `tabFile Data`
where content_hash=%s
and name!=%s
and attached_to_doctype=%s
and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype,
self.attached_to_name))
if len(n_records) > 0:
self.duplicate_entry = n_records[0][0]
frappe.throw(frappe._("Same file has already been attached to the record"), frappe.DuplicateEntryError)
def on_trash(self):
if self.attached_to_name:
# check persmission
try:
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)
except frappe.DoesNotExistError:
pass
# if file not attached to any other record, delete it
if self.file_name and self.content_hash and (not frappe.db.count("File Data",
{"content_hash": self.content_hash, "name": ["!=", self.name]})):
delete_file_data_content(self)
def on_rollback(self):
self.on_trash()
def on_doctype_update():
frappe.db.add_index("File Data", ["attached_to_doctype", "attached_to_name"])

View file

@ -1,31 +1,71 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:module_name",
"creation": "2013-01-10 16:34:03",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "module_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Module Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "module_name",
"oldfieldtype": "Data",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "app_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "App Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-sitemap",
"idx": 1,
"modified": "2015-02-05 05:11:41.388856",
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:50.398063",
"modified_by": "Administrator",
"module": "Core",
"name": "Module Def",
@ -33,23 +73,45 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"role": "System Manager"
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
]
],
"read_only": 0,
"read_only_onload": 0
}

View file

@ -7,7 +7,6 @@ import frappe, os
from frappe.model.document import Document
class ModuleDef(Document):
__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."""
@ -39,7 +38,3 @@ class ModuleDef(Document):
frappe.clear_cache()
frappe.setup_module_map()

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Module Def')
class TestModuleDef(unittest.TestCase):
pass

View file

@ -1,105 +1,254 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:page_name",
"creation": "2012-12-20 17:16:49",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "page_html",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Page HTML",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Section Break",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "page_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Page Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "page_name",
"oldfieldtype": "Data",
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Title",
"permlevel": 0
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "icon",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "icon",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "module",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Module",
"length": 0,
"no_copy": 0,
"oldfieldname": "module",
"oldfieldtype": "Select",
"options": "Module Def",
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "standard",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Standard",
"length": 0,
"no_copy": 0,
"oldfieldname": "standard",
"oldfieldtype": "Select",
"options": "\nYes\nNo",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break0",
"fieldtype": "Section Break",
"permlevel": 0
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "roles",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Roles",
"length": 0,
"no_copy": 0,
"oldfieldname": "roles",
"oldfieldtype": "Table",
"options": "Page Role",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-file",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-02-05 05:11:41.982758",
"max_attachments": 0,
"modified": "2015-11-16 06:29:51.370746",
"modified_by": "Administrator",
"module": "Core",
"name": "Page",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
@ -112,6 +261,7 @@
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
@ -122,15 +272,8 @@
"share": 1,
"submit": 0,
"write": 1
},
{
"apply_user_permissions": 1,
"email": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"role": "All"
}
],
"read_only": 0
"read_only": 0,
"read_only_onload": 0
}

View file

@ -5,6 +5,8 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.build import html_to_js_template
from frappe import conf, _
from frappe.desk.form.meta import get_code_files_via_hooks, get_js
class Page(Document):
def autoname(self):
@ -26,13 +28,18 @@ class Page(Document):
cnt = 1
self.name += '-' + str(cnt)
def validate(self):
if self.is_new() and not getattr(conf,'developer_mode', 0):
frappe.throw(_("Not in Developer Mode"))
if frappe.session.user!="Administrator":
frappe.throw(_("Only Administrator can edit"))
# export
def on_update(self):
"""
Writes the .txt for this page and if write_content is checked,
it will write out a .html file
"""
from frappe import conf
from frappe.core.doctype.doctype.doctype import make_module_and_roles
make_module_and_roles(self, "roles")
@ -62,20 +69,37 @@ class Page(Document):
d[key] = self.get(key)
return d
def is_permitted(self):
"""Returns true if Page Role is not set or the user is allowed."""
from frappe.utils import has_common
allowed = [d.role for d in frappe.get_all("Page Role", fields=["role"],
filters={"parent": self.name})]
if not allowed:
return True
roles = frappe.get_roles()
if has_common(roles, allowed):
return True
def load_assets(self):
from frappe.modules import get_module_path, scrub
import os
path = os.path.join(get_module_path(self.module), 'page', scrub(self.name))
page_name = scrub(self.name)
path = os.path.join(get_module_path(self.module), 'page', page_name)
# script
fpath = os.path.join(path, scrub(self.name) + '.js')
fpath = os.path.join(path, page_name + '.js')
if os.path.exists(fpath):
with open(fpath, 'r') as f:
self.script = unicode(f.read(), "utf-8")
# css
fpath = os.path.join(path, scrub(self.name) + '.css')
fpath = os.path.join(path, page_name + '.css')
if os.path.exists(fpath):
with open(fpath, 'r') as f:
self.style = unicode(f.read(), "utf-8")
@ -85,8 +109,27 @@ class Page(Document):
if fname.endswith(".html"):
with open(os.path.join(path, fname), 'r') as f:
template = unicode(f.read(), "utf-8")
if "<!-- jinja -->" in template:
context = {}
try:
context = frappe.get_attr("{app}.{module}.page.{page}.{page}.get_context".format(
app = frappe.local.module_app[scrub(self.module)],
module = scrub(self.module),
page = page_name
))(context)
except (AttributeError, ImportError):
pass
template = frappe.render_template(template, context)
self.script = html_to_js_template(fname, template) + self.script
if frappe.lang != 'en':
from frappe.translate import get_lang_js
self.script += get_lang_js("page", self.name)
for path in get_code_files_via_hooks("page_js", self.name):
js = get_js(path)
if js:
self.script += "\n\n" + js

View file

@ -1,34 +1,54 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
"creation": "2013-02-22 01:27:34",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "role",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Role",
"length": 0,
"no_copy": 0,
"oldfieldname": "role",
"oldfieldtype": "Link",
"options": "Role",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-19 01:07:00.897854",
"max_attachments": 0,
"modified": "2015-11-16 06:29:51.420589",
"modified_by": "Administrator",
"module": "Core",
"name": "Page Role",
"owner": "Administrator",
"permissions": [],
"read_only": 0
"read_only": 0,
"read_only_onload": 0
}

View file

@ -1,33 +1,75 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "PATCHLOG.#####",
"creation": "2013-01-17 11:36:45.000000",
"creation": "2013-01-17 11:36:45",
"custom": 0,
"description": "List of patches executed",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "patch",
"fieldtype": "Data",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Patch",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-cog",
"idx": 1,
"modified": "2013-12-20 19:24:15.000000",
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:51.487656",
"modified_by": "Administrator",
"module": "Core",
"name": "Patch Log",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator"
"role": "Administrator",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
]
],
"read_only": 0,
"read_only_onload": 0
}

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Patch Log')
class TestPatchLog(unittest.TestCase):
pass

View file

@ -1,173 +1,408 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:report_name",
"creation": "2013-03-09 15:45:57",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "report_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Report Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"reqd": 1
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "ref_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Ref DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"reqd": 1
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "is_standard",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Is Standard",
"length": 0,
"no_copy": 0,
"options": "No\nYes",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"reqd": 1
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "module",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Module",
"length": 0,
"no_copy": 0,
"options": "Module Def",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "add_total_row",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Add Total Row",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"read_only": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "report_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Report Type",
"length": 0,
"no_copy": 0,
"options": "Report Builder\nQuery Report\nScript Report",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"reqd": 1
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Disabled",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"read_only": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "1",
"depends_on": "eval:[\"Query Report\", \"Script Report\"].indexOf(doc.report_type)!==-1",
"fieldname": "apply_user_permissions",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Apply User Permissions",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"read_only": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:doc.report_type==\"Query Report\"",
"fieldname": "query",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Query",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"read_only": 0
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "",
"description": "JavaScript Format: frappe.query_reports['REPORTNAME'] = {}",
"fieldname": "javascript",
"fieldtype": "Code",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Javascript",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:doc.report_type==\"Report Builder\"",
"fieldname": "json",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "JSON",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"read_only": 1
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-table",
"idx": 1,
"modified": "2015-02-05 05:11:44.753200",
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:55.357855",
"modified_by": "Administrator",
"module": "Core",
"name": "Report",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Report Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"apply_user_permissions": 1,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"submit": 0
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
],
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC"
}

View file

@ -1,29 +1,49 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:role_name",
"creation": "2013-01-08 15:50:01",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "role_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Role Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "role_name",
"oldfieldtype": "Data",
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-bookmark",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-02-05 05:11:44.831475",
"max_attachments": 0,
"modified": "2015-11-16 06:29:55.424835",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",
@ -31,36 +51,65 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"apply_user_permissions": 1,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"role": "All"
"report": 0,
"role": "All",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
],
"read_only": 0
"read_only": 0,
"read_only_onload": 0
}

View file

@ -7,4 +7,9 @@ import frappe
from frappe.model.document import Document
class Role(Document):
pass
def after_insert(self):
# Add role to Administrator
if frappe.flags.in_install != "frappe":
user = frappe.get_doc("User", "Administrator")
user.flags.ignore_permissions = True
user.add_roles(self.name)

View file

@ -1,55 +1,121 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "SCHLOG.#####",
"creation": "2013-01-16 13:09:40",
"custom": 0,
"description": "Log of Scheduler Errors",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Seen",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "method",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Method",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "error",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Error",
"permlevel": 0
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-warning-sign",
"idx": 1,
"modified": "2015-05-28 02:49:12.819934",
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:57.440897",
"modified_by": "Administrator",
"module": "Core",
"name": "Scheduler Log",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
]
],
"read_only": 0,
"read_only_onload": 0
}

View file

@ -16,4 +16,4 @@ class SchedulerLog(Document):
def set_old_logs_as_seen():
frappe.db.sql("""update `tabScheduler Log` set seen=1
where ifnull(seen, 0)=0 and datediff(curdate(), creation) > 7""")
where seen=0 and datediff(curdate(), creation) > 7""")

View file

@ -1,152 +1,461 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2014-04-17 16:53:52.640856",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "localization",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "",
"permlevel": 0
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "language",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Language",
"no_copy": 0,
"options": "Loading...",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "time_zone",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Time Zone",
"no_copy": 0,
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "date_and_number_format",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "",
"permlevel": 0
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "date_format",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Date Format",
"no_copy": 0,
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\ndd.mm.yyyy\nmm/dd/yyyy\nmm-dd-yyyy",
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "number_format",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Number Format",
"no_copy": 0,
"options": "#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###",
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "float_precision",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Float Precision",
"no_copy": 0,
"options": "\n2\n3\n4\n5\n6",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "security",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Security",
"permlevel": 0
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "06:00",
"description": "Session Expiry in Hours e.g. 06:00",
"fieldname": "session_expiry",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Session Expiry",
"no_copy": 0,
"options": "",
"permlevel": 0
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "720:00",
"description": "In Hours",
"fieldname": "session_expiry_mobile",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Session Expiry Mobile",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Run scheduled jobs only if checked",
"fieldname": "enable_scheduler",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Enable Scheduled Jobs",
"permlevel": 0
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "scheduler_last_event",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Scheduler Last Event",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"report_hide": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "eg. If Apply User Permissions is checked for Report DocType but no User Permissions are defined for Report for a User, then all Reports are shown to that User",
"fieldname": "ignore_user_permissions_if_missing",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Ignore User Permissions If Missing",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "email",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "EMail",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Your organization name and address for the email footer.",
"fieldname": "email_footer_address",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Email Footer Address",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_18",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "disable_standard_email_footer",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Disable Standard Email Footer",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-cog",
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1,
"modified": "2015-05-21 07:15:55.682132",
"istable": 0,
"modified": "2015-09-07 11:36:15.465900",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",
@ -154,12 +463,26 @@
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
]
],
"read_only": 0,
"read_only_onload": 0
}

View file

@ -35,10 +35,6 @@ cur_frm.cscript.before_load = function(doc, dt, dn, callback) {
}
}
cur_frm.cscript.user_image = function(doc) {
refresh_field("user_image_show");
}
cur_frm.cscript.refresh = function(doc) {
if(doc.name===user && !doc.__unsaved && frappe.languages && (doc.language || frappe.boot.user.language)
&& doc.language !== frappe.boot.user.language) {
@ -76,7 +72,7 @@ cur_frm.cscript.refresh = function(doc) {
cur_frm.cscript.enabled = function(doc) {
if(!doc.__islocal && has_common(user_roles, ["Administrator", "System Manager"])) {
cur_frm.toggle_display(['sb1', 'sb3', 'modules_access'], doc.enabled);
cur_frm.toggle_enable('*', doc.enabled);
// cur_frm.toggle_enable('*', doc.enabled);
cur_frm.set_df_property('enabled', 'read_only', 0);
}

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,7 @@ from frappe.desk.notifications import clear_notifications
from frappe.utils.user import get_system_managers
import frappe.permissions
import frappe.share
import re
STANDARD_USERS = ("Guest", "Administrator")
@ -38,6 +39,8 @@ class User(Document):
self.update_gravatar()
self.ensure_unique_roles()
self.remove_all_roles_for_guest()
self.validate_username()
if self.language == "Loading...":
self.language = None
@ -85,7 +88,7 @@ class User(Document):
self.share_with_self()
clear_notifications(user=self.name)
frappe.clear_cache(user=self.name)
self.send_password_notifcation(self.__new_password)
self.send_password_notification(self.__new_password)
def share_with_self(self):
if self.user_type=="System User":
@ -103,7 +106,7 @@ class User(Document):
else:
frappe.throw(_("Sorry! Sharing with Website User is prohibited."))
def send_password_notifcation(self, new_password):
def send_password_notification(self, new_password):
try:
if self.in_insert:
if self.name not in STANDARD_USERS:
@ -141,7 +144,7 @@ class User(Document):
return frappe.db.sql("""select distinct user.name from tabUserRole user_role, tabUser user
where user_role.role='System Manager'
and user.docstatus<2
and ifnull(user.enabled,0)=1
and user.enabled=1
and user_role.parent = user.name
and user_role.parent not in ('Administrator', %s) limit 1""", (self.name,))
@ -164,7 +167,7 @@ class User(Document):
link = get_url("/update-password?key=" + key)
self.send_login_mail(_("Verify Your Account"), "templates/emails/new_user.html",
{"link": link})
{"link": link, "site_url": get_url()})
def send_login_mail(self, subject, template, add_args):
"""send mail with login details"""
@ -296,6 +299,48 @@ class User(Document):
else:
exists.append(d.role)
def validate_username(self):
if not self.username and self.is_new() and self.first_name:
self.username = frappe.scrub(self.first_name)
if not self.username:
return
# strip space and @
self.username = self.username.strip(" @")
if self.username_exists():
frappe.msgprint(_("Username {0} already exists").format(self.username))
self.suggest_username()
self.username = ""
# should be made up of characters, numbers and underscore only
if self.username and not re.match(r"^[\w]+$", self.username):
frappe.msgprint(_("Username should not contain any special characters other than letters, numbers and underscore"))
self.username = ""
def suggest_username(self):
def _check_suggestion(suggestion):
if self.username != suggestion and not self.username_exists(suggestion):
return suggestion
return None
# @firstname
username = _check_suggestion(frappe.scrub(self.first_name))
if not username:
# @firstname_last_name
username = _check_suggestion(frappe.scrub("{0} {1}".format(self.first_name, self.last_name or "")))
if username:
frappe.msgprint(_("Suggested Username: {0}").format(username))
return username
def username_exists(self, username=None):
return frappe.db.get_value("User", {"username": username or self.username, "name": ("!=", self.name)})
@frappe.whitelist()
def get_languages():
from frappe.translate import get_lang_dict
@ -397,7 +442,7 @@ def user_query(doctype, txt, searchfield, start, page_len, filters):
txt = "%{}%".format(txt)
return frappe.db.sql("""select name, concat_ws(' ', first_name, middle_name, last_name)
from `tabUser`
where ifnull(enabled, 0)=1
where enabled=1
and docstatus < 2
and name not in ({standard_users})
and user_type != 'Website User'
@ -437,7 +482,7 @@ def get_active_users():
return frappe.db.sql("""select count(*) from `tabUser`
where enabled = 1 and user_type != 'Website User'
and name not in ({})
and hour(timediff(now(), last_login)) < 72""".format(", ".join(["%s"]*len(STANDARD_USERS))), STANDARD_USERS)[0][0]
and hour(timediff(now(), last_active)) < 72""".format(", ".join(["%s"]*len(STANDARD_USERS))), STANDARD_USERS)[0][0]
def get_website_users():
"""Returns total no. of website users"""
@ -448,7 +493,7 @@ def get_active_website_users():
"""Returns No. of website users who logged in, in the last 3 days"""
return frappe.db.sql("""select count(*) from `tabUser`
where enabled = 1 and user_type = 'Website User'
and hour(timediff(now(), last_login)) < 72""")[0][0]
and hour(timediff(now(), last_active)) < 72""")[0][0]
def get_permission_query_conditions(user):
if user=="Administrator":
@ -490,3 +535,7 @@ def notifify_admin_access_to_system_manager(login_manager=None):
frappe.sendmail(recipients=get_system_managers(), subject=_("Administrator Logged In"),
message=message, bulk=True)
def extract_mentions(txt):
"""Find all instances of @username in the string.
The mentions will be separated by non-word characters or may appear at the start of the string"""
return re.findall(r'(?:[^\w]|^)@([\w]*)', txt)

View file

@ -1,36 +1,56 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
"creation": "2013-02-06 11:30:13",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "role",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Role",
"length": 0,
"no_copy": 0,
"oldfieldname": "role",
"oldfieldtype": "Link",
"options": "Role",
"permlevel": 0,
"print_hide": 0,
"print_width": "200px",
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "200px"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-19 01:07:02.561834",
"max_attachments": 0,
"modified": "2015-11-16 06:30:00.004591",
"modified_by": "Administrator",
"module": "Core",
"name": "UserRole",
"owner": "Administrator",
"permissions": [],
"read_only": 0
"read_only": 0,
"read_only_onload": 0
}

View file

@ -1,50 +1,119 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "_VER.######",
"creation": "2014-02-20 17:22:37",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "ref_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Ref DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "docname",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Docname",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "doclist_json",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Doclist JSON",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"reqd": 1
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-copy",
"idx": 1,
"modified": "2014-08-05 01:23:37.541856",
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:30:00.036254",
"modified_by": "Administrator",
"module": "Core",
"name": "Version",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 1,
"role": "System Manager"
"role": "System Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
]
],
"read_only": 0,
"read_only_onload": 0
}

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