Merge pull request #1 from frappe/develop
Merge from frappe/frappe:develop
This commit is contained in:
commit
adcd29cd4f
260 changed files with 5613 additions and 12992 deletions
|
|
@ -6,10 +6,15 @@ python:
|
|||
services:
|
||||
- mysql
|
||||
|
||||
before_install:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
|
||||
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
|
||||
|
|
@ -23,7 +28,7 @@ script:
|
|||
- bench build-website
|
||||
- bench serve &
|
||||
- sleep 10
|
||||
- bench --verbose run-tests
|
||||
- bench --verbose run-tests --driver Firefox
|
||||
|
||||
before_script:
|
||||
- mysql -e 'create database test_frappe'
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ def init(site, sites_path=None):
|
|||
|
||||
local.module_app = None
|
||||
local.app_modules = None
|
||||
local.system_settings = None
|
||||
|
||||
local.user = None
|
||||
local.user_obj = None
|
||||
|
|
@ -425,11 +426,19 @@ def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=F
|
|||
if not user:
|
||||
user = session.user
|
||||
|
||||
for method in (get_hooks("has_website_permission") or {}).get(doctype, []):
|
||||
if not call(get_attr(method), doc=doc, ptype=ptype, user=user, verbose=verbose):
|
||||
return False
|
||||
hooks = (get_hooks("has_website_permission") or {}).get(doctype, [])
|
||||
if hooks:
|
||||
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
|
||||
if not result:
|
||||
return False
|
||||
|
||||
return True
|
||||
# else it is Truthy
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_table(doctype):
|
||||
"""Returns True if `istable` property (indicating child Table) is set for given DocType."""
|
||||
|
|
@ -529,9 +538,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.
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
from __future__ import unicode_literals
|
||||
__version__ = "5.0.9"
|
||||
__version__ = "5.1.3"
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ def application(request):
|
|||
|
||||
except Exception, e:
|
||||
http_status_code = getattr(e, "http_status_code", 500)
|
||||
#print frappe.get_traceback()
|
||||
|
||||
if (http_status_code==500
|
||||
and isinstance(e, MySQLdb.OperationalError)
|
||||
|
|
|
|||
|
|
@ -57,14 +57,6 @@ class HTTPRequest:
|
|||
# run login triggers
|
||||
if frappe.form_dict.get('cmd')=='login':
|
||||
frappe.local.login_manager.run_trigger('on_session_creation')
|
||||
self.clear_active_sessions()
|
||||
|
||||
def clear_active_sessions(self):
|
||||
if not frappe.conf.get("deny_multiple_sessions"):
|
||||
return
|
||||
|
||||
if frappe.session.user != "Guest":
|
||||
clear_sessions(frappe.session.user, keep_current=True)
|
||||
|
||||
|
||||
def set_lang(self, lang_codes):
|
||||
|
|
@ -90,7 +82,13 @@ class LoginManager:
|
|||
if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login":
|
||||
self.login()
|
||||
else:
|
||||
self.make_session(resume=True)
|
||||
try:
|
||||
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 +97,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 +137,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):
|
||||
|
|
@ -192,7 +203,7 @@ class LoginManager:
|
|||
return
|
||||
|
||||
from frappe.utils import now_datetime
|
||||
current_hour = int(now_datetime(user=frappe.form_dict.get('usr')).strftime('%H'))
|
||||
current_hour = int(now_datetime().strftime('%H'))
|
||||
|
||||
if login_before and current_hour > login_before:
|
||||
frappe.throw(_("Login not allowed at this time"), frappe.AuthenticationError)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ def get_bootinfo():
|
|||
|
||||
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
|
||||
|
||||
|
|
@ -104,9 +105,16 @@ 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,
|
||||
|
|
@ -142,14 +150,10 @@ def add_home_page(bootinfo, docs):
|
|||
docs.append(page)
|
||||
|
||||
def add_timezone_info(bootinfo):
|
||||
user = bootinfo.user.get("time_zone")
|
||||
system = bootinfo.sysdefaults.get("time_zone")
|
||||
if user and user != system:
|
||||
import frappe.utils.momentjs
|
||||
bootinfo.timezone_info = {"zones":{}, "rules":{}, "links":{}}
|
||||
|
||||
frappe.utils.momentjs.update(user, bootinfo.timezone_info)
|
||||
frappe.utils.momentjs.update(system, bootinfo.timezone_info)
|
||||
import frappe.utils.momentjs
|
||||
bootinfo.timezone_info = {"zones":{}, "rules":{}, "links":{}}
|
||||
frappe.utils.momentjs.update(system, bootinfo.timezone_info)
|
||||
|
||||
def load_print(bootinfo, doclist):
|
||||
print_settings = frappe.db.get_singles_dict("Print Settings")
|
||||
|
|
@ -158,4 +162,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)
|
||||
|
|
|
|||
|
|
@ -153,13 +153,13 @@ def pack(target, sources, no_compress, verbose):
|
|||
|
||||
def html_to_js_template(path, content):
|
||||
# remove whitespace to a single space
|
||||
content = re.sub("\s+", " ", content).replace("'", "\'")
|
||||
content = re.sub("\s+", " ", content)
|
||||
|
||||
# strip comments
|
||||
content = re.sub("(<!--.*?-->)", "", content)
|
||||
|
||||
return """frappe.templates["{key}"] = '{content}';\n""".format(\
|
||||
key=path.rsplit("/", 1)[-1][:-5], content=content)
|
||||
key=path.rsplit("/", 1)[-1][:-5], content=content.replace("'", "\'"))
|
||||
|
||||
def files_dirty():
|
||||
for target, sources in get_build_maps().iteritems():
|
||||
|
|
|
|||
1
frappe/change_log/current/if_owner.md
Normal file
1
frappe/change_log/current/if_owner.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Ability to set permissions based on owner by checking **If Owner** in Role Permissions Manager
|
||||
3
frappe/change_log/current/readme.md
Normal file
3
frappe/change_log/current/readme.md
Normal 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)
|
||||
6
frappe/change_log/v5/v5_0_18.md
Normal file
6
frappe/change_log/v5/v5_0_18.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#### Updates to Web Forms
|
||||
|
||||
- Web Forms list now is a standard portal list and includes paging and other extensions
|
||||
- Section, Column Breaks in Web Forms
|
||||
- Consistent User Interface
|
||||
- Cleanup of Portal Pages
|
||||
1
frappe/change_log/v5/v5_0_20.md
Normal file
1
frappe/change_log/v5/v5_0_20.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Ability to send yourself a copy of the outgoing email added back.
|
||||
5
frappe/change_log/v5/v5_0_32.md
Normal file
5
frappe/change_log/v5/v5_0_32.md
Normal 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
|
||||
3
frappe/change_log/v5/v5_1_0.md
Normal file
3
frappe/change_log/v5/v5_1_0.md
Normal 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
|
||||
1
frappe/change_log/v5/v5_1_1.md
Normal file
1
frappe/change_log/v5/v5_1_1.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Ability to **Share with Everyone** (except Guest) using **Share With**
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import click
|
||||
import hashlib
|
||||
|
|
@ -140,7 +139,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:
|
||||
|
|
@ -191,8 +190,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 +309,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()
|
||||
|
|
@ -341,32 +339,17 @@ def build_website(context):
|
|||
frappe.destroy()
|
||||
|
||||
@click.command('setup-docs')
|
||||
@click.argument('app')
|
||||
@click.argument('docs-app')
|
||||
@click.argument('path')
|
||||
@pass_context
|
||||
def setup_docs(context,app, docs_app, path):
|
||||
def setup_docs(context):
|
||||
"Setup docs in target folder of target app"
|
||||
from frappe.utils.setup_docs import setup_docs
|
||||
from frappe.website import statics
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
setup_docs(app, docs_app, path)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('build-docs')
|
||||
@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()
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
build(app)
|
||||
setup_docs()
|
||||
statics.sync_statics(rebuild=True)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
|
@ -483,7 +466,7 @@ def export_json(context, doctype, name, path):
|
|||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
data_import_tool.export_json(doctype, name, path)
|
||||
data_import_tool.export_json(doctype, path, name=name)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
|
@ -613,6 +596,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()
|
||||
|
||||
|
|
@ -825,7 +809,6 @@ commands = [
|
|||
sync_www,
|
||||
build_website,
|
||||
setup_docs,
|
||||
build_docs,
|
||||
reset_perms,
|
||||
execute,
|
||||
celery,
|
||||
|
|
|
|||
|
|
@ -208,15 +208,6 @@ def get_data():
|
|||
"description": _("Install Applications."),
|
||||
"icon": "icon-download"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Backup Manager",
|
||||
"label": _("Download Backup"),
|
||||
"onclick": "frappe.ui.toolbar.download_backup",
|
||||
"icon": "icon-download-alt",
|
||||
"description": _("Send download link of a recent backup to System Managers"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Backup Manager",
|
||||
|
|
|
|||
|
|
@ -103,12 +103,31 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Unsubscribed",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference DocType",
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"label": "Reference Name",
|
||||
"options": "reference_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "icon-comments",
|
||||
"idx": 1,
|
||||
"issingle": 0,
|
||||
"modified": "2015-02-11 15:32:45.807458",
|
||||
"modified": "2015-06-08 12:31:15.122312",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Comment",
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ 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
|
||||
|
||||
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):
|
||||
|
|
@ -38,6 +38,9 @@ class Comment(Document):
|
|||
and comment_docname=%s""", (self.doctype, self.name))[0][0] >= 50:
|
||||
frappe.throw(_("Cannot add more than 50 comments"))
|
||||
|
||||
if not self.comment_by_fullname and self.comment_by:
|
||||
self.comment_by_fullname = get_fullname(self.comment_by)
|
||||
|
||||
def on_update(self):
|
||||
"""Updates `_comments` property in parent Document."""
|
||||
self.update_comment_in_doc()
|
||||
|
|
@ -97,7 +100,7 @@ class Comment(Document):
|
|||
"""Updates `_comments` property in parent Document with given dict.
|
||||
|
||||
:param _comments: Dict of comments."""
|
||||
if frappe.db.get_value("DocType", self.comment_doctype, "issingle"):
|
||||
if not self.comment_doctype or frappe.db.get_value("DocType", self.comment_doctype, "issingle"):
|
||||
return
|
||||
|
||||
# use sql, so that we do not mess with the timestamp
|
||||
|
|
@ -130,4 +133,3 @@ def on_doctype_update():
|
|||
frappe.db.commit()
|
||||
frappe.db.sql("""alter table `tabComment`
|
||||
add index comment_doctype_docname_index(comment_doctype, comment_docname)""")
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ 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
|
||||
from frappe.utils import get_url, get_formatted_email, cstr, cint
|
||||
from frappe.utils.file_manager import get_file
|
||||
import frappe.email.smtp
|
||||
from frappe import _
|
||||
|
|
@ -43,13 +43,15 @@ class Communication(Document):
|
|||
if to_status in status_field.options.splitlines():
|
||||
frappe.db.set_value(parent.doctype, parent.name, "status", to_status)
|
||||
|
||||
def send(self, print_html=None, print_format=None, attachments=None):
|
||||
def send(self, print_html=None, print_format=None, attachments=None,
|
||||
send_me_a_copy=False, recipients=None):
|
||||
"""Send communication via Email.
|
||||
|
||||
:param print_html: Send given value as HTML attachment.
|
||||
:param print_format: Attach print format of parent document."""
|
||||
|
||||
self.notify(print_html, print_format, attachments)
|
||||
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
|
||||
|
|
@ -59,17 +61,20 @@ class Communication(Document):
|
|||
{"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")
|
||||
{"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}, "email_id")
|
||||
|
||||
if not self.outgoing_email_account:
|
||||
self.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1}, "email_id")
|
||||
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()
|
||||
|
||||
def notify(self, print_html=None, print_format=None, attachments=None, except_recipient=False):
|
||||
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)
|
||||
recipients = self.get_recipients(except_recipient=except_recipient)
|
||||
if not recipients:
|
||||
recipients = self.get_recipients(except_recipient=except_recipient)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=recipients,
|
||||
|
|
@ -96,8 +101,8 @@ class Communication(Document):
|
|||
|
||||
self.set_incoming_outgoing_accounts()
|
||||
|
||||
if not self.sender:
|
||||
self.sender = formataddr([frappe.session.data.full_name or "Notification", self.outgoing_email_account])
|
||||
if not self.sender or cint(self.outgoing_email_account.always_use_account_email_id_as_sender):
|
||||
self.sender = formataddr([frappe.session.data.full_name or "Notification", self.outgoing_email_account.email_id])
|
||||
|
||||
self.attachments = []
|
||||
|
||||
|
|
@ -122,7 +127,8 @@ class Communication(Document):
|
|||
|
||||
def get_recipients(self, except_recipient=False):
|
||||
"""Build a list of users to which this email should go to"""
|
||||
original_recipients = [s.strip() for s in self.recipients.split(",")]
|
||||
# [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[:]
|
||||
|
||||
if self.reference_doctype and self.reference_name:
|
||||
|
|
@ -153,6 +159,9 @@ class Communication(Document):
|
|||
if e not in filtered and email_id not in filtered:
|
||||
filtered.append(e)
|
||||
|
||||
if getattr(self, "send_me_a_copy", False):
|
||||
filtered.append(self.sender)
|
||||
|
||||
return filtered
|
||||
|
||||
def get_starrers(self):
|
||||
|
|
@ -198,7 +207,8 @@ def on_doctype_update():
|
|||
@frappe.whitelist()
|
||||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
|
||||
sender=None, recipients=None, communication_medium="Email", send_email=False,
|
||||
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False):
|
||||
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False,
|
||||
send_me_a_copy=False):
|
||||
"""Make a new communication.
|
||||
|
||||
:param doctype: Reference DocType.
|
||||
|
|
@ -212,7 +222,9 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
:param send_mail: Send via email (default **False**).
|
||||
:param print_html: HTML Print format to be sent as attachment.
|
||||
:param print_format: Print Format name of parent document to be sent as attachment.
|
||||
:param attachments: List of attachments as list of files or JSON string."""
|
||||
:param attachments: List of attachments as list of files or JSON string.
|
||||
:param send_me_a_copy: Send a copy to the sender (default **False**).
|
||||
"""
|
||||
|
||||
is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report")
|
||||
|
||||
|
|
@ -235,11 +247,17 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
"reference_name": name
|
||||
})
|
||||
comm.insert(ignore_permissions=True)
|
||||
|
||||
|
||||
recipients = None
|
||||
if send_email:
|
||||
comm.send(print_html, print_format, attachments)
|
||||
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)
|
||||
|
||||
return comm.name
|
||||
return {
|
||||
"name": comm.name,
|
||||
"recipients": ", ".join(recipients) if recipients else None
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_convert_to():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@
|
|||
"label": "Apply User Permissions",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "Apply this rule if the User is the Owner",
|
||||
"fieldname": "if_owner",
|
||||
"fieldtype": "Check",
|
||||
"label": "If user is the owner",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
|
|
@ -232,7 +240,7 @@
|
|||
"idx": 1,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"modified": "2015-03-18 06:09:58.928014",
|
||||
"modified": "2015-07-22 07:39:40.471092",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocPerm",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0
|
||||
},
|
||||
|
|
@ -129,6 +129,13 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "everyone",
|
||||
"fieldtype": "Check",
|
||||
"label": "Everyone",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
|
|
@ -138,7 +145,7 @@
|
|||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-02-12 11:30:52.968078",
|
||||
"modified": "2015-07-17 07:02:10.632582",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocShare",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ 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 +27,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())):
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ form_grid_templates = {
|
|||
}
|
||||
|
||||
class DocType(Document):
|
||||
__doclink__ = "https://frappe.io/docs/models/core/doctype"
|
||||
def get_feed(self):
|
||||
return self.name
|
||||
|
||||
|
|
@ -315,6 +314,7 @@ def validate_fields(meta):
|
|||
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)
|
||||
|
|
@ -362,14 +362,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':
|
||||
|
|
@ -382,8 +389,8 @@ def validate_permissions(doctype, for_remove=False):
|
|||
if not has_zero_perm:
|
||||
frappe.throw(_("{0}: Permission at level 0 must be set before higher levels are set").format(get_txt(d)))
|
||||
|
||||
if d.create or d.submit or d.cancel or d.amend:
|
||||
frappe.throw(_("{0}: Create, Submit, Cancel and Amend only valid at level 0").format(get_txt(d)))
|
||||
for invalid in ("create", "submit", "cancel", "amend"):
|
||||
if d.get(invalid): d.set(invalid, 0)
|
||||
|
||||
def check_permission_dependency(d):
|
||||
if d.cancel and not d.submit:
|
||||
|
|
@ -466,4 +473,3 @@ def init_list(doctype):
|
|||
doc = frappe.get_meta(doctype)
|
||||
make_boilerplate("controller_list.js", doc)
|
||||
make_boilerplate("controller_list.html", doc)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
"idx": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-02-05 05:11:41.982758",
|
||||
"modified": "2015-07-13 04:45:55.942795",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Page",
|
||||
|
|
@ -122,14 +122,6 @@
|
|||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"apply_user_permissions": 1,
|
||||
"email": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"read_only": 0
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ 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
|
||||
|
||||
class Page(Document):
|
||||
def autoname(self):
|
||||
|
|
@ -26,13 +27,16 @@ class Page(Document):
|
|||
cnt = 1
|
||||
self.name += '-' + str(cnt)
|
||||
|
||||
def validate(self):
|
||||
if not getattr(conf,'developer_mode', 0):
|
||||
frappe.throw(_("Not in Developer Mode"))
|
||||
|
||||
# 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,6 +66,21 @@ 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
|
||||
|
|
|
|||
|
|
@ -6,6 +6,15 @@
|
|||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"fields": [
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Seen",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Data",
|
||||
|
|
@ -22,7 +31,7 @@
|
|||
],
|
||||
"icon": "icon-warning-sign",
|
||||
"idx": 1,
|
||||
"modified": "2015-02-05 05:11:46.339879",
|
||||
"modified": "2015-05-28 02:49:12.819934",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Scheduler Log",
|
||||
|
|
|
|||
|
|
@ -9,4 +9,11 @@ import frappe
|
|||
from frappe.model.document import Document
|
||||
|
||||
class SchedulerLog(Document):
|
||||
pass
|
||||
def onload(self):
|
||||
if not self.seen:
|
||||
self.seen = 1
|
||||
self.save()
|
||||
|
||||
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""")
|
||||
|
|
|
|||
11
frappe/core/doctype/scheduler_log/scheduler_log_list.js
Normal file
11
frappe/core/doctype/scheduler_log/scheduler_log_list.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
frappe.listview_settings['Scheduler Log'] = {
|
||||
add_fields: ["seen"],
|
||||
get_indicator: function(doc) {
|
||||
if(cint(doc.seen)) {
|
||||
return [__("Seen"), "green", "seen,=,1"];
|
||||
} else {
|
||||
return [__("Not Seen"), "red", "seen,=,0"];
|
||||
}
|
||||
},
|
||||
order_by: "seen asc, modified desc",
|
||||
};
|
||||
12
frappe/core/doctype/scheduler_log/test_scheduler_log.py
Normal file
12
frappe/core/doctype/scheduler_log/test_scheduler_log.py
Normal 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('Scheduler Log')
|
||||
|
||||
class TestSchedulerLog(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
{
|
||||
"fieldname": "localization",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Localization",
|
||||
"label": "",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
|
|
@ -20,6 +20,12 @@
|
|||
"reqd": 1,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "time_zone",
|
||||
"fieldtype": "Select",
|
||||
|
|
@ -30,7 +36,7 @@
|
|||
{
|
||||
"fieldname": "date_and_number_format",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Date and Number Format",
|
||||
"label": "",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
|
|
@ -41,6 +47,12 @@
|
|||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "number_format",
|
||||
"fieldtype": "Select",
|
||||
|
|
@ -80,6 +92,12 @@
|
|||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"description": "Run scheduled jobs only if checked",
|
||||
"fieldname": "enable_scheduler",
|
||||
|
|
@ -96,11 +114,39 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "EMail",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"description": "Your organization name and address for the email footer.",
|
||||
"fieldname": "email_footer_address",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Email Footer Address",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "disable_standard_email_footer",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Standard Email Footer",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
"issingle": 1,
|
||||
"modified": "2015-05-18 05:11:38.759688",
|
||||
"modified": "2015-05-21 07:15:55.682132",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model import no_value_fields
|
||||
from frappe.translate import get_lang_dict, set_default_language
|
||||
from frappe.utils import cint
|
||||
from frappe.utils.momentjs import get_all_timezones
|
||||
|
|
@ -19,7 +20,7 @@ class SystemSettings(Document):
|
|||
|
||||
def on_update(self):
|
||||
for df in self.meta.get("fields"):
|
||||
if df.fieldtype in ("Select", "Data", "Check"):
|
||||
if df.fieldtype not in no_value_fields:
|
||||
frappe.db.set_default(df.fieldname, self.get(df.fieldname))
|
||||
|
||||
if self.language:
|
||||
|
|
|
|||
|
|
@ -46,9 +46,7 @@ cur_frm.cscript.refresh = function(doc) {
|
|||
window.location.reload();
|
||||
}
|
||||
|
||||
cur_frm.toggle_display('change_password', !doc.__islocal);
|
||||
|
||||
cur_frm.toggle_display(['sb1', 'sb3'], false);
|
||||
cur_frm.toggle_display(['sb1', 'sb3', 'modules_access'], false);
|
||||
|
||||
if(!doc.__islocal){
|
||||
cur_frm.add_custom_button(__("Set User Permissions"), function() {
|
||||
|
|
@ -59,7 +57,7 @@ cur_frm.cscript.refresh = function(doc) {
|
|||
}, null, "btn-default")
|
||||
|
||||
if(has_common(user_roles, ["Administrator", "System Manager"])) {
|
||||
cur_frm.toggle_display(['sb1', 'sb3'], true);
|
||||
cur_frm.toggle_display(['sb1', 'sb3', 'modules_access'], true);
|
||||
}
|
||||
cur_frm.cscript.enabled(doc);
|
||||
|
||||
|
|
@ -77,7 +75,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'], doc.enabled);
|
||||
cur_frm.toggle_display(['sb1', 'sb3', 'modules_access'], doc.enabled);
|
||||
cur_frm.toggle_enable('*', doc.enabled);
|
||||
cur_frm.set_df_property('enabled', 'read_only', 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@
|
|||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "change_password",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "",
|
||||
|
|
@ -127,6 +128,14 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "",
|
||||
"fieldname": "send_password_update_notification",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Password Update Notification",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "reset_password_key",
|
||||
"fieldtype": "Data",
|
||||
|
|
@ -473,7 +482,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 5,
|
||||
"modified": "2015-04-24 14:37:26.430454",
|
||||
"modified": "2015-06-01 01:00:32.901851",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
|
|||
|
|
@ -76,8 +76,9 @@ class User(Document):
|
|||
if new_password and not self.in_insert:
|
||||
_update_password(self.name, new_password)
|
||||
|
||||
self.password_update_mail(new_password)
|
||||
frappe.msgprint(_("New password emailed"))
|
||||
if self.send_password_update_notification:
|
||||
self.password_update_mail(new_password)
|
||||
frappe.msgprint(_("New password emailed"))
|
||||
|
||||
def on_update(self):
|
||||
# clear new password
|
||||
|
|
@ -324,12 +325,16 @@ def get_perm_info(arg=None):
|
|||
and docstatus<2 order by parent, permlevel""", (frappe.form_dict['role'],), as_dict=1)
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def update_password(new_password, key=None):
|
||||
def update_password(new_password, key=None, old_password=None):
|
||||
# verify old password
|
||||
if key:
|
||||
user = frappe.db.get_value("User", {"reset_password_key":key})
|
||||
if not user:
|
||||
return _("Cannot Update: Incorrect / Expired Link.")
|
||||
elif old_password:
|
||||
# verify old password
|
||||
frappe.local.login_manager.check_password(frappe.session.user, old_password)
|
||||
user = frappe.session.user
|
||||
|
||||
_update_password(user, new_password)
|
||||
|
||||
|
|
@ -342,6 +347,10 @@ def update_password(new_password, key=None):
|
|||
else:
|
||||
return "/"
|
||||
|
||||
@frappe.whitelist()
|
||||
def verify_password(password):
|
||||
frappe.local.login_manager.check_password(frappe.session.user, password)
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def sign_up(email, full_name):
|
||||
user = frappe.db.get("User", {"email": email})
|
||||
|
|
|
|||
|
|
@ -15,3 +15,5 @@ frappe.listview_settings['User'] = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
frappe.help.youtube_id["User"] = "fnBoRhBrwR4";
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import frappe
|
|||
|
||||
def get_notification_config():
|
||||
return {
|
||||
"for_doctype": {
|
||||
"Scheduler Log": {"seen": 0},
|
||||
},
|
||||
"for_module_doctypes": {
|
||||
"ToDo": "To Do",
|
||||
"Event": "Calendar",
|
||||
|
|
|
|||
|
|
@ -7,16 +7,25 @@ frappe.DataImportTool = Class.extend({
|
|||
title: __("Data Import Tool"),
|
||||
single_column: true
|
||||
});
|
||||
this.page.add_inner_button(__("Help"), function() {
|
||||
frappe.help.show_video("6wiriRKPhmg");
|
||||
});
|
||||
this.make();
|
||||
this.make_upload();
|
||||
},
|
||||
set_route_options: function() {
|
||||
if(frappe.route_options
|
||||
&& frappe.route_options.doctype
|
||||
&& in_list(frappe.boot.user.can_import, frappe.route_options.doctype)) {
|
||||
this.select.val(frappe.route_options.doctype).change();
|
||||
frappe.route_options = null;
|
||||
var doctype = null;
|
||||
if(frappe.get_route()[1]) {
|
||||
doctype = frappe.get_route()[1];
|
||||
} else if(frappe.route_options && frappe.route_options.doctype) {
|
||||
doctype = frappe.route_options.doctype;
|
||||
}
|
||||
|
||||
if(in_list(frappe.boot.user.can_import, doctype)) {
|
||||
this.select.val(doctype).change();
|
||||
}
|
||||
|
||||
frappe.route_options = null;
|
||||
},
|
||||
make: function() {
|
||||
var me = this;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, json, os
|
||||
import frappe, os
|
||||
from frappe import _
|
||||
import frappe.modules.import_file
|
||||
|
||||
|
|
@ -30,12 +30,13 @@ def get_doctype_options():
|
|||
doctype = frappe.form_dict['doctype']
|
||||
return [doctype] + [d.options for d in frappe.get_meta(doctype).get_table_fields()]
|
||||
|
||||
def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False):
|
||||
def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False, pre_process=None):
|
||||
from frappe.utils.csvutils import read_csv_content
|
||||
from frappe.core.page.data_import_tool.importer import upload
|
||||
print "Importing " + path
|
||||
with open(path, "r") as infile:
|
||||
upload(rows = read_csv_content(infile.read()), ignore_links=ignore_links, overwrite=overwrite, submit_after_import=submit)
|
||||
upload(rows = read_csv_content(infile.read()), ignore_links=ignore_links, overwrite=overwrite,
|
||||
submit_after_import=submit, pre_process=pre_process)
|
||||
|
||||
def export_csv(doctype, path):
|
||||
from frappe.core.page.data_import_tool.exporter import get_template
|
||||
|
|
@ -43,7 +44,7 @@ def export_csv(doctype, path):
|
|||
get_template(doctype=doctype, all_doctypes="Yes", with_data="Yes")
|
||||
csvfile.write(frappe.response.result.encode("utf-8"))
|
||||
|
||||
def export_json(doctype, path, filters=None):
|
||||
def export_json(doctype, path, filters=None, name=None):
|
||||
def post_process(out):
|
||||
del_keys = ('parent', 'parentfield', 'parenttype', 'modified_by', 'creation', 'owner', 'idx')
|
||||
for doc in out:
|
||||
|
|
@ -57,9 +58,10 @@ def export_json(doctype, path, filters=None):
|
|||
if key in child:
|
||||
del child[key]
|
||||
|
||||
from frappe.utils.response import json_handler
|
||||
out = []
|
||||
if frappe.db.get_value("DocType", doctype, "issingle"):
|
||||
if name:
|
||||
out.append(frappe.get_doc(doctype, name).as_dict())
|
||||
elif frappe.db.get_value("DocType", doctype, "issingle"):
|
||||
out.append(frappe.get_doc(doctype).as_dict())
|
||||
else:
|
||||
for doc in frappe.get_all(doctype, fields=["name"], filters=filters, limit_page_length=0, order_by="creation asc"):
|
||||
|
|
@ -79,18 +81,18 @@ def export_fixture(doctype, app):
|
|||
export_json(doctype, frappe.get_app_path(app, "fixtures", frappe.scrub(doctype) + ".json"))
|
||||
|
||||
|
||||
def import_doc(path, overwrite=False, ignore_links=False, ignore_insert=False, insert=False, submit=False):
|
||||
def import_doc(path, overwrite=False, ignore_links=False, ignore_insert=False,
|
||||
insert=False, submit=False, pre_process=None):
|
||||
if os.path.isdir(path):
|
||||
files = [os.path.join(path, f) for f in os.listdir(path)]
|
||||
else:
|
||||
files = [path]
|
||||
|
||||
|
||||
for f in files:
|
||||
if f.endswith(".json"):
|
||||
frappe.flags.mute_emails = True
|
||||
frappe.modules.import_file.import_file_by_path(f, data_import=True)
|
||||
frappe.modules.import_file.import_file_by_path(f, data_import=True, force=True, pre_process=pre_process)
|
||||
frappe.flags.mute_emails = False
|
||||
elif f.endswith(".csv"):
|
||||
import_file_by_path(f, ignore_links=ignore_links, overwrite=overwrite, submit=submit)
|
||||
import_file_by_path(f, ignore_links=ignore_links, overwrite=overwrite, submit=submit, pre_process=pre_process)
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from frappe import _
|
|||
import frappe.permissions
|
||||
import re
|
||||
from frappe.utils.csvutils import UnicodeWriter
|
||||
from frappe.utils import cstr, cint, flt
|
||||
from frappe.utils import cstr, cint, flt, formatdate, format_datetime
|
||||
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys
|
||||
|
||||
reflags = {
|
||||
|
|
@ -31,7 +31,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
if len(doctype) > 1:
|
||||
docs_to_export = doctype[1]
|
||||
doctype = doctype[0]
|
||||
|
||||
|
||||
if not parent_doctype:
|
||||
parent_doctype = doctype
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
|
||||
def get_data_keys_definition():
|
||||
return get_data_keys()
|
||||
|
||||
|
||||
def add_main_header():
|
||||
w.writerow([_('Data Import Template')])
|
||||
w.writerow([get_data_keys_definition().main_table, doctype])
|
||||
|
|
@ -155,6 +155,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
def add_data():
|
||||
def add_data_row(row_group, dt, doc, rowidx):
|
||||
d = doc.copy()
|
||||
meta = frappe.get_meta(dt)
|
||||
if all_doctypes:
|
||||
d.name = '"'+ d.name+'"'
|
||||
|
||||
|
|
@ -162,7 +163,16 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
row_group.append([""] * (len(columns) + 1))
|
||||
row = row_group[rowidx]
|
||||
for i, c in enumerate(columns[column_start_end[dt].start:column_start_end[dt].end]):
|
||||
row[column_start_end[dt].start + i + 1] = d.get(c, "")
|
||||
df = meta.get_field(c)
|
||||
fieldtype = df.fieldtype if df else "Data"
|
||||
value = d.get(c, "")
|
||||
if value:
|
||||
if fieldtype == "Date":
|
||||
value = formatdate(value)
|
||||
elif fieldtype == "Datetime":
|
||||
value = format_datetime(value)
|
||||
|
||||
row[column_start_end[dt].start + i + 1] = value
|
||||
|
||||
if with_data=='Yes':
|
||||
frappe.permissions.can_export(parent_doctype, raise_exception=True)
|
||||
|
|
@ -172,7 +182,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
for doc in data:
|
||||
op = docs_to_export.get("op")
|
||||
names = docs_to_export.get("name")
|
||||
|
||||
|
||||
if names and op:
|
||||
if op == '=' and doc.name not in names:
|
||||
continue
|
||||
|
|
@ -184,7 +194,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
flags = 0
|
||||
for a in re.split('\W+',sflags):
|
||||
flags = flags | reflags.get(a,0)
|
||||
|
||||
|
||||
c = re.compile(names, flags)
|
||||
m = c.match(doc.name)
|
||||
if not m:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ from frappe.utils import cint, cstr, flt
|
|||
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys
|
||||
|
||||
@frappe.whitelist()
|
||||
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None, ignore_links=False):
|
||||
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None,
|
||||
ignore_links=False, pre_process=None):
|
||||
"""upload data"""
|
||||
frappe.flags.mute_emails = True
|
||||
# extra input params
|
||||
|
|
@ -200,6 +201,9 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
|
|||
doc = None
|
||||
|
||||
doc = get_doc(row_idx)
|
||||
if pre_process:
|
||||
pre_process(doc)
|
||||
|
||||
try:
|
||||
frappe.local.message_log = []
|
||||
if parentfield:
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ frappe.PermissionEngine = Class.extend({
|
|||
|
||||
if (d.permlevel===0) {
|
||||
me.setup_user_permissions(d, role_cell);
|
||||
me.setup_if_owner(d, role_cell);
|
||||
}
|
||||
|
||||
var cell = me.add_cell(row, d, "permlevel");
|
||||
|
|
@ -269,6 +270,12 @@ frappe.PermissionEngine = Class.extend({
|
|||
d.help = "";
|
||||
},
|
||||
|
||||
setup_if_owner: function(d, role_cell) {
|
||||
var checkbox = this.add_check(role_cell, d, "if_owner")
|
||||
.removeClass("col-md-4")
|
||||
.css({"margin-top": "15px"});
|
||||
},
|
||||
|
||||
rights: ["read", "write", "create", "delete", "submit", "cancel", "amend",
|
||||
"print", "email", "report", "import", "export", "set_user_permissions"],
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ def get_roles_and_doctypes():
|
|||
name not in ('DocType') and
|
||||
exists(select * from `tabDocField` where parent=dt.name)""")],
|
||||
"roles": [d[0] for d in frappe.db.sql("""select name from tabRole where name not in
|
||||
('Guest', 'Administrator')""")]
|
||||
('Administrator')""")]
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ frappe.UserPermissions = Class.extend({
|
|||
+__("These restrictions will apply for Document Types where 'Apply User Permissions' is checked for the permission rule and a field with this value is present.")
|
||||
+'</p>').appendTo(this.body);
|
||||
|
||||
$.each([[__("Allow User If"), 150], [__("Document Type"), 150], [__("Is"),150], ["", 50]],
|
||||
$.each([[__("Allow User"), 150], [__("If Document Type"), 150], [__("Is"),150], ["", 50]],
|
||||
function(i, col) {
|
||||
$("<th>").html(col[0]).css("width", col[1]+"px")
|
||||
.appendTo(me.table.find("thead tr"));
|
||||
|
|
@ -300,9 +300,9 @@ frappe.UserPermissions = Class.extend({
|
|||
var d = new frappe.ui.Dialog({
|
||||
title: __("Add A New Restriction"),
|
||||
fields: [
|
||||
{fieldtype:"Select", label:__("Allow User If"),
|
||||
{fieldtype:"Select", label:__("Allow User"),
|
||||
options:me.options.users, reqd:1, fieldname:"user"},
|
||||
{fieldtype:"Select", label: __("Select Document Type"), fieldname:"defkey",
|
||||
{fieldtype:"Select", label: __("If Document Type"), fieldname:"defkey",
|
||||
options:me.get_link_names(), reqd:1},
|
||||
{fieldtype:"Link", label:__("Is"), fieldname:"defvalue",
|
||||
options:'[Select]', reqd:1},
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class CustomizeForm(Document):
|
|||
meta = frappe.get_meta(self.doc_type)
|
||||
# doctype property setters
|
||||
for property in self.doctype_properties:
|
||||
if property != "idx" and self.get(property) != meta.get(property):
|
||||
if self.get(property) != meta.get(property):
|
||||
self.make_property_setter(property=property, value=self.get(property),
|
||||
property_type=self.doctype_properties[property])
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ class CustomizeForm(Document):
|
|||
continue
|
||||
|
||||
for property in self.docfield_properties:
|
||||
if df.get(property) != meta_df[0].get(property):
|
||||
if property != "idx" and df.get(property) != meta_df[0].get(property):
|
||||
if property == "fieldtype":
|
||||
self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property))
|
||||
|
||||
|
|
@ -260,4 +260,3 @@ class CustomizeForm(Document):
|
|||
and ifnull(field_name, '')!='naming_series'""", self.doc_type)
|
||||
frappe.clear_cache(doctype=self.doc_type)
|
||||
self.fetch_to_customize()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
# DEPRECATED only for reference
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# app configuration
|
||||
|
||||
# database config
|
||||
db_name = '%(db_name)s'
|
||||
db_password = '%(db_password)s'
|
||||
|
||||
# user attachments stored in
|
||||
files_path = 'public/files'
|
||||
public_path = 'public'
|
||||
|
||||
# max file attachment size (default 1MB)
|
||||
max_file_size = 1000000
|
||||
|
||||
# max email size in bytes
|
||||
max_email_size = 0
|
||||
|
||||
# total pop session timeout in seconds
|
||||
pop_timeout = 0
|
||||
|
||||
# generate schema (.txt files)
|
||||
developer_mode = 0
|
||||
|
||||
# clear cache on refresh
|
||||
auto_cache_clear = 0
|
||||
|
||||
# email logs to admin (beta)
|
||||
admin_email_notification = 0
|
||||
|
||||
# user timezone
|
||||
user_timezone = 'Asia/Calcutta'
|
||||
|
||||
# outgoing mail settings
|
||||
mail_server = None
|
||||
mail_login = None
|
||||
mail_password = None
|
||||
mail_port = None
|
||||
use_ssl = None
|
||||
auto_email_id = None
|
||||
|
||||
# logging settings
|
||||
log_file_name = 'logs/error_log.txt'
|
||||
debug_log_dbs = []
|
||||
log_level = 'logging.INFO'
|
||||
log_file_size = 5000
|
||||
log_file_backup_count = 5
|
||||
|
||||
|
|
@ -2,7 +2,7 @@ ar العربية
|
|||
bg bǎlgarski
|
||||
bs bosanski
|
||||
ca català
|
||||
cz česky
|
||||
cs česky
|
||||
da dansk
|
||||
de deutsch
|
||||
el ελληνικά
|
||||
|
|
|
|||
|
|
@ -511,6 +511,10 @@ class Database:
|
|||
tabSingles where doctype=%s and field=%s""", (doctype, fieldname))
|
||||
return val[0][0] if val else None
|
||||
|
||||
def get_singles_value(self, *args, **kwargs):
|
||||
"""Alias for get_single_value"""
|
||||
return self.get_single_value(*args, **kwargs)
|
||||
|
||||
def _get_values_from_table(self, fields, filters, doctype, as_dict, debug, order_by=None, update=None):
|
||||
fl = []
|
||||
if isinstance(fields, (list, tuple)):
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ def get(name):
|
|||
Return the :term:`doclist` of the `Page` specified by `name`
|
||||
"""
|
||||
page = frappe.get_doc('Page', name)
|
||||
if has_permission(page):
|
||||
if page.is_permitted():
|
||||
page.load_assets()
|
||||
return page
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ from frappe.utils.user import get_enabled_system_users
|
|||
weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
||||
|
||||
class Event(Document):
|
||||
def get_route(self):
|
||||
"""for test-case"""
|
||||
return "/Event/" + self.name
|
||||
|
||||
def validate(self):
|
||||
if self.starts_on and self.ends_on and self.starts_on > self.ends_on:
|
||||
frappe.msgprint(frappe._("Event end must be after start"), raise_exception=True)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 0,
|
||||
"label": "Assigned To",
|
||||
"label": "Allocated To",
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
"in_dialog": 0,
|
||||
"issingle": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-05-04 07:44:46.567785",
|
||||
"modified": "2015-06-11 16:06:34.561469",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "ToDo",
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ sender_field = "sender"
|
|||
class ToDo(Document):
|
||||
def validate(self):
|
||||
if self.is_new():
|
||||
self.add_assign_comment(frappe._("Assigned to {0}").format(get_fullname(self.owner)), "Assigned")
|
||||
self.add_assign_comment(frappe._("Assigned to {0}: {1}").format(get_fullname(self.owner), self.description), "Assigned")
|
||||
else:
|
||||
cur_status = frappe.db.get_value("ToDo", self.name, "status")
|
||||
if cur_status != self.status:
|
||||
# NOTE the previous value is only available in validate method
|
||||
if self.get_db_value("status") != self.status:
|
||||
self.add_assign_comment(frappe._("Assignment closed by {0}".format(get_fullname(frappe.session.user))),
|
||||
"Assignment Completed")
|
||||
|
||||
|
|
@ -25,20 +25,29 @@ class ToDo(Document):
|
|||
self.update_in_reference()
|
||||
|
||||
def on_trash(self):
|
||||
# unlink assignment comment
|
||||
frappe.db.sql("""update `tabComment` set reference_doctype=null and reference_name=null
|
||||
where reference_doctype='ToDo' and reference_name=%s""", self.name)
|
||||
|
||||
self.update_in_reference()
|
||||
|
||||
def add_assign_comment(self, text, comment_type):
|
||||
if not self.reference_type and self.reference_name:
|
||||
return
|
||||
|
||||
frappe.get_doc({
|
||||
comment = frappe.get_doc({
|
||||
"doctype":"Comment",
|
||||
"comment_by": frappe.session.user,
|
||||
"comment_type": comment_type,
|
||||
"comment_doctype": self.reference_type,
|
||||
"comment_docname": self.reference_name,
|
||||
"comment": """{text}""".format(text=text)
|
||||
}).insert(ignore_permissions=True)
|
||||
"comment": """{text}""".format(text=text),
|
||||
"reference_doctype": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
comment.flags.ignore_permissions = True
|
||||
comment.flags.ignore_links = True
|
||||
comment.insert()
|
||||
|
||||
def update_in_reference(self):
|
||||
if not (self.reference_type and self.reference_name):
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ def get_docinfo(doc=None, doctype=None, name=None):
|
|||
"assignments": get_assignments(doc.doctype, doc.name),
|
||||
"permissions": get_doc_permissions(doc),
|
||||
"shared": frappe.share.get_users(doc.doctype, doc.name,
|
||||
fields=["user", "read", "write", "share"])
|
||||
fields=["user", "read", "write", "share", "everyone"])
|
||||
}
|
||||
|
||||
def get_user_permissions(meta):
|
||||
|
|
@ -105,7 +105,8 @@ def get_attachments(dt, dn):
|
|||
|
||||
def get_comments(dt, dn, limit=100):
|
||||
comments = frappe.db.sql("""select name, comment, comment_by, creation,
|
||||
comment_type, "Comment" as doctype from `tabComment`
|
||||
reference_doctype, reference_name, comment_type, "Comment" as doctype
|
||||
from `tabComment`
|
||||
where comment_doctype=%s and comment_docname=%s
|
||||
order by creation desc limit %s""" % ('%s','%s', limit),
|
||||
(dt, dn), as_dict=1)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from frappe.model.workflow import get_workflow_name
|
|||
from frappe.utils import get_html_format
|
||||
from frappe.translate import make_dict_from_messages, extract_messages_from_code
|
||||
from frappe.utils.jinja import render_include
|
||||
from frappe.build import html_to_js_template
|
||||
|
||||
######
|
||||
|
||||
|
|
@ -70,12 +71,25 @@ class FormMeta(Meta):
|
|||
self.add_code_via_hook("doctype_js", "__js")
|
||||
self.add_code_via_hook("doctype_list_js", "__list_js")
|
||||
self.add_custom_script()
|
||||
self.add_html_templates(path)
|
||||
|
||||
def _add_code(self, path, fieldname):
|
||||
js = frappe.read_file(path)
|
||||
if js:
|
||||
self.set(fieldname, (self.get(fieldname) or "") + "\n\n" + render_include(js))
|
||||
|
||||
def add_html_templates(self, path):
|
||||
if self.custom:
|
||||
return
|
||||
js = ""
|
||||
for fname in os.listdir(path):
|
||||
if fname.endswith(".html"):
|
||||
with open(os.path.join(path, fname), 'r') as f:
|
||||
template = unicode(f.read(), "utf-8")
|
||||
js += html_to_js_template(fname, template)
|
||||
|
||||
self.set("__js", (self.get("__js") or "") + js)
|
||||
|
||||
def add_code_via_hook(self, hook, fieldname):
|
||||
for app_name in frappe.get_installed_apps():
|
||||
code_hook = frappe.get_hooks(hook, default={}, app_name=app_name)
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ def get_linked_docs(doctype, name, metadata_loaded=None, no_metadata=False):
|
|||
filters=[[dt, link.get("fieldname"), '=', name]])
|
||||
|
||||
except frappe.PermissionError:
|
||||
frappe.local.message_log.pop()
|
||||
continue
|
||||
|
||||
if ret:
|
||||
|
|
|
|||
|
|
@ -137,7 +137,8 @@ def apply_permissions(data):
|
|||
|
||||
if ((item.type=="doctype" and item.name in user.can_read)
|
||||
or (item.type=="page" and item.name in allowed_pages)
|
||||
or (item.type=="report" and item.doctype in user.can_get_report)):
|
||||
or (item.type=="report" and item.doctype in user.can_get_report)
|
||||
or item.type=="help"):
|
||||
|
||||
new_items.append(item)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from frappe.modules import scrub, get_module_path
|
|||
from frappe.utils import flt, cint, get_html_format
|
||||
from frappe.translate import send_translations
|
||||
import frappe.desk.reportview
|
||||
from frappe.permissions import get_role_permissions
|
||||
|
||||
def get_report_doc(report_name):
|
||||
doc = frappe.get_doc("Report", report_name)
|
||||
|
|
@ -144,20 +145,35 @@ def get_filtered_data(ref_doctype, columns, data):
|
|||
linked_doctypes = get_linked_doctypes(columns, data)
|
||||
match_filters_per_doctype = get_user_match_filters(linked_doctypes, ref_doctype)
|
||||
shared = frappe.share.get_shared(ref_doctype)
|
||||
columns_dict = get_columns_dict(columns)
|
||||
|
||||
role_permissions = get_role_permissions(frappe.get_meta(ref_doctype))
|
||||
if_owner = role_permissions.get("if_owner", {}).get("report")
|
||||
|
||||
if match_filters_per_doctype:
|
||||
for row in data:
|
||||
if shared and row[linked_doctypes[ref_doctype]] in shared:
|
||||
result.append(row)
|
||||
|
||||
elif has_match(row, linked_doctypes, match_filters_per_doctype):
|
||||
elif has_match(row, linked_doctypes, match_filters_per_doctype, ref_doctype, if_owner, columns_dict):
|
||||
result.append(row)
|
||||
else:
|
||||
result = list(data)
|
||||
|
||||
return result
|
||||
|
||||
def has_match(row, linked_doctypes, doctype_match_filters):
|
||||
def has_match(row, linked_doctypes, doctype_match_filters, ref_doctype, if_owner, columns_dict):
|
||||
"""Returns True if after evaluating permissions for each linked doctype
|
||||
- There is an owner match for the ref_doctype
|
||||
- `and` There is a user permission match for all linked doctypes
|
||||
|
||||
Returns True if the row is empty
|
||||
|
||||
Note:
|
||||
Each doctype could have multiple conflicting user permission doctypes.
|
||||
Hence even if one of the sets allows a match, it is true.
|
||||
This behavior is equivalent to the trickling of user permissions of linked doctypes to the ref doctype.
|
||||
"""
|
||||
resultant_match = True
|
||||
|
||||
if not row:
|
||||
|
|
@ -167,20 +183,33 @@ def has_match(row, linked_doctypes, doctype_match_filters):
|
|||
for doctype, filter_list in doctype_match_filters.items():
|
||||
matched_for_doctype = False
|
||||
|
||||
for match_filters in filter_list:
|
||||
match = True
|
||||
for dt, idx in linked_doctypes.items():
|
||||
if dt in match_filters and row[idx] not in match_filters[dt]:
|
||||
match = False
|
||||
if doctype==ref_doctype and if_owner:
|
||||
idx = linked_doctypes.get("User")
|
||||
if (idx is not None
|
||||
and row[idx]==frappe.session.user
|
||||
and columns_dict[idx]==columns_dict.get("owner")):
|
||||
# owner match is true
|
||||
matched_for_doctype = True
|
||||
|
||||
if not matched_for_doctype:
|
||||
for match_filters in filter_list:
|
||||
match = True
|
||||
for dt, idx in linked_doctypes.items():
|
||||
# case handled above
|
||||
if dt=="User" and columns_dict[idx]==columns_dict.get("owner"):
|
||||
continue
|
||||
|
||||
if dt in match_filters and row[idx] not in match_filters[dt]:
|
||||
match = False
|
||||
break
|
||||
|
||||
# each doctype could have multiple conflicting user permission doctypes, hence using OR
|
||||
# so that even if one of the sets allows a match, it is true
|
||||
matched_for_doctype = matched_for_doctype or match
|
||||
|
||||
if matched_for_doctype:
|
||||
break
|
||||
|
||||
# each doctype could have multiple conflicting user permission doctypes, hence using OR
|
||||
# so that even if one of the sets allows a match, it is true
|
||||
matched_for_doctype = matched_for_doctype or match
|
||||
|
||||
if matched_for_doctype:
|
||||
break
|
||||
|
||||
# each doctype's user permissions should match the row! hence using AND
|
||||
resultant_match = resultant_match and matched_for_doctype
|
||||
|
||||
|
|
@ -192,16 +221,16 @@ def has_match(row, linked_doctypes, doctype_match_filters):
|
|||
def get_linked_doctypes(columns, data):
|
||||
linked_doctypes = {}
|
||||
|
||||
for idx, col in enumerate(columns):
|
||||
if isinstance(col, basestring):
|
||||
col = col.split(":")
|
||||
if len(col) > 1 and col[1].startswith("Link"):
|
||||
link_dt = col[1].split("/")[1]
|
||||
linked_doctypes[link_dt] = idx
|
||||
columns_dict = get_columns_dict(columns)
|
||||
|
||||
# dict
|
||||
elif col.get("fieldtype")=="Link" and col.get("options"):
|
||||
linked_doctypes[col["options"]] = col["fieldname"]
|
||||
for idx, col in enumerate(columns):
|
||||
df = columns_dict[idx]
|
||||
if df.get("fieldtype")=="Link":
|
||||
if isinstance(col, basestring):
|
||||
linked_doctypes[df["options"]] = idx
|
||||
else:
|
||||
# dict
|
||||
linked_doctypes[df["options"]] = df["fieldname"]
|
||||
|
||||
# remove doctype if column is empty
|
||||
for doctype, key in linked_doctypes.items():
|
||||
|
|
@ -210,6 +239,35 @@ def get_linked_doctypes(columns, data):
|
|||
|
||||
return linked_doctypes
|
||||
|
||||
def get_columns_dict(columns):
|
||||
"""Returns a dict with column docfield values as dict
|
||||
The keys for the dict are both idx and fieldname,
|
||||
so either index or fieldname can be used to search for a column's docfield properties
|
||||
"""
|
||||
columns_dict = {}
|
||||
for idx, col in enumerate(columns):
|
||||
col_dict = {}
|
||||
|
||||
# string
|
||||
if isinstance(col, basestring):
|
||||
col = col.split(":")
|
||||
if len(col) > 1:
|
||||
if "/" in col[1]:
|
||||
col_dict["fieldtype"], col_dict["options"] = col[1].split("/")
|
||||
else:
|
||||
col_dict["fieldtype"] = col[1]
|
||||
|
||||
col_dict["fieldname"] = col[0].lower()
|
||||
|
||||
# dict
|
||||
else:
|
||||
col_dict.update(col)
|
||||
|
||||
columns_dict[idx] = col_dict
|
||||
columns_dict[col_dict["fieldname"]] = col_dict
|
||||
|
||||
return columns_dict
|
||||
|
||||
def get_user_match_filters(doctypes, ref_doctype):
|
||||
match_filters = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,13 +72,13 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
|
|||
unsubscribe_method, unsubscribe_params)
|
||||
|
||||
# add to queue
|
||||
email_content = add_unsubscribe_link(email_content, email, reference_doctype, reference_name,
|
||||
unsubscribe_url, unsubscribe_message)
|
||||
email_content = add_unsubscribe_link(email_content, email, reference_doctype,
|
||||
reference_name, unsubscribe_url, unsubscribe_message)
|
||||
|
||||
email_text_context += "\n" + _("Unsubscribe link: {0}").format(unsubscribe_url)
|
||||
email_text_context += "\n" + _("This email was sent to {0}. To unsubscribe click on this link: {1}").format(email, unsubscribe_url)
|
||||
|
||||
add(email, sender, subject, email_content, email_text_context, reference_doctype, reference_name, attachments, reply_to,
|
||||
cc, message_id, send_after)
|
||||
add(email, sender, subject, email_content, email_text_context, reference_doctype,
|
||||
reference_name, attachments, reply_to, cc, message_id, send_after)
|
||||
|
||||
def add(email, sender, subject, formatted, text_content=None,
|
||||
reference_doctype=None, reference_name=None, attachments=None, reply_to=None,
|
||||
|
|
@ -112,7 +112,8 @@ def check_bulk_limit(recipients):
|
|||
|
||||
# No limit for own email settings
|
||||
smtp_server = SMTPServer()
|
||||
if smtp_server.email_account and not getattr(smtp_server.email_account,
|
||||
|
||||
if smtp_server.email_account and getattr(smtp_server.email_account,
|
||||
"from_site_config", False) or frappe.flags.in_test:
|
||||
monthly_bulk_mail_limit = frappe.conf.get('monthly_bulk_mail_limit') or 500
|
||||
|
||||
|
|
@ -121,11 +122,12 @@ def check_bulk_limit(recipients):
|
|||
BulkLimitCrossedError)
|
||||
|
||||
def add_unsubscribe_link(message, email, reference_doctype, reference_name, unsubscribe_url, unsubscribe_message):
|
||||
unsubscribe_link = """<div style="padding: 7px; margin-top: 7px;">
|
||||
<a href="{unsubscribe_url}" style="color: #8D99A6; text-decoration: none; font-size: 85%;" target="_blank">
|
||||
{unsubscribe_message}
|
||||
unsubscribe_link = """<div style="padding: 7px; text-align: center; color: #8D99A6;">
|
||||
{email}. <a href="{unsubscribe_url}" style="color: #8D99A6; text-decoration: underline;
|
||||
target="_blank">{unsubscribe_message}.
|
||||
</a>
|
||||
</div>""".format(unsubscribe_url = unsubscribe_url,
|
||||
email= _("This email was sent to {0}").format(email),
|
||||
unsubscribe_message = unsubscribe_message or _("Unsubscribe from this list"))
|
||||
|
||||
message = message.replace("<!--unsubscribe link here-->", unsubscribe_link)
|
||||
|
|
@ -164,7 +166,7 @@ def unsubscribe(doctype, name, email):
|
|||
return_unsubscribed_page(email, doctype, name)
|
||||
|
||||
def return_unsubscribed_page(email, doctype, name):
|
||||
frappe.respond_as_web_page(_("Unsubscribed"), _("{0} has has left the conversation in {1} {2}").format(email, _(doctype), name))
|
||||
frappe.respond_as_web_page(_("Unsubscribed"), _("{0} has left the conversation in {1} {2}").format(email, _(doctype), name))
|
||||
|
||||
def flush(from_test=False):
|
||||
"""flush email queue, every time: called from scheduler"""
|
||||
|
|
@ -176,6 +178,9 @@ def flush(from_test=False):
|
|||
msgprint(_("Emails are muted"))
|
||||
from_test = True
|
||||
|
||||
frappe.db.sql("""update `tabBulk Email` set status='Expired'
|
||||
where datediff(curdate(), creation) > 3""", auto_commit=auto_commit)
|
||||
|
||||
for i in xrange(500):
|
||||
email = frappe.db.sql("""select * from `tabBulk Email` where
|
||||
status='Not Sent' and ifnull(send_after, "2000-01-01 00:00:00") < %s
|
||||
|
|
|
|||
|
|
@ -73,13 +73,14 @@
|
|||
"icon": "icon-envelope",
|
||||
"idx": 1,
|
||||
"in_create": 1,
|
||||
"modified": "2015-04-01 10:00:20.892939",
|
||||
"modified": "2015-05-21 07:26:13.627637",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Bulk Email",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
|
|
|
|||
|
|
@ -38,9 +38,15 @@ frappe.ui.form.on("Email Account", {
|
|||
+ toTitle(frm.doc.email_id.split("@")[0].replace(/[._]/g, " ")));
|
||||
}
|
||||
},
|
||||
enable_incoming: function(frm) {
|
||||
frm.set_df_property("append_to", "reqd", frm.doc.enable_incoming);
|
||||
},
|
||||
onload: function(frm) {
|
||||
frm.set_df_property("append_to", "only_select", true);
|
||||
frm.set_query("append_to", "frappe.email.doctype.email_account.email_account.get_append_to");
|
||||
},
|
||||
refresh: function(frm) {
|
||||
frm.set_df_property("append_to", "reqd", frm.doc.enable_incoming);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,21 @@
|
|||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "login_id_is_different",
|
||||
"fieldtype": "Check",
|
||||
"label": "Login Id is Different",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "login_id_is_different",
|
||||
"fieldname": "login_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Login Id",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "password",
|
||||
|
|
@ -276,6 +291,15 @@
|
|||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_outgoing",
|
||||
"description": "Uses the Email ID mentioned in this Account as the Sender for all emails sent using this Account. ",
|
||||
"fieldname": "always_use_account_email_id_as_sender",
|
||||
"fieldtype": "Check",
|
||||
"label": "Always use Account's Email ID as Sender",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "signature_section",
|
||||
|
|
@ -364,6 +388,7 @@
|
|||
{
|
||||
"allow_on_submit": 0,
|
||||
"depends_on": "enable_auto_reply",
|
||||
"description": "ProTip: Add <code>Reference: {{ reference_doctype }} {{ reference_name }}</code> to send document reference",
|
||||
"fieldname": "auto_reply_message",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
|
|
@ -404,7 +429,7 @@
|
|||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-04-21 15:31:02.269145",
|
||||
"modified": "2015-07-16 10:11:06.466258",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Account",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import validate_email_add, cint, get_datetime, DATE_FORMAT
|
||||
from frappe.utils import validate_email_add, cint, get_datetime, DATE_FORMAT, strip, comma_or
|
||||
from frappe.utils.user import is_system_user
|
||||
from frappe.utils.jinja import render_template
|
||||
from frappe.email.smtp import SMTPServer
|
||||
from frappe.email.receive import POP3Server, Email
|
||||
from poplib import error_proto
|
||||
|
|
@ -30,9 +32,18 @@ class EmailAccount(Document):
|
|||
if self.email_id:
|
||||
validate_email_add(self.email_id, True)
|
||||
|
||||
if self.login_id_is_different:
|
||||
if not self.login_id:
|
||||
frappe.throw(_("Login Id is required"))
|
||||
else:
|
||||
self.login_id = None
|
||||
|
||||
if frappe.local.flags.in_patch or frappe.local.flags.in_test:
|
||||
return
|
||||
|
||||
if self.enable_incoming and not self.append_to:
|
||||
frappe.throw(_("Append To is mandatory for incoming mails"))
|
||||
|
||||
if not frappe.local.flags.in_install and not frappe.local.flags.in_patch:
|
||||
if self.enable_incoming:
|
||||
self.get_pop3()
|
||||
|
|
@ -44,6 +55,11 @@ class EmailAccount(Document):
|
|||
for e in self.get_unreplied_notification_emails():
|
||||
validate_email_add(e, True)
|
||||
|
||||
if self.enable_incoming and self.append_to:
|
||||
valid_doctypes = [d[0] for d in get_append_to()]
|
||||
if self.append_to not in valid_doctypes:
|
||||
frappe.throw(_("Append To can be one of {0}").format(comma_or(valid_doctypes)))
|
||||
|
||||
def on_update(self):
|
||||
"""Check there is only one default of each type."""
|
||||
self.there_must_be_only_one_default()
|
||||
|
|
@ -67,7 +83,8 @@ class EmailAccount(Document):
|
|||
if not self.smtp_server:
|
||||
frappe.throw(_("{0} is required").format("SMTP Server"))
|
||||
|
||||
server = SMTPServer(login = self.email_id,
|
||||
server = SMTPServer(login = getattr(self, "login_id", None) \
|
||||
or self.email_id,
|
||||
password = self.password,
|
||||
server = self.smtp_server,
|
||||
port = cint(self.smtp_port),
|
||||
|
|
@ -80,7 +97,7 @@ class EmailAccount(Document):
|
|||
args = {
|
||||
"host": self.pop3_server,
|
||||
"use_ssl": self.use_ssl,
|
||||
"username": self.email_id,
|
||||
"username": getattr(self, "login_id", None) or self.email_id,
|
||||
"password": self.password
|
||||
}
|
||||
|
||||
|
|
@ -171,36 +188,39 @@ class EmailAccount(Document):
|
|||
sender_field = None
|
||||
|
||||
if in_reply_to:
|
||||
if "@" in in_reply_to:
|
||||
if "@{0}".format(frappe.local.site) in in_reply_to:
|
||||
|
||||
# reply to a communication sent from the system
|
||||
in_reply_to = in_reply_to.split("@", 1)[0]
|
||||
in_reply_to, domain = in_reply_to.split("@", 1)
|
||||
|
||||
if frappe.db.exists("Communication", in_reply_to):
|
||||
parent = frappe.get_doc("Communication", in_reply_to)
|
||||
|
||||
if parent.reference_name:
|
||||
if self.append_to:
|
||||
# parent must reference only if name matches
|
||||
if parent.reference_doctype==self.append_to:
|
||||
# parent same as parent of last communication
|
||||
parent = frappe.get_doc(parent.reference_doctype,
|
||||
parent.reference_name)
|
||||
else:
|
||||
parent = frappe.get_doc(parent.reference_doctype,
|
||||
parent.reference_name)
|
||||
parent = frappe.get_doc(parent.reference_doctype,
|
||||
parent.reference_name)
|
||||
|
||||
if not parent and self.append_to and subject_field and sender_field:
|
||||
# try and match by subject and sender
|
||||
# if sent by same sender with same subject,
|
||||
# append it to old coversation
|
||||
if not parent and self.append_to and sender_field:
|
||||
if subject_field:
|
||||
# try and match by subject and sender
|
||||
# if sent by same sender with same subject,
|
||||
# append it to old coversation
|
||||
subject = strip(re.sub("^\s*(Re|RE)[^:]*:\s*", "", email.subject))
|
||||
|
||||
subject = re.sub("Re[^:]*:\s*", "", email.subject)
|
||||
parent = frappe.db.get_all(self.append_to, filters={
|
||||
sender_field: email.from_email,
|
||||
subject_field: ("like", "%{0}%".format(subject)),
|
||||
"creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT))
|
||||
}, fields="name")
|
||||
|
||||
parent = frappe.db.get_all(self.append_to, filters={
|
||||
sender_field: email.from_email,
|
||||
subject_field: ("like", "%{0}%".format(subject)),
|
||||
"creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT))
|
||||
}, fields="name")
|
||||
# match only subject field
|
||||
# when the from_email is of a user in the system
|
||||
# and subject is atleast 10 chars long
|
||||
if not parent and len(subject) > 10 and is_system_user(email.from_email):
|
||||
parent = frappe.db.get_all(self.append_to, filters={
|
||||
subject_field: ("like", "%{0}%".format(subject)),
|
||||
"creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT))
|
||||
}, fields="name")
|
||||
|
||||
if parent:
|
||||
parent = frappe.get_doc(self.append_to, parent[0].name)
|
||||
|
|
@ -218,8 +238,17 @@ class EmailAccount(Document):
|
|||
|
||||
parent.flags.ignore_mandatory = True
|
||||
|
||||
parent.insert(ignore_permissions=True)
|
||||
try:
|
||||
parent.insert(ignore_permissions=True)
|
||||
except frappe.DuplicateEntryError:
|
||||
# try and find matching parent
|
||||
parent_name = frappe.db.get_value(self.append_to, {sender_field: email.from_email})
|
||||
if parent_name:
|
||||
parent.name = parent_name
|
||||
else:
|
||||
parent = None
|
||||
|
||||
# NOTE if parent isn't found and there's no subject match, it is likely that it is a new conversation thread and hence is_first = True
|
||||
communication.is_first = True
|
||||
|
||||
if parent:
|
||||
|
|
@ -228,14 +257,14 @@ class EmailAccount(Document):
|
|||
|
||||
def send_auto_reply(self, communication, email):
|
||||
"""Send auto reply if set."""
|
||||
if self.auto_reply_message:
|
||||
if self.enable_auto_reply:
|
||||
communication.set_incoming_outgoing_accounts()
|
||||
|
||||
frappe.sendmail(recipients = [email.from_email],
|
||||
sender = self.email_id,
|
||||
reply_to = communication.incoming_email_account,
|
||||
subject = _("Re: ") + communication.subject,
|
||||
content = self.auto_reply_message or \
|
||||
content = render_template(self.auto_reply_message or "", communication.as_dict()) or \
|
||||
frappe.get_template("templates/emails/auto_reply.html").render(communication.as_dict()),
|
||||
reference_doctype = communication.reference_doctype,
|
||||
reference_name = communication.reference_name,
|
||||
|
|
@ -254,7 +283,7 @@ class EmailAccount(Document):
|
|||
frappe.db.sql("update `tabCommunication` set email_account='' where email_account=%s", self.name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_append_to(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None):
|
||||
if not txt: txt = ""
|
||||
return [[d] for d in frappe.get_hooks("email_append_to") if txt in d]
|
||||
|
||||
|
|
|
|||
|
|
@ -19,3 +19,5 @@ frappe.listview_settings["Email Account"] = {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
frappe.help.youtube_id["Email Account"] = "YFYe0DrB95o";
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class TestEmailAccount(unittest.TestCase):
|
|||
# send
|
||||
sent_name = make(subject = "Test", content="test content",
|
||||
recipients="test_receiver@example.com", sender="test@example.com",
|
||||
send_email=True)
|
||||
send_email=True)["name"]
|
||||
|
||||
sent_mail = email.message_from_string(frappe.get_last_doc("Bulk Email").message)
|
||||
with open(os.path.join(os.path.dirname(__file__), "test_mails", "reply-1.raw"), "r") as f:
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@
|
|||
"fieldname": "message_examples",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Message Examples",
|
||||
"options": "<h5>Message Example (Markdown)</h5>\n<pre>Transaction {{ doc.name }} has exceeded Due Date. Please take relevant action\n\n#### Details\n\nCustomer: {{ doc.customer }}\nAmount: {{ doc.total_amount }}</pre>",
|
||||
"options": "<h5>Message Example</h5>\n\n<pre>\n<h3>Order Overdue</h3>\n\n<p>Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.</p>\n\n<h4>Details</h4>\n\n<ul>\n<li>Customer: {{ doc.customer }}\n<li>Amount: {{ doc.total_amount }}\n</ul>\n</pre>",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
}
|
||||
],
|
||||
"icon": "icon-envelope",
|
||||
"modified": "2015-03-25 06:20:07.472953",
|
||||
"modified": "2015-07-09 00:27:00.169741",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Alert",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe.utils.pdf import get_pdf
|
||||
from frappe.email.smtp import get_outgoing_email_account
|
||||
from frappe.utils.data import get_url, scrub_urls, strip, expand_relative_urls
|
||||
from frappe.utils import get_url, scrub_urls, strip, expand_relative_urls, cint
|
||||
import email.utils
|
||||
from markdown2 import markdown
|
||||
|
||||
|
|
@ -211,9 +211,11 @@ def get_formatted_html(subject, message, footer=None, print_html=None):
|
|||
# imported here to avoid cyclic import
|
||||
|
||||
message = scrub_urls(message)
|
||||
email_account = get_outgoing_email_account(False)
|
||||
rendered_email = frappe.get_template("templates/emails/standard.html").render({
|
||||
"content": message,
|
||||
"footer": get_footer(footer),
|
||||
"signature": get_signature(email_account),
|
||||
"footer": get_footer(email_account, footer),
|
||||
"title": subject,
|
||||
"print_html": print_html,
|
||||
"subject": subject
|
||||
|
|
@ -221,21 +223,29 @@ def get_formatted_html(subject, message, footer=None, print_html=None):
|
|||
|
||||
return rendered_email
|
||||
|
||||
def get_footer(footer=None):
|
||||
def get_signature(email_account):
|
||||
if email_account and email_account.add_signature and email_account.signature:
|
||||
return "<br><br>" + email_account.signature
|
||||
else:
|
||||
return ""
|
||||
|
||||
def get_footer(email_account, footer=None):
|
||||
"""append a footer (signature)"""
|
||||
footer = footer or ""
|
||||
|
||||
email_account = get_outgoing_email_account(False)
|
||||
|
||||
if email_account and email_account.add_signature and email_account.signature:
|
||||
footer += email_account.signature
|
||||
|
||||
if email_account and email_account.footer:
|
||||
footer += email_account.footer
|
||||
else:
|
||||
for default_mail_footer in frappe.get_hooks("default_mail_footer"):
|
||||
footer += default_mail_footer
|
||||
|
||||
footer += "<!--unsubscribe link here-->"
|
||||
|
||||
company_address = frappe.db.get_default("email_footer_address")
|
||||
|
||||
if company_address:
|
||||
footer += '<div style="text-align: center; color: #8d99a6">{0}</div>'\
|
||||
.format(company_address.replace("\n", "<br>"))
|
||||
|
||||
if not cint(frappe.db.get_default("disable_standard_email_footer")):
|
||||
for default_mail_footer in frappe.get_hooks("default_mail_footer"):
|
||||
footer += default_mail_footer
|
||||
|
||||
return footer
|
||||
|
|
|
|||
|
|
@ -44,28 +44,37 @@ class POP3Server:
|
|||
|
||||
self.pop.user(self.settings.username)
|
||||
self.pop.pass_(self.settings.password)
|
||||
|
||||
# connection established!
|
||||
return True
|
||||
|
||||
except _socket.error:
|
||||
# Invalid mail server -- due to refusing connection
|
||||
frappe.msgprint(_('Invalid Mail Server. Please rectify and try again.'))
|
||||
raise
|
||||
|
||||
except poplib.error_proto, e:
|
||||
if not "SYS/TEMP" in str(e):
|
||||
if self.is_temporary_system_problem(e):
|
||||
return False
|
||||
|
||||
else:
|
||||
frappe.msgprint(_('Invalid User Name or Support Password. Please rectify and try again.'))
|
||||
raise
|
||||
raise
|
||||
|
||||
def get_messages(self):
|
||||
"""Returns new email messages in a list."""
|
||||
if not self.check_mails():
|
||||
return # nothing to do
|
||||
|
||||
self.latest_messages = []
|
||||
|
||||
frappe.db.commit()
|
||||
self.connect()
|
||||
|
||||
if not self.connect():
|
||||
return []
|
||||
|
||||
try:
|
||||
# track if errors arised
|
||||
self.errors = False
|
||||
self.latest_messages = []
|
||||
pop_list = self.pop.list()[1]
|
||||
num = num_copy = len(pop_list)
|
||||
|
||||
|
|
@ -138,6 +147,16 @@ class POP3Server:
|
|||
def has_login_limit_exceeded(self, e):
|
||||
return "-ERR Exceeded the login limit" in strip(cstr(e.message))
|
||||
|
||||
def is_temporary_system_problem(self, e):
|
||||
messages = (
|
||||
"-ERR [SYS/TEMP] Temporary system problem. Please try again later.",
|
||||
"Connection timed out",
|
||||
)
|
||||
for message in messages:
|
||||
if message in strip(cstr(e.message)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def validate_pop(self, pop_meta):
|
||||
# throttle based on email size
|
||||
if not self.max_email_size:
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ def send(email, append_to=None):
|
|||
|
||||
try:
|
||||
smtpserver = SMTPServer(append_to=append_to)
|
||||
if hasattr(smtpserver, "always_use_login_id_as_sender") and \
|
||||
cint(smtpserver.always_use_login_id_as_sender) and smtpserver.login:
|
||||
if hasattr(smtpserver, "always_use_account_email_id_as_sender") and \
|
||||
cint(smtpserver.always_use_account_email_id_as_sender) and smtpserver.login:
|
||||
if not email.reply_to:
|
||||
email.reply_to = email.sender
|
||||
email.sender = smtpserver.login
|
||||
|
|
@ -55,7 +55,8 @@ def get_outgoing_email_account(raise_exception_not_set=True, append_to=None):
|
|||
email_account = get_default_outgoing_email_account(raise_exception_not_set=raise_exception_not_set)
|
||||
|
||||
if not email_account and raise_exception_not_set:
|
||||
frappe.throw(_("Please setup default Email Account from Setup > Email > Email Account"))
|
||||
frappe.throw(_("Please setup default Email Account from Setup > Email > Email Account"),
|
||||
frappe.OutgoingEmailError)
|
||||
|
||||
frappe.local.outgoing_email_account[append_to or "default"] = email_account
|
||||
|
||||
|
|
@ -114,11 +115,13 @@ class SMTPServer:
|
|||
self.email_account = get_outgoing_email_account(raise_exception_not_set=False, append_to=append_to)
|
||||
if self.email_account:
|
||||
self.server = self.email_account.smtp_server
|
||||
self.login = self.email_account.email_id
|
||||
self.login = getattr(self.email_account, "login_id", None) \
|
||||
or self.email_account.email_id
|
||||
self.password = self.email_account.password
|
||||
self.port = self.email_account.smtp_port
|
||||
self.use_ssl = self.email_account.use_tls
|
||||
self.sender = self.email_account.email_id
|
||||
self.always_use_account_email_id_as_sender = self.email_account.get("always_use_account_email_id_as_sender")
|
||||
|
||||
@property
|
||||
def sess(self):
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
"icon": "icon-bitcoin",
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"modified": "2015-02-05 05:11:36.294972",
|
||||
"modified": "2015-07-13 05:01:14.014983",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Geo",
|
||||
"name": "Currency",
|
||||
|
|
@ -81,12 +81,38 @@
|
|||
{
|
||||
"apply_user_permissions": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"email": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All"
|
||||
"report": 0,
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Sales User",
|
||||
"share": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Purchase User",
|
||||
"share": 0,
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"read_only": 0
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ def logout():
|
|||
def web_logout():
|
||||
frappe.local.login_manager.logout()
|
||||
frappe.db.commit()
|
||||
frappe.respond_as_web_page("Logged Out", """<p>You have been logged out.</p>
|
||||
<p><a href='index'>Back to Home</a></p>""")
|
||||
frappe.respond_as_web_page("Logged Out", """<p><a href="/index" class="text-muted">Back to Home</a></p>""")
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def run_custom_method(doctype, name, custom_method):
|
||||
|
|
|
|||
|
|
@ -2,12 +2,35 @@ from __future__ import unicode_literals
|
|||
app_name = "frappe"
|
||||
app_title = "Frappe Framework"
|
||||
app_publisher = "Frappe Technologies Pvt. Ltd."
|
||||
app_description = "Full Stack Web Application Framework in Python"
|
||||
app_icon = "octicon octicon-circuit-board"
|
||||
app_version = "5.0.9"
|
||||
app_color = "orange"
|
||||
app_description = """## Frappe Framework
|
||||
|
||||
app_email = "support@frappe.io"
|
||||
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.
|
||||
|
||||
### Links:
|
||||
|
||||
- Project Home: [https://frappe.io](https://frappe.io)
|
||||
- Tutorial: [https://frappe.io/tutorial](https://frappe.io/tutorial)
|
||||
- GitHub: [https://github.com/frappe/frappe](https://github.com/frappe/frappe)
|
||||
- Forum: [https://discuss.erpnext.com](https://discuss.erpnext.com)
|
||||
"""
|
||||
|
||||
app_icon = "octicon octicon-circuit-board"
|
||||
app_version = "5.1.3"
|
||||
app_color = "orange"
|
||||
github_link = "https://github.com/frappe/frappe"
|
||||
|
||||
app_email = "info@frappe.io"
|
||||
|
||||
before_install = "frappe.utils.install.before_install"
|
||||
after_install = "frappe.utils.install.after_install"
|
||||
|
|
@ -35,8 +58,7 @@ web_include_js = [
|
|||
|
||||
bootstrap = "assets/frappe/css/bootstrap.css"
|
||||
web_include_css = [
|
||||
"assets/css/frappe-web.css",
|
||||
"website_theme.css"
|
||||
"assets/css/frappe-web.css"
|
||||
]
|
||||
website_route_rules = [
|
||||
{"from_route": "/blog", "to_route": "Blog Post"},
|
||||
|
|
@ -59,6 +81,8 @@ website_generators = ["Web Page", "Blog Post", "Blog Category", "Web Form"]
|
|||
|
||||
email_append_to = ["Event", "ToDo", "Communication"]
|
||||
|
||||
calendars = ["Event"]
|
||||
|
||||
# login
|
||||
|
||||
on_session_creation = [
|
||||
|
|
@ -119,6 +143,7 @@ scheduler_events = {
|
|||
"daily": [
|
||||
"frappe.email.bulk.clear_outbox",
|
||||
"frappe.desk.notifications.clear_notifications",
|
||||
"frappe.core.doctype.scheduler_log.scheduler_log.set_old_logs_as_seen",
|
||||
"frappe.desk.doctype.event.event.send_event_digest",
|
||||
"frappe.sessions.clear_expired_sessions",
|
||||
"frappe.email.doctype.email_alert.email_alert.trigger_daily_alerts",
|
||||
|
|
|
|||
|
|
@ -231,11 +231,12 @@ def add_module_defs(app):
|
|||
|
||||
def remove_missing_apps():
|
||||
apps = ('frappe_subscription', 'shopping_cart')
|
||||
installed_apps = frappe.get_installed_apps()
|
||||
installed_apps = json.loads(frappe.db.get_global("installed_apps") or "[]")
|
||||
for app in apps:
|
||||
if app in installed_apps:
|
||||
try:
|
||||
importlib.import_module(app)
|
||||
|
||||
except ImportError:
|
||||
installed_apps.remove(app)
|
||||
frappe.db.set_global("installed_apps", json.dumps(installed_apps))
|
||||
|
|
|
|||
|
|
@ -72,111 +72,3 @@ def delete_fields(args_dict, delete=0):
|
|||
", ".join(["DROP COLUMN `%s`" % f for f in fields if f in existing_fields])
|
||||
frappe.db.commit()
|
||||
frappe.db.sql(query)
|
||||
|
||||
def rename_field(doctype, old_fieldname, new_fieldname):
|
||||
"""This functions assumes that doctype is already synced"""
|
||||
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
new_field = meta.get_field(new_fieldname)
|
||||
if not new_field:
|
||||
print "rename_field: " + (new_fieldname) + " not found in " + doctype
|
||||
return
|
||||
|
||||
if new_field.fieldtype == "Table":
|
||||
# change parentfield of table mentioned in options
|
||||
frappe.db.sql("""update `tab%s` set parentfield=%s
|
||||
where parentfield=%s""" % (new_field.options.split("\n")[0], "%s", "%s"),
|
||||
(new_fieldname, old_fieldname))
|
||||
elif new_field.fieldtype not in no_value_fields:
|
||||
if meta.issingle:
|
||||
frappe.db.sql("""update `tabSingles` set field=%s
|
||||
where doctype=%s and field=%s""",
|
||||
(new_fieldname, doctype, old_fieldname))
|
||||
else:
|
||||
# copy field value
|
||||
frappe.db.sql("""update `tab%s` set `%s`=`%s`""" % \
|
||||
(doctype, new_fieldname, old_fieldname))
|
||||
|
||||
update_reports(doctype, old_fieldname, new_fieldname)
|
||||
update_users_report_view_settings(doctype, old_fieldname, new_fieldname)
|
||||
|
||||
# update in property setter
|
||||
frappe.db.sql("""update `tabProperty Setter` set field_name = %s
|
||||
where doc_type=%s and field_name=%s""", (new_fieldname, doctype, old_fieldname))
|
||||
|
||||
def update_reports(doctype, old_fieldname, new_fieldname):
|
||||
def _get_new_sort_by(report_dict, report, key):
|
||||
sort_by = report_dict.get(key) or ""
|
||||
if sort_by:
|
||||
sort_by = sort_by.split(".")
|
||||
if len(sort_by) > 1:
|
||||
if sort_by[0]==doctype and sort_by[1]==old_fieldname:
|
||||
sort_by = doctype + "." + new_fieldname
|
||||
report_dict["updated"] = True
|
||||
elif report.ref_doctype == doctype and sort_by[0]==old_fieldname:
|
||||
sort_by = doctype + "." + new_fieldname
|
||||
report_dict["updated"] = True
|
||||
|
||||
if isinstance(sort_by, list):
|
||||
sort_by = '.'.join(sort_by)
|
||||
|
||||
return sort_by
|
||||
|
||||
reports = frappe.db.sql("""select name, ref_doctype, json from tabReport
|
||||
where report_type = 'Report Builder' and ifnull(is_standard, 'No') = 'No'
|
||||
and json like %s and json like %s""",
|
||||
('%%%s%%' % old_fieldname , '%%%s%%' % doctype), as_dict=True)
|
||||
|
||||
for r in reports:
|
||||
report_dict = json.loads(r.json)
|
||||
|
||||
# update filters
|
||||
new_filters = []
|
||||
for f in report_dict.get("filters"):
|
||||
if f[0] == doctype and f[1] == old_fieldname:
|
||||
new_filters.append([doctype, new_fieldname, f[2], f[3]])
|
||||
report_dict["updated"] = True
|
||||
else:
|
||||
new_filters.append(f)
|
||||
|
||||
# update columns
|
||||
new_columns = []
|
||||
for c in report_dict.get("columns"):
|
||||
if c[0] == old_fieldname and c[1] == doctype:
|
||||
new_columns.append([new_fieldname, doctype])
|
||||
report_dict["updated"] = True
|
||||
else:
|
||||
new_columns.append(c)
|
||||
|
||||
# update sort by
|
||||
new_sort_by = _get_new_sort_by(report_dict, r, "sort_by")
|
||||
new_sort_by_next = _get_new_sort_by(report_dict, r, "sort_by_next")
|
||||
|
||||
if report_dict.get("updated"):
|
||||
new_val = json.dumps({
|
||||
"filters": new_filters,
|
||||
"columns": new_columns,
|
||||
"sort_by": new_sort_by,
|
||||
"sort_order": report_dict.get("sort_order"),
|
||||
"sort_by_next": new_sort_by_next,
|
||||
"sort_order_next": report_dict.get("sort_order_next")
|
||||
})
|
||||
|
||||
frappe.db.sql("""update `tabReport` set `json`=%s where name=%s""", (new_val, r.name))
|
||||
|
||||
def update_users_report_view_settings(doctype, ref_fieldname, new_fieldname):
|
||||
user_report_cols = frappe.db.sql("""select defkey, defvalue from `tabDefaultValue` where
|
||||
defkey like '_list_settings:%'""")
|
||||
for key, value in user_report_cols:
|
||||
new_columns = []
|
||||
columns_modified = False
|
||||
for field, field_doctype in json.loads(value):
|
||||
if field == ref_fieldname and field_doctype == doctype:
|
||||
new_columns.append([new_fieldname, field_doctype])
|
||||
columns_modified=True
|
||||
else:
|
||||
new_columns.append([field, field_doctype])
|
||||
|
||||
if columns_modified:
|
||||
frappe.db.sql("""update `tabDefaultValue` set defvalue=%s
|
||||
where defkey=%s""" % ('%s', '%s'), (json.dumps(new_columns), key))
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ class BaseDocument(object):
|
|||
return
|
||||
type, value, traceback = sys.exc_info()
|
||||
frappe.msgprint(_("Duplicate name {0} {1}").format(self.doctype, self.name))
|
||||
raise frappe.NameError, (self.doctype, self.name, e), traceback
|
||||
raise frappe.DuplicateEntryError, (self.doctype, self.name, e), traceback
|
||||
else:
|
||||
raise
|
||||
|
||||
|
|
@ -453,7 +453,7 @@ class BaseDocument(object):
|
|||
return self._precision[cache_key][fieldname]
|
||||
|
||||
|
||||
def get_formatted(self, fieldname, doc=None, currency=None):
|
||||
def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False):
|
||||
from frappe.utils.formatters import format_value
|
||||
|
||||
df = self.meta.get_field(fieldname)
|
||||
|
|
@ -461,7 +461,10 @@ class BaseDocument(object):
|
|||
from frappe.model.meta import get_default_df
|
||||
df = get_default_df(fieldname)
|
||||
|
||||
return format_value(self.get(fieldname), df=df, doc=doc or self, currency=currency)
|
||||
val = self.get(fieldname)
|
||||
if absolute_value and isinstance(val, (int, float)):
|
||||
val = abs(self.get(fieldname))
|
||||
return format_value(val, df=df, doc=doc or self, currency=currency)
|
||||
|
||||
def is_print_hide(self, fieldname, df=None, for_print=True):
|
||||
"""Returns true if fieldname is to be hidden for print.
|
||||
|
|
@ -524,7 +527,7 @@ class BaseDocument(object):
|
|||
|
||||
def cast(self, val, df):
|
||||
if df.fieldtype in ("Currency", "Float", "Percent"):
|
||||
val = flt(val, self.precision(df.fieldname))
|
||||
val = flt(val)
|
||||
|
||||
elif df.fieldtype in ("Int", "Check"):
|
||||
val = cint(val)
|
||||
|
|
|
|||
|
|
@ -270,7 +270,6 @@ class DatabaseQuery(object):
|
|||
"""add match conditions if applicable"""
|
||||
self.match_filters = []
|
||||
self.match_conditions = []
|
||||
|
||||
only_if_shared = False
|
||||
|
||||
if not self.tables: self.extract_tables()
|
||||
|
|
@ -283,7 +282,7 @@ class DatabaseQuery(object):
|
|||
if not meta.istable and not role_permissions.get("read") and not self.flags.ignore_permissions:
|
||||
only_if_shared = True
|
||||
if not self.shared:
|
||||
frappe.throw(_("No permission to read {0}").format(self.doctype))
|
||||
frappe.throw(_("No permission to read {0}").format(self.doctype), frappe.PermissionError)
|
||||
else:
|
||||
self.conditions.append(self.get_share_condition())
|
||||
|
||||
|
|
@ -295,9 +294,9 @@ class DatabaseQuery(object):
|
|||
self.add_user_permissions(user_permissions,
|
||||
user_permission_doctypes=role_permissions.get("user_permission_doctypes").get("read"))
|
||||
|
||||
# share is an OR condition, if there is a role permission
|
||||
if not only_if_shared and self.shared:
|
||||
self.or_conditions.append(self.get_share_condition())
|
||||
if role_permissions.get("if_owner", {}).get("read"):
|
||||
self.match_conditions.append("`tab{0}`.owner = '{1}'".format(self.doctype,
|
||||
frappe.db.escape(frappe.session.user)))
|
||||
|
||||
if as_condition:
|
||||
conditions = ""
|
||||
|
|
@ -309,6 +308,11 @@ class DatabaseQuery(object):
|
|||
if doctype_conditions:
|
||||
conditions += (' and ' + doctype_conditions) if conditions else doctype_conditions
|
||||
|
||||
# share is an OR condition, if there is a role permission
|
||||
if not only_if_shared and self.shared and conditions:
|
||||
conditions = "({conditions}) or ({shared_condition})".format(
|
||||
conditions=conditions, shared_condition=self.get_share_condition())
|
||||
|
||||
return conditions
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -234,7 +234,14 @@ class DbTable:
|
|||
query.append('alter column `{}` set default {}'.format(col.fieldname, col_default))
|
||||
|
||||
if query:
|
||||
frappe.db.sql("alter table `{}` {}".format(self.name, ", ".join(query)))
|
||||
try:
|
||||
frappe.db.sql("alter table `{}` {}".format(self.name, ", ".join(query)))
|
||||
except Exception, e:
|
||||
# sanitize
|
||||
if e.args[0]==1060:
|
||||
frappe.throw(str(e))
|
||||
else:
|
||||
raise e
|
||||
|
||||
class DbColumn:
|
||||
def __init__(self, table, fieldname, fieldtype, length, default,
|
||||
|
|
@ -302,17 +309,34 @@ class DbColumn:
|
|||
|
||||
def default_changed(self, current_def):
|
||||
if "decimal" in current_def['type']:
|
||||
try:
|
||||
if current_def['default'] in ("", None) and self.default in ("", None):
|
||||
# both none, empty
|
||||
return False
|
||||
else:
|
||||
return float(current_def['default'])!=float(self.default)
|
||||
except TypeError:
|
||||
return True
|
||||
return self.default_changed_for_decimal(current_def)
|
||||
else:
|
||||
return current_def['default'] != self.default
|
||||
|
||||
def default_changed_for_decimal(self, current_def):
|
||||
try:
|
||||
if current_def['default'] in ("", None) and self.default in ("", None):
|
||||
# both none, empty
|
||||
return False
|
||||
|
||||
elif current_def['default'] in ("", None):
|
||||
try:
|
||||
# check if new default value is valid
|
||||
float(self.default)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
elif self.default in ("", None):
|
||||
# new default value is empty
|
||||
return True
|
||||
|
||||
else:
|
||||
# NOTE float() raise ValueError when "" or None is passed
|
||||
return float(current_def['default'])!=float(self.default)
|
||||
except TypeError:
|
||||
return True
|
||||
|
||||
class DbManager:
|
||||
"""
|
||||
Basically, a wrapper for oft-used mysql commands. like show tables,databases, variables etc...
|
||||
|
|
@ -452,4 +476,3 @@ def add_column(doctype, column_name, fieldtype, precision=None):
|
|||
frappe.db.commit()
|
||||
frappe.db.sql("alter table `tab%s` add column %s %s" % (doctype,
|
||||
column_name, get_definition(fieldtype, precision)))
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,9 @@ class Document(BaseDocument):
|
|||
|
||||
def raise_no_permission_to(self, perm_type):
|
||||
"""Raise `frappe.PermissionError`."""
|
||||
raise frappe.PermissionError("No permission to {} {} {}".format(perm_type, self.doctype, self.name or ""))
|
||||
msg = _("No permission to {0} {1} {2}".format(perm_type, self.doctype, self.name or ""))
|
||||
frappe.msgprint(msg)
|
||||
raise frappe.PermissionError(msg)
|
||||
|
||||
def insert(self, ignore_permissions=None):
|
||||
"""Insert the document in the database (as a new document).
|
||||
|
|
@ -560,11 +562,13 @@ class Document(BaseDocument):
|
|||
elif self._action=="submit":
|
||||
self.run_method("on_update")
|
||||
self.run_method("on_submit")
|
||||
self.add_comment("Submitted")
|
||||
if not self.flags.ignore_submit_comment:
|
||||
self.add_comment("Submitted")
|
||||
elif self._action=="cancel":
|
||||
self.run_method("on_cancel")
|
||||
self.check_no_back_links_exist()
|
||||
self.add_comment("Cancelled")
|
||||
if not self.flags.ignore_submit_comment:
|
||||
self.add_comment("Cancelled")
|
||||
elif self._action=="update_after_submit":
|
||||
self.run_method("on_update_after_submit")
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,17 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None,
|
|||
for df in source_doc.meta.get_table_fields():
|
||||
source_child_doctype = df.options
|
||||
table_map = table_maps.get(source_child_doctype)
|
||||
|
||||
# if table_map isn't explicitly specified check if both source and target have the same fieldname and same table options and both of them don't have no_copy
|
||||
if not table_map:
|
||||
target_df = target_doc.meta.get_field(df.fieldname)
|
||||
if target_df:
|
||||
target_child_doctype = target_df.options
|
||||
if target_df and target_child_doctype==source_child_doctype and not df.no_copy and not target_df.no_copy:
|
||||
table_map = {
|
||||
"doctype": target_child_doctype
|
||||
}
|
||||
|
||||
if table_map:
|
||||
for source_d in source_doc.get(df.fieldname):
|
||||
if "condition" in table_map:
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ class Meta(Document):
|
|||
list_fields.append(self.title_field)
|
||||
return list_fields
|
||||
|
||||
def get_title_field(self):
|
||||
return self.title_field or "name"
|
||||
|
||||
def process(self):
|
||||
# don't process for special doctypes
|
||||
# prevent's circular dependency
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ def append_number_if_name_exists(doc):
|
|||
if frappe.db.exists(doc.doctype, doc.name):
|
||||
last = frappe.db.sql("""select name from `tab{}`
|
||||
where name regexp '{}-[[:digit:]]+'
|
||||
order by name desc limit 1""".format(doc.doctype, doc.name))
|
||||
order by length(name) desc, name desc limit 1""".format(doc.doctype, doc.name))
|
||||
|
||||
if last:
|
||||
count = str(cint(last[0][0].rsplit("-", 1)[1]) + 1)
|
||||
|
|
|
|||
116
frappe/model/utils/rename_field.py
Normal file
116
frappe/model/utils/rename_field.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import json
|
||||
from frappe.model import no_value_fields
|
||||
|
||||
def rename_field(doctype, old_fieldname, new_fieldname):
|
||||
"""This functions assumes that doctype is already synced"""
|
||||
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
new_field = meta.get_field(new_fieldname)
|
||||
if not new_field:
|
||||
print "rename_field: " + (new_fieldname) + " not found in " + doctype
|
||||
return
|
||||
|
||||
if new_field.fieldtype == "Table":
|
||||
# change parentfield of table mentioned in options
|
||||
frappe.db.sql("""update `tab%s` set parentfield=%s
|
||||
where parentfield=%s""" % (new_field.options.split("\n")[0], "%s", "%s"),
|
||||
(new_fieldname, old_fieldname))
|
||||
elif new_field.fieldtype not in no_value_fields:
|
||||
if meta.issingle:
|
||||
frappe.db.sql("""update `tabSingles` set field=%s
|
||||
where doctype=%s and field=%s""",
|
||||
(new_fieldname, doctype, old_fieldname))
|
||||
else:
|
||||
# copy field value
|
||||
frappe.db.sql("""update `tab%s` set `%s`=`%s`""" % \
|
||||
(doctype, new_fieldname, old_fieldname))
|
||||
|
||||
update_reports(doctype, old_fieldname, new_fieldname)
|
||||
update_users_report_view_settings(doctype, old_fieldname, new_fieldname)
|
||||
|
||||
# update in property setter
|
||||
frappe.db.sql("""update `tabProperty Setter` set field_name = %s
|
||||
where doc_type=%s and field_name=%s""", (new_fieldname, doctype, old_fieldname))
|
||||
|
||||
def update_reports(doctype, old_fieldname, new_fieldname):
|
||||
def _get_new_sort_by(report_dict, report, key):
|
||||
sort_by = report_dict.get(key) or ""
|
||||
if sort_by:
|
||||
sort_by = sort_by.split(".")
|
||||
if len(sort_by) > 1:
|
||||
if sort_by[0]==doctype and sort_by[1]==old_fieldname:
|
||||
sort_by = doctype + "." + new_fieldname
|
||||
report_dict["updated"] = True
|
||||
elif report.ref_doctype == doctype and sort_by[0]==old_fieldname:
|
||||
sort_by = doctype + "." + new_fieldname
|
||||
report_dict["updated"] = True
|
||||
|
||||
if isinstance(sort_by, list):
|
||||
sort_by = '.'.join(sort_by)
|
||||
|
||||
return sort_by
|
||||
|
||||
reports = frappe.db.sql("""select name, ref_doctype, json from tabReport
|
||||
where report_type = 'Report Builder' and ifnull(is_standard, 'No') = 'No'
|
||||
and json like %s and json like %s""",
|
||||
('%%%s%%' % old_fieldname , '%%%s%%' % doctype), as_dict=True)
|
||||
|
||||
for r in reports:
|
||||
report_dict = json.loads(r.json)
|
||||
|
||||
# update filters
|
||||
new_filters = []
|
||||
for f in report_dict.get("filters"):
|
||||
if f and len(f) > 1 and f[0] == doctype and f[1] == old_fieldname:
|
||||
new_filters.append([doctype, new_fieldname, f[2], f[3]])
|
||||
report_dict["updated"] = True
|
||||
else:
|
||||
new_filters.append(f)
|
||||
|
||||
# update columns
|
||||
new_columns = []
|
||||
for c in report_dict.get("columns"):
|
||||
if c and len(c) > 1 and c[0] == old_fieldname and c[1] == doctype:
|
||||
new_columns.append([new_fieldname, doctype])
|
||||
report_dict["updated"] = True
|
||||
else:
|
||||
new_columns.append(c)
|
||||
|
||||
# update sort by
|
||||
new_sort_by = _get_new_sort_by(report_dict, r, "sort_by")
|
||||
new_sort_by_next = _get_new_sort_by(report_dict, r, "sort_by_next")
|
||||
|
||||
if report_dict.get("updated"):
|
||||
new_val = json.dumps({
|
||||
"filters": new_filters,
|
||||
"columns": new_columns,
|
||||
"sort_by": new_sort_by,
|
||||
"sort_order": report_dict.get("sort_order"),
|
||||
"sort_by_next": new_sort_by_next,
|
||||
"sort_order_next": report_dict.get("sort_order_next")
|
||||
})
|
||||
|
||||
frappe.db.sql("""update `tabReport` set `json`=%s where name=%s""", (new_val, r.name))
|
||||
|
||||
def update_users_report_view_settings(doctype, ref_fieldname, new_fieldname):
|
||||
user_report_cols = frappe.db.sql("""select defkey, defvalue from `tabDefaultValue` where
|
||||
defkey like '_list_settings:%'""")
|
||||
for key, value in user_report_cols:
|
||||
new_columns = []
|
||||
columns_modified = False
|
||||
for field, field_doctype in json.loads(value):
|
||||
if field == ref_fieldname and field_doctype == doctype:
|
||||
new_columns.append([new_fieldname, field_doctype])
|
||||
columns_modified=True
|
||||
else:
|
||||
new_columns.append([field, field_doctype])
|
||||
|
||||
if columns_modified:
|
||||
frappe.db.sql("""update `tabDefaultValue` set defvalue=%s
|
||||
where defkey=%s""" % ('%s', '%s'), (json.dumps(new_columns), key))
|
||||
|
|
@ -7,19 +7,19 @@ import frappe, os, json
|
|||
from frappe.modules import get_module_path, scrub_dt_dn
|
||||
from frappe.utils import get_datetime_str
|
||||
|
||||
def import_files(module, dt=None, dn=None, force=False):
|
||||
def import_files(module, dt=None, dn=None, force=False, pre_process=None):
|
||||
if type(module) is list:
|
||||
out = []
|
||||
for m in module:
|
||||
out.append(import_file(m[0], m[1], m[2], force=force))
|
||||
out.append(import_file(m[0], m[1], m[2], force=force, pre_process=pre_process))
|
||||
return out
|
||||
else:
|
||||
return import_file(module, dt, dn, force=force)
|
||||
return import_file(module, dt, dn, force=force, pre_process=pre_process)
|
||||
|
||||
def import_file(module, dt, dn, force=False):
|
||||
def import_file(module, dt, dn, force=False, pre_process=None):
|
||||
"""Sync a file from txt if modifed, return false if not updated"""
|
||||
path = get_file_path(module, dt, dn)
|
||||
ret = import_file_by_path(path, force)
|
||||
ret = import_file_by_path(path, force, pre_process=pre_process)
|
||||
return ret
|
||||
|
||||
def get_file_path(module, dt, dn):
|
||||
|
|
@ -30,7 +30,7 @@ def get_file_path(module, dt, dn):
|
|||
|
||||
return path
|
||||
|
||||
def import_file_by_path(path, force=False, data_import=False):
|
||||
def import_file_by_path(path, force=False, data_import=False, pre_process=None):
|
||||
frappe.flags.in_import = True
|
||||
try:
|
||||
docs = read_doc_from_file(path)
|
||||
|
|
@ -51,7 +51,7 @@ def import_file_by_path(path, force=False, data_import=False):
|
|||
|
||||
original_modified = doc.get("modified")
|
||||
|
||||
import_doc(doc, force=force, data_import=data_import)
|
||||
import_doc(doc, force=force, data_import=data_import, pre_process=pre_process)
|
||||
|
||||
if original_modified:
|
||||
# since there is a new timestamp on the file, update timestamp in
|
||||
|
|
@ -87,10 +87,12 @@ ignore_values = {
|
|||
|
||||
ignore_doctypes = ["Page Role", "DocPerm"]
|
||||
|
||||
def import_doc(docdict, force=False, data_import=False):
|
||||
def import_doc(docdict, force=False, data_import=False, pre_process=None):
|
||||
frappe.flags.in_import = True
|
||||
docdict["__islocal"] = 1
|
||||
doc = frappe.get_doc(docdict)
|
||||
if pre_process:
|
||||
pre_process(doc)
|
||||
|
||||
ignore = []
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ execute:frappe.db.sql("""update `tabPatch Log` set patch=replace(patch, '.4_0.',
|
|||
frappe.patches.v5_0.convert_to_barracuda_and_utf8mb4
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2014-01-24
|
||||
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2015-05-15
|
||||
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2014-06-04
|
||||
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2014-06-24
|
||||
execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26
|
||||
execute:frappe.reload_doc('core', 'doctype', 'report') #2014-06-03
|
||||
execute:frappe.reload_doc('core', 'doctype', 'version') #2014-02-21
|
||||
|
|
@ -66,6 +66,7 @@ frappe.patches.v5_0.clear_website_group_and_notifications
|
|||
execute:frappe.db.sql("""update tabComment set comment = substr(comment, 6, locate(":", comment)-6) where comment_type in ("Assigned", "Assignment Completed")""")
|
||||
frappe.patches.v5_0.fix_feed
|
||||
frappe.patches.v5_0.update_shared
|
||||
execute:frappe.reload_doc("core", "doctype", "docshare") #2015-07-21
|
||||
frappe.patches.v5_0.bookmarks_to_stars
|
||||
frappe.patches.v5_0.style_settings_to_website_theme
|
||||
frappe.patches.v5_0.rename_ref_type_fieldnames
|
||||
|
|
@ -79,3 +80,6 @@ execute:frappe.db.sql("update tabUser set new_password='' where ifnull(new_passw
|
|||
frappe.patches.v5_0.fix_text_editor_file_urls
|
||||
execute:frappe.db.sql("update `tabComment` set comment_type='Comment' where comment_doctype='Blog Post' and ifnull(comment_type, '')=''")
|
||||
frappe.patches.v5_0.modify_session
|
||||
frappe.patches.v5_0.expire_old_scheduler_logs
|
||||
execute:frappe.permissions.reset_perms("DocType")
|
||||
execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.model import rename_field
|
||||
from frappe.model.utils.rename_field import rename_field
|
||||
from frappe.model.meta import get_table_columns
|
||||
|
||||
def execute():
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.model import rename_field
|
||||
from frappe.model.utils.rename_field import rename_field
|
||||
|
||||
def execute():
|
||||
tables = frappe.db.sql_list("show tables")
|
||||
|
|
|
|||
|
|
@ -25,4 +25,8 @@ def execute():
|
|||
continue
|
||||
|
||||
if frappe.db.exists(doctype, docname):
|
||||
if (doctype=="DocType"
|
||||
or int(frappe.db.get_value("DocType", doctype, "issingle") or 0)
|
||||
or not frappe.db.table_exists(doctype)):
|
||||
continue
|
||||
_toggle_star(doctype, docname, add="Yes", user=username)
|
||||
|
|
|
|||
7
frappe/patches/v5_0/expire_old_scheduler_logs.py
Normal file
7
frappe/patches/v5_0/expire_old_scheduler_logs.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Scheduler Log")
|
||||
|
||||
from frappe.core.doctype.scheduler_log.scheduler_log import set_old_logs_as_seen
|
||||
set_old_logs_as_seen()
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.db.sql("alter table tabSessions add column `device` varchar(255) default 'desktop'")
|
||||
if "device" not in frappe.db.get_table_columns("Sessions"):
|
||||
frappe.db.sql("alter table tabSessions add column `device` varchar(255) default 'desktop'")
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model import rename_field
|
||||
from frappe.model.utils.rename_field import rename_field
|
||||
from frappe.modules import scrub, get_doctype_module
|
||||
|
||||
rename_map = {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,11 @@ def check_admin_or_system_manager(user=None):
|
|||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||
|
||||
def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
|
||||
"""check if user has permission"""
|
||||
"""Returns True if user has permission `ptype` for given `doctype`.
|
||||
If `doc` is passed, it also checks user, share and owner permissions.
|
||||
|
||||
Note: if Table DocType is passed, it always returns True.
|
||||
"""
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
if frappe.is_table(doctype):
|
||||
|
|
@ -39,13 +43,17 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
|
|||
return True
|
||||
|
||||
def false_if_not_shared():
|
||||
if ptype in ("read", "write", "share"):
|
||||
shared = frappe.share.get_shared(doctype, user, [ptype])
|
||||
if ptype in ("read", "write", "share", "email", "print"):
|
||||
if doc:
|
||||
doc_name = doc if isinstance(doc, basestring) else doc.name
|
||||
shared = frappe.share.get_shared(doctype, user,
|
||||
["read" if ptype in ("email", "print") else ptype])
|
||||
|
||||
if doc_name in shared:
|
||||
if verbose: print "Shared"
|
||||
return True
|
||||
if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype):
|
||||
return True
|
||||
|
||||
else:
|
||||
if verbose: print "Has a shared document"
|
||||
return True
|
||||
|
|
@ -61,6 +69,11 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
|
|||
if isinstance(doc, basestring):
|
||||
doc = frappe.get_doc(meta.name, doc)
|
||||
|
||||
# if owner match, then return True
|
||||
if doc.owner == frappe.session.user and role_permissions["if_owner"].get(ptype) and ptype!="create":
|
||||
return True
|
||||
|
||||
# check if user permission
|
||||
if role_permissions["apply_user_permissions"].get(ptype):
|
||||
if not user_has_permission(doc, verbose=verbose, user=user,
|
||||
user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or []):
|
||||
|
|
@ -76,6 +89,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
|
|||
return True
|
||||
|
||||
def get_doc_permissions(doc, verbose=False, user=None):
|
||||
"""Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`"""
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
if frappe.is_table(doc.doctype):
|
||||
|
|
@ -98,23 +112,62 @@ def get_doc_permissions(doc, verbose=False, user=None):
|
|||
user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or []):
|
||||
role_permissions[ptype] = 0
|
||||
|
||||
# update share permissions
|
||||
role_permissions.update(frappe.db.get_value("DocShare",
|
||||
{"share_doctype": doc.doctype, "share_name": doc.name, "user": user},
|
||||
["read", "write", "share"], as_dict=True) or {})
|
||||
# apply owner permissions on top of existing permissions
|
||||
if doc.owner == frappe.session.user:
|
||||
role_permissions.update(role_permissions.if_owner)
|
||||
|
||||
update_share_permissions(role_permissions, doc, user)
|
||||
|
||||
return role_permissions
|
||||
|
||||
def update_share_permissions(role_permissions, doc, user):
|
||||
"""Updates share permissions on `role_permissions` for given doc, if shared"""
|
||||
share_ptypes = ("read", "write", "share")
|
||||
permissions_by_share = frappe.db.get_value("DocShare",
|
||||
{"share_doctype": doc.doctype, "share_name": doc.name, "user": user},
|
||||
share_ptypes, as_dict=True)
|
||||
|
||||
if permissions_by_share:
|
||||
for ptype in share_ptypes:
|
||||
if ptype:
|
||||
role_permissions[ptype] = 1
|
||||
|
||||
def get_role_permissions(meta, user=None, verbose=False):
|
||||
"""Returns dict of evaluated role permissions like `{"read": True, "write":False}`
|
||||
|
||||
If user permissions are applicable, it adds a dict of user permissions like
|
||||
|
||||
{
|
||||
// user permissions will apply on these rights
|
||||
"apply_user_permissions": {"read": 1, "write": 1},
|
||||
|
||||
// doctypes that will be applicable for each right
|
||||
"user_permission_doctypes": {
|
||||
"read": [
|
||||
// AND between "DocType 1" and "DocType 2"
|
||||
["DocType 1", "DocType 2"],
|
||||
|
||||
// OR
|
||||
|
||||
["DocType 3"]
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
"if_owner": {"read": 1, "write": 1}
|
||||
}
|
||||
"""
|
||||
if not user: user = frappe.session.user
|
||||
cache_key = (meta.name, user)
|
||||
|
||||
if not frappe.local.role_permissions.get(cache_key):
|
||||
perms = frappe._dict({ "apply_user_permissions": {}, "user_permission_doctypes": {} })
|
||||
perms = frappe._dict({ "apply_user_permissions": {}, "user_permission_doctypes": {}, "if_owner": {} })
|
||||
user_roles = frappe.get_roles(user)
|
||||
|
||||
for p in meta.permissions:
|
||||
if cint(p.permlevel)==0 and (p.role in user_roles):
|
||||
# apply only for level 0
|
||||
|
||||
for ptype in rights:
|
||||
perms[ptype] = perms.get(ptype, 0) or cint(p.get(ptype))
|
||||
|
||||
|
|
@ -122,6 +175,10 @@ def get_role_permissions(meta, user=None, verbose=False):
|
|||
perms["apply_user_permissions"][ptype] = (perms["apply_user_permissions"].get(ptype, 1)
|
||||
and p.get("apply_user_permissions"))
|
||||
|
||||
# build if_owner dict if applicable for this right
|
||||
if p.if_owner and p.get(ptype):
|
||||
perms["if_owner"][ptype] = 1
|
||||
|
||||
if p.apply_user_permissions:
|
||||
if p.user_permission_doctypes:
|
||||
# set user_permission_doctypes in perms
|
||||
|
|
|
|||
|
|
@ -19,12 +19,10 @@
|
|||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.custom_format",
|
||||
"fieldname": "edit_format",
|
||||
"fieldtype": "Button",
|
||||
"label": "Edit Format",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
|
|
@ -51,17 +49,6 @@
|
|||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_format",
|
||||
"fieldtype": "Check",
|
||||
|
|
@ -69,6 +56,12 @@
|
|||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "custom_format",
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"default": "Server",
|
||||
"depends_on": "custom_format",
|
||||
|
|
@ -99,6 +92,44 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.custom_format",
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.custom_format",
|
||||
"fieldname": "edit_format",
|
||||
"fieldtype": "Button",
|
||||
"label": "Edit Format",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"default": "Default",
|
||||
"depends_on": "eval:!doc.custom_format",
|
||||
"fieldname": "font",
|
||||
"fieldtype": "Select",
|
||||
"label": "Font",
|
||||
"options": "Default\nArial\nHelvetica\nVerdana\nMonospace",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "custom_format",
|
||||
"fieldname": "section_break_13",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "custom_format",
|
||||
"fieldname": "print_format_help",
|
||||
|
|
@ -134,7 +165,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-02-05 05:11:42.667447",
|
||||
"modified": "2015-07-15 08:01:06.284031",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Print",
|
||||
"name": "Print Format",
|
||||
|
|
|
|||
|
|
@ -42,9 +42,13 @@
|
|||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
"default": "Default",
|
||||
"fieldname": "font",
|
||||
"fieldtype": "Select",
|
||||
"label": "Font",
|
||||
"options": "Default\nArial\nHelvetica\nVerdana\nMonospace",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"description": "In points. Default is 9.",
|
||||
|
|
@ -53,6 +57,11 @@
|
|||
"label": "Font Size",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Print with Letterhead, unless unchecked in a particular Document",
|
||||
|
|
@ -76,7 +85,7 @@
|
|||
],
|
||||
"icon": "icon-cog",
|
||||
"issingle": 1,
|
||||
"modified": "2015-03-25 07:10:38.893958",
|
||||
"modified": "2015-07-15 08:03:23.743143",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Print",
|
||||
"name": "Print Settings",
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ frappe.PrintFormatBuilder = Class.extend({
|
|||
me.page.set_primary_action(__("Save"), function() {
|
||||
me.save_print_format();
|
||||
});
|
||||
me.page.clear_menu();
|
||||
me.page.add_menu_item(__("Start new Format"), function() {
|
||||
me.print_format = null;
|
||||
me.refresh();
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@
|
|||
"public/js/lib/notify.js",
|
||||
"public/js/lib/bootstrap.min.js",
|
||||
"public/js/lib/nprogress.js",
|
||||
"public/js/lib/moment/moment.min.js",
|
||||
"public/js/lib/moment/moment-timezone.min.js",
|
||||
"public/js/lib/moment/moment-with-locales.min.js",
|
||||
"public/js/lib/moment/moment-timezone-with-data.min.js",
|
||||
|
||||
"public/js/frappe/provide.js",
|
||||
"public/js/frappe/class.js",
|
||||
|
|
@ -95,6 +95,7 @@
|
|||
"public/js/frappe/misc/tools.js",
|
||||
"public/js/frappe/misc/datetime.js",
|
||||
"public/js/frappe/misc/number_format.js",
|
||||
"public/js/frappe/misc/help.js",
|
||||
|
||||
"public/js/frappe/ui/upload.html",
|
||||
"public/js/frappe/upload.js",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
}
|
||||
.avatar-empty {
|
||||
border: 1px dashed #d1d8dd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.avatar-small {
|
||||
margin-right: 5px;
|
||||
|
|
|
|||
|
|
@ -361,6 +361,11 @@ ul.linked-with-list li {
|
|||
padding: 7px;
|
||||
font-size: 12px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.video-modal {
|
||||
width: 700px;
|
||||
}
|
||||
}
|
||||
/* z-index hack */
|
||||
@media (min-width: 768px) {
|
||||
.hidden-xs-inline {
|
||||
|
|
@ -389,6 +394,18 @@ ul.linked-with-list li {
|
|||
.modal-title {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.form-control {
|
||||
position: relative;
|
||||
}
|
||||
.link-field.ui-front {
|
||||
z-index: inherit;
|
||||
}
|
||||
.modal .hasDatepicker {
|
||||
z-index: 1140;
|
||||
}
|
||||
.modal .link-field .ui-autocomplete {
|
||||
z-index: 1141;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue