@@ -14,6 +14,8 @@ install: | |||
- sudo apt-get update | |||
- sudo apt-get purge -y mysql-common | |||
- sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev | |||
- wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.1/wkhtmltox-0.12.1_linux-precise-amd64.deb | |||
- sudo dpkg -i wkhtmltox-0.12.1_linux-precise-amd64.deb | |||
- CFLAGS=-O0 pip install -r requirements.txt | |||
- pip install --editable . | |||
@@ -23,10 +25,12 @@ script: | |||
- frappe --use test_site | |||
- frappe --reinstall | |||
- frappe -b | |||
- frappe --build_website | |||
- frappe --serve_test & | |||
- frappe --verbose --run_tests | |||
before_script: | |||
- mysql -e 'create database test_frappe' | |||
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root | |||
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root | |||
@@ -0,0 +1,49 @@ | |||
# Contributing to Frappe / ERPNext | |||
## Reporting issues | |||
We only accept issues that are bug reports or feature requests. Bugs must be isolated and reproducible problems. Please read the following guidelines before opening any issue. | |||
1. **Search for existing issues:** We want to avoid duplication, and you'd help us out a lot by first checking if someone else has reported the same issue. The issue may have already been resolved with a fix available. | |||
1. **Report each issue separately:** Don't club multiple, unreleated issues in one note. | |||
1. **Mention the version number:** Please mention the application, browser and platform version numbers. | |||
### Issues | |||
1. **Share as much information as possible:** Include operating system and version, browser and version, when did you last update ERPNext, how is it customized, etc. where appropriate. Also include steps to reproduce the bug. | |||
1. **Include Screenshots if possible:** Consider adding screenshots annotated with what goes wrong. | |||
1. **Find and post the trace for bugs:** If you are reporting an issue from the browser, Open the Javascript Console and paste us any error messages you see. | |||
### Feature Requests | |||
1. We need as much information you can to consider a feature request. | |||
1. Think about **how** you want us to build the feature. Consider including: | |||
1. Mockups (wireframes of features) | |||
1. Screenshots (annotated with what should change) | |||
1. Screenshots from other products if you want us to implement features present in other products. | |||
1. Basically, the more you help us, the faster your request is likely to be completed. | |||
1. A one line feature request like **Implement Capacity Planning** will be closed. | |||
## Pull Requests | |||
General guidelines for sending pull requests: | |||
#### Don't Repeat Yourself (DRY) | |||
We believe that the most effective way to manage a product like this is to ensure that | |||
there is minimum repetition of code. So before contributing a function, please make sure | |||
that such a feature or function does not exist else where. If it does, the try and extend | |||
that function to accommodate your use case. | |||
#### Don't create new DocTypes Unless Absolutely Necessary | |||
DocTypes are easy to create but hard to maintain. If you find that there is a another DocType with a similar functionality, then please try and extend that functionality. For example, by adding a "type" field to classify the new type of record. | |||
#### Tabs or spaces? | |||
Tabs! | |||
### Copyright | |||
Please see README.md |
@@ -25,4 +25,5 @@ recursive-include frappe *.csv | |||
recursive-include frappe *.ico | |||
recursive-include frappe *.less | |||
recursive-include frappe *.txt | |||
recursive-include frappe/public * | |||
recursive-exclude * *.pyc |
@@ -1,41 +1,17 @@ | |||
## frappe [](https://travis-ci.org/frappe/frappe) | |||
Full-stack web application framework that uses Python/MySql on the server side and a tightly integrated client side library. Primarily built for erpnext. | |||
Projects: [erpnext](http://erpnext.org) | [frappe/erpnext](https://github.com/frappe/erpnext) | |||
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. [Built for ERPNext](https://erpnext.com) | |||
## Setup | |||
### Installation | |||
To start a new project, in the application root: | |||
[Install via Frappe Bench](https://github.com/frappe/bench) | |||
Install: | |||
### Website | |||
* Go to the project folder | |||
* Install frappe and your app: | |||
``` | |||
mkdir bench | |||
cd bench | |||
git clone https://github.com/frappe/frappe.git | |||
git clone https://github.com/frappe/[your_app] | |||
sudo pip install -e frappe/ erpnext/ your_app/ | |||
mkdir sites | |||
echo app >> sites/apps.txt | |||
cd sites | |||
frappe site.local --install dbname | |||
frappe site.local --install_app your_app | |||
``` | |||
* Run development server: | |||
For details and documentation, see the website | |||
``` | |||
cd sites | |||
frappe site.local --serve | |||
``` | |||
[https://frappe.io](https://frappe.io) | |||
enjoy! | |||
### License | |||
## wnf.py | |||
`frappe --help` for more info | |||
## License | |||
frappe is freely available to use under the MIT License | |||
MIT License |
@@ -4,19 +4,15 @@ | |||
globals attached to frappe module | |||
+ some utility functions that should probably be moved | |||
""" | |||
from __future__ import unicode_literals | |||
from werkzeug.local import Local, release_local | |||
from werkzeug.exceptions import NotFound | |||
from MySQLdb import ProgrammingError as SQLError | |||
import os, sys, importlib, inspect | |||
import json | |||
import os, importlib, inspect, logging, json | |||
# public | |||
from frappe.__version__ import __version__ | |||
from .exceptions import * | |||
__version__ = "4.0.1" | |||
from .utils.jinja import get_jenv, get_template, render_template | |||
local = Local() | |||
@@ -68,7 +64,6 @@ response = local("response") | |||
session = local("session") | |||
user = local("user") | |||
flags = local("flags") | |||
restrictions = local("restrictions") | |||
error_log = local("error_log") | |||
debug_log = local("debug_log") | |||
@@ -84,30 +79,37 @@ def init(site, sites_path=None): | |||
sites_path = '.' | |||
local.error_log = [] | |||
local.message_log = [] | |||
local.debug_log = [] | |||
local.flags = _dict({}) | |||
local.rollback_observers = [] | |||
local.test_objects = {} | |||
local.site = site | |||
local.sites_path = sites_path | |||
local.site_path = os.path.join(sites_path, site) | |||
local.message_log = [] | |||
local.debug_log = [] | |||
local.request_method = request.method if request else None | |||
local.request_ip = None | |||
local.response = _dict({"docs":[]}) | |||
local.conf = _dict(get_site_config()) | |||
local.lang = local.conf.lang or "en" | |||
local.initialised = True | |||
local.flags = _dict({}) | |||
local.rollback_observers = [] | |||
local.module_app = None | |||
local.app_modules = None | |||
local.user = None | |||
local.restrictions = None | |||
local.user_perms = {} | |||
local.test_objects = {} | |||
local.role_permissions = {} | |||
local.jenv = None | |||
local.jloader =None | |||
local.cache = {} | |||
setup_module_map() | |||
local.initialised = True | |||
def connect(site=None, db_name=None): | |||
from database import Database | |||
if site: | |||
@@ -158,7 +160,7 @@ def get_traceback(): | |||
def errprint(msg): | |||
from utils import cstr | |||
if not request: | |||
if not request or (not "cmd" in local.form_dict): | |||
print cstr(msg) | |||
error_log.append(cstr(msg)) | |||
@@ -199,36 +201,46 @@ def msgprint(msg, small=0, raise_exception=0, as_table=False): | |||
def throw(msg, exc=ValidationError): | |||
msgprint(msg, raise_exception=exc) | |||
def create_folder(path): | |||
if not os.path.exists(path): os.makedirs(path) | |||
def create_folder(path, with_init=False): | |||
from frappe.utils import touch_file | |||
if not os.path.exists(path): | |||
os.makedirs(path) | |||
if with_init: | |||
touch_file(os.path.join(path, "__init__.py")) | |||
def set_user(username): | |||
from frappe.utils.user import User | |||
local.session.user = username | |||
local.session.sid = username | |||
local.cache = {} | |||
local.form_dict = _dict() | |||
local.jenv = None | |||
local.session.data = {} | |||
local.user = User(username) | |||
local.restrictions = None | |||
local.user_perms = {} | |||
local.role_permissions = {} | |||
def get_request_header(key, default=None): | |||
return request.headers.get(key, default) | |||
def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", | |||
as_markdown=False, bulk=False): | |||
as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None, | |||
add_unsubscribe_link=False, attachments=None): | |||
if bulk: | |||
import frappe.utils.email_lib.bulk | |||
frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender, | |||
subject=subject, message=message, add_unsubscribe_link=False) | |||
subject=subject, message=message, ref_doctype = ref_doctype, | |||
ref_docname = ref_docname, add_unsubscribe_link=add_unsubscribe_link, attachments=attachments) | |||
else: | |||
import frappe.utils.email_lib | |||
if as_markdown: | |||
frappe.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message) | |||
frappe.utils.email_lib.sendmail_md(recipients, sender=sender, | |||
subject=subject, msg=message, attachments=attachments) | |||
else: | |||
frappe.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message) | |||
frappe.utils.email_lib.sendmail(recipients, sender=sender, | |||
subject=subject, msg=message, attachments=attachments) | |||
logger = None | |||
whitelisted = [] | |||
@@ -276,18 +288,27 @@ def clear_cache(user=None, doctype=None): | |||
translate.clear_cache() | |||
reset_metadata_version() | |||
for fn in frappe.get_hooks("clear_cache"): | |||
get_attr(fn)() | |||
frappe.local.role_permissions = {} | |||
def get_roles(username=None): | |||
from frappe.utils.user import User | |||
if not local.session: | |||
return ["Guest"] | |||
elif not username or username==local.session.user: | |||
return local.user.get_roles() | |||
return get_user(username).get_roles() | |||
def get_user(username): | |||
from frappe.utils.user import User | |||
if not username or username == local.session.user: | |||
return local.user | |||
else: | |||
return User(username).get_roles() | |||
return User(username) | |||
def has_permission(doctype, ptype="read", doc=None): | |||
def has_permission(doctype, ptype="read", doc=None, user=None): | |||
import frappe.permissions | |||
return frappe.permissions.has_permission(doctype, ptype, doc) | |||
return frappe.permissions.has_permission(doctype, ptype, doc, user=user) | |||
def is_table(doctype): | |||
tables = cache().get_value("is_table") | |||
@@ -300,6 +321,9 @@ def clear_perms(doctype): | |||
db.sql("""delete from tabDocPerm where parent=%s""", doctype) | |||
def reset_perms(doctype): | |||
from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for | |||
delete_notification_count_for(doctype) | |||
clear_perms(doctype) | |||
reload_doc(db.get_value("DocType", doctype, "module"), | |||
"DocType", doctype, force=True) | |||
@@ -307,7 +331,8 @@ def reset_perms(doctype): | |||
def generate_hash(txt=None): | |||
"""Generates random hash for session id""" | |||
import hashlib, time | |||
return hashlib.sha224((txt or "") + repr(time.time())).hexdigest() | |||
from .utils import random_string | |||
return hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest() | |||
def reset_metadata_version(): | |||
v = generate_hash() | |||
@@ -332,15 +357,7 @@ def get_meta(doctype, cached=True): | |||
def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False): | |||
import frappe.model.delete_doc | |||
if not ignore_doctypes: | |||
ignore_doctypes = [] | |||
if isinstance(name, list): | |||
for n in name: | |||
frappe.model.delete_doc.delete_doc(doctype, n, force, ignore_doctypes, for_reload, ignore_permissions) | |||
else: | |||
frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions) | |||
frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions) | |||
def delete_doc_if_exists(doctype, name): | |||
if db.exists(doctype, name): | |||
@@ -362,7 +379,7 @@ def get_module(modulename): | |||
return importlib.import_module(modulename) | |||
def scrub(txt): | |||
return txt.replace(' ','_').replace('-', '_').replace('/', '_').lower() | |||
return txt.replace(' ','_').replace('-', '_').lower() | |||
def unscrub(txt): | |||
return txt.replace('_',' ').replace('-', ' ').title() | |||
@@ -396,11 +413,26 @@ def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None): | |||
return apps | |||
def get_installed_apps(): | |||
if flags.in_install_db: | |||
if getattr(flags, "in_install_db", True): | |||
return [] | |||
installed = json.loads(db.get_global("installed_apps") or "[]") | |||
return installed | |||
@whitelist() | |||
def get_versions(): | |||
versions = {} | |||
for app in get_installed_apps(): | |||
versions[app] = { | |||
"title": get_hooks("app_title", app_name=app), | |||
"description": get_hooks("app_description", app_name=app) | |||
} | |||
try: | |||
versions[app]["version"] = get_attr(app + ".__version__") | |||
except AttributeError: | |||
versions[app]["version"] = '0.0.1' | |||
return versions | |||
def get_hooks(hook=None, default=None, app_name=None): | |||
def load_app_hooks(app_name=None): | |||
hooks = {} | |||
@@ -449,6 +481,7 @@ def setup_module_map(): | |||
if app=="webnotes": app="frappe" | |||
local.app_modules.setdefault(app, []) | |||
for module in get_module_list(app): | |||
module = scrub(module) | |||
local.module_app[module] = app | |||
local.app_modules[app].append(module) | |||
@@ -456,10 +489,13 @@ def setup_module_map(): | |||
_cache.set_value("app_modules", local.app_modules) | |||
_cache.set_value("module_app", local.module_app) | |||
def get_file_items(path, raise_not_found=False): | |||
def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): | |||
content = read_file(path, raise_not_found=raise_not_found) | |||
if content: | |||
return [p.strip() for p in content.splitlines() if p.strip() and not p.startswith("#")] | |||
# \ufeff is no-width-break, \u200b is no-width-space | |||
content = content.replace("\ufeff", "").replace("\u200b", "").strip() | |||
return [p.strip() for p in content.splitlines() if (not ignore_empty_lines) or (p.strip() and not p.startswith("#"))] | |||
else: | |||
return [] | |||
@@ -494,9 +530,9 @@ def call(fn, *args, **kwargs): | |||
newargs[a] = kwargs.get(a) | |||
return fn(*args, **newargs) | |||
def make_property_setter(args): | |||
def make_property_setter(args, ignore_validate=False): | |||
args = _dict(args) | |||
get_doc({ | |||
ps = get_doc({ | |||
'doctype': "Property Setter", | |||
'doctype_or_field': args.doctype_or_field or "DocField", | |||
'doc_type': args.doctype, | |||
@@ -505,13 +541,16 @@ def make_property_setter(args): | |||
'value': args.value, | |||
'property_type': args.property_type or "Data", | |||
'__islocal': 1 | |||
}).save() | |||
}) | |||
ps.ignore_validate = ignore_validate | |||
ps.insert() | |||
def import_doc(path, ignore_links=False, ignore_insert=False, insert=False): | |||
from frappe.core.page.data_import_tool import data_import_tool | |||
data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert) | |||
def copy_doc(doc): | |||
""" No_copy fields also get copied.""" | |||
import copy | |||
if not isinstance(doc, dict): | |||
d = doc.as_dict() | |||
@@ -523,6 +562,8 @@ def copy_doc(doc): | |||
newdoc.set("__islocal", 1) | |||
newdoc.owner = None | |||
newdoc.creation = None | |||
newdoc.amended_from = None | |||
newdoc.amendment_date = None | |||
for d in newdoc.get_all_children(): | |||
d.name = None | |||
d.parent = None | |||
@@ -540,7 +581,7 @@ def respond_as_web_page(title, html, success=None, http_status_code=None): | |||
local.message = html | |||
local.message_success = success | |||
local.response['type'] = 'page' | |||
local.response['page_name'] = 'message.html' | |||
local.response['page_name'] = 'message' | |||
if http_status_code: | |||
local.response['http_status_code'] = http_status_code | |||
@@ -550,70 +591,16 @@ def build_match_conditions(doctype, as_condition=True): | |||
def get_list(doctype, filters=None, fields=None, or_filters=None, docstatus=None, | |||
group_by=None, order_by=None, limit_start=0, limit_page_length=None, | |||
as_list=False, debug=False, ignore_permissions=False): | |||
as_list=False, debug=False, ignore_permissions=False, user=None): | |||
import frappe.model.db_query | |||
return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters, | |||
fields=fields, docstatus=docstatus, or_filters=or_filters, | |||
group_by=group_by, order_by=order_by, limit_start=limit_start, | |||
limit_page_length=limit_page_length, as_list=as_list, debug=debug, | |||
ignore_permissions=ignore_permissions) | |||
ignore_permissions=ignore_permissions, user=user) | |||
run_query = get_list | |||
def get_jenv(): | |||
if not local.jenv: | |||
from jinja2 import Environment, DebugUndefined | |||
import frappe.utils | |||
# frappe will be loaded last, so app templates will get precedence | |||
jenv = Environment(loader = get_jloader(), undefined=DebugUndefined) | |||
set_filters(jenv) | |||
jenv.globals.update({ | |||
"frappe": sys.modules[__name__], | |||
"frappe.utils": frappe.utils, | |||
"_": _ | |||
}) | |||
local.jenv = jenv | |||
return local.jenv | |||
def get_jloader(): | |||
if not local.jloader: | |||
from jinja2 import ChoiceLoader, PackageLoader | |||
apps = get_installed_apps() | |||
apps.remove("frappe") | |||
local.jloader = ChoiceLoader([PackageLoader(app, ".") \ | |||
for app in apps + ["frappe"]]) | |||
return local.jloader | |||
def set_filters(jenv): | |||
from frappe.utils import global_date_format | |||
from frappe.website.utils import get_hex_shade | |||
from markdown2 import markdown | |||
from json import dumps | |||
jenv.filters["global_date_format"] = global_date_format | |||
jenv.filters["markdown"] = markdown | |||
jenv.filters["json"] = dumps | |||
jenv.filters["get_hex_shade"] = get_hex_shade | |||
# load jenv_filters from hooks.txt | |||
for app in get_all_apps(True): | |||
for jenv_filter in (get_hooks(app_name=app).jenv_filter or []): | |||
filter_name, filter_function = jenv_filter.split(":") | |||
jenv.filters[filter_name] = get_attr(filter_function) | |||
def get_template(path): | |||
return get_jenv().get_template(path) | |||
def get_website_route(doctype, name): | |||
return db.get_value("Website Route", {"ref_doctype": doctype, "docname": name}) | |||
def add_version(doc): | |||
get_doc({ | |||
"doctype": "Version", | |||
@@ -630,3 +617,37 @@ def get_test_records(doctype): | |||
return json.loads(f.read()) | |||
else: | |||
return [] | |||
def format_value(value, df, doc=None, currency=None): | |||
import frappe.utils.formatters | |||
return frappe.utils.formatters.format_value(value, df, doc, currency=currency) | |||
def get_print_format(doctype, name, print_format=None, style=None, as_pdf=False): | |||
from frappe.website.render import build_page | |||
local.form_dict.doctype = doctype | |||
local.form_dict.name = name | |||
local.form_dict.format = print_format | |||
local.form_dict.style = style | |||
html = build_page("print") | |||
if as_pdf: | |||
print_settings = db.get_singles_dict("Print Settings") | |||
if int(print_settings.send_print_as_pdf or 0): | |||
from utils.pdf import get_pdf | |||
return get_pdf(html, {"page-size": print_settings.pdf_page_size}) | |||
else: | |||
return html | |||
else: | |||
return html | |||
logging_setup_complete = False | |||
def get_logger(module=None): | |||
from frappe.setup_logging import setup_logging | |||
global logging_setup_complete | |||
if not logging_setup_complete: | |||
setup_logging() | |||
logging_setup_complete = True | |||
return logging.getLogger(module or "frappe") |
@@ -0,0 +1 @@ | |||
__version__ = "4.3.0" |
@@ -25,7 +25,7 @@ def handle(): | |||
DELETE will delete | |||
/api/resource/{doctype}/{name}?run_method={method} will run a whitelisted controller method | |||
""" | |||
parts = frappe.request.path[1:].split("/") | |||
parts = frappe.request.path[1:].split("/",3) | |||
call = doctype = name = None | |||
if len(parts) > 1: | |||
@@ -43,18 +43,20 @@ def handle(): | |||
elif call=="resource": | |||
if "run_method" in frappe.local.form_dict: | |||
method = frappe.local.form_dict.pop("run_method") | |||
doc = frappe.get_doc(doctype, name) | |||
doc.is_whitelisted(frappe.local.form_dict.run_method) | |||
doc.is_whitelisted(method) | |||
if frappe.local.request.method=="GET": | |||
if not doc.has_permission("read"): | |||
frappe.throw(_("Not permitted"), frappe.PermissionError) | |||
doc.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict) | |||
doc.run_method(method, **frappe.local.form_dict) | |||
if frappe.local.request.method=="POST": | |||
if not doc.has_permission("write"): | |||
frappe.throw(_("Not permitted"), frappe.PermissionError) | |||
doc.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict) | |||
doc.run_method(method, **frappe.local.form_dict) | |||
frappe.db.commit() | |||
else: | |||
@@ -76,7 +78,6 @@ def handle(): | |||
frappe.db.commit() | |||
if frappe.local.request.method=="DELETE": | |||
doc.update(data) | |||
# Not checking permissions here because it's checked in delete_doc | |||
frappe.delete_doc(doctype, name) | |||
frappe.local.response.http_status_code = 202 | |||
@@ -3,6 +3,7 @@ | |||
import sys, os | |||
import json | |||
import logging | |||
from werkzeug.wrappers import Request, Response | |||
from werkzeug.local import LocalManager | |||
@@ -25,6 +26,8 @@ local_manager = LocalManager([frappe.local]) | |||
_site = None | |||
_sites_path = os.environ.get("SITES_PATH", ".") | |||
logger = frappe.get_logger() | |||
@Request.application | |||
def application(request): | |||
frappe.local.request = request | |||
@@ -36,7 +39,7 @@ def application(request): | |||
init_site(request) | |||
if frappe.local.conf.get('maintainance_mode'): | |||
if frappe.local.conf.get('maintenance_mode'): | |||
raise frappe.SessionStopped | |||
make_form_dict(request) | |||
@@ -63,28 +66,35 @@ def application(request): | |||
except frappe.SessionStopped, e: | |||
response = frappe.utils.response.handle_session_stopped() | |||
except (frappe.AuthenticationError, | |||
frappe.PermissionError, | |||
frappe.DoesNotExistError, | |||
frappe.NameError, | |||
frappe.OutgoingEmailError, | |||
frappe.ValidationError, | |||
frappe.UnsupportedMediaType), e: | |||
except Exception, e: | |||
http_status_code = getattr(e, "http_status_code", 500) | |||
if frappe.local.is_ajax: | |||
response = frappe.utils.response.report_error(e.http_status_code) | |||
response = frappe.utils.response.report_error(http_status_code) | |||
else: | |||
response = frappe.website.render.render("error", e.http_status_code) | |||
frappe.respond_as_web_page("Server Error", | |||
"<pre>"+frappe.get_traceback()+"</pre>", | |||
http_status_code=http_status_code) | |||
response = frappe.website.render.render("message", http_status_code=http_status_code) | |||
if e.__class__ == frappe.AuthenticationError: | |||
if hasattr(frappe.local, "login_manager"): | |||
frappe.local.login_manager.clear_cookies() | |||
if http_status_code==500: | |||
logger.error('Request Error') | |||
else: | |||
if frappe.local.request.method in ("POST", "PUT") and frappe.db: | |||
frappe.db.commit() | |||
rollback = False | |||
# update session | |||
if getattr(frappe.local, "session_obj", None): | |||
updated_in_db = frappe.local.session_obj.update() | |||
if updated_in_db: | |||
frappe.db.commit() | |||
finally: | |||
if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback: | |||
frappe.db.rollback() | |||
@@ -131,4 +141,4 @@ def serve(port=8000, profile=False, site=None, sites_path='.'): | |||
}) | |||
run_simple('0.0.0.0', int(port), application, use_reloader=True, | |||
use_debugger=True, use_evalex=True) | |||
use_debugger=True, use_evalex=True, threaded=True) |
@@ -13,6 +13,8 @@ from frappe import conf | |||
from frappe.sessions import Session, clear_sessions, delete_session | |||
from frappe.modules.patch_handler import check_session_stopped | |||
from urllib import quote | |||
class HTTPRequest: | |||
def __init__(self): | |||
# Get Environment variables | |||
@@ -20,6 +22,8 @@ class HTTPRequest: | |||
if self.domain and self.domain.startswith('www.'): | |||
self.domain = self.domain[4:] | |||
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') \ | |||
or frappe.get_request_header('X-Forwarded-For') or '127.0.0.1' | |||
# language | |||
self.set_lang(frappe.get_request_header('HTTP_ACCEPT_LANGUAGE')) | |||
@@ -157,7 +161,7 @@ class LoginManager: | |||
ip_list = [i.strip() for i in ip_list] | |||
for ip in ip_list: | |||
if frappe.get_request_header('REMOTE_ADDR', '').startswith(ip) or frappe.get_request_header('X-Forwarded-For', '').startswith(ip): | |||
if frappe.local.request_ip.startswith(ip): | |||
return | |||
frappe.throw(_("Not allowed from this IP Address"), frappe.AuthenticationError) | |||
@@ -223,7 +227,8 @@ class CookieManager: | |||
def flush_cookies(self, response): | |||
for key, opts in self.cookies.items(): | |||
response.set_cookie(key, opts.get("value"), expires=opts.get("expires")) | |||
response.set_cookie(key, quote((opts.get("value") or "").encode('utf-8')), | |||
expires=opts.get("expires")) | |||
# expires yesterday! | |||
expires = datetime.datetime.now() + datetime.timedelta(days=-1) | |||
@@ -9,6 +9,7 @@ bootstrap client session | |||
import frappe | |||
import frappe.defaults | |||
import frappe.widgets.page | |||
from frappe.utils import get_gravatar | |||
def get_bootinfo(): | |||
"""build and return boot info""" | |||
@@ -23,8 +24,6 @@ def get_bootinfo(): | |||
# system info | |||
bootinfo['sysdefaults'] = frappe.defaults.get_defaults() | |||
bootinfo['server_date'] = frappe.utils.nowdate() | |||
bootinfo["send_print_in_body_and_attachment"] = frappe.db.get_value("Outgoing Email Settings", | |||
None, "send_print_in_body_and_attachment") | |||
if frappe.session['user'] != 'Guest': | |||
bootinfo['user_info'] = get_fullnames() | |||
@@ -37,6 +36,8 @@ def get_bootinfo(): | |||
bootinfo.modules.update(frappe.get_attr(app + ".config.desktop.get_data")() or {}) | |||
except ImportError: | |||
pass | |||
except AttributeError: | |||
pass | |||
bootinfo.module_app = frappe.local.module_app | |||
bootinfo.hidden_modules = frappe.db.get_global("hidden_modules") | |||
@@ -48,7 +49,9 @@ def get_bootinfo(): | |||
add_home_page(bootinfo, doclist) | |||
add_allowed_pages(bootinfo) | |||
load_translations(bootinfo) | |||
add_timezone_info(bootinfo) | |||
load_conf_settings(bootinfo) | |||
load_print(bootinfo, doclist) | |||
# ipinfo | |||
if frappe.session['data'].get('ipinfo'): | |||
@@ -63,6 +66,8 @@ def get_bootinfo(): | |||
if bootinfo.lang: | |||
bootinfo.lang = unicode(bootinfo.lang) | |||
bootinfo.error_report_email = frappe.get_hooks("error_report_email") | |||
return bootinfo | |||
def load_conf_settings(bootinfo): | |||
@@ -72,14 +77,23 @@ def load_conf_settings(bootinfo): | |||
def add_allowed_pages(bootinfo): | |||
roles = frappe.get_roles() | |||
bootinfo.page_info = dict(frappe.db.sql("""select distinct parent, modified from `tabPage Role` | |||
where role in (%s)""" % ', '.join(['%s']*len(roles)), roles)) | |||
bootinfo.page_info = {} | |||
for p in frappe.db.sql("""select distinct | |||
tabPage.name, tabPage.modified, tabPage.title | |||
from `tabPage Role`, `tabPage` | |||
where `tabPage Role`.role in (%s) | |||
and `tabPage Role`.parent = `tabPage`.name""" % ', '.join(['%s']*len(roles)), | |||
roles, as_dict=True): | |||
bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title} | |||
# pages where role is not set are also allowed | |||
bootinfo.page_info.update(dict(frappe.db.sql("""select parent, modified | |||
for p in frappe.db.sql("""select name, modified, title | |||
from `tabPage` where | |||
(select count(*) from `tabPage Role` | |||
where `tabPage Role`.parent=tabPage.name) = 0"""))) | |||
where `tabPage Role`.parent=tabPage.name) = 0""", as_dict=1): | |||
bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title} | |||
def load_translations(bootinfo): | |||
if frappe.local.lang != 'en': | |||
@@ -90,18 +104,15 @@ def get_fullnames(): | |||
"""map of user fullnames""" | |||
ret = frappe.db.sql("""select name, | |||
concat(ifnull(first_name, ''), | |||
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')), | |||
user_image, gender, email | |||
from tabUser where ifnull(enabled, 0)=1""", as_list=1) | |||
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')) as fullname, | |||
user_image as image, gender, email | |||
from tabUser where ifnull(enabled, 0)=1""", as_dict=1) | |||
d = {} | |||
for r in ret: | |||
if not r[2]: | |||
r[2] = '/assets/frappe/images/ui/avatar.png' | |||
else: | |||
r[2] = r[2] | |||
d[r[0]]= {'fullname': r[1], 'image': r[2], 'gender': r[3], | |||
'email': r[4] or r[0]} | |||
if not r.image: | |||
r.image = get_gravatar() | |||
d[r.name] = r | |||
return d | |||
@@ -113,16 +124,13 @@ def get_startup_js(): | |||
def get_user(bootinfo): | |||
"""get user info""" | |||
bootinfo['user'] = frappe.user.load_user() | |||
bootinfo.user = frappe.user.load_user() | |||
def add_home_page(bootinfo, docs): | |||
"""load home page""" | |||
if frappe.session.user=="Guest": | |||
return | |||
home_page = frappe.db.get_default("desktop:home_page") | |||
try: | |||
page = frappe.widgets.page.get(home_page) | |||
except (frappe.DoesNotExistError, frappe.PermissionError): | |||
@@ -131,3 +139,22 @@ def add_home_page(bootinfo, docs): | |||
bootinfo['home_page'] = page.name | |||
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) | |||
def load_print(bootinfo, doclist): | |||
print_settings = frappe.db.get_singles_dict("Print Settings") | |||
print_settings.doctype = ":Print Settings" | |||
doclist.append(print_settings) | |||
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") |
@@ -1,5 +1,5 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
from frappe.utils.minify import JavascriptMinify | |||
@@ -11,12 +11,12 @@ Build the `public` folders and setup languages | |||
import os, sys, frappe, json, shutil | |||
from cssmin import cssmin | |||
def bundle(no_compress, make_copy=False): | |||
def bundle(no_compress, make_copy=False, verbose=False): | |||
"""concat / minify js files""" | |||
# build js files | |||
make_asset_dirs(make_copy=make_copy) | |||
build(no_compress) | |||
build(no_compress, verbose) | |||
def watch(no_compress): | |||
"""watch and rebuild if necessary""" | |||
import time | |||
@@ -25,18 +25,18 @@ def watch(no_compress): | |||
while True: | |||
if files_dirty(): | |||
build(no_compress=True) | |||
time.sleep(3) | |||
def make_asset_dirs(make_copy=False): | |||
assets_path = os.path.join(frappe.local.sites_path, "assets") | |||
for dir_path in [ | |||
os.path.join(assets_path, 'js'), | |||
os.path.join(assets_path, 'js'), | |||
os.path.join(assets_path, 'css')]: | |||
if not os.path.exists(dir_path): | |||
os.makedirs(dir_path) | |||
# symlink app/public > assets/app | |||
for app_name in frappe.get_all_apps(True): | |||
pymodule = frappe.get_module(app_name) | |||
@@ -49,11 +49,11 @@ def make_asset_dirs(make_copy=False): | |||
else: | |||
os.symlink(os.path.abspath(source), target) | |||
def build(no_compress=False): | |||
def build(no_compress=False, verbose=False): | |||
assets_path = os.path.join(frappe.local.sites_path, "assets") | |||
for target, sources in get_build_maps().iteritems(): | |||
pack(os.path.join(assets_path, target), sources, no_compress) | |||
pack(os.path.join(assets_path, target), sources, no_compress, verbose) | |||
shutil.copy(os.path.join(os.path.dirname(os.path.abspath(frappe.__file__)), 'data', 'languages.txt'), frappe.local.sites_path) | |||
# reset_app_html() | |||
@@ -79,39 +79,52 @@ def get_build_maps(): | |||
else: | |||
s = os.path.join(app_path, source) | |||
source_paths.append(s) | |||
build_maps[target] = source_paths | |||
except Exception, e: | |||
print path | |||
raise | |||
return build_maps | |||
timestamps = {} | |||
def pack(target, sources, no_compress): | |||
def pack(target, sources, no_compress, verbose): | |||
from cStringIO import StringIO | |||
outtype, outtxt = target.split(".")[-1], '' | |||
jsm = JavascriptMinify() | |||
for f in sources: | |||
suffix = None | |||
if ':' in f: f, suffix = f.split(':') | |||
if not os.path.exists(f) or os.path.isdir(f): continue | |||
if not os.path.exists(f) or os.path.isdir(f): | |||
print "did not find " + f | |||
continue | |||
timestamps[f] = os.path.getmtime(f) | |||
try: | |||
with open(f, 'r') as sourcefile: | |||
with open(f, 'r') as sourcefile: | |||
data = unicode(sourcefile.read(), 'utf-8', errors='ignore') | |||
if outtype=="js" and (not no_compress) and suffix!="concat" and (".min." not in f): | |||
extn = f.rsplit(".", 1)[1] | |||
if outtype=="js" and extn=="js" and (not no_compress) and suffix!="concat" and (".min." not in f): | |||
tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO() | |||
jsm.minify(tmpin, tmpout) | |||
outtxt += unicode(tmpout.getvalue() or '', 'utf-8').strip('\n') + ';' | |||
minified = tmpout.getvalue() | |||
outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';' | |||
if verbose: | |||
print "{0}: {1}k".format(f, int(len(minified) / 1024)) | |||
elif outtype=="js" and extn=="html": | |||
# add to frappe.templates | |||
content = data.replace("\n", " ").replace("'", "\'") | |||
outtxt += """frappe.templates["{key}"] = '{content}';\n""".format(\ | |||
key=f.rsplit("/", 1)[1][:-5], content=content) | |||
else: | |||
outtxt += ('\n/*\n *\t%s\n */' % f) | |||
outtxt += '\n' + data + '\n' | |||
except Exception, e: | |||
print "--Error in:" + f + "--" | |||
print frappe.get_traceback() | |||
@@ -119,10 +132,10 @@ def pack(target, sources, no_compress): | |||
if not no_compress and outtype == 'css': | |||
pass | |||
#outtxt = cssmin(outtxt) | |||
with open(target, 'w') as f: | |||
f.write(outtxt.encode("utf-8")) | |||
print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))) | |||
def files_dirty(): | |||
@@ -135,4 +148,4 @@ def files_dirty(): | |||
return True | |||
else: | |||
return False | |||
@@ -7,6 +7,7 @@ from __future__ import unicode_literals | |||
import os | |||
import subprocess | |||
import frappe | |||
from frappe.utils import cint | |||
site_arg_optional = ['serve', 'build', 'watch', 'celery', 'resize_images'] | |||
@@ -35,7 +36,10 @@ def main(): | |||
args = parsed_args.copy() | |||
args["site"] = site | |||
frappe.init(site, sites_path=sites_path) | |||
return run(fn, args) | |||
ret = run(fn, args) | |||
if ret: | |||
# if there's a return value, it's an error, so quit | |||
return ret | |||
else: | |||
site = get_site(parsed_args) | |||
if fn not in site_arg_optional and not site: | |||
@@ -128,18 +132,20 @@ def setup_install(parser): | |||
help="Make a new application with boilerplate") | |||
parser.add_argument("--install", metavar="DB-NAME", nargs=1, | |||
help="Install a new db") | |||
parser.add_argument("--root_password", metavar="ROOT-PASSWD", | |||
help="MariaDB root password") | |||
parser.add_argument("--sites_path", metavar="SITES_PATH", nargs=1, | |||
help="path to directory with sites") | |||
parser.add_argument("--install_app", metavar="APP-NAME", nargs=1, | |||
help="Install a new app") | |||
parser.add_argument("--add_to_installed_apps", metavar="APP-NAME", nargs="*", | |||
help="Add these app(s) to Installed Apps") | |||
parser.add_argument("--root-password", nargs=1, | |||
help="Root password for new app") | |||
parser.add_argument("--reinstall", default=False, action="store_true", | |||
help="Install a fresh app in db_name specified in conf.py") | |||
parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2, | |||
help="Restore from an sql file") | |||
parser.add_argument("--with_scheduler_enabled", default=False, action="store_true", | |||
help="Enable scheduler on restore") | |||
parser.add_argument("--add_system_manager", nargs="+", | |||
metavar=("EMAIL", "[FIRST-NAME] [LAST-NAME]"), help="Add a user with all roles") | |||
@@ -201,8 +207,10 @@ def setup_utilities(parser): | |||
parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"), | |||
help="Create new conf.py file") | |||
parser.add_argument("--make_custom_server_script", nargs=1, metavar="DOCTYPE", | |||
help="Create new conf.py file") | |||
parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs=1, | |||
help="Create new custome server script") | |||
parser.add_argument("--init_list", nargs=1, metavar="DOCTYPE", | |||
help="Create new list.js and list.html files") | |||
parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs="*", | |||
help="Set administrator password") | |||
parser.add_argument("--request", metavar='URL-ARGS', nargs=1, help="Run request as admin") | |||
parser.add_argument("--mysql", action="store_true", help="get mysql shell for a site") | |||
@@ -224,8 +232,8 @@ def setup_utilities(parser): | |||
# clear | |||
parser.add_argument("--clear_web", default=False, action="store_true", | |||
help="Clear website cache") | |||
parser.add_argument("--build_sitemap", default=False, action="store_true", | |||
help="Build Website Route") | |||
parser.add_argument("--build_website", default=False, action="store_true", | |||
help="Sync statics and clear cache") | |||
parser.add_argument("--sync_statics", default=False, action="store_true", | |||
help="Sync files from templates/statics to Web Pages") | |||
parser.add_argument("--clear_cache", default=False, action="store_true", | |||
@@ -242,6 +250,11 @@ def setup_utilities(parser): | |||
parser.add_argument("--run_scheduler_event", nargs=1, | |||
metavar="all | daily | weekly | monthly", | |||
help="Run a scheduler event") | |||
parser.add_argument("--enable_scheduler", default=False, action="store_true", | |||
help="Enable scheduler") | |||
parser.add_argument("--disable_scheduler", default=False, action="store_true", | |||
help="Disable scheduler") | |||
# replace | |||
parser.add_argument("--replace", nargs=3, | |||
@@ -280,21 +293,52 @@ def use(sites_path): | |||
sitefile.write(frappe.local.site) | |||
# install | |||
@cmd | |||
def install(db_name, root_login="root", root_password=None, source_sql=None, | |||
admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False): | |||
def _install(db_name, root_login="root", root_password=None, source_sql=None, | |||
admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False, install_apps=None): | |||
from frappe.installer import install_db, install_app, make_site_dirs | |||
import frappe.utils.scheduler | |||
verbose = not quiet | |||
# enable scheduler post install? | |||
enable_scheduler = _is_scheduler_enabled() | |||
install_db(root_login=root_login, root_password=root_password, db_name=db_name, source_sql=source_sql, | |||
admin_password = admin_password, verbose=verbose, force=force, site_config=site_config, reinstall=reinstall) | |||
make_site_dirs() | |||
install_app("frappe", verbose=verbose, set_as_patched=not source_sql) | |||
if frappe.conf.get("install_apps"): | |||
for app in frappe.conf.install_apps: | |||
install_app(app, verbose=verbose, set_as_patched=not source_sql) | |||
if install_apps: | |||
for app in install_apps: | |||
install_app(app, verbose=verbose, set_as_patched=not source_sql) | |||
frappe.utils.scheduler.toggle_scheduler(enable_scheduler) | |||
scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled" | |||
print "*** Scheduler is", scheduler_status, "***" | |||
@cmd | |||
def install(db_name, root_login="root", root_password=None, source_sql=None, | |||
admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False, install_apps=None): | |||
_install(db_name, root_login, root_password, source_sql, admin_password, force, site_config, reinstall, quiet, install_apps) | |||
frappe.destroy() | |||
def _is_scheduler_enabled(): | |||
enable_scheduler = False | |||
try: | |||
frappe.connect() | |||
enable_scheduler = cint(frappe.db.get_default("enable_scheduler")) | |||
except: | |||
pass | |||
finally: | |||
frappe.db.close() | |||
return enable_scheduler | |||
@cmd | |||
def install_app(app_name, quiet=False): | |||
verbose = not quiet | |||
@@ -310,7 +354,7 @@ def add_to_installed_apps(*apps): | |||
all_apps = frappe.get_all_apps(with_frappe=True) | |||
for each in apps: | |||
if each in all_apps: | |||
add_to_installed_apps(each, rebuild_sitemap=False) | |||
add_to_installed_apps(each, rebuild_website=False) | |||
frappe.destroy() | |||
@cmd | |||
@@ -318,18 +362,27 @@ def reinstall(quiet=False): | |||
verbose = not quiet | |||
try: | |||
frappe.connect() | |||
installed = frappe.get_installed_apps() | |||
frappe.clear_cache() | |||
except: | |||
pass | |||
installed = [] | |||
finally: | |||
frappe.db.close() | |||
install(db_name=frappe.conf.db_name, verbose=verbose, force=True, reinstall=True) | |||
install(db_name=frappe.conf.db_name, verbose=verbose, force=True, reinstall=True, install_apps=installed) | |||
@cmd | |||
def restore(db_name, source_sql, force=False, quiet=False): | |||
verbose = not quiet | |||
install(db_name, source_sql=source_sql, verbose=verbose, force=force) | |||
def restore(db_name, source_sql, force=False, quiet=False, with_scheduler_enabled=False): | |||
import frappe.utils.scheduler | |||
_install(db_name, source_sql=source_sql, quiet=quiet, force=force) | |||
try: | |||
frappe.connect() | |||
frappe.utils.scheduler.toggle_scheduler(with_scheduler_enabled) | |||
scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled" | |||
print "*** Scheduler is", scheduler_status, "***" | |||
finally: | |||
frappe.destroy() | |||
@cmd | |||
def add_system_manager(email, first_name=None, last_name=None): | |||
@@ -353,13 +406,12 @@ def update(remote=None, branch=None, reload_gunicorn=False): | |||
subprocess.check_output("killall -HUP gunicorn".split()) | |||
@cmd | |||
def latest(rebuild_website_config=True, quiet=False): | |||
def latest(rebuild_website=True, quiet=False): | |||
import frappe.modules.patch_handler | |||
import frappe.model.sync | |||
from frappe.website import rebuild_config | |||
from frappe.utils.fixtures import sync_fixtures | |||
import frappe.translate | |||
from frappe.website import statics | |||
from frappe.core.doctype.notification_count.notification_count import clear_notifications | |||
verbose = not quiet | |||
@@ -370,16 +422,13 @@ def latest(rebuild_website_config=True, quiet=False): | |||
frappe.modules.patch_handler.run_all() | |||
# sync | |||
frappe.model.sync.sync_all(verbose=verbose) | |||
frappe.translate.clear_cache() | |||
sync_fixtures() | |||
statics.sync().start() | |||
# build website config if any changes in templates etc. | |||
if rebuild_website_config: | |||
rebuild_config() | |||
frappe.translate.clear_cache() | |||
clear_notifications() | |||
if rebuild_website: | |||
build_website() | |||
finally: | |||
frappe.destroy() | |||
@@ -419,10 +468,10 @@ def reload_doc(module, doctype, docname, force=False): | |||
frappe.destroy() | |||
@cmd | |||
def build(make_copy=False): | |||
def build(make_copy=False, verbose=False): | |||
import frappe.build | |||
import frappe | |||
frappe.build.bundle(False, make_copy=make_copy) | |||
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose) | |||
@cmd | |||
def watch(): | |||
@@ -487,12 +536,19 @@ def make_custom_server_script(doctype): | |||
make_custom_server_script_file(doctype) | |||
frappe.destroy() | |||
@cmd | |||
def init_list(doctype): | |||
import frappe.core.doctype.doctype.doctype | |||
frappe.core.doctype.doctype.doctype.init_list(doctype) | |||
# clear | |||
@cmd | |||
def clear_cache(): | |||
import frappe.sessions | |||
from frappe.core.doctype.notification_count.notification_count import clear_notifications | |||
frappe.connect() | |||
frappe.clear_cache() | |||
clear_notifications() | |||
frappe.destroy() | |||
@cmd | |||
@@ -511,17 +567,19 @@ def clear_all_sessions(): | |||
frappe.destroy() | |||
@cmd | |||
def build_sitemap(): | |||
from frappe.website import rebuild_config | |||
def build_website(verbose=False): | |||
from frappe.website import render, statics | |||
frappe.connect() | |||
rebuild_config() | |||
render.clear_cache() | |||
statics.sync(verbose=verbose).start() | |||
frappe.db.commit() | |||
frappe.destroy() | |||
@cmd | |||
def sync_statics(): | |||
def sync_statics(force=False): | |||
from frappe.website import statics | |||
frappe.connect() | |||
statics.sync_statics() | |||
statics.sync_statics(rebuild = force) | |||
frappe.db.commit() | |||
frappe.destroy() | |||
@@ -558,6 +616,24 @@ def run_scheduler_event(event, force=False): | |||
frappe.utils.scheduler.trigger(frappe.local.site, event, now=force) | |||
frappe.destroy() | |||
@cmd | |||
def enable_scheduler(): | |||
import frappe.utils.scheduler | |||
frappe.connect() | |||
frappe.utils.scheduler.enable_scheduler() | |||
frappe.db.commit() | |||
print "Enabled" | |||
frappe.destroy() | |||
@cmd | |||
def disable_scheduler(): | |||
import frappe.utils.scheduler | |||
frappe.connect() | |||
frappe.utils.scheduler.disable_scheduler() | |||
frappe.db.commit() | |||
print "Disabled" | |||
frappe.destroy() | |||
# replace | |||
@cmd | |||
def replace(search_regex, replacement, extn, force=False): | |||
@@ -665,8 +741,13 @@ def checkout(branch): | |||
git(("checkout", branch)) | |||
@cmd | |||
def set_admin_password(admin_password): | |||
def set_admin_password(admin_password=None): | |||
import frappe | |||
import getpass | |||
while not admin_password: | |||
admin_password = getpass.getpass("Administrator's password: ") | |||
frappe.connect() | |||
frappe.db.sql("""update __Auth set `password`=password(%s) | |||
where user='Administrator'""", (admin_password,)) | |||
@@ -706,7 +787,7 @@ def smtp_debug_server(): | |||
os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"]) | |||
@cmd | |||
def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None): | |||
def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None, force=False): | |||
import frappe.test_runner | |||
from frappe.utils import sel | |||
@@ -715,7 +796,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driv | |||
ret = 1 | |||
try: | |||
ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose, | |||
tests=tests) | |||
tests=tests, force=force) | |||
if len(ret.failures) == 0 and len(ret.errors) == 0: | |||
ret = 0 | |||
finally: | |||
@@ -42,7 +42,11 @@ def set_value(doctype, name, fieldname, value): | |||
child.set(fieldname, value) | |||
else: | |||
doc = frappe.get_doc(doctype, name) | |||
doc.set(fieldname, value) | |||
df = doc.meta.get_field(fieldname) | |||
if df.fieldtype == "Read Only" or df.read_only: | |||
frappe.throw(_("Can not edit Read Only fields")) | |||
else: | |||
doc.set(fieldname, value) | |||
doc.save() | |||
@@ -88,7 +92,7 @@ def submit(doclist): | |||
doclistobj = frappe.get_doc(doclist) | |||
doclistobj.submit() | |||
return doclist.as_dict() | |||
return doclistobj.as_dict() | |||
@frappe.whitelist() | |||
def cancel(doctype, name): | |||
@@ -108,8 +112,9 @@ def set_default(key, value, parent=None): | |||
frappe.clear_cache(user=frappe.session.user) | |||
@frappe.whitelist() | |||
def make_width_property_setter(): | |||
doc = json.loads(frappe.form_dict) | |||
def make_width_property_setter(doc): | |||
if isinstance(doc, basestring): | |||
doc = json.loads(doc) | |||
if doc["doctype"]=="Property Setter" and doc["property"]=="width": | |||
frappe.get_doc(doc).insert(ignore_permissions = True) | |||
@@ -139,6 +144,8 @@ def has_permission(doctype, docname, perm_type="read"): | |||
@frappe.whitelist() | |||
def get_js(src): | |||
if src[0]=="/": | |||
src = src[1:] | |||
contentpath = os.path.join(frappe.local.sites_path, src) | |||
with open(contentpath, "r") as srcfile: | |||
code = frappe.utils.cstr(srcfile.read()) | |||
@@ -20,17 +20,25 @@ def get_data(): | |||
{ | |||
"type": "page", | |||
"name": "permission-manager", | |||
"label": "Permission Manager", | |||
"label": _("Role Permissions Manager"), | |||
"icon": "icon-lock", | |||
"description": _("Set Permissions on Document Types and Roles") | |||
}, | |||
{ | |||
"type": "page", | |||
"name": "user-properties", | |||
"label": _("User Permission Restrictions"), | |||
"icon": "icon-user", | |||
"description": _("Set Defaults and Restrictions for Users") | |||
}, | |||
"name": "user-permissions", | |||
"label": _("User Permissions Manager"), | |||
"icon": "icon-shield", | |||
"description": _("Set Permissions per User") | |||
}, | |||
{ | |||
"type": "report", | |||
"is_query_report": True, | |||
"doctype": "User", | |||
"icon": "icon-eye-open", | |||
"name": "Permitted Documents For User", | |||
"description": _("Check which Documents are readable by a User") | |||
} | |||
] | |||
}, | |||
{ | |||
@@ -113,12 +121,27 @@ def get_data(): | |||
"name": "Outgoing Email Settings", | |||
"description": _("Set outgoing mail server.") | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Email Alert", | |||
"description": _("Setup Email Alert based on various criteria.") | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Standard Reply", | |||
"description": _("Standard replies to common queries.") | |||
}, | |||
] | |||
}, | |||
{ | |||
"label": _("Printing and Branding"), | |||
"icon": "icon-print", | |||
"items": [ | |||
{ | |||
"type": "doctype", | |||
"name": "Print Settings", | |||
"description": _("Set default format, page size, print style etc.") | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Print Format", | |||
@@ -168,11 +191,6 @@ def get_data(): | |||
"description": _("Send download link of a recent backup to System Managers"), | |||
"hide_count": True | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Social Login Keys", | |||
"description": _("Enter keys to enable login via Facebook, Google, GitHub."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Backup Manager", | |||
@@ -16,6 +16,11 @@ def get_data(): | |||
"name": "Blog Post", | |||
"description": _("Single Post (article)."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Web Form", | |||
"description": _("User editable form on Website."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Blogger", | |||
@@ -47,13 +52,6 @@ def get_data(): | |||
"name": "Website Settings", | |||
"description": _("Setup of top navigation bar, footer and logo."), | |||
}, | |||
{ | |||
"type": "page", | |||
"name":"sitemap-browser", | |||
"label": _("Sitemap Browser"), | |||
"description": _("View or manage Website Route tree."), | |||
"icon": "icon-sitemap" | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Style Settings", | |||
@@ -89,6 +87,11 @@ def get_data(): | |||
"name": "Website Page Permission", | |||
"description": _("Define read, write, admin permissions for a Website Page."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Social Login Keys", | |||
"description": _("Enter keys to enable login via Facebook, Google, GitHub."), | |||
} | |||
] | |||
}, | |||
] |
@@ -1,5 +1,6 @@ | |||
{ | |||
"creation": "2012-08-02 15:17:28.000000", | |||
"autoname": "hash", | |||
"creation": "2012-08-02 15:17:28", | |||
"description": "Bulk Email records.", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
@@ -8,30 +9,35 @@ | |||
{ | |||
"fieldname": "sender", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Sender", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "recipient", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Recipient", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "message", | |||
"fieldtype": "Long Text", | |||
"in_list_view": 1, | |||
"label": "Message", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "status", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Status", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "error", | |||
"fieldtype": "Text", | |||
"in_list_view": 1, | |||
"label": "Error", | |||
"permlevel": 0 | |||
}, | |||
@@ -56,7 +62,7 @@ | |||
"icon": "icon-envelope", | |||
"idx": 1, | |||
"in_create": 1, | |||
"modified": "2014-02-12 21:11:05.000000", | |||
"modified": "2014-06-03 02:22:18.860832", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Bulk Email", | |||
@@ -67,8 +73,9 @@ | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager" | |||
} | |||
], | |||
"read_only": 1 | |||
"read_only": 0 | |||
} |
@@ -1,22 +1,31 @@ | |||
{ | |||
"autoname": "CWR/.#####", | |||
"creation": "2012-08-08 10:40:11.000000", | |||
"autoname": "hash", | |||
"creation": "2012-08-08 10:40:11", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"fieldname": "comment", | |||
"fieldtype": "Text", | |||
"in_list_view": 1, | |||
"label": "Comment", | |||
"no_copy": 0, | |||
"oldfieldname": "comment", | |||
"oldfieldtype": "Text", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "comment_type", | |||
"fieldtype": "Data", | |||
"label": "Comment Type", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "comment_by", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Comment By", | |||
"no_copy": 0, | |||
"oldfieldname": "comment_by", | |||
@@ -27,6 +36,7 @@ | |||
{ | |||
"fieldname": "comment_by_fullname", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Comment By Fullname", | |||
"no_copy": 0, | |||
"oldfieldname": "comment_by_fullname", | |||
@@ -37,6 +47,7 @@ | |||
{ | |||
"fieldname": "comment_date", | |||
"fieldtype": "Date", | |||
"in_list_view": 1, | |||
"label": "Comment Date", | |||
"no_copy": 0, | |||
"oldfieldname": "comment_date", | |||
@@ -47,6 +58,7 @@ | |||
{ | |||
"fieldname": "comment_time", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Comment Time", | |||
"no_copy": 0, | |||
"oldfieldname": "comment_time", | |||
@@ -94,7 +106,7 @@ | |||
"icon": "icon-comments", | |||
"idx": 1, | |||
"issingle": 0, | |||
"modified": "2014-01-24 13:00:20.000000", | |||
"modified": "2014-08-22 05:24:28.072749", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Comment", | |||
@@ -19,7 +19,7 @@ class Comment(Document): | |||
self.update_comment_in_doc() | |||
def update_comment_in_doc(self): | |||
if self.comment_doctype and self.comment_docname and self.comment: | |||
if self.comment_doctype and self.comment_docname and self.comment and self.comment_type=="Comment": | |||
try: | |||
_comments = self.get_comments_from_parent() | |||
updated = False | |||
@@ -59,14 +59,14 @@ class Comment(Document): | |||
frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (self.comment_doctype, | |||
"%s", "%s"), (json.dumps(_comments), self.comment_docname)) | |||
# clear parent cache if route exists: | |||
route = frappe.db.get_value("Website Route", {"ref_doctype": self.comment_doctype, | |||
"docname": self.comment_docname}) | |||
if route: | |||
clear_cache(route) | |||
comment_doc = frappe.get_doc(self.comment_doctype, self.comment_docname) | |||
if getattr(comment_doc, "get_route", None): | |||
clear_cache(comment_doc.get_route()) | |||
def on_trash(self): | |||
if (self.comment_type or "Comment") != "Comment": | |||
frappe.only_for("System Manager") | |||
_comments = self.get_comments_from_parent() | |||
for c in _comments: | |||
if c.get("name")==self.name: | |||
@@ -0,0 +1,7 @@ | |||
[ | |||
{ | |||
"doctype": "Comment", | |||
"name": "_Test Comment 1", | |||
"comment": "test comment" | |||
} | |||
] |
@@ -1,8 +1,7 @@ | |||
{ | |||
"allow_attach": 1, | |||
"allow_import": 1, | |||
"allow_import": 1, | |||
"autoname": "naming_series:", | |||
"creation": "2013-01-29 10:47:14.000000", | |||
"creation": "2013-01-29 10:47:14", | |||
"description": "Keep a track of all communications", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
@@ -122,6 +121,7 @@ | |||
"default": "__user", | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"ignore_user_permissions": 1, | |||
"label": "User", | |||
"options": "User", | |||
"permlevel": 0, | |||
@@ -154,7 +154,7 @@ | |||
"idx": 1, | |||
"in_dialog": 0, | |||
"issingle": 0, | |||
"modified": "2014-01-24 13:01:25.000000", | |||
"modified": "2014-08-14 09:39:23.219125", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Communication", | |||
@@ -162,7 +162,7 @@ | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"apply_user_permissions": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -176,7 +176,6 @@ | |||
}, | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -190,7 +189,7 @@ | |||
}, | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"apply_user_permissions": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -203,7 +202,6 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -216,7 +214,6 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -16,14 +16,17 @@ from frappe import _ | |||
from frappe.model.document import Document | |||
class Communication(Document): | |||
def validate(self): | |||
if not self.parentfield: | |||
self.parentfield = "communications" | |||
def get_parent_doc(self): | |||
return frappe.get_doc(self.parenttype, self.parent) | |||
def update_parent(self): | |||
"""update status of parent Lead or Contact based on who is replying""" | |||
observer = getattr(self.get_parent_doc(), "on_communication", None) | |||
if observer: | |||
observer() | |||
parent_doc = self.get_parent_doc() | |||
parent_doc.run_method("on_communication") | |||
def on_update(self): | |||
self.update_parent() | |||
@@ -31,7 +34,7 @@ class Communication(Document): | |||
@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, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): | |||
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): | |||
if doctype and name and not frappe.has_permission(doctype, "email", name): | |||
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format( | |||
@@ -39,12 +42,12 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = | |||
_make(doctype=doctype, name=name, content=content, subject=subject, sent_or_received=sent_or_received, | |||
sender=sender, recipients=recipients, communication_medium=communication_medium, send_email=send_email, | |||
print_html=print_html, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead, | |||
print_html=print_html, print_format=print_format, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead, | |||
date=date) | |||
def _make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | |||
sender=None, recipients=None, communication_medium="Email", send_email=False, | |||
print_html=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): | |||
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): | |||
# add to Communication | |||
sent_via = None | |||
@@ -86,7 +89,7 @@ def _make(doctype=None, name=None, content=None, subject=None, sent_or_received | |||
if send_email: | |||
d = comm | |||
send_comm_email(d, name, sent_via, print_html, attachments, send_me_a_copy) | |||
send_comm_email(d, name, sent_via, print_html, print_format, attachments, send_me_a_copy) | |||
@frappe.whitelist() | |||
def get_customer_supplier(args=None): | |||
@@ -107,9 +110,10 @@ def get_customer_supplier(args=None): | |||
} | |||
return {} | |||
def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', send_me_a_copy=False): | |||
def send_comm_email(d, name, sent_via=None, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False): | |||
footer = None | |||
if sent_via: | |||
if hasattr(sent_via, "get_sender"): | |||
d.sender = sent_via.get_sender(d) or d.sender | |||
@@ -118,21 +122,16 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s | |||
if hasattr(sent_via, "get_content"): | |||
d.content = sent_via.get_content(d) | |||
footer = set_portal_link(sent_via, d) | |||
send_print_in_body = frappe.db.get_value("Outgoing Email Settings", None, "send_print_in_body_and_attachment") | |||
if print_html and not send_print_in_body: | |||
d.content += "<p>Please see attachment for document details.</p>" | |||
footer = "<hr>" + set_portal_link(sent_via, d) | |||
mail = get_email(d.recipients, sender=d.sender, subject=d.subject, | |||
msg=d.content, footer=footer, print_html=print_html if send_print_in_body else None) | |||
msg=d.content, footer=footer) | |||
if send_me_a_copy: | |||
mail.cc.append(frappe.db.get_value("User", frappe.session.user, "email")) | |||
if print_html: | |||
print_html = scrub_urls(print_html) | |||
mail.add_attachment(name.replace(' ','').replace('/','-') + '.html', print_html) | |||
if print_html or print_format: | |||
attach_print(mail, sent_via, print_html, print_format) | |||
for a in json.loads(attachments): | |||
try: | |||
@@ -142,10 +141,31 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s | |||
send(mail) | |||
def attach_print(mail, sent_via, print_html, print_format): | |||
name = sent_via.name | |||
if not print_html and print_format: | |||
print_html = frappe.get_print_format(sent_via.doctype, sent_via.name, print_format) | |||
print_settings = frappe.db.get_singles_dict("Print Settings") | |||
send_print_as_pdf = cint(print_settings.send_print_as_pdf) | |||
if send_print_as_pdf: | |||
try: | |||
mail.add_pdf_attachment(name.replace(' ','').replace('/','-') + '.pdf', print_html) | |||
except Exception: | |||
frappe.msgprint(_("Error generating PDF, attachment sent as HTML")) | |||
frappe.errprint(frappe.get_traceback()) | |||
send_print_as_pdf = 0 | |||
if not send_print_as_pdf: | |||
print_html = scrub_urls(print_html) | |||
mail.add_attachment(name.replace(' ','').replace('/','-') + '.html', | |||
print_html, 'text/html') | |||
def set_portal_link(sent_via, comm): | |||
"""set portal link in footer""" | |||
footer = None | |||
footer = "" | |||
if is_signup_enabled() and hasattr(sent_via, "get_portal_page"): | |||
portal_page = sent_via.get_portal_page() | |||
@@ -154,7 +174,7 @@ def set_portal_link(sent_via, comm): | |||
sent_via.get("contact_email")) in comm.recipients | |||
if is_valid_recipient: | |||
url = "%s/%s?name=%s" % (get_url(), portal_page, urllib.quote(sent_via.name)) | |||
footer = """<!-- Portal Link --><hr> | |||
<a href="%s" target="_blank">View this on our website</a>""" % url | |||
footer = """<!-- Portal Link --> | |||
<p><a href="%s" target="_blank">View this on our website</a></p>""" % url | |||
return footer |
@@ -0,0 +1,37 @@ | |||
<div class="row" style="max-height: 30px;"> | |||
<div class="col-xs-10"> | |||
<div class="text-ellipsis"> | |||
{%= list.get_avatar_and_id(doc) %} | |||
<!-- sample icon --> | |||
<span style="margin-right: 8px;" | |||
title="{%= __(doc.sent_or_received) %}" class="filterable" | |||
data-filter="sent_or_received,=,{%= doc.sent_or_received %}"> | |||
<i class="icon-{%= doc.sent_or_received=="Sent" ? | |||
"arrow-right" : "arrow-left" %} text-muted"></i> | |||
</span> | |||
<span style="margin-right: 8px;" class="filterable" | |||
title="{%= doc.communication_medium %}" | |||
data-filter="communication_medium,=,{%= doc.communication_medium %}"> | |||
<i class="icon-{%= { | |||
"Chat": "comments", | |||
"Phone": "phone", | |||
"Email": "envelope", | |||
"SMS": "comment", | |||
"Visit": "male", | |||
"Other": "comments" | |||
}[doc.communication_medium] || "comments" %}"></i> | |||
</span> | |||
</div> | |||
</div> | |||
<div class="col-xs-2"> | |||
<div class="text-ellipsis"> | |||
<span style="margin-right: 8px;" class="filterable small" | |||
data-filter="recipients,=,{%= doc.recipients %}" title="{%= doc.recipients %}"> | |||
{%= doc.recipients %}</span> | |||
</div> | |||
</div> | |||
</div> |
@@ -0,0 +1,3 @@ | |||
frappe.listview_settings['Communication'] = { | |||
add_fields: ["sent_or_received", "recipients", "subject", "communication_medium"] | |||
}; |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
import frappe | |||
import unittest | |||
test_records = frappe.get_test_records('Communication') | |||
class TestCommunication(unittest.TestCase): | |||
pass |
@@ -0,0 +1,10 @@ | |||
[ | |||
{ | |||
"doctype": "Communication", | |||
"name": "_Test Communication 1", | |||
"subject": "Test Subject", | |||
"sent_or_received": "Received", | |||
"parenttype": "User", | |||
"parent": "Administrator" | |||
} | |||
] |
@@ -47,9 +47,18 @@ cur_frm.fields_dict['dt'].get_query = function(doc, dt, dn) { | |||
} | |||
cur_frm.cscript.fieldtype = function(doc, dt, dn) { | |||
if(doc.fieldtype == 'Link') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter name of the document you want this field to be linked to in <b>Options</b>.<br> Eg.: Customer'; | |||
else if(doc.fieldtype == 'Select') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter values in <b>Options</b>, with each option on a new line. <br>Eg.: <b>Field:</b> Country <br><b>Options:</b><br>China<br>India<br>United States<br><br><b>'; | |||
else cur_frm.fields_dict['options_help'].disp_area.innerHTML = ''; | |||
if(doc.fieldtype == 'Link') { | |||
cur_frm.fields_dict['options_help'].disp_area.innerHTML = | |||
__('Name of the Document Type (DocType) you want this field to be linked to. e.g. Customer'); | |||
} else if(doc.fieldtype == 'Select') { | |||
cur_frm.fields_dict['options_help'].disp_area.innerHTML = | |||
__('Options for select. Each option on a new line. e.g.: <br>Option 1<br>Option 2<br>Option 3<br>'); | |||
} else if(doc.fieldtype == 'Dynamic Link') { | |||
cur_frm.fields_dict['options_help'].disp_area.innerHTML = | |||
__('Fieldname which will be the DocType for this link field.'); | |||
} else { | |||
cur_frm.fields_dict['options_help'].disp_area.innerHTML = ''; | |||
} | |||
} | |||
@@ -1,5 +1,5 @@ | |||
{ | |||
"creation": "2013-01-10 16:34:01.000000", | |||
"creation": "2013-01-10 16:34:01", | |||
"description": "Adds a custom field to a DocType", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
@@ -57,11 +57,21 @@ | |||
"no_copy": 0, | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", | |||
"options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", | |||
"description": "Set non-standard precision for a Float or Currency field", | |||
"fieldname": "precision", | |||
"fieldtype": "Select", | |||
"label": "Precision", | |||
"options": "\n1\n2\n3\n4\n5\n6", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "options_help", | |||
"fieldtype": "HTML", | |||
@@ -143,9 +153,9 @@ | |||
}, | |||
{ | |||
"depends_on": "eval:doc.fieldtype===\"Link\"", | |||
"fieldname": "ignore_restrictions", | |||
"fieldname": "ignore_user_permissions", | |||
"fieldtype": "Check", | |||
"label": "Ignore Restrictions", | |||
"label": "Ignore User Permissions", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
@@ -257,7 +267,7 @@ | |||
], | |||
"icon": "icon-glass", | |||
"idx": 1, | |||
"modified": "2014-01-20 17:48:31.000000", | |||
"modified": "2014-09-05 07:41:13.076820", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Custom Field", | |||
@@ -33,10 +33,10 @@ class CustomField(Document): | |||
# validate field | |||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype | |||
validate_fields_for_doctype(self.dt) | |||
frappe.clear_cache(doctype=self.dt) | |||
validate_fields_for_doctype(self.dt) | |||
# create property setter to emulate insert after | |||
self.create_property_setter() | |||
@@ -78,22 +78,27 @@ class CustomField(Document): | |||
@frappe.whitelist() | |||
def get_fields_label(doctype=None): | |||
return [{"value": df.fieldname, "label": _(df.label)} for df in frappe.get_meta(doctype).get("fields")] | |||
return [{"value": df.fieldname or "", "label": _(df.label or "")} for df in frappe.get_meta(doctype).get("fields")] | |||
def create_custom_field_if_values_exist(doctype, df): | |||
df = frappe._dict(df) | |||
if df.fieldname in frappe.db.get_table_columns(doctype) and \ | |||
frappe.db.sql("""select count(*) from `tab{doctype}` | |||
where ifnull({fieldname},'')!=''""".format(doctype=doctype, fieldname=df.fieldname))[0][0] and \ | |||
not frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df.fieldname}): | |||
frappe.get_doc({ | |||
"doctype":"Custom Field", | |||
"dt": doctype, | |||
"permlevel": df.permlevel or 0, | |||
"label": df.label, | |||
"fieldname": df.fieldname, | |||
"fieldtype": df.fieldtype, | |||
"options": df.options, | |||
"insert_after": df.insert_after | |||
}).insert() | |||
where ifnull({fieldname},'')!=''""".format(doctype=doctype, fieldname=df.fieldname))[0][0]: | |||
create_custom_field(doctype, df) | |||
def create_custom_field(doctype, df): | |||
if not frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df.fieldname}): | |||
frappe.get_doc({ | |||
"doctype":"Custom Field", | |||
"dt": doctype, | |||
"permlevel": df.get("permlevel") or 0, | |||
"label": df.get("label"), | |||
"fieldname": df.get("fieldname"), | |||
"fieldtype": df.get("fieldtype"), | |||
"options": df.get("options"), | |||
"insert_after": df.get("insert_after"), | |||
"print_hide": df.get("print_hide") | |||
}).insert() |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
import frappe | |||
import unittest | |||
test_records = frappe.get_test_records('Custom Field') | |||
class TestCustomField(unittest.TestCase): | |||
pass |
@@ -0,0 +1 @@ | |||
[] |
@@ -1,6 +1,6 @@ | |||
{ | |||
"autoname": "CustomScript.####", | |||
"creation": "2013-01-10 16:34:01.000000", | |||
"creation": "2013-01-10 16:34:01", | |||
"description": "Adds a custom script (client or server) to a DocType", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
@@ -8,6 +8,7 @@ | |||
{ | |||
"fieldname": "dt", | |||
"fieldtype": "Link", | |||
"in_list_view": 1, | |||
"label": "DocType", | |||
"oldfieldname": "dt", | |||
"oldfieldtype": "Link", | |||
@@ -19,6 +20,7 @@ | |||
"fieldname": "script_type", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"in_list_view": 1, | |||
"label": "Script Type", | |||
"oldfieldname": "script_type", | |||
"oldfieldtype": "Select", | |||
@@ -29,16 +31,24 @@ | |||
{ | |||
"fieldname": "script", | |||
"fieldtype": "Code", | |||
"in_list_view": 1, | |||
"label": "Script", | |||
"oldfieldname": "script", | |||
"oldfieldtype": "Code", | |||
"options": "Script", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "sample", | |||
"fieldtype": "HTML", | |||
"label": "Sample", | |||
"options": "<h3>Custom Script Help</h3>\n<p>Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started</p>\n<pre><code>\n// additional validation on dates\ncur_frm.cscript.custom_validate = function(doc) {\n if (doc.from_date < get_today()) {\n msgprint(\"You can not select past date in From Date\");\n validated = false;\n }\n}\n\n// make a field read-only after saving\ncur_frm.cscript.custom_refresh = function(doc) {\n // use the __islocal value of doc, to check if the doc is saved or not\n cur_frm.set_df_property(\"myfield\", \"read_only\", doc.__islocal ? 0 : 1);\n}\n\n// addtional permission checking\ncur_frm.cscript.custom_validate = function(doc) {\n if(user==\"user1@example.com\" && doc.purpose!=\"Material Receipt\") {\n msgprint(\"You are only allowed Material Receipt\");\n validated = false;\n }\n}\n\n// calculate sales incentive\ncur_frm.cscript.custom_validate = function(doc) {\n // calculate incentives for each person on the deal\n total_incentive = 0\n $.each(wn.model.get(\"Sales Team\", {parent:doc.name}), function(i, d) {\n\n // calculate incentive\n var incentive_percent = 2;\n if(doc.grand_total > 400) incentive_percent = 4;\n\n // actual incentive\n d.incentives = flt(doc.grand_total) * incentive_percent / 100;\n total_incentive += flt(d.incentives)\n });\n\n doc.total_incentive = total_incentive;\n}\n</code>\n</pre>", | |||
"permlevel": 0 | |||
} | |||
], | |||
"icon": "icon-glass", | |||
"idx": 1, | |||
"modified": "2014-01-20 17:48:31.000000", | |||
"modified": "2014-06-19 06:55:02.522204", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Custom Script", | |||
@@ -58,11 +58,11 @@ frappe.ui.form.on("Customize Form", "refresh", function(frm) { | |||
frm.add_custom_button('Refresh Form', function() { | |||
frm.script_manager.trigger("doc_type"); | |||
}, "icon-refresh"); | |||
}, "icon-refresh", "btn-default"); | |||
frm.add_custom_button('Reset to defaults', function() { | |||
frappe.customize_form.confirm(__('Remove all customizations?'), frm); | |||
}, "icon-eraser"); | |||
}, "icon-eraser", "btn-default"); | |||
} | |||
// if(!frm.doc.doc_type) { | |||
@@ -73,8 +73,10 @@ frappe.ui.form.on("Customize Form", "refresh", function(frm) { | |||
// } | |||
if(frappe.route_options) { | |||
frappe.model.set_value("Customize Form", null, "doc_type", frappe.route_options.doctype) | |||
frappe.route_options = null; | |||
setTimeout(function() { | |||
frm.set_value("doc_type", frappe.route_options.doctype); | |||
frappe.route_options = null; | |||
}, 1000); | |||
} | |||
}); | |||
@@ -83,40 +85,25 @@ frappe.customize_form.confirm = function(msg, frm) { | |||
var d = new frappe.ui.Dialog({ | |||
title: 'Reset To Defaults', | |||
width: 500 | |||
}); | |||
$y(d.body, {padding: '32px', textAlign: 'center'}); | |||
$a(d.body, 'div', '', '', msg); | |||
var button_wrapper = $a(d.body, 'div'); | |||
$y(button_wrapper, {paddingTop: '15px'}); | |||
var proceed_btn = $btn(button_wrapper, 'Proceed', function() { | |||
return frm.call({ | |||
doc: frm.doc, | |||
method: "reset_to_defaults", | |||
callback: function(r) { | |||
if(r.exc) { | |||
msgprint(r.exc); | |||
} else { | |||
frappe.customize_form.confirm.dialog.hide(); | |||
frappe.customize_form.clear_locals_and_refresh(frm); | |||
fields: [ | |||
{fieldtype:"HTML", options:__("All customizations will be removed. Please confirm.")}, | |||
], | |||
primary_action: function() { | |||
return frm.call({ | |||
doc: frm.doc, | |||
method: "reset_to_defaults", | |||
callback: function(r) { | |||
if(r.exc) { | |||
msgprint(r.exc); | |||
} else { | |||
d.hide(); | |||
frappe.customize_form.clear_locals_and_refresh(frm); | |||
} | |||
} | |||
} | |||
}); | |||
}); | |||
$y(proceed_btn, {marginRight: '20px', fontWeight: 'bold'}); | |||
var cancel_btn = $btn(button_wrapper, 'Cancel', function() { | |||
frappe.customize_form.confirm.dialog.hide(); | |||
}); | |||
} | |||
}); | |||
$(cancel_btn).addClass('btn-small btn-info'); | |||
$y(cancel_btn, {fontWeight: 'bold'}); | |||
frappe.customize_form.confirm.dialog = d; | |||
d.show(); | |||
} | |||
@@ -159,7 +146,7 @@ frappe.customize_form.add_fields_help = function(frm) { | |||
<td><b>Perm Level</b></td>\ | |||
<td>\ | |||
Assign a permission level to the field.<br />\ | |||
(Permissions can be managed via Setup > Permission Manager)\ | |||
(Permissions can be managed via Setup > Role Permissions Manager)\ | |||
</td>\ | |||
</tr>\ | |||
<tr>\ | |||
@@ -63,22 +63,6 @@ | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "allow_print", | |||
"fieldtype": "Check", | |||
"label": "Hide Print", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "allow_email", | |||
"fieldtype": "Check", | |||
"label": "Hide Email", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "allow_copy", | |||
"fieldtype": "Check", | |||
@@ -88,16 +72,7 @@ | |||
"search_index": 0 | |||
}, | |||
{ | |||
"description": "Note: maximum attachment size = 1mb", | |||
"fieldname": "allow_attach", | |||
"fieldtype": "Check", | |||
"label": "Allow Attach", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"depends_on": "eval:cint(doc.allow_attach)", | |||
"depends_on": "", | |||
"fieldname": "max_attachments", | |||
"fieldtype": "Int", | |||
"label": "Max Attachments", | |||
@@ -126,7 +101,7 @@ | |||
"icon": "icon-glass", | |||
"idx": 1, | |||
"issingle": 1, | |||
"modified": "2014-05-08 09:27:44.167026", | |||
"modified": "2014-08-22 05:42:45.083260", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Customize Form", | |||
@@ -18,7 +18,6 @@ class CustomizeForm(Document): | |||
'sort_order': 'Data', | |||
'default_print_format': 'Data', | |||
'read_only_onload': 'Check', | |||
'allow_attach': 'Check', | |||
'allow_copy': 'Check', | |||
'max_attachments': 'Int' | |||
} | |||
@@ -32,7 +31,7 @@ class CustomizeForm(Document): | |||
'width': 'Data', | |||
'print_width': 'Data', | |||
'reqd': 'Check', | |||
'ignore_restrictions': 'Check', | |||
'ignore_user_permissions': 'Check', | |||
'in_filter': 'Check', | |||
'in_list_view': 'Check', | |||
'hidden': 'Check', | |||
@@ -41,11 +40,12 @@ class CustomizeForm(Document): | |||
'allow_on_submit': 'Check', | |||
'depends_on': 'Data', | |||
'description': 'Text', | |||
'default': 'Text' | |||
'default': 'Text', | |||
'precision': 'Select' | |||
} | |||
allowed_fieldtype_change = (('Currency', 'Float'), ('Small Text', 'Data'), | |||
('Text', 'Text Editor', 'Code')) | |||
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), | |||
('Text', 'Text Editor', 'Code'), ('Data', 'Select'), ('Text', 'Small Text')) | |||
def on_update(self): | |||
frappe.db.sql("delete from tabSingles where doctype='Customize Form'") | |||
@@ -187,6 +187,7 @@ class CustomizeForm(Document): | |||
return | |||
# create a new property setter | |||
# ignore validation becuase it will be done at end | |||
frappe.make_property_setter({ | |||
"doctype": self.doc_type, | |||
"doctype_or_field": "DocField" if fieldname else "DocType", | |||
@@ -194,7 +195,7 @@ class CustomizeForm(Document): | |||
"property": property, | |||
"value": value, | |||
"property_type": property_type | |||
}) | |||
}, ignore_validate=True) | |||
def delete_existing_property_setter(self, property, fieldname=None): | |||
# first delete existing property setter | |||
@@ -204,14 +205,14 @@ class CustomizeForm(Document): | |||
if existing_property_setter: | |||
frappe.delete_doc("Property Setter", existing_property_setter) | |||
def get_existing_property_value(self, property, fieldname=None): | |||
def get_existing_property_value(self, property_name, fieldname=None): | |||
# check if there is any need to make property setter! | |||
if fieldname: | |||
property_value = frappe.db.get_value("DocField", {"parent": self.doc_type, | |||
"fieldname": fieldname}, property) | |||
"fieldname": fieldname}, property_name) | |||
else: | |||
try: | |||
property_value = frappe.db.get_value("DocType", self.doc_type, property) | |||
property_value = frappe.db.get_value("DocType", self.doc_type, property_name) | |||
except Exception, e: | |||
if e.args[0]==1054: | |||
property_value = None | |||
@@ -221,18 +222,19 @@ class CustomizeForm(Document): | |||
return property_value | |||
def validate_fieldtype_change(self, df, old_value, new_value): | |||
allowed = False | |||
for allowed_changes in self.allowed_fieldtype_change: | |||
if ((old_value in allowed_changes and new_value in allowed_changes) | |||
or (old_value not in allowed_changes and new_value not in allowed_changes)): | |||
continue | |||
else: | |||
frappe.throw(_("Fieldtype must be one of {0} in row {1}").format(", ".join([_(fieldtype) for fieldtype in allowed_changes]), df.idx)) | |||
if (old_value in allowed_changes and new_value in allowed_changes): | |||
allowed = True | |||
if not allowed: | |||
frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx)) | |||
def reset_to_defaults(self): | |||
if not self.doc_type: | |||
return | |||
frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s""", self.doc_type) | |||
frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s | |||
and ifnull(field_name, '')!='naming_series'""", self.doc_type) | |||
frappe.clear_cache(doctype=self.doc_type) | |||
self.fetch_to_customize() | |||
@@ -47,7 +47,7 @@ class TestCustomizeForm(unittest.TestCase): | |||
d = self.get_customize_form("User") | |||
self.assertEquals(d.doc_type, "User") | |||
self.assertEquals(len(d.get("customize_form_fields")), 53) | |||
self.assertEquals(len(d.get("customize_form_fields")), 55) | |||
self.assertEquals(d.get("customize_form_fields")[-1].fieldname, "test_custom_field") | |||
self.assertEquals(d.get("customize_form_fields", {"fieldname": "location"})[0].in_list_view, 1) | |||
@@ -112,7 +112,7 @@ class TestCustomizeForm(unittest.TestCase): | |||
def test_save_customization_custom_field_property(self): | |||
d = self.get_customize_form("User") | |||
self.assertEquals(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), None) | |||
self.assertEquals(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 0) | |||
custom_field = d.get("customize_form_fields", {"fieldname": "test_custom_field"})[0] | |||
custom_field.reqd = 1 | |||
@@ -1,10 +1,17 @@ | |||
{ | |||
"allow_copy": 0, | |||
"autoname": "DLF.#####", | |||
"creation": "2013-02-22 01:27:32.000000", | |||
"creation": "2013-02-22 01:27:32", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"fieldname": "label_and_type", | |||
"fieldtype": "Section Break", | |||
"label": "Label and Type", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "label", | |||
"fieldtype": "Data", | |||
@@ -19,6 +26,7 @@ | |||
"search_index": 1 | |||
}, | |||
{ | |||
"default": "Data", | |||
"fieldname": "fieldtype", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
@@ -26,7 +34,7 @@ | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", | |||
"options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"reqd": 1, | |||
@@ -46,6 +54,42 @@ | |||
"reqd": 0, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"fieldname": "reqd", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Mandatory", | |||
"oldfieldname": "reqd", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
}, | |||
{ | |||
"fieldname": "in_list_view", | |||
"fieldtype": "Check", | |||
"label": "In List View", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "column_break_7", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", | |||
"description": "Set non-standard precision for a Float or Currency field", | |||
"fieldname": "precision", | |||
"fieldtype": "Select", | |||
"label": "Precision", | |||
"options": "\n1\n2\n3\n4\n5\n6", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", | |||
"fieldname": "options", | |||
@@ -60,6 +104,25 @@ | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "permissions", | |||
"fieldtype": "Section Break", | |||
"label": "Permissions", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): <br>\nmyfield\neval:doc.myfield=='My Value'<br>\neval:doc.age>18", | |||
"fieldname": "depends_on", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "Depends On", | |||
"oldfieldname": "depends_on", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"reqd": 0 | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "permlevel", | |||
@@ -75,13 +138,12 @@ | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "width", | |||
"fieldtype": "Data", | |||
"fieldname": "hidden", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Width", | |||
"oldfieldname": "width", | |||
"oldfieldtype": "Data", | |||
"label": "Hidden", | |||
"oldfieldname": "hidden", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_width": "50px", | |||
@@ -90,111 +152,76 @@ | |||
"width": "50px" | |||
}, | |||
{ | |||
"fieldname": "print_width", | |||
"fieldtype": "Data", | |||
"label": "Print Width", | |||
"fieldname": "column_break_14", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"width": "50px" | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "reqd", | |||
"fieldname": "ignore_user_permissions", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Reqd", | |||
"oldfieldname": "reqd", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
}, | |||
{ | |||
"fieldname": "ignore_restrictions", | |||
"fieldtype": "Check", | |||
"label": "Ignore Restrictions", | |||
"label": "Ignore User Permissions", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "in_filter", | |||
"fieldname": "allow_on_submit", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "In Filter", | |||
"oldfieldname": "in_filter", | |||
"label": "Allow on Submit", | |||
"oldfieldname": "allow_on_submit", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"width": "50px" | |||
}, | |||
{ | |||
"fieldname": "in_list_view", | |||
"fieldtype": "Check", | |||
"label": "In List View", | |||
"permlevel": 0 | |||
"reqd": 0 | |||
}, | |||
{ | |||
"fieldname": "hidden", | |||
"fieldname": "report_hide", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Hidden", | |||
"oldfieldname": "hidden", | |||
"label": "Report Hide", | |||
"oldfieldname": "report_hide", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
"reqd": 0 | |||
}, | |||
{ | |||
"fieldname": "print_hide", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Print Hide", | |||
"oldfieldname": "print_hide", | |||
"oldfieldtype": "Check", | |||
"fieldname": "display", | |||
"fieldtype": "Section Break", | |||
"label": "Display", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "report_hide", | |||
"fieldtype": "Check", | |||
"fieldname": "default", | |||
"fieldtype": "Text", | |||
"hidden": 0, | |||
"label": "Report Hide", | |||
"oldfieldname": "report_hide", | |||
"oldfieldtype": "Check", | |||
"label": "Default", | |||
"oldfieldname": "default", | |||
"oldfieldtype": "Text", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"reqd": 0 | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "allow_on_submit", | |||
"fieldname": "in_filter", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Allow on Submit", | |||
"oldfieldname": "allow_on_submit", | |||
"label": "In Filter", | |||
"oldfieldname": "in_filter", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"reqd": 0 | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"width": "50px" | |||
}, | |||
{ | |||
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): <br>\nmyfield\neval:doc.myfield=='My Value'<br>\neval:doc.age>18", | |||
"fieldname": "depends_on", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"label": "Depends On", | |||
"oldfieldname": "depends_on", | |||
"oldfieldtype": "Data", | |||
"fieldname": "column_break_21", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"reqd": 0 | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "description", | |||
@@ -210,16 +237,40 @@ | |||
"width": "300px" | |||
}, | |||
{ | |||
"fieldname": "default", | |||
"fieldtype": "Text", | |||
"fieldname": "print_hide", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"label": "Default", | |||
"oldfieldname": "default", | |||
"oldfieldtype": "Text", | |||
"label": "Print Hide", | |||
"oldfieldname": "print_hide", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"description": "Print Width of the field, if the field is a column in a table", | |||
"fieldname": "print_width", | |||
"fieldtype": "Data", | |||
"label": "Print Width", | |||
"permlevel": 0, | |||
"print_width": "50px", | |||
"width": "50px" | |||
}, | |||
{ | |||
"fieldname": "width", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"in_list_view": 1, | |||
"label": "Width", | |||
"oldfieldname": "width", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_width": "50px", | |||
"reqd": 0, | |||
"search_index": 0, | |||
"width": "50px" | |||
} | |||
], | |||
"hide_heading": 0, | |||
@@ -227,10 +278,11 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2013-12-23 16:12:45.000000", | |||
"modified": "2014-09-05 07:41:29.641454", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Customize Form Field", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"read_only": 0 | |||
} |
@@ -34,7 +34,7 @@ | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", | |||
"options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 1 | |||
@@ -91,6 +91,16 @@ | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", | |||
"description": "Set non-standard precision for a Float or Currency field", | |||
"fieldname": "precision", | |||
"fieldtype": "Select", | |||
"label": "Precision", | |||
"options": "\n1\n2\n3\n4\n5\n6", | |||
"permlevel": 0, | |||
"print_hide": 1 | |||
}, | |||
{ | |||
"description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", | |||
"fieldname": "options", | |||
@@ -166,10 +176,10 @@ | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "User restrictions should not apply for this Link", | |||
"fieldname": "ignore_restrictions", | |||
"description": "User permissions should not apply for this Link", | |||
"fieldname": "ignore_user_permissions", | |||
"fieldtype": "Check", | |||
"label": "Ignore Restrictions", | |||
"label": "Ignore User Permissions", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
@@ -304,7 +314,7 @@ | |||
"in_dialog": 1, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2014-04-24 15:56:23.561687", | |||
"modified": "2014-09-05 07:41:05.956027", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocField", | |||
@@ -1,7 +0,0 @@ | |||
Defines a permission rule for a DocType. The permission rule is set for a Role and a level and has permission for read, write, create, submit, cancel, amend and report. | |||
#### Match | |||
If a fieldname is set in `match` property, then the rule will only apply for those records that have a value for that fieldname which is one of the user's default values (user properties). | |||
This is used to restrict users to view records belonging to a company in case of a multi-company system where the `company` field is present in most forms. |
@@ -26,6 +26,13 @@ | |||
"search_index": 0, | |||
"width": "150px" | |||
}, | |||
{ | |||
"description": "Filter records based on User Permissions defined for a user", | |||
"fieldname": "apply_user_permissions", | |||
"fieldtype": "Check", | |||
"label": "Apply User Permissions", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "column_break_2", | |||
"fieldtype": "Column Break", | |||
@@ -46,6 +53,15 @@ | |||
"search_index": 0, | |||
"width": "40px" | |||
}, | |||
{ | |||
"depends_on": "", | |||
"description": "JSON list of DocTypes used to apply User Permissions. If empty, all linked DocTypes will be used to apply User Permissions.", | |||
"fieldname": "user_permission_doctypes", | |||
"fieldtype": "Text", | |||
"label": "User Permission DocTypes", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
{ | |||
"fieldname": "section_break_4", | |||
"fieldtype": "Section Break", | |||
@@ -171,15 +187,10 @@ | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "print", | |||
"description": "This role update User Permissions for a user", | |||
"fieldname": "set_user_permissions", | |||
"fieldtype": "Check", | |||
"label": "Print", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "email", | |||
"fieldtype": "Check", | |||
"label": "Email", | |||
"label": "Set User Permissions", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
@@ -188,17 +199,15 @@ | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "Only restricted users can access", | |||
"fieldname": "restricted", | |||
"fieldname": "print", | |||
"fieldtype": "Check", | |||
"label": "Only Restricted Documents", | |||
"label": "Print", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "This role can restrict users for accessing the record.", | |||
"fieldname": "restrict", | |||
"fieldname": "email", | |||
"fieldtype": "Check", | |||
"label": "Can Restrict Others", | |||
"label": "Email", | |||
"permlevel": 0 | |||
} | |||
], | |||
@@ -207,7 +216,7 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2014-05-01 05:20:48.162224", | |||
"modified": "2014-08-26 01:43:31.499363", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocPerm", | |||
@@ -1,4 +1,4 @@ | |||
# Copyright (c) 2013, {app_publisher} | |||
# Copyright (c) 2013, {app_publisher} and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
@@ -6,4 +6,4 @@ import frappe | |||
from frappe.model.document import Document | |||
class {classname}(Document): | |||
pass | |||
pass |
@@ -0,0 +1,34 @@ | |||
<div class="row" style="max-height: 30px;"> | |||
<div class="col-xs-12"> | |||
<div class="text-ellipsis"> | |||
{{%= list.get_avatar_and_id(doc) %}} | |||
<!-- sample text --> | |||
<span style="margin-right: 8px;" class="filterable" | |||
data-filter="text,=,{{%= doc.text %}}"> | |||
{{%= doc.text %}}</span> | |||
<!-- sample icon --> | |||
{{% if(doc.check) {{ %}} | |||
<span style="margin-right: 8px;" | |||
title="{{%= __("Title") %}}" class="filterable" | |||
data-filter="check,=,Yes"> | |||
<i class="icon-icon text-muted"></i> | |||
</span> | |||
{{% }} %}} | |||
<!-- sample label --> | |||
<span class="label | |||
label-{{%= frappe.utils.guess_style(doc.status) %}} filterable" | |||
title="{{%= __("Title") %}}" | |||
data-filter="status,=,{{%= doc.status %}}"> | |||
{{%= doc.status %}} | |||
</span> | |||
</div> | |||
</div> | |||
<!-- sample graph --> | |||
<div class="col-xs-1 text-right"> | |||
{{% var completed = doc.completed, title = __("Completed") %}} | |||
{{% include "templates/form_grid/includes/progress.html" %}} | |||
</div> | |||
</div> |
@@ -0,0 +1,4 @@ | |||
frappe.listview_settings['{doctype}'] = {{ | |||
add_fields: ["status"], | |||
filters:[["status","=", "Open"]] | |||
}}; |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2013, {app_publisher} and Contributors | |||
# See license.txt | |||
import frappe | |||
import unittest | |||
test_records = frappe.get_test_records('{doctype}') | |||
class Test{classname}(unittest.TestCase): | |||
pass |
@@ -0,0 +1,6 @@ | |||
[ | |||
{{ | |||
"doctype": "{doctype}", | |||
"name": "_Test {doctype} 1" | |||
}} | |||
] |
@@ -11,18 +11,6 @@ $(cur_frm.wrapper).on("grid-row-render", function(e, grid_row) { | |||
} | |||
}) | |||
cur_frm.cscript.allow_attach = function(doc, cdt, cdn) { | |||
if(doc.allow_attach) { | |||
unhide_field('max_attachments'); | |||
} else { | |||
hide_field('max_attachments'); | |||
} | |||
} | |||
cur_frm.cscript.onload = function(doc, cdt, cdn) { | |||
this.allow_attach(doc, cdt, cdn); | |||
} | |||
cur_frm.cscript.refresh = function(doc, cdt, cdn) { | |||
if(in_list(user_roles, 'System Manager') && !in_list(user_roles, 'Administrator')) { | |||
// make the document read-only | |||
@@ -1,6 +1,5 @@ | |||
{ | |||
"allow_attach": 0, | |||
"allow_copy": 0, | |||
"allow_copy": 0, | |||
"autoname": "Prompt", | |||
"creation": "2013-02-18 13:36:19", | |||
"custom": 0, | |||
@@ -276,18 +275,10 @@ | |||
"oldfieldtype": "Check", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "allow_attach", | |||
"fieldtype": "Check", | |||
"label": "Allow Attach", | |||
"oldfieldname": "allow_attach", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "max_attachments", | |||
"fieldtype": "Int", | |||
"hidden": 1, | |||
"hidden": 0, | |||
"label": "Max Attachments", | |||
"oldfieldname": "max_attachments", | |||
"oldfieldtype": "Int", | |||
@@ -345,7 +336,7 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2014-05-08 09:23:56.952829", | |||
"modified": "2014-08-22 05:33:03.067964", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocType", | |||
@@ -5,12 +5,18 @@ from __future__ import unicode_literals | |||
import frappe | |||
from frappe import _ | |||
import os | |||
from frappe.utils import now, cint | |||
from frappe.model import no_value_fields | |||
from frappe.model.document import Document | |||
from frappe.model.db_schema import type_map | |||
from frappe.core.doctype.property_setter.property_setter import make_property_setter | |||
from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for | |||
from frappe.modules import make_boilerplate | |||
form_grid_templates = { | |||
"fields": "templates/form_grid/fields.html" | |||
} | |||
class DocType(Document): | |||
def validate(self): | |||
@@ -22,7 +28,7 @@ class DocType(Document): | |||
self.validate_series() | |||
self.scrub_field_names() | |||
self.validate_title_field() | |||
validate_fields(self.get("fields")) | |||
validate_fields(self) | |||
if self.istable: | |||
# no permission records for child table | |||
@@ -41,7 +47,7 @@ class DocType(Document): | |||
frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0])) | |||
def scrub_field_names(self): | |||
restricted = ('name','parent','idx','owner','creation','modified','modified_by', | |||
restricted = ('name','parent','creation','modified','modified_by', | |||
'parentfield','parenttype',"file_list") | |||
for d in self.get("fields"): | |||
if d.fieldtype: | |||
@@ -66,8 +72,11 @@ class DocType(Document): | |||
if not autoname and self.get("fields", {"fieldname":"naming_series"}): | |||
self.autoname = "naming_series:" | |||
if autoname and (not autoname.startswith('field:')) and (not autoname.startswith('eval:')) \ | |||
and (not autoname=='Prompt') and (not autoname.startswith('naming_series:')): | |||
if autoname and (not autoname.startswith('field:')) \ | |||
and (not autoname.startswith('eval:')) \ | |||
and (not autoname in ('Prompt', 'hash')) \ | |||
and (not autoname.startswith('naming_series:')): | |||
prefix = autoname.split('.')[0] | |||
used_in = frappe.db.sql('select name from tabDocType where substring_index(autoname, ".", 1) = %s and name!=%s', (prefix, name)) | |||
if used_in: | |||
@@ -81,7 +90,7 @@ class DocType(Document): | |||
make_module_and_roles(self) | |||
from frappe import conf | |||
if (not frappe.flags.in_import) and conf.get('developer_mode') or 0: | |||
if not (frappe.flags.in_import or frappe.flags.in_test) and conf.get('developer_mode') or 0: | |||
self.export_doc() | |||
self.make_controller_template() | |||
@@ -91,13 +100,9 @@ class DocType(Document): | |||
module = load_doctype_module(self.name, self.module) | |||
if hasattr(module, "on_doctype_update"): | |||
module.on_doctype_update() | |||
frappe.clear_cache(doctype=self.name) | |||
def on_trash(self): | |||
frappe.db.sql("delete from `tabCustom Field` where dt = %s", self.name) | |||
frappe.db.sql("delete from `tabCustom Script` where dt = %s", self.name) | |||
frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", self.name) | |||
frappe.db.sql("delete from `tabReport` where ref_doctype=%s", self.name) | |||
delete_notification_count_for(doctype=self.name) | |||
frappe.clear_cache(doctype=self.name) | |||
def before_rename(self, old, new, merge=False): | |||
if merge: | |||
@@ -109,6 +114,29 @@ class DocType(Document): | |||
else: | |||
frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new)) | |||
def before_reload(self): | |||
if not (self.issingle and self.istable): | |||
self.preserve_naming_series_options_in_property_setter() | |||
def preserve_naming_series_options_in_property_setter(self): | |||
"""preserve naming_series as property setter if it does not exist""" | |||
naming_series = self.get("fields", {"fieldname": "naming_series"}) | |||
if not naming_series: | |||
return | |||
# check if atleast 1 record exists | |||
if not (frappe.db.table_exists("tab" + self.name) and frappe.db.sql("select name from `tab{}` limit 1".format(self.name))): | |||
return | |||
existing_property_setter = frappe.db.get_value("Property Setter", {"doc_type": self.name, | |||
"property": "options", "field_name": "naming_series"}) | |||
if not existing_property_setter: | |||
make_property_setter(self.name, "naming_series", "options", naming_series[0].options, "Text", validate_fields_for_doctype=False) | |||
if naming_series[0].default: | |||
make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text", validate_fields_for_doctype=False) | |||
def export_doc(self): | |||
from frappe.modules.export_file import export_to_files | |||
export_to_files(record_list=[['DocType', self.name]]) | |||
@@ -118,22 +146,11 @@ class DocType(Document): | |||
import_from_files(record_list=[[self.module, 'doctype', self.name]]) | |||
def make_controller_template(self): | |||
from frappe.modules import get_doc_path, get_module_path, scrub | |||
pypath = os.path.join(get_doc_path(self.module, | |||
self.doctype, self.name), scrub(self.name) + '.py') | |||
if not os.path.exists(pypath): | |||
# get app publisher for copyright | |||
app = frappe.local.module_app[frappe.scrub(self.module)] | |||
if not app: | |||
frappe.throw(_("App not found")) | |||
app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0] | |||
make_boilerplate("controller.py", self) | |||
with open(pypath, 'w') as pyfile: | |||
with open(os.path.join(get_module_path("core"), "doctype", "doctype", | |||
"doctype_template.py"), 'r') as srcfile: | |||
pyfile.write(srcfile.read().format(app_publisher=app_publisher, classname=self.name.replace(" ", ""))) | |||
if not (self.istable or self.issingle): | |||
make_boilerplate("test_controller.py", self) | |||
make_boilerplate("test_records.json", self) | |||
def make_amendable(self): | |||
""" | |||
@@ -158,9 +175,10 @@ class DocType(Document): | |||
return max_idx and max_idx[0][0] or 0 | |||
def validate_fields_for_doctype(doctype): | |||
validate_fields(frappe.get_meta(doctype).get("fields")) | |||
validate_fields(frappe.get_meta(doctype)) | |||
def validate_fields(fields): | |||
# this is separate because it is also called via custom field | |||
def validate_fields(meta): | |||
def check_illegal_characters(fieldname): | |||
for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$', | |||
'(', ')', '[', ']', '/']: | |||
@@ -173,7 +191,7 @@ def validate_fields(fields): | |||
frappe.throw(_("Fieldname {0} appears multiple times in rows {1}").format(fieldname, ", ".join(duplicates))) | |||
def check_illegal_mandatory(d): | |||
if d.fieldtype in ('HTML', 'Button', 'Section Break', 'Column Break') and d.reqd: | |||
if (d.fieldtype in no_value_fields) and d.fieldtype!="Table" and d.reqd: | |||
frappe.throw(_("Field {0} of type {1} cannot be mandatory").format(d.label, d.fieldtype)) | |||
def check_link_table_options(d): | |||
@@ -203,6 +221,47 @@ def validate_fields(fields): | |||
if d.in_list_view and d.fieldtype!="Image" and (d.fieldtype in no_value_fields): | |||
frappe.throw(_("'In List View' not allowed for type {0} in row {1}").format(d.fieldtype, d.idx)) | |||
def check_dynamic_link_options(d): | |||
if d.fieldtype=="Dynamic Link": | |||
doctype_pointer = filter(lambda df: df.fieldname==d.options, fields) | |||
if not doctype_pointer or (doctype_pointer[0].fieldtype!="Link") \ | |||
or (doctype_pointer[0].options!="DocType"): | |||
frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'")) | |||
def check_illegal_default(d): | |||
if d.fieldtype == "Check" and d.default and d.default not in ('0', '1'): | |||
frappe.throw(_("Default for 'Check' type of field must be either '0' or '1'")) | |||
def check_precision(d): | |||
if d.fieldtype in ("Currency", "Float", "Percent") and d.precision is not None and not (1 <= cint(d.precision) <= 6): | |||
frappe.throw(_("Precision should be between 1 and 6")) | |||
def check_fold(fields): | |||
fold_exists = False | |||
for i, f in enumerate(fields): | |||
if f.fieldtype=="Fold": | |||
if fold_exists: | |||
frappe.throw(_("There can be only one Fold in a form")) | |||
fold_exists = True | |||
if i < len(fields)-1: | |||
nxt = fields[i+1] | |||
if nxt.fieldtype != "Section Break" \ | |||
or (nxt.fieldtype=="Section Break" and not nxt.label): | |||
frappe.throw(_("Fold must come before a labelled Section Break")) | |||
else: | |||
frappe.throw(_("Fold can not be at the end of the form")) | |||
def check_search_fields(meta): | |||
if not meta.search_fields: | |||
return | |||
fieldname_list = [d.fieldname for d in fields] | |||
for fieldname in (meta.search_fields or "").split(","): | |||
fieldname = fieldname.strip() | |||
if fieldname not in fieldname_list: | |||
frappe.throw(_("Search Fields should contain valid fieldnames")) | |||
fields = meta.get("fields") | |||
for d in fields: | |||
if not d.permlevel: d.permlevel = 0 | |||
if not d.fieldname: | |||
@@ -211,13 +270,28 @@ def validate_fields(fields): | |||
check_unique_fieldname(d.fieldname) | |||
check_illegal_mandatory(d) | |||
check_link_table_options(d) | |||
check_dynamic_link_options(d) | |||
check_hidden_and_mandatory(d) | |||
check_in_list_view(d) | |||
check_illegal_default(d) | |||
check_min_items_in_list(fields) | |||
check_fold(fields) | |||
check_search_fields(meta) | |||
def validate_permissions_for_doctype(doctype, for_remove=False): | |||
validate_permissions(frappe.get_doc("DocType", doctype), for_remove) | |||
doctype = frappe.get_doc("DocType", doctype) | |||
if frappe.conf.developer_mode and not frappe.flags.in_test: | |||
# save doctype | |||
doctype.save() | |||
else: | |||
validate_permissions(doctype, for_remove) | |||
# save permissions | |||
for perm in doctype.get("permissions"): | |||
perm.db_update() | |||
def validate_permissions(doctype, for_remove=False): | |||
permissions = doctype.get("permissions") | |||
@@ -239,12 +313,13 @@ def validate_permissions(doctype, for_remove=False): | |||
def check_double(d): | |||
has_similar = False | |||
for p in permissions: | |||
if p.role==d.role and p.permlevel==d.permlevel and p.match==d.match and p!=d: | |||
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 has_similar: | |||
frappe.throw(_("{0}: Only one rule allowed at a Role and Level").format(get_txt(d))) | |||
frappe.throw(_("{0}: Only one rule allowed with the same Role, Level and Apply User Permissions").format(get_txt(d))) | |||
def check_level_zero_is_set(d): | |||
if cint(d.permlevel) > 0 and d.role != 'All': | |||
@@ -281,9 +356,12 @@ def validate_permissions(doctype, for_remove=False): | |||
d.set("import", 0) | |||
d.set("export", 0) | |||
if d.restrict: | |||
frappe.msgprint(_("Restrict cannot be set for Single types")) | |||
d.restrict = 0 | |||
for ptype, label in ( | |||
("set_user_permissions", _("Set User Permissions")), | |||
("apply_user_permissions", _("Apply User Permissions"))): | |||
if d.get(ptype): | |||
d.set(ptype, 0) | |||
frappe.msgprint(_("{0} cannot be set for Single types").format(label)) | |||
def check_if_submittable(d): | |||
if d.submit and not issubmittable: | |||
@@ -331,3 +409,9 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): | |||
pass | |||
else: | |||
raise | |||
def init_list(doctype): | |||
doc = frappe.get_meta(doctype) | |||
make_boilerplate("controller_list.js", doc) | |||
make_boilerplate("controller_list.html", doc) | |||
@@ -0,0 +1,41 @@ | |||
frappe.email_alert = { | |||
setup_fieldname_select: function(frm) { | |||
// get the doctype to update fields | |||
if(!frm.doc.document_type) { | |||
return; | |||
} | |||
frappe.model.with_doctype(frm.doc.document_type, function() { | |||
var get_select_options = function(df) { | |||
return {value: df.fieldname, label: df.fieldname + " (" + __(df.label) + ")"}; | |||
} | |||
var fields = frappe.get_doc("DocType", frm.doc.document_type).fields; | |||
var options = $.map(fields, | |||
function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ? | |||
null : get_select_options(d); }); | |||
// set value changed options | |||
frm.set_df_property("value_changed", "options", [""].concat(options)); | |||
// set date changed options | |||
frm.set_df_property("date_changed", "options", $.map(fields, | |||
function(d) { return (d.fieldtype=="Date" || d.fieldtype=="Datetime") ? | |||
get_select_options(d) : null; })); | |||
// set email recipient options | |||
frappe.meta.get_docfield("Email Alert Recipient", "email_by_document_field", | |||
frm.doc.name).options = ["owner"].concat(options); | |||
}); | |||
} | |||
} | |||
frappe.ui.form.on("Email Alert", "refresh", function(frm) { | |||
frappe.email_alert.setup_fieldname_select(frm); | |||
}); | |||
frappe.ui.form.on("Email Alert", "document_type", function(frm) { | |||
frappe.email_alert.setup_fieldname_select(frm); | |||
}); |
@@ -0,0 +1,152 @@ | |||
{ | |||
"autoname": "hash", | |||
"creation": "2014-07-11 17:18:09.923399", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"fields": [ | |||
{ | |||
"default": "1", | |||
"fieldname": "enabled", | |||
"fieldtype": "Check", | |||
"label": "Enabled", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "filters", | |||
"fieldtype": "Section Break", | |||
"label": "Filters", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Subject", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "document_type", | |||
"fieldtype": "Link", | |||
"in_list_view": 1, | |||
"label": "Document Type", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"fieldname": "event", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Send Alert On", | |||
"options": "\nNew\nSave\nSubmit\nCancel\nDate Change\nValue Change", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"depends_on": "eval:doc.event==\"Date Change\"", | |||
"description": "Send alert if date matches this field's value", | |||
"fieldname": "date_changed", | |||
"fieldtype": "Select", | |||
"label": "Date Changed", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "eval:doc.event==\"Date Change\"", | |||
"description": "[Optional] Send the email X days in advance of the specified date. 0 equals same day.", | |||
"fieldname": "days_in_advance", | |||
"fieldtype": "Int", | |||
"label": "Days in Advance", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"depends_on": "eval:doc.event==\"Value Change\"", | |||
"description": "Send alert if this field's value changes", | |||
"fieldname": "value_changed", | |||
"fieldtype": "Select", | |||
"label": "Value Changed", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"depends_on": "", | |||
"description": "Optional: The alert will be sent if this expression is true", | |||
"fieldname": "condition", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Condition", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "column_break_6", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "html_7", | |||
"fieldtype": "HTML", | |||
"options": "<p><strong>Condition Examples:</strong></p>\n<pre>doc.status==\"Open\"\ndoc.due_date==nowdate()\ndoc.total > 40000\n</pre>\n<p><strong>Hints:</strong></p>\n<ol>\n<li>To check for an event every day, select \"Date Change\" in Event</li>\n<li>To send an alert if a particular value changes, select \"Value Change\"</li>\n</ol>", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "column_break_5", | |||
"fieldtype": "Section Break", | |||
"label": "Recipients", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "email_alert_recipients", | |||
"fieldtype": "Table", | |||
"label": "Email Alert Recipients", | |||
"options": "Email Alert Recipient", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "message_sb", | |||
"fieldtype": "Section Break", | |||
"label": "Message", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "message", | |||
"fieldtype": "Text", | |||
"label": "Message", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"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>", | |||
"permlevel": 0 | |||
} | |||
], | |||
"icon": "icon-envelope", | |||
"modified": "2014-07-15 05:07:14.002351", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Email Alert", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"export": 1, | |||
"import": 0, | |||
"permlevel": 0, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"write": 1 | |||
} | |||
], | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"title_field": "subject" | |||
} |
@@ -0,0 +1,103 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe import _ | |||
from frappe.model.document import Document | |||
from frappe.utils import validate_email_add, nowdate | |||
class EmailAlert(Document): | |||
def validate(self): | |||
if self.event=="Date Change" and not self.date_changed: | |||
frappe.throw(_("Please specify which date field must be checked")) | |||
if self.event=="Value Change" and not self.value_changed: | |||
frappe.throw(_("Please specify which value field must be checked")) | |||
forbidden_document_types = ("Bulk Email",) | |||
if self.document_type in forbidden_document_types: | |||
frappe.throw(_("Cannot set Email Alert on Document Type {0}").format(self.document_type)) | |||
def trigger_daily_alerts(): | |||
trigger_email_alerts(None, "Date Change") | |||
def trigger_email_alerts(doc, method=None): | |||
if frappe.flags.in_import or frappe.flags.in_patch: | |||
# don't send email alerts while syncing or patching | |||
return | |||
if method=="Date Change": | |||
for alert in frappe.db.sql_list("""select name from `tabEmail Alert` | |||
where event='Date Change' and enabled=1"""): | |||
alert = frappe.get_doc("Email Alert", alert) | |||
for name in frappe.db.sql_list("""select name from `tab{0}` where | |||
DATE({1}) = ADDDATE(DATE(%s), INTERVAL %s DAY)""".format(alert.document_type, alert.date_changed), | |||
(nowdate(), alert.days_in_advance or 0)): | |||
evaluate_alert(frappe.get_doc(alert.document_type, name), | |||
alert, "Date Change") | |||
else: | |||
if method in ("on_update", "validate") and doc.get("__in_insert"): | |||
# don't call email alerts multiple times for inserts | |||
# on insert only "New" type alert must be called | |||
return | |||
eevent = { | |||
"on_update": "Save", | |||
"after_insert": "New", | |||
"validate": "Value Change", | |||
"on_submit": "Submit", | |||
"on_cancel": "Cancel", | |||
}[method] | |||
for alert in frappe.db.sql_list("""select name from `tabEmail Alert` | |||
where document_type=%s and event=%s and enabled=1""", (doc.doctype, eevent)): | |||
evaluate_alert(doc, alert, eevent) | |||
def evaluate_alert(doc, alert, event): | |||
if isinstance(alert, basestring): | |||
alert = frappe.get_doc("Email Alert", alert) | |||
context = {"doc": doc, "nowdate": nowdate} | |||
if alert.condition: | |||
if not eval(alert.condition, context): | |||
return | |||
if event=="Value Change" and not doc.is_new(): | |||
if doc.get(alert.value_changed) == frappe.db.get_value(doc.doctype, | |||
doc.name, alert.value_changed): | |||
return # value not changed | |||
for recipient in alert.email_alert_recipients: | |||
recipients = [] | |||
if recipient.condition: | |||
if not eval(recipient.condition, context): | |||
continue | |||
if recipient.email_by_document_field: | |||
if validate_email_add(doc.get(recipient.email_by_document_field)): | |||
recipients.append(doc.get(recipient.email_by_document_field)) | |||
# else: | |||
# print "invalid email" | |||
if recipient.cc: | |||
recipient.cc = recipient.cc.replace(",", "\n") | |||
recipients = recipients + recipient.cc.split("\n") | |||
if not recipients: | |||
return | |||
template = alert.message + footer | |||
# send alert | |||
frappe.sendmail(recipients=recipients, subject=alert.subject, | |||
message= frappe.render_template(template, {"doc": doc, "alert":alert}), | |||
bulk=True, ref_doctype = doc.doctype, ref_docname = doc.name) | |||
footer = """<div style='margin-top: 20px; font-size: 80%; color: #888'> | |||
This Email Alert <em>{{alert.subject}}</em> was autogenerated for | |||
{{ doc.doctype }} <a href="/desk#Form/{{doc.doctype}}/{{doc.name}}">{{doc.name}}</a>. | |||
To update, modify it, go to Setup > Email > <a href="/desk#List/Email Alert">Email Alert</a> | |||
""" |
@@ -0,0 +1,97 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
import frappe, frappe.utils, frappe.utils.scheduler | |||
import unittest | |||
test_records = frappe.get_test_records('Email Alert') | |||
class TestEmailAlert(unittest.TestCase): | |||
def setUp(self): | |||
frappe.db.sql("""delete from `tabBulk Email`""") | |||
frappe.set_user("test1@example.com") | |||
def tearDown(self): | |||
frappe.set_user("Administrator") | |||
def test_new_and_save(self): | |||
comment = frappe.new_doc("Comment") | |||
comment.comment = "test" | |||
comment.insert(ignore_permissions=True) | |||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Comment", | |||
"ref_docname": comment.name, "status":"Not Sent"})) | |||
frappe.db.sql("""delete from `tabBulk Email`""") | |||
comment.description = "test" | |||
comment.save() | |||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Comment", | |||
"ref_docname": comment.name, "status":"Not Sent"})) | |||
def test_condition(self): | |||
event = frappe.new_doc("Event") | |||
event.subject = "test", | |||
event.event_type = "Private" | |||
event.starts_on = "2014-06-06 12:00:00" | |||
event.insert() | |||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) | |||
event.event_type = "Public" | |||
event.save() | |||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) | |||
def test_value_changed(self): | |||
event = frappe.new_doc("Event") | |||
event.subject = "test", | |||
event.event_type = "Private" | |||
event.starts_on = "2014-06-06 12:00:00" | |||
event.insert() | |||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) | |||
event.subject = "test 1" | |||
event.save() | |||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) | |||
event.description = "test" | |||
event.save() | |||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) | |||
def test_date_changed(self): | |||
event = frappe.new_doc("Event") | |||
event.subject = "test", | |||
event.event_type = "Private" | |||
event.starts_on = "2014-01-01 12:00:00" | |||
event.insert() | |||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) | |||
frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True) | |||
# not today, so no alert | |||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) | |||
event.starts_on = frappe.utils.add_days(frappe.utils.nowdate(), 2) + " 12:00:00" | |||
event.save() | |||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) | |||
frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True) | |||
# today so show alert | |||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||
"ref_docname": event.name, "status":"Not Sent"})) |
@@ -0,0 +1,56 @@ | |||
[ | |||
{ | |||
"doctype": "Email Alert", | |||
"subject":"_Test Email Alert 1", | |||
"document_type": "Comment", | |||
"event": "New", | |||
"message": "New comment {{ doc.comment }} created", | |||
"email_alert_recipients": [ | |||
{ "email_by_document_field": "owner" } | |||
] | |||
}, | |||
{ | |||
"doctype": "Email Alert", | |||
"subject":"_Test Email Alert 2", | |||
"document_type": "Comment", | |||
"event": "Save", | |||
"message": "New comment {{ doc.comment }} saved", | |||
"email_alert_recipients": [ | |||
{ "email_by_document_field": "owner" } | |||
] | |||
}, | |||
{ | |||
"doctype": "Email Alert", | |||
"subject":"_Test Email Alert 3", | |||
"document_type": "Event", | |||
"event": "Save", | |||
"condition": "doc.event_type=='Public'", | |||
"message": "A new public event {{ doc.subject }} on {{ doc.starts_on }} is created", | |||
"email_alert_recipients": [ | |||
{ "email_by_document_field": "owner" } | |||
] | |||
}, | |||
{ | |||
"doctype": "Email Alert", | |||
"subject":"_Test Email Alert 4", | |||
"document_type": "Event", | |||
"event": "Value Change", | |||
"value_changed": "description", | |||
"message": "Description changed", | |||
"email_alert_recipients": [ | |||
{ "email_by_document_field": "owner" } | |||
] | |||
}, | |||
{ | |||
"doctype": "Email Alert", | |||
"subject":"_Test Email Alert 5", | |||
"document_type": "Event", | |||
"event": "Date Change", | |||
"date_changed": "starts_on", | |||
"days_in_advance": 2, | |||
"message": "Description changed", | |||
"email_alert_recipients": [ | |||
{ "email_by_document_field": "owner" } | |||
] | |||
} | |||
] |
@@ -0,0 +1,42 @@ | |||
{ | |||
"creation": "2014-07-11 17:19:37.037109", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "", | |||
"fields": [ | |||
{ | |||
"description": "Optional: Alert will only be sent if value is a valid email id.", | |||
"fieldname": "email_by_document_field", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Email By Document Field", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "Optional: Always send to these ids. Each email id on a new row", | |||
"fieldname": "cc", | |||
"fieldtype": "Text", | |||
"in_list_view": 1, | |||
"label": "CC", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "Expression, Optional", | |||
"fieldname": "condition", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Condition", | |||
"permlevel": 0 | |||
} | |||
], | |||
"istable": 1, | |||
"modified": "2014-07-11 17:54:53.298526", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Email Alert Recipient", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
} |
@@ -0,0 +1,9 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.model.document import Document | |||
class EmailAlertRecipient(Document): | |||
pass |
@@ -1,6 +1,15 @@ | |||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt | |||
frappe.ui.form.on("Event", "refresh", function(frm) { | |||
if(frm.doc.ref_type && frm.doc.ref_name) { | |||
frm.add_custom_button(__(frm.doc.ref_name), function() { | |||
frappe.set_route("Form", frm.doc.ref_type, frm.doc.ref_name); | |||
}, frappe.boot.doctype_icons[frm.doc.ref_type]); | |||
} | |||
}); | |||
cur_frm.cscript.repeat_on = function(doc, cdt, cdn) { | |||
if(doc.repeat_on==="Every Day") { | |||
$.each(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"], function(i,v) { | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"autoname": "EV.#####", | |||
"creation": "2013-06-10 13:17:47.000000", | |||
"creation": "2013-06-10 13:17:47", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
@@ -218,24 +218,26 @@ | |||
}, | |||
{ | |||
"fieldname": "ref_type", | |||
"fieldtype": "Data", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"label": "Ref Type", | |||
"no_copy": 0, | |||
"oldfieldname": "ref_type", | |||
"oldfieldtype": "Data", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"read_only": 1, | |||
"read_only": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "ref_name", | |||
"fieldtype": "Data", | |||
"fieldtype": "Dynamic Link", | |||
"hidden": 0, | |||
"label": "Ref Name", | |||
"no_copy": 0, | |||
"oldfieldname": "ref_name", | |||
"oldfieldtype": "Data", | |||
"options": "ref_type", | |||
"permlevel": 0, | |||
"read_only": 1, | |||
"search_index": 0 | |||
@@ -244,14 +246,14 @@ | |||
"icon": "icon-calendar", | |||
"idx": 1, | |||
"in_create": 1, | |||
"modified": "2014-01-24 13:00:01.000000", | |||
"modified": "2014-06-20 06:40:05.415405", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Event", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"cancel": 0, | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"delete": 0, | |||
"email": 1, | |||
@@ -264,7 +266,6 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -15,7 +15,8 @@ class Event(Document): | |||
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) | |||
def get_permission_query_conditions(): | |||
def get_permission_query_conditions(user): | |||
if not user: user = frappe.session.user | |||
return """(tabEvent.event_type='Public' or tabEvent.owner='%(user)s' | |||
or exists(select * from `tabEvent User` where | |||
`tabEvent User`.parent=tabEvent.name and `tabEvent User`.person='%(user)s') | |||
@@ -23,18 +24,18 @@ def get_permission_query_conditions(): | |||
`tabEvent Role`.parent=tabEvent.name | |||
and `tabEvent Role`.role in ('%(roles)s'))) | |||
""" % { | |||
"user": frappe.session.user, | |||
"roles": "', '".join(frappe.get_roles(frappe.session.user)) | |||
"user": user, | |||
"roles": "', '".join(frappe.get_roles(user)) | |||
} | |||
def has_permission(doc): | |||
if doc.event_type=="Public" or doc.owner==frappe.session.user: | |||
def has_permission(doc, user): | |||
if doc.event_type=="Public" or doc.owner==user: | |||
return True | |||
if doc.get("event_individuals", {"person":frappe.session.user}): | |||
if doc.get("event_individuals", {"person": user}): | |||
return True | |||
if doc.get("event_roles", {"role":("in", frappe.get_roles())}): | |||
if doc.get("event_roles", {"role":("in", frappe.get_roles(user))}): | |||
return True | |||
return False | |||
@@ -1,11 +1,12 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
"""Use blog post test to test permission restriction logic""" | |||
"""Use blog post test to test user permissions logic""" | |||
import frappe | |||
import frappe.defaults | |||
import unittest | |||
import json | |||
test_records = frappe.get_test_records('Event') | |||
@@ -40,3 +41,56 @@ class TestEvent(unittest.TestCase): | |||
self.assertTrue("_Test Event 1" in subjects) | |||
self.assertTrue("_Test Event 3" in subjects) | |||
self.assertFalse("_Test Event 2" in subjects) | |||
def test_revert_logic(self): | |||
ev = frappe.get_doc(test_records[0]).insert() | |||
name = ev.name | |||
frappe.delete_doc("Event", ev.name) | |||
# insert again | |||
ev = frappe.get_doc(test_records[0]).insert() | |||
# the name should be same! | |||
self.assertEquals(ev.name, name) | |||
def test_assign(self): | |||
from frappe.widgets.form.assign_to import add | |||
ev = frappe.get_doc(test_records[0]).insert() | |||
add({ | |||
"assign_to": "test@example.com", | |||
"doctype": "Event", | |||
"name": ev.name, | |||
"description": "Test Assignment" | |||
}) | |||
ev = frappe.get_doc("Event", ev.name) | |||
self.assertEquals(ev._assign, json.dumps(["test@example.com"])) | |||
# add another one | |||
add({ | |||
"assign_to": "test1@example.com", | |||
"doctype": "Event", | |||
"name": ev.name, | |||
"description": "Test Assignment" | |||
}) | |||
ev = frappe.get_doc("Event", ev.name) | |||
self.assertEquals(ev._assign, json.dumps(["test@example.com", "test1@example.com"])) | |||
# close an assignment | |||
todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name, | |||
"owner": "test1@example.com"}) | |||
todo.status = "Closed" | |||
todo.save() | |||
ev = frappe.get_doc("Event", ev.name) | |||
self.assertEquals(ev._assign, json.dumps(["test@example.com"])) | |||
# cleanup | |||
ev.delete() | |||
@@ -1,255 +1,83 @@ | |||
{ | |||
"_last_update": null, | |||
"_user_tags": null, | |||
"allow_attach": null, | |||
"allow_copy": null, | |||
"allow_email": null, | |||
"allow_import": 1, | |||
"allow_print": null, | |||
"allow_rename": null, | |||
"allow_trash": null, | |||
"autoname": "File.######", | |||
"change_log": null, | |||
"client_script": null, | |||
"client_script_core": null, | |||
"client_string": null, | |||
"colour": null, | |||
"creation": "2012-12-12 11:19:22", | |||
"custom": null, | |||
"default_print_format": null, | |||
"description": null, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": null, | |||
"dt_template": null, | |||
"allow_import": 1, | |||
"autoname": "File.######", | |||
"creation": "2012-12-12 11:19:22", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "file_name", | |||
"fieldtype": "Data", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": 1, | |||
"label": "File Name", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": "file_name", | |||
"oldfieldtype": "Data", | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": 1, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "file_name", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "File Name", | |||
"oldfieldname": "file_name", | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "file_url", | |||
"fieldtype": "Data", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": 1, | |||
"label": "File URL", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": 1, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "file_url", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "File URL", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "attached_to_doctype", | |||
"fieldtype": "Link", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": 1, | |||
"label": "Attached To DocType", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": 1, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": 1, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "attached_to_doctype", | |||
"fieldtype": "Link", | |||
"in_list_view": 1, | |||
"label": "Attached To DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"read_only": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "attached_to_name", | |||
"fieldtype": "Data", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": 1, | |||
"label": "Attached To Name", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": 1, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": 1, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "attached_to_name", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Attached To Name", | |||
"permlevel": 0, | |||
"read_only": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "file_size", | |||
"fieldtype": "Int", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": 1, | |||
"label": "File Size", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": 1, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "file_size", | |||
"fieldtype": "Int", | |||
"in_list_view": 1, | |||
"label": "File Size", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "content_hash", | |||
"fieldtype": "Data", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Content Hash", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": 1, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
"fieldname": "content_hash", | |||
"fieldtype": "Data", | |||
"label": "Content Hash", | |||
"permlevel": 0, | |||
"search_index": 1 | |||
} | |||
], | |||
"hide_heading": null, | |||
"hide_toolbar": null, | |||
"icon": "icon-file", | |||
"idx": 1, | |||
"in_create": 1, | |||
"in_dialog": null, | |||
"is_submittable": null, | |||
"is_transaction_doc": null, | |||
"issingle": null, | |||
"istable": null, | |||
"max_attachments": null, | |||
"menu_index": null, | |||
"modified": "2014-04-16 09:28:02.979858", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "File Data", | |||
"name_case": null, | |||
"owner": "Administrator", | |||
"parent": null, | |||
"parent_node": null, | |||
"parentfield": null, | |||
"parenttype": null, | |||
], | |||
"icon": "icon-file", | |||
"idx": 1, | |||
"in_create": 1, | |||
"modified": "2014-05-26 03:35:40.362967", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "File Data", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": null, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": null, | |||
"import": null, | |||
"match": null, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": null, | |||
"restrict": null, | |||
"restricted": null, | |||
"role": "System Manager", | |||
"submit": null, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"role": "System Manager", | |||
"write": 1 | |||
} | |||
], | |||
"plugin": null, | |||
"print_outline": null, | |||
"read_only": 0, | |||
"read_only_onload": null, | |||
"search_fields": null, | |||
"section_style": null, | |||
"server_code": null, | |||
"server_code_compiled": null, | |||
"server_code_core": null, | |||
"server_code_error": null, | |||
"show_in_menu": null, | |||
"smallicon": null, | |||
"subject": null, | |||
"tag_fields": null, | |||
"title_field": null, | |||
"use_template": null, | |||
"version": null | |||
} | |||
], | |||
"read_only": 0 | |||
} |
@@ -1,5 +1,5 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
""" | |||
@@ -16,20 +16,20 @@ from frappe.utils.file_manager import delete_file_data_content | |||
class FileData(Document): | |||
def before_insert(self): | |||
frappe.local.rollback_observers.append(self) | |||
def on_update(self): | |||
# check duplicate assignement | |||
n_records = frappe.db.sql("""select name from `tabFile Data` | |||
where content_hash=%s | |||
and name!=%s | |||
and attached_to_doctype=%s | |||
and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype, | |||
self.attached_to_name)) | |||
if len(n_records) > 0: | |||
self.duplicate_entry = n_records[0][0] | |||
frappe.msgprint(frappe._("Same file has already been attached to the record")) | |||
frappe.db.rollback() | |||
raise frappe.DuplicateEntryError | |||
if not getattr(self, "ignore_duplicate_entry_error", False): | |||
# check duplicate assignement | |||
n_records = frappe.db.sql("""select name from `tabFile Data` | |||
where content_hash=%s | |||
and name!=%s | |||
and attached_to_doctype=%s | |||
and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype, | |||
self.attached_to_name)) | |||
if len(n_records) > 0: | |||
self.duplicate_entry = n_records[0][0] | |||
frappe.msgprint(frappe._("Same file has already been attached to the record")) | |||
raise frappe.DuplicateEntryError | |||
def on_trash(self): | |||
if self.attached_to_name: | |||
@@ -37,9 +37,9 @@ class FileData(Document): | |||
try: | |||
if not getattr(self, 'ignore_permissions', False) and \ | |||
not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name): | |||
frappe.msgprint(frappe._("No permission to write / remove."), raise_exception=True) | |||
except frappe.DoesNotExistError: | |||
pass | |||
@@ -1,6 +1,5 @@ | |||
{ | |||
"allow_attach": 1, | |||
"autoname": "field:letter_head_name", | |||
"autoname": "field:letter_head_name", | |||
"creation": "2012-11-22 17:45:46", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
@@ -35,7 +34,8 @@ | |||
"label": "Is Default", | |||
"oldfieldname": "is_default", | |||
"oldfieldtype": "Check", | |||
"permlevel": 0 | |||
"permlevel": 0, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"depends_on": "letter_head_name", | |||
@@ -52,14 +52,13 @@ | |||
"icon": "icon-font", | |||
"idx": 1, | |||
"max_attachments": 3, | |||
"modified": "2014-05-07 06:03:07.760995", | |||
"modified": "2014-07-21 05:57:56.052191", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Letter Head", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -72,6 +71,7 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"apply_user_permissions": 1, | |||
"delete": 0, | |||
"email": 0, | |||
"permlevel": 0, | |||
@@ -1,107 +1,35 @@ | |||
{ | |||
"_last_update": null, | |||
"_user_tags": null, | |||
"allow_attach": null, | |||
"allow_copy": null, | |||
"allow_email": null, | |||
"allow_import": null, | |||
"allow_print": null, | |||
"allow_rename": 1, | |||
"allow_trash": null, | |||
"autoname": "field:module_name", | |||
"change_log": null, | |||
"client_script": null, | |||
"client_script_core": null, | |||
"client_string": null, | |||
"colour": null, | |||
"creation": "2013-01-10 16:34:03", | |||
"custom": null, | |||
"default_print_format": null, | |||
"description": null, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": null, | |||
"dt_template": null, | |||
"fields": [ | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "module_name", | |||
"fieldtype": "Data", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": 1, | |||
"label": "Module Name", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": "module_name", | |||
"oldfieldtype": "Data", | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "app_name", | |||
"fieldtype": "Data", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": 1, | |||
"label": "App Name", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": 1, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
"reqd": 1 | |||
} | |||
], | |||
"hide_heading": null, | |||
"hide_toolbar": null, | |||
"icon": "icon-sitemap", | |||
"idx": 1, | |||
"in_create": null, | |||
"in_dialog": null, | |||
"is_submittable": null, | |||
"is_transaction_doc": null, | |||
"issingle": null, | |||
"istable": null, | |||
"max_attachments": null, | |||
"menu_index": null, | |||
"modified": "2014-04-07 13:00:27.894115", | |||
"modified": "2014-06-12 01:00:52.304755", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Module Def", | |||
"name_case": null, | |||
"owner": "Administrator", | |||
"parent": null, | |||
"parent_node": null, | |||
"parentfield": null, | |||
"parenttype": null, | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
@@ -109,54 +37,18 @@ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": null, | |||
"import": null, | |||
"match": null, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"restrict": null, | |||
"restricted": null, | |||
"role": "Administrator", | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": null, | |||
"cancel": null, | |||
"create": 1, | |||
"delete": 1, | |||
"email": null, | |||
"export": null, | |||
"import": null, | |||
"match": null, | |||
"permlevel": 0, | |||
"print": null, | |||
"read": 1, | |||
"report": null, | |||
"restrict": null, | |||
"restricted": null, | |||
"role": "System Manager", | |||
"submit": null, | |||
"write": 1 | |||
"role": "System Manager" | |||
} | |||
], | |||
"plugin": null, | |||
"print_outline": null, | |||
"read_only": null, | |||
"read_only_onload": null, | |||
"search_fields": null, | |||
"section_style": null, | |||
"server_code": null, | |||
"server_code_compiled": null, | |||
"server_code_core": null, | |||
"server_code_error": null, | |||
"show_in_menu": null, | |||
"smallicon": null, | |||
"subject": null, | |||
"tag_fields": null, | |||
"title_field": null, | |||
"use_template": null, | |||
"version": null | |||
] | |||
} |
@@ -8,13 +8,16 @@ from frappe.model.document import Document | |||
class ModuleDef(Document): | |||
def validate(self): | |||
if not frappe.conf.get("developer_mode"): | |||
return | |||
modules = None | |||
if not frappe.local.module_app.get(self.name): | |||
if not frappe.local.module_app.get(frappe.scrub(self.name)): | |||
with open(frappe.get_app_path(self.app_name, "modules.txt"), "r") as f: | |||
content = f.read() | |||
if not frappe.scrub(self.name) in content.splitlines(): | |||
if not self.name in content.splitlines(): | |||
modules = filter(None, content.splitlines()) | |||
modules.append(frappe.scrub(self.name)) | |||
modules.append(self.name) | |||
if modules: | |||
with open(frappe.get_app_path(self.app_name, "modules.txt"), "w") as f: | |||
@@ -5,9 +5,11 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
import MySQLdb | |||
from frappe.model.document import Document | |||
logger = frappe.get_logger() | |||
class NotificationCount(Document): | |||
pass | |||
@@ -15,6 +17,7 @@ class NotificationCount(Document): | |||
def get_notifications(): | |||
if frappe.flags.in_install_app: | |||
return | |||
config = get_notification_config() | |||
can_read = frappe.user.get_can_read() | |||
open_count_doctype = {} | |||
@@ -32,34 +35,63 @@ def get_notifications(): | |||
open_count_doctype[d] = notification_count[d] | |||
else: | |||
result = frappe.get_list(d, fields=["count(*)"], | |||
filters=[[d, key, "=", condition[key]]], as_list=True, limit_page_length=1)[0][0] | |||
frappe.get_doc({"doctype":"Notification Count", "for_doctype":d, | |||
"open_count":result}).insert(ignore_permissions=True) | |||
filters=[[d, key, "=", condition[key]]], as_list=True)[0][0] | |||
open_count_doctype[d] = result | |||
try: | |||
frappe.get_doc({"doctype":"Notification Count", "for_doctype":d, | |||
"open_count":result}).insert(ignore_permissions=True) | |||
except MySQLdb.OperationalError, e: | |||
if e.args[0] != 1213: | |||
raise | |||
logger.error("Deadlock") | |||
for m in config.for_module: | |||
if m in notification_count: | |||
open_count_module[m] = notification_count[m] | |||
else: | |||
open_count_module[m] = frappe.get_attr(config.for_module[m])() | |||
frappe.get_doc({"doctype":"Notification Count", "for_doctype":m, | |||
"open_count":open_count_module[m]}).insert(ignore_permissions=True) | |||
try: | |||
frappe.get_doc({"doctype":"Notification Count", "for_doctype":m, | |||
"open_count":open_count_module[m]}).insert(ignore_permissions=True) | |||
except MySQLdb.OperationalError, e: | |||
if e.args[0] != 1213: | |||
raise | |||
logger.error("Deadlock") | |||
return { | |||
"open_count_doctype": open_count_doctype, | |||
"open_count_module": open_count_module | |||
} | |||
def clear_notifications(user=None): | |||
if frappe.flags.in_install_app=="frappe": | |||
return | |||
try: | |||
if user: | |||
frappe.db.sql("""delete from `tabNotification Count` where owner=%s""", (user,)) | |||
else: | |||
frappe.db.sql("""delete from `tabNotification Count`""") | |||
except MySQLdb.OperationalError, e: | |||
if e.args[0] != 1213: | |||
raise | |||
logger.error("Deadlock") | |||
def delete_notification_count_for(doctype): | |||
if frappe.flags.in_import: return | |||
frappe.db.sql("""delete from `tabNotification Count` where for_doctype = %s""", (doctype,)) | |||
def delete_event_notification_count(): | |||
delete_notification_count_for("Event") | |||
def clear_doctype_notifications(doc, method=None): | |||
def clear_doctype_notifications(doc, method=None, *args, **kwargs): | |||
if frappe.flags.in_import: | |||
return | |||
@@ -1,14 +1,28 @@ | |||
{ | |||
"allow_copy": 1, | |||
"creation": "2014-03-03 19:48:01.000000", | |||
"creation": "2014-03-03 19:48:01", | |||
"description": "Email Settings for Outgoing and Incoming Emails.", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"fieldname": "enabled", | |||
"fieldtype": "Check", | |||
"label": "Enabled", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"depends_on": "eval:doc.enabled", | |||
"fieldname": "section_break_2", | |||
"fieldtype": "Section Break", | |||
"label": "Server & Credentials", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "SMTP Server (e.g. smtp.gmail.com)", | |||
"fieldname": "mail_server", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Outgoing Mail Server", | |||
"permlevel": 0 | |||
}, | |||
@@ -16,6 +30,7 @@ | |||
"description": "<a href=\"https://en.wikipedia.org/wiki/Transport_Layer_Security\" target=\"_blank\">[?]</a>", | |||
"fieldname": "use_ssl", | |||
"fieldtype": "Check", | |||
"in_list_view": 1, | |||
"label": "Use TLS", | |||
"permlevel": 0 | |||
}, | |||
@@ -23,6 +38,7 @@ | |||
"description": "If non standard port (e.g. 587)", | |||
"fieldname": "mail_port", | |||
"fieldtype": "Int", | |||
"in_list_view": 1, | |||
"label": "Port", | |||
"permlevel": 0 | |||
}, | |||
@@ -35,6 +51,7 @@ | |||
"description": "Set Login and Password if authentication is required.", | |||
"fieldname": "mail_login", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Login Id", | |||
"permlevel": 0 | |||
}, | |||
@@ -59,19 +76,25 @@ | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"default": "1", | |||
"description": "If checked, an email with an attached HTML format will be added to part of the EMail body as well as attachment. To only send as attachment, uncheck this.", | |||
"fieldname": "send_print_in_body_and_attachment", | |||
"fieldtype": "Check", | |||
"label": "Send Print in Body and Attachment", | |||
"fieldname": "section_break_15", | |||
"fieldtype": "Section Break", | |||
"label": "Email Footer", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"default": "<div style=\"padding: 7px; text-align: right; color: #888\"><small>Sent via \n\t<a style=\"color: #888\" href=\"http://frappe.io\">Frappe</a></div>", | |||
"fieldname": "footer", | |||
"fieldtype": "Text Editor", | |||
"label": "", | |||
"permlevel": 0, | |||
"reqd": 0 | |||
} | |||
], | |||
"icon": "icon-cog", | |||
"idx": 1, | |||
"in_create": 1, | |||
"issingle": 1, | |||
"modified": "2014-03-03 20:20:09.000000", | |||
"modified": "2014-07-17 08:08:00.483391", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Outgoing Email Settings", | |||
@@ -5,21 +5,28 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe import _, throw | |||
from frappe.utils import validate_email_add | |||
from frappe.model.document import Document | |||
class OutgoingEmailSettings(Document): | |||
def validate(self): | |||
if self.mail_server: | |||
if self.auto_email_id and not validate_email_add(self.auto_email_id): | |||
throw(_("{0} is not a valid email id").format(self.auto_email_id), frappe.InvalidEmailAddressError) | |||
if self.mail_server and not frappe.local.flags.in_patch: | |||
from frappe.utils import cint | |||
from frappe.utils.email_lib.smtp import SMTPServer | |||
smtpserver = SMTPServer(login = self.mail_login, | |||
password = self.mail_password, | |||
server = self.mail_server, | |||
port = cint(self.mail_port), | |||
use_ssl = self.use_ssl | |||
use_ssl = cint(self.use_ssl) | |||
) | |||
# exceptions are handled in session connect | |||
sess = smtpserver.sess | |||
sess = smtpserver.sess | |||
def get_mail_footer(): | |||
return frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "footer") or "" |
@@ -2,7 +2,7 @@ | |||
"allow_copy": 0, | |||
"allow_rename": 1, | |||
"autoname": "field:page_name", | |||
"creation": "2012-12-20 17:16:49.000000", | |||
"creation": "2012-12-20 17:16:49", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
@@ -16,6 +16,7 @@ | |||
{ | |||
"fieldname": "page_name", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Page Name", | |||
"oldfieldname": "page_name", | |||
"oldfieldtype": "Data", | |||
@@ -25,12 +26,14 @@ | |||
{ | |||
"fieldname": "title", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Title", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "icon", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "icon", | |||
"permlevel": 0 | |||
}, | |||
@@ -84,14 +87,13 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2013-12-30 13:48:02.000000", | |||
"modified": "2014-05-27 03:49:14.476843", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Page", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
@@ -102,6 +104,7 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"apply_user_permissions": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
@@ -1,13 +1,21 @@ | |||
cur_frm.cscript.refresh = function (doc) { | |||
frappe.ui.form.on("Print Format", "onload", function(frm) { | |||
frm.add_fetch("doc_type", "module", "module"); | |||
}); | |||
frappe.ui.form.on("Print Format", "refresh", function(frm) { | |||
frm.set_intro(""); | |||
if (user!="Administrator") { | |||
if (doc.standard == 'Yes') { | |||
cur_frm.toggle_enable(["html", "doc_type", "module"], false); | |||
cur_frm.disable_save(); | |||
if (frm.doc.standard == 'Yes') { | |||
frm.toggle_enable(["html", "doc_type", "module"], false); | |||
frm.disable_save(); | |||
} else { | |||
cur_frm.toggle_enable(["html", "doc_type", "module"], true); | |||
cur_frm.enable_save(); | |||
frm.toggle_enable(["html", "doc_type", "module"], true); | |||
frm.enable_save(); | |||
} | |||
frm.toggle_enable("standard", false); | |||
} else { | |||
if(frm.doc.standard==="Yes") { | |||
frm.set_intro(__("This is a standard format. To make changes, please copy it make make a new format.")) | |||
} | |||
cur_frm.toggle_enable("standard", false); | |||
} | |||
} | |||
}) |
@@ -1,12 +1,23 @@ | |||
{ | |||
"allow_attach": 0, | |||
"allow_copy": 0, | |||
"allow_copy": 0, | |||
"allow_rename": 0, | |||
"autoname": "Prompt", | |||
"creation": "2013-01-23 19:54:43", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
{ | |||
"description": "Belongs to", | |||
"fieldname": "doc_type", | |||
"fieldtype": "Link", | |||
"in_filter": 1, | |||
"in_list_view": 1, | |||
"label": "DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "module", | |||
@@ -25,18 +36,6 @@ | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
{ | |||
"description": "Belongs to", | |||
"fieldname": "doc_type", | |||
"fieldtype": "Link", | |||
"in_filter": 1, | |||
"in_list_view": 1, | |||
"label": "DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "column_break_3", | |||
"fieldtype": "Column Break", | |||
@@ -63,11 +62,20 @@ | |||
"search_index": 1 | |||
}, | |||
{ | |||
"default": "Server", | |||
"description": "Client-side formats are now deprecated", | |||
"fieldname": "print_format_type", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Print Format Type", | |||
"options": "Client\nServer", | |||
"options": "Server\nClient", | |||
"permlevel": 0, | |||
"read_only": 0 | |||
}, | |||
{ | |||
"fieldname": "disabled", | |||
"fieldtype": "Check", | |||
"label": "Disabled", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
@@ -77,7 +85,7 @@ | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"depends_on": "eval:doc.print_format_type!=\"Server\"", | |||
"depends_on": "", | |||
"fieldname": "html", | |||
"fieldtype": "Code", | |||
"hidden": 0, | |||
@@ -92,6 +100,13 @@ | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "print_format_help", | |||
"fieldtype": "HTML", | |||
"label": "Print Format Help", | |||
"options": "<h3>Print Format Help</h3>\n<hr>\n<h4>Introduction</h4>\n<p>Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the <code>doc</code> object which contains information about the document that is being formatted. You can also access common utilities via the <code>frappe</code> module.</p>\n<p>For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.</p>\n<hr>\n<h4>References</h4>\n<ol>\n\t<li><a href=\"http://jinja.pocoo.org/docs/templates/\" target=\"_blank\">Jinja Tempalting Language: Reference</a></li>\n\t<li><a href=\"http://getbootstrap.com\" target=\"_blank\">Bootstrap CSS Framework</a></li>\n</ol>\n<hr>\n<h4>Example</h4>\n<pre><code><h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.entries -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table></code></pre>\n<hr>\n<h4>Common Functions</h4>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%\"><code>doc.get_formatted(\"[fieldname]\", [parent_doc])</code></td>\n\t\t\t<td>Get document value formatted as Date, Currency etc. Pass parent <code>doc</code> for curreny type fields.</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%\"><code>frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")</code></td>\n\t\t\t<td>Get value from another document.</td>\n\t\t</tr>\n\t</tbody>\n</table>\n", | |||
"permlevel": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
@@ -103,7 +118,7 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2014-05-09 02:12:39.540952", | |||
"modified": "2014-07-31 03:39:35.898711", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Print Format", | |||
@@ -2,11 +2,8 @@ | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe, os | |||
import frappe | |||
import frappe.utils | |||
from frappe.modules import get_doc_path | |||
standard_format = "templates/print_formats/standard.html" | |||
from frappe.model.document import Document | |||
@@ -38,76 +35,3 @@ class PrintFormat(Document): | |||
if self.doc_type: | |||
frappe.clear_cache(doctype=self.doc_type) | |||
def get_args(): | |||
if not frappe.form_dict.format: | |||
frappe.form_dict.format = standard_format | |||
if not frappe.form_dict.doctype or not frappe.form_dict.name: | |||
return { | |||
"body": """<h1>Error</h1> | |||
<p>Parameters doctype, name and format required</p> | |||
<pre>%s</pre>""" % repr(frappe.form_dict) | |||
} | |||
doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name) | |||
for ptype in ("read", "print"): | |||
if not frappe.has_permission(doc.doctype, ptype, doc): | |||
return { | |||
"body": """<h1>Error</h1> | |||
<p>No {ptype} permission</p>""".format(ptype=ptype) | |||
} | |||
return { | |||
"body": get_html(doc), | |||
"css": get_print_style(frappe.form_dict.style), | |||
"comment": frappe.session.user | |||
} | |||
def get_html(doc, name=None, print_format=None): | |||
from jinja2 import Environment | |||
if isinstance(doc, basestring) and isinstance(name, basestring): | |||
doc = frappe.get_doc(doc, name) | |||
template = Environment().from_string(get_print_format_name(doc.doctype, | |||
print_format or frappe.form_dict.format)) | |||
meta = frappe.get_meta(doc.doctype) | |||
args = { | |||
"doc": doc, | |||
"meta": meta, | |||
"frappe": frappe, | |||
"utils": frappe.utils | |||
} | |||
html = template.render(args) | |||
return html | |||
def get_print_format_name(doctype, format_name): | |||
if format_name==standard_format: | |||
return format_name | |||
# server, find template | |||
path = os.path.join(get_doc_path(frappe.db.get_value("DocType", doctype, "module"), | |||
"Print Format", format_name), format_name + ".html") | |||
if os.path.exists(path): | |||
with open(path, "r") as pffile: | |||
return pffile.read() | |||
else: | |||
html = frappe.db.get_value("Print Format", format_name, "html") | |||
if html: | |||
return html | |||
else: | |||
return "No template found.\npath: " + path | |||
def get_print_style(style=None): | |||
if not style: | |||
style = frappe.db.get_default("print_style") or "Standard" | |||
path = os.path.join(get_doc_path("Core", "DocType", "Print Format"), "styles", | |||
style.lower() + ".css") | |||
if not os.path.exists(path): | |||
if style!="Standard": | |||
return get_print_style("Standard") | |||
else: | |||
return "/* Standard Style Missing ?? */" | |||
else: | |||
with open(path, 'r') as sfile: | |||
return sfile.read() |
@@ -1,82 +0,0 @@ | |||
/* | |||
common style for whole page | |||
This should include: | |||
+ page size related settings | |||
+ font family settings | |||
+ line spacing settings | |||
*/ | |||
@media screen { | |||
body { | |||
width: 8.3in; | |||
} | |||
} | |||
html, body, div, span, td { | |||
font-family: "Georgia", serif; | |||
font-size: 12px; | |||
} | |||
body { | |||
padding: 10px; | |||
margin: auto; | |||
font-size: 12px; | |||
line-height: 150%; | |||
} | |||
.common { | |||
font-family: "Georgia", serif !important; | |||
font-size: 12px; | |||
padding: 10px 0px; | |||
} | |||
table { | |||
border-collapse: collapse; | |||
width: 100%; | |||
vertical-align: top; | |||
} | |||
table td { | |||
padding: 2px 0px; | |||
} | |||
table.table-bordered td, | |||
table.table-bordered th { | |||
border: 1px solid #000; | |||
padding: 2px; | |||
vertical-align: top; | |||
} | |||
table h1, h2, h3, h4, h5, h6 { | |||
padding: 0px; | |||
margin: 0px; | |||
} | |||
table.header-table td { | |||
vertical-align: top; | |||
} | |||
table.header-table thead { | |||
border-bottom: 1px solid black; | |||
} | |||
table.header-table h3 { | |||
color: gray; | |||
} | |||
table.header-table thead td { | |||
padding: 5px 0px; | |||
} | |||
div.page-body table td:nth-child(6), | |||
div.page-body table td:nth-child(7) { | |||
text-align: right; | |||
} | |||
table.footer-table td { | |||
vertical-align: top; | |||
} | |||
table.footer-table td table td:nth-child(2), | |||
table.footer-table td table td:nth-child(3) { | |||
text-align: right; | |||
} |
@@ -1,97 +0,0 @@ | |||
@media screen { | |||
body { | |||
width: 8.3in; | |||
} | |||
} | |||
html, body, div, span, td { | |||
font-family: "Helvetica", "Arial", sans-serif; | |||
font-size: 12px; | |||
} | |||
body { | |||
padding: 10px; | |||
margin: auto; | |||
font-size: 12px; | |||
line-height: 150%; | |||
} | |||
.common { | |||
font-family: "Helvetica", "Arial", sans-serif !important; | |||
font-size: 12px; | |||
padding: 10px 0px; | |||
} | |||
table { | |||
border-collapse: collapse; | |||
width: 100%; | |||
vertical-align: top; | |||
border-style: none !important; | |||
} | |||
table td { | |||
padding: 2px 0px; | |||
border-style: none !important; | |||
} | |||
table.table-bordered td, | |||
table.table-bordered th { | |||
padding: 2px; | |||
vertical-align: top; | |||
} | |||
table h1, h2, h3, h4, h5, h6 { | |||
padding: 0px; | |||
margin: 0px; | |||
} | |||
table.header-table td { | |||
vertical-align: top; | |||
} | |||
table.header-table h1 { | |||
text-transform: uppercase; | |||
color: white; | |||
font-size: 55px; | |||
font-style: italic; | |||
} | |||
table.header-table thead tr:nth-child(1) td { | |||
height: 24px; | |||
background-color: #696969; | |||
vertical-align: middle; | |||
padding: 12px 0px 0px 0px; | |||
width: 100%; | |||
} | |||
div.page-body table td:nth-child(6), | |||
div.page-body table td:nth-child(7) { | |||
text-align: right; | |||
} | |||
div.page-body table tr td { | |||
background-color: #DCDCDC !important; | |||
} | |||
div.page-body table th { | |||
background-color: #696969 !important; | |||
color: white !important; | |||
} | |||
table.footer-table td { | |||
vertical-align: top; | |||
} | |||
table.footer-table td table td:nth-child(2), | |||
table.footer-table td table td:nth-child(3) { | |||
text-align: right; | |||
} | |||
table.footer-table tfoot td { | |||
background-color: #696969; | |||
height: 10px; | |||
} | |||
.imp-details { | |||
background-color: #DCDCDC; | |||
} |
@@ -1,355 +0,0 @@ | |||
@media screen { | |||
body { | |||
width: 8.3in; | |||
} | |||
} | |||
body { | |||
padding: 10px; | |||
margin: auto; | |||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |||
font-size: 13px; | |||
line-height: 20px; | |||
color: #333333; | |||
background-color: #ffffff; | |||
} | |||
* { | |||
/*color: #000 !important;*/ | |||
text-shadow: none !important; | |||
background: transparent !important; | |||
box-shadow: none !important; | |||
} | |||
a, | |||
a:visited { | |||
text-decoration: underline; | |||
} | |||
a[href]:after { | |||
content: " (" attr(href) ")"; | |||
} | |||
abbr[title]:after { | |||
content: " (" attr(title) ")"; | |||
} | |||
.ir a:after, | |||
a[href^="javascript:"]:after, | |||
a[href^="#"]:after { | |||
content: ""; | |||
} | |||
pre, | |||
blockquote { | |||
border: 1px solid #999; | |||
page-break-inside: avoid; | |||
} | |||
thead { | |||
display: table-header-group; | |||
} | |||
tr, | |||
img { | |||
page-break-inside: avoid; | |||
} | |||
img { | |||
max-width: 100% !important; | |||
} | |||
@page { | |||
margin: 0.5cm; | |||
} | |||
p, | |||
h2, | |||
h3 { | |||
widows: 3; | |||
orphans: 3; | |||
} | |||
h2, | |||
h3 { | |||
page-break-after: avoid; | |||
} | |||
h1, | |||
h2, | |||
h3, | |||
h4, | |||
h5, | |||
h6 { | |||
margin: 10px 0; | |||
font-family: inherit; | |||
font-weight: bold; | |||
line-height: 20px; | |||
color: inherit; | |||
text-rendering: optimizelegibility; | |||
} | |||
table { | |||
max-width: 100%; | |||
background-color: transparent; | |||
border-collapse: collapse; | |||
border-spacing: 0; | |||
} | |||
td { | |||
vertical-align: top; | |||
} | |||
.table { | |||
width: 100%; | |||
margin-bottom: 20px; | |||
} | |||
.table th, | |||
.table td { | |||
padding: 8px; | |||
line-height: 20px; | |||
text-align: left; | |||
vertical-align: top; | |||
border-top: 1px solid #dddddd; | |||
} | |||
.table th { | |||
font-weight: bold; | |||
} | |||
.table thead th { | |||
vertical-align: bottom; | |||
} | |||
.table caption + thead tr:first-child th, | |||
.table caption + thead tr:first-child td, | |||
.table colgroup + thead tr:first-child th, | |||
.table colgroup + thead tr:first-child td, | |||
.table thead:first-child tr:first-child th, | |||
.table thead:first-child tr:first-child td { | |||
border-top: 0; | |||
} | |||
.table tbody + tbody { | |||
border-top: 2px solid #dddddd; | |||
} | |||
.table .table { | |||
background-color: #ffffff; | |||
} | |||
.table-condensed th, | |||
.table-condensed td { | |||
padding: 4px 5px; | |||
} | |||
.table-bordered { | |||
border: 1px solid #dddddd; | |||
border-collapse: separate; | |||
*border-collapse: collapse; | |||
border-left: 0; | |||
-webkit-border-radius: 4px; | |||
-moz-border-radius: 4px; | |||
border-radius: 4px; | |||
} | |||
.table-bordered th, | |||
.table-bordered td { | |||
border-left: 1px solid #dddddd; | |||
} | |||
.table-bordered caption + thead tr:first-child th, | |||
.table-bordered caption + tbody tr:first-child th, | |||
.table-bordered caption + tbody tr:first-child td, | |||
.table-bordered colgroup + thead tr:first-child th, | |||
.table-bordered colgroup + tbody tr:first-child th, | |||
.table-bordered colgroup + tbody tr:first-child td, | |||
.table-bordered thead:first-child tr:first-child th, | |||
.table-bordered tbody:first-child tr:first-child th, | |||
.table-bordered tbody:first-child tr:first-child td { | |||
border-top: 0; | |||
} | |||
.table-bordered thead:first-child tr:first-child > th:first-child, | |||
.table-bordered tbody:first-child tr:first-child > td:first-child { | |||
-webkit-border-top-left-radius: 4px; | |||
border-top-left-radius: 4px; | |||
-moz-border-radius-topleft: 4px; | |||
} | |||
.table-bordered thead:first-child tr:first-child > th:last-child, | |||
.table-bordered tbody:first-child tr:first-child > td:last-child { | |||
-webkit-border-top-right-radius: 4px; | |||
border-top-right-radius: 4px; | |||
-moz-border-radius-topright: 4px; | |||
} | |||
.table-bordered thead:last-child tr:last-child > th:first-child, | |||
.table-bordered tbody:last-child tr:last-child > td:first-child, | |||
.table-bordered tfoot:last-child tr:last-child > td:first-child { | |||
-webkit-border-bottom-left-radius: 4px; | |||
border-bottom-left-radius: 4px; | |||
-moz-border-radius-bottomleft: 4px; | |||
} | |||
.table-bordered thead:last-child tr:last-child > th:last-child, | |||
.table-bordered tbody:last-child tr:last-child > td:last-child, | |||
.table-bordered tfoot:last-child tr:last-child > td:last-child { | |||
-webkit-border-bottom-right-radius: 4px; | |||
border-bottom-right-radius: 4px; | |||
-moz-border-radius-bottomright: 4px; | |||
} | |||
.table-bordered tfoot + tbody:last-child tr:last-child td:first-child { | |||
-webkit-border-bottom-left-radius: 0; | |||
border-bottom-left-radius: 0; | |||
-moz-border-radius-bottomleft: 0; | |||
} | |||
.table-bordered tfoot + tbody:last-child tr:last-child td:last-child { | |||
-webkit-border-bottom-right-radius: 0; | |||
border-bottom-right-radius: 0; | |||
-moz-border-radius-bottomright: 0; | |||
} | |||
.table-bordered caption + thead tr:first-child th:first-child, | |||
.table-bordered caption + tbody tr:first-child td:first-child, | |||
.table-bordered colgroup + thead tr:first-child th:first-child, | |||
.table-bordered colgroup + tbody tr:first-child td:first-child { | |||
-webkit-border-top-left-radius: 4px; | |||
border-top-left-radius: 4px; | |||
-moz-border-radius-topleft: 4px; | |||
} | |||
.table-bordered caption + thead tr:first-child th:last-child, | |||
.table-bordered caption + tbody tr:first-child td:last-child, | |||
.table-bordered colgroup + thead tr:first-child th:last-child, | |||
.table-bordered colgroup + tbody tr:first-child td:last-child { | |||
-webkit-border-top-right-radius: 4px; | |||
border-top-right-radius: 4px; | |||
-moz-border-radius-topright: 4px; | |||
} | |||
.table-striped tbody > tr:nth-child(odd) > td, | |||
.table-striped tbody > tr:nth-child(odd) > th { | |||
background-color: #f9f9f9; | |||
} | |||
.table-hover tbody tr:hover td, | |||
.table-hover tbody tr:hover th { | |||
background-color: #f5f5f5; | |||
} | |||
table td[class*="span"], | |||
table th[class*="span"], | |||
.row-fluid table td[class*="span"], | |||
.row-fluid table th[class*="span"] { | |||
display: table-cell; | |||
float: none; | |||
margin-left: 0; | |||
} | |||
.table td.span1, | |||
.table th.span1 { | |||
float: none; | |||
width: 44px; | |||
margin-left: 0; | |||
} | |||
.table td.span2, | |||
.table th.span2 { | |||
float: none; | |||
width: 124px; | |||
margin-left: 0; | |||
} | |||
.table td.span3, | |||
.table th.span3 { | |||
float: none; | |||
width: 204px; | |||
margin-left: 0; | |||
} | |||
.table td.span4, | |||
.table th.span4 { | |||
float: none; | |||
width: 284px; | |||
margin-left: 0; | |||
} | |||
.table td.span5, | |||
.table th.span5 { | |||
float: none; | |||
width: 364px; | |||
margin-left: 0; | |||
} | |||
.table td.span6, | |||
.table th.span6 { | |||
float: none; | |||
width: 444px; | |||
margin-left: 0; | |||
} | |||
.table td.span7, | |||
.table th.span7 { | |||
float: none; | |||
width: 524px; | |||
margin-left: 0; | |||
} | |||
.table td.span8, | |||
.table th.span8 { | |||
float: none; | |||
width: 604px; | |||
margin-left: 0; | |||
} | |||
.table td.span9, | |||
.table th.span9 { | |||
float: none; | |||
width: 684px; | |||
margin-left: 0; | |||
} | |||
.table td.span10, | |||
.table th.span10 { | |||
float: none; | |||
width: 764px; | |||
margin-left: 0; | |||
} | |||
.table td.span11, | |||
.table th.span11 { | |||
float: none; | |||
width: 844px; | |||
margin-left: 0; | |||
} | |||
.table td.span12, | |||
.table th.span12 { | |||
float: none; | |||
width: 924px; | |||
margin-left: 0; | |||
} | |||
.table tbody tr.success td { | |||
background-color: #dff0d8; | |||
} | |||
.table tbody tr.error td { | |||
background-color: #f2dede; | |||
} | |||
.table tbody tr.warning td { | |||
background-color: #fcf8e3; | |||
} | |||
.table tbody tr.info td { | |||
background-color: #d9edf7; | |||
} | |||
.table-hover tbody tr.success:hover td { | |||
background-color: #d0e9c6; | |||
} | |||
.table-hover tbody tr.error:hover td { | |||
background-color: #ebcccc; | |||
} | |||
.table-hover tbody tr.warning:hover td { | |||
background-color: #faf2cc; | |||
} | |||
.table-hover tbody tr.info:hover td { | |||
background-color: #c4e3f3; | |||
} |
@@ -0,0 +1,29 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# See license.txt | |||
import frappe | |||
import unittest | |||
import re | |||
test_records = frappe.get_test_records('Print Format') | |||
class TestPrintFormat(unittest.TestCase): | |||
def test_print_user(self, style=None): | |||
print_html = frappe.get_print_format("User", "Administrator", style=style) | |||
self.assertTrue("<label>First Name</label>" in print_html) | |||
self.assertTrue(re.findall('<div class="col-xs-7[\s]*">[\s]*Administrator[\s]*</div>', print_html)) | |||
return print_html | |||
def test_print_user_standard(self): | |||
print_html = self.test_print_user("Standard") | |||
self.assertTrue(re.findall('\.print-format {[\s]*font-size: 9pt;', print_html)) | |||
self.assertFalse(re.findall('th {[\s]*background-color: #eee;[\s]*}', print_html)) | |||
self.assertFalse("font-family: serif;" in print_html) | |||
def test_print_user_modern(self): | |||
print_html = self.test_print_user("Modern") | |||
self.assertTrue(re.findall('th {[\s]*background-color: #eee;[\s]*}', print_html)) | |||
def test_print_user_classic(self): | |||
print_html = self.test_print_user("Classic") | |||
self.assertTrue("font-family: serif;" in print_html) |
@@ -0,0 +1,8 @@ | |||
[ | |||
{ | |||
"doctype": "Print Format", | |||
"name": "_Test Print Format 1", | |||
"module": "core", | |||
"doc_type": "User" | |||
} | |||
] |
@@ -0,0 +1,11 @@ | |||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and contributors | |||
// For license information, please see license.txt | |||
frappe.ui.form.on("Print Settings", "print_style", function (frm) { | |||
frm.get_field("print_style_preview").html('<img src="/assets/frappe/images/help/print-style-' + | |||
frm.doc.print_style.toLowerCase() + '.png" class="img-responsive">'); | |||
}); | |||
frappe.ui.form.on("Print Settings", "onload", function (frm) { | |||
frm.script_manager.trigger("print_style"); | |||
}); |
@@ -0,0 +1,96 @@ | |||
{ | |||
"creation": "2014-07-17 06:54:20.782907", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"fields": [ | |||
{ | |||
"fieldname": "pdf_settings", | |||
"fieldtype": "Section Break", | |||
"label": "PDF Settings", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"default": "1", | |||
"description": "Send Email Print Attachments as PDF (Recommended)", | |||
"fieldname": "send_print_as_pdf", | |||
"fieldtype": "Check", | |||
"label": "Send Print as PDF", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"default": "A4", | |||
"fieldname": "pdf_page_size", | |||
"fieldtype": "Select", | |||
"label": "PDF Page Size", | |||
"options": "A4\nLetter", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "print_style_section", | |||
"fieldtype": "Section Break", | |||
"label": "Print Style", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"default": "", | |||
"fieldname": "print_style", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Print Style", | |||
"options": "Modern\nClassic\nStandard\nMonochrome", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "column_break_6", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "In points. Default is 9.", | |||
"fieldname": "font_size", | |||
"fieldtype": "Float", | |||
"label": "Font Size", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"default": "1", | |||
"description": "Print with Letterhead, unless unchecked in a particular Document", | |||
"fieldname": "with_letterhead", | |||
"fieldtype": "Check", | |||
"label": "With Letterhead", | |||
"permlevel": 0, | |||
"reqd": 0 | |||
}, | |||
{ | |||
"fieldname": "section_break_8", | |||
"fieldtype": "Section Break", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "print_style_preview", | |||
"fieldtype": "HTML", | |||
"label": "Print Style Preview", | |||
"permlevel": 0 | |||
} | |||
], | |||
"icon": "icon-cog", | |||
"issingle": 1, | |||
"modified": "2014-08-05 09:03:02.337355", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Print Settings", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"write": 1 | |||
} | |||
], | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
} |
@@ -0,0 +1,10 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.model.document import Document | |||
class PrintSettings(Document): | |||
def on_update(self): | |||
frappe.clear_cache() |
@@ -1,5 +1,5 @@ | |||
{ | |||
"creation": "2013-01-10 16:34:04.000000", | |||
"creation": "2013-01-10 16:34:04", | |||
"description": "Property Setter overrides a standard DocType or Field property", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
@@ -20,6 +20,7 @@ | |||
"depends_on": "eval:doc.__islocal", | |||
"fieldname": "doctype_or_field", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "DocType or Field", | |||
"options": "\nDocField\nDocType", | |||
"permlevel": 0, | |||
@@ -29,6 +30,7 @@ | |||
"description": "New value to be set", | |||
"fieldname": "value", | |||
"fieldtype": "Text", | |||
"in_list_view": 1, | |||
"label": "Set Value", | |||
"permlevel": 0 | |||
}, | |||
@@ -83,7 +85,7 @@ | |||
], | |||
"icon": "icon-glass", | |||
"idx": 1, | |||
"modified": "2014-01-20 17:49:03.000000", | |||
"modified": "2014-07-24 02:04:26.838056", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Property Setter", | |||
@@ -116,5 +118,5 @@ | |||
"write": 1 | |||
} | |||
], | |||
"search_fields": "doc_name,property" | |||
"search_fields": "doc_type,property" | |||
} |
@@ -1,5 +1,5 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
@@ -7,7 +7,6 @@ import frappe | |||
from frappe.model.document import Document | |||
class PropertySetter(Document): | |||
def autoname(self): | |||
self.name = self.doc_type + "-" \ | |||
+ (self.field_name and (self.field_name + "-") or "") \ | |||
@@ -21,47 +20,52 @@ class PropertySetter(Document): | |||
and doc_type = %(doc_type)s | |||
and ifnull(field_name,'') = ifnull(%(field_name)s, '') | |||
and property = %(property)s""", self.get_valid_dict()) | |||
# clear cache | |||
frappe.clear_cache(doctype = self.doc_type) | |||
def get_property_list(self, dt): | |||
return frappe.db.sql("""select fieldname, label, fieldtype | |||
return frappe.db.sql("""select fieldname, label, fieldtype | |||
from tabDocField | |||
where parent=%s | |||
and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table') | |||
and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table', 'Fold') | |||
and ifnull(fieldname, '') != '' | |||
order by label asc""", dt, as_dict=1) | |||
def get_setup_data(self): | |||
return { | |||
'doctypes': [d[0] for d in frappe.db.sql("select name from tabDocType")], | |||
'dt_properties': self.get_property_list('DocType'), | |||
'df_properties': self.get_property_list('DocField') | |||
} | |||
def get_field_ids(self): | |||
return frappe.db.sql("select name, fieldtype, label, fieldname from tabDocField where parent=%s", self.doc_type, as_dict = 1) | |||
def get_defaults(self): | |||
if not self.field_name: | |||
return frappe.db.sql("select * from `tabDocType` where name=%s", self.doc_type, as_dict = 1)[0] | |||
else: | |||
return frappe.db.sql("select * from `tabDocField` where fieldname=%s and parent=%s", | |||
return frappe.db.sql("select * from `tabDocField` where fieldname=%s and parent=%s", | |||
(self.field_name, self.doc_type), as_dict = 1)[0] | |||
def on_update(self): | |||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype | |||
validate_fields_for_doctype(self.doc_type) | |||
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False): | |||
return frappe.get_doc({ | |||
"doctype":"Property Setter", | |||
"doctype_or_field": for_doctype and "DocType" or "DocField", | |||
"doc_type": doctype, | |||
"field_name": fieldname, | |||
"property": property, | |||
"value": value, | |||
"property_type": property_type | |||
}).insert() | |||
if not getattr(self, "ignore_validate", False) and getattr(self, "validate_fields_for_doctype", True): | |||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype | |||
validate_fields_for_doctype(self.doc_type) | |||
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False, validate_fields_for_doctype=True): | |||
# WARNING: Ignores Permissions | |||
property_setter = frappe.get_doc({ | |||
"doctype":"Property Setter", | |||
"doctype_or_field": for_doctype and "DocType" or "DocField", | |||
"doc_type": doctype, | |||
"field_name": fieldname, | |||
"property": property, | |||
"value": value, | |||
"property_type": property_type | |||
}) | |||
property_setter.ignore_permissions = True | |||
property_setter.validate_fields_for_doctype = validate_fields_for_doctype | |||
property_setter.insert() | |||
return property_setter |
@@ -0,0 +1,8 @@ | |||
// Copyright (c) 2013, {app_publisher} and contributors | |||
// For license information, please see license.txt | |||
frappe.query_reports["{name}"] = {{ | |||
"filters": [ | |||
] | |||
}} |
@@ -0,0 +1,9 @@ | |||
# Copyright (c) 2013, {app_publisher} and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
def execute(filters=None): | |||
columns, data = [], [] | |||
return columns, data |
@@ -11,7 +11,22 @@ cur_frm.cscript.refresh = function(doc) { | |||
frappe.set_route("query-report", doc.name); | |||
break; | |||
} | |||
}, "icon-table") | |||
}, "icon-table"); | |||
if (doc.is_standard === "Yes") { | |||
cur_frm.add_custom_button(doc.disabled ? __("Enable Report") : __("Disable Report"), function() { | |||
$.ajax({ | |||
url: "/api/resource/Report/" + encodeURIComponent(doc.name), | |||
type: "POST", | |||
data: { | |||
run_method: "toggle_disable", | |||
disable: doc.disabled ? 0 : 1 | |||
} | |||
}).always(function() { | |||
cur_frm.reload_doc(); | |||
}); | |||
}, doc.disabled ? "icon-ok" : "icon-off"); | |||
} | |||
cur_frm.set_intro(""); | |||
switch(doc.report_type) { | |||
@@ -61,6 +61,15 @@ | |||
"permlevel": 0, | |||
"read_only": 0 | |||
}, | |||
{ | |||
"default": "1", | |||
"depends_on": "eval:[\"Query Report\", \"Script Report\"].indexOf(doc.report_type)!==-1", | |||
"fieldname": "apply_user_permissions", | |||
"fieldtype": "Check", | |||
"in_list_view": 0, | |||
"label": "Apply User Permissions", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "section_break_6", | |||
"fieldtype": "Section Break", | |||
@@ -101,14 +110,13 @@ | |||
], | |||
"icon": "icon-table", | |||
"idx": 1, | |||
"modified": "2014-05-12 17:08:04.185601", | |||
"modified": "2014-06-03 07:25:41.509885", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Report", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -121,7 +129,6 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -134,7 +141,6 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
@@ -147,7 +153,7 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"cancel": 0, | |||
"apply_user_permissions": 1, | |||
"delete": 0, | |||
"email": 1, | |||
"permlevel": 0, | |||
@@ -3,35 +3,50 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe import conf, _ | |||
from frappe import _ | |||
from frappe.utils import cint | |||
from frappe.model.document import Document | |||
from frappe.modules.export_file import export_to_files | |||
from frappe.modules import make_boilerplate | |||
class Report(Document): | |||
def validate(self): | |||
"""only administrator can save standard report""" | |||
if not self.module: | |||
self.module = frappe.db.get_value("DocType", self.ref_doctype, "module") | |||
if not self.is_standard: | |||
self.is_standard = "No" | |||
if frappe.session.user=="Administrator" and getattr(conf, 'developer_mode',0)==1: | |||
if frappe.session.user=="Administrator" and getattr(frappe.local.conf, 'developer_mode',0)==1: | |||
self.is_standard = "Yes" | |||
if self.is_standard == "Yes" and frappe.session.user!="Administrator": | |||
frappe.msgprint(_("Only Administrator can save a standard report. Please rename and save."), | |||
frappe.msgprint(_("Only Administrator can save a standard report. Please rename and save."), | |||
raise_exception=True) | |||
if self.report_type in ("Query Report", "Script Report") \ | |||
and frappe.session.user!="Administrator": | |||
frappe.msgprint(_("Only Administrator allowed to create Query / Script Reports"), | |||
raise_exception=True) | |||
def on_update(self): | |||
self.export_doc() | |||
def export_doc(self): | |||
from frappe.modules.export_file import export_to_files | |||
if self.is_standard == 'Yes' and (conf.get('developer_mode') or 0) == 1: | |||
export_to_files(record_list=[['Report', self.name]], | |||
if frappe.flags.in_import: | |||
return | |||
if self.is_standard == 'Yes' and (frappe.local.conf.get('developer_mode') or 0) == 1: | |||
export_to_files(record_list=[['Report', self.name]], | |||
record_module=self.module) | |||
self.create_report_py() | |||
def create_report_py(self): | |||
if self.report_type == "Script Report": | |||
make_boilerplate("controller.py", self, {"name": self.name}) | |||
make_boilerplate("controller.js", self, {"name": self.name}) | |||
@Document.whitelist | |||
def toggle_disable(self, disable): | |||
self.db_set("disabled", cint(disable)) |
@@ -2,7 +2,7 @@ | |||
// MIT License. See license.txt | |||
cur_frm.cscript.refresh = function(doc) { | |||
cur_frm.permission_manager = cur_frm.add_custom_button("Permission Manager", function() { | |||
cur_frm.permission_manager = cur_frm.add_custom_button("Role Permissions Manager", function() { | |||
frappe.route_options = {"role": doc.name}; | |||
frappe.set_route("permission-manager"); | |||
}); | |||
@@ -23,7 +23,7 @@ | |||
"idx": 1, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2014-05-02 06:35:09.528809", | |||
"modified": "2014-08-05 05:24:42.185395", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Role", | |||
@@ -31,7 +31,6 @@ | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
@@ -43,8 +42,8 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
@@ -55,6 +54,7 @@ | |||
"write": 1 | |||
}, | |||
{ | |||
"apply_user_permissions": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "All" | |||
@@ -1,79 +0,0 @@ | |||
{ | |||
"creation": "2014-03-04 08:29:52.000000", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"fields": [ | |||
{ | |||
"fieldname": "facebook", | |||
"fieldtype": "Section Break", | |||
"label": "Facebook", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "facebook_client_id", | |||
"fieldtype": "Data", | |||
"label": "Facebook Client ID", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "facebook_client_secret", | |||
"fieldtype": "Data", | |||
"label": "Facebook Client Secret", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "google", | |||
"fieldtype": "Section Break", | |||
"label": "Google", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "google_client_id", | |||
"fieldtype": "Data", | |||
"label": "Google Client ID", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "google_client_secret", | |||
"fieldtype": "Data", | |||
"label": "Google Client Secret", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "github", | |||
"fieldtype": "Section Break", | |||
"label": "GitHub", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "github_client_id", | |||
"fieldtype": "Data", | |||
"label": "GitHub Client ID", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "github_client_secret", | |||
"fieldtype": "Data", | |||
"label": "GitHub Client Secret", | |||
"permlevel": 0 | |||
} | |||
], | |||
"icon": "icon-signin", | |||
"idx": 1, | |||
"issingle": 1, | |||
"modified": "2014-03-04 08:47:32.000000", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Social Login Keys", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"write": 1 | |||
} | |||
] | |||
} |
@@ -0,0 +1,71 @@ | |||
{ | |||
"allow_import": 1, | |||
"autoname": "field:subject", | |||
"creation": "2014-06-19 05:20:26.331041", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Transaction", | |||
"fields": [ | |||
{ | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Subject", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "response", | |||
"fieldtype": "Text Editor", | |||
"in_list_view": 1, | |||
"label": "Response", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"default": "user", | |||
"fieldname": "owner", | |||
"fieldtype": "Link", | |||
"hidden": 1, | |||
"label": "Owner", | |||
"options": "User", | |||
"permlevel": 0 | |||
} | |||
], | |||
"icon": "icon-comment", | |||
"modified": "2014-06-19 05:45:09.855045", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Standard Reply", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "All" | |||
}, | |||
{ | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "All", | |||
"write": 1 | |||
}, | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": 1, | |||
"import": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"write": 1 | |||
} | |||
], | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
} |
@@ -0,0 +1,9 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.model.document import Document | |||
class StandardReply(Document): | |||
pass |
@@ -1,339 +1,100 @@ | |||
{ | |||
"_last_update": null, | |||
"_user_tags": null, | |||
"allow_attach": null, | |||
"allow_copy": null, | |||
"allow_email": null, | |||
"allow_import": null, | |||
"allow_print": null, | |||
"allow_rename": null, | |||
"allow_trash": null, | |||
"autoname": null, | |||
"change_log": null, | |||
"client_script": null, | |||
"client_script_core": null, | |||
"client_string": null, | |||
"colour": null, | |||
"creation": "2014-04-17 16:53:52.640856", | |||
"custom": null, | |||
"default_print_format": null, | |||
"description": null, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"dt_template": null, | |||
"creation": "2014-04-17 16:53:52.640856", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"fields": [ | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "localization", | |||
"fieldtype": "Section Break", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Localization", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "localization", | |||
"fieldtype": "Section Break", | |||
"label": "Localization", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "language", | |||
"fieldtype": "Select", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": 1, | |||
"label": "Language", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": "Loading...", | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "language", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Language", | |||
"options": "Loading...", | |||
"permlevel": 0, | |||
"reqd": 1, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "time_zone", | |||
"fieldtype": "Select", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Time Zone", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": 1, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "time_zone", | |||
"fieldtype": "Select", | |||
"label": "Time Zone", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "date_and_number_format", | |||
"fieldtype": "Section Break", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Date and Number Format", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "date_and_number_format", | |||
"fieldtype": "Section Break", | |||
"label": "Date and Number Format", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "date_format", | |||
"fieldtype": "Select", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Date Format", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\nmm/dd/yyyy\nmm-dd-yyyy", | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": 1, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "date_format", | |||
"fieldtype": "Select", | |||
"label": "Date Format", | |||
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\ndd.mm.yyyy\nmm/dd/yyyy\nmm-dd-yyyy", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "number_format", | |||
"fieldtype": "Select", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Number Format", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": "#,###.##\n#.###,##\n# ###.##\n#,###.###\n#,##,###.##\n#.###\n#,###", | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": 1, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "number_format", | |||
"fieldtype": "Select", | |||
"label": "Number Format", | |||
"options": "#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "float_precision", | |||
"fieldtype": "Select", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Float Precision", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": "\n2\n3\n4\n5\n6", | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "float_precision", | |||
"fieldtype": "Select", | |||
"label": "Float Precision", | |||
"options": "\n2\n3\n4\n5\n6", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": null, | |||
"depends_on": null, | |||
"description": null, | |||
"fieldname": "security", | |||
"fieldtype": "Section Break", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Security", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": null, | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
}, | |||
"fieldname": "security", | |||
"fieldtype": "Section Break", | |||
"label": "Security", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"allow_on_submit": null, | |||
"default": "06:00", | |||
"depends_on": null, | |||
"description": "Session Expiry in Hours e.g. 06:00", | |||
"fieldname": "session_expiry", | |||
"fieldtype": "Data", | |||
"hidden": null, | |||
"ignore_restrictions": null, | |||
"in_filter": null, | |||
"in_list_view": null, | |||
"label": "Session Expiry", | |||
"no_column": null, | |||
"no_copy": null, | |||
"oldfieldname": null, | |||
"oldfieldtype": null, | |||
"options": "", | |||
"permlevel": 0, | |||
"print_hide": null, | |||
"print_width": null, | |||
"read_only": null, | |||
"report_hide": null, | |||
"reqd": null, | |||
"search_index": null, | |||
"set_only_once": null, | |||
"trigger": null, | |||
"width": null | |||
"default": "06:00", | |||
"description": "Session Expiry in Hours e.g. 06:00", | |||
"fieldname": "session_expiry", | |||
"fieldtype": "Data", | |||
"label": "Session Expiry", | |||
"options": "", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"description": "Run scheduled jobs only if checked", | |||
"fieldname": "enable_scheduler", | |||
"fieldtype": "Check", | |||
"in_list_view": 0, | |||
"label": "Enable Scheduled Jobs", | |||
"permlevel": 0 | |||
} | |||
], | |||
"hide_heading": null, | |||
"hide_toolbar": null, | |||
"icon": "icon-cog", | |||
"idx": null, | |||
"in_create": null, | |||
"in_dialog": null, | |||
"is_submittable": null, | |||
"is_transaction_doc": null, | |||
"issingle": 1, | |||
"istable": null, | |||
"max_attachments": null, | |||
"menu_index": null, | |||
"modified": "2014-04-17 17:52:27.046530", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "System Settings", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"parent": null, | |||
"parent_node": null, | |||
"parentfield": null, | |||
"parenttype": null, | |||
], | |||
"icon": "icon-cog", | |||
"issingle": 1, | |||
"modified": "2014-06-18 02:09:03.623094", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "System Settings", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": null, | |||
"cancel": null, | |||
"create": 1, | |||
"delete": null, | |||
"email": null, | |||
"export": null, | |||
"import": null, | |||
"match": null, | |||
"permlevel": 0, | |||
"print": null, | |||
"read": 1, | |||
"report": null, | |||
"restrict": null, | |||
"restricted": null, | |||
"role": "System Manager", | |||
"submit": null, | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"write": 1 | |||
} | |||
], | |||
"plugin": null, | |||
"print_outline": null, | |||
"read_only": null, | |||
"read_only_onload": null, | |||
"search_fields": null, | |||
"section_style": null, | |||
"server_code": null, | |||
"server_code_compiled": null, | |||
"server_code_core": null, | |||
"server_code_error": null, | |||
"show_in_menu": null, | |||
"smallicon": null, | |||
"subject": null, | |||
"tag_fields": null, | |||
"title_field": null, | |||
"use_template": null, | |||
"version": null | |||
} | |||
] | |||
} |
@@ -2,11 +2,12 @@ | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe, pytz | |||
import frappe | |||
from frappe import _ | |||
from frappe.model.document import Document | |||
from frappe.translate import get_lang_dict, set_default_language | |||
from frappe.utils import cint | |||
from frappe.utils.momentjs import get_all_timezones | |||
class SystemSettings(Document): | |||
def validate(self): | |||
@@ -17,11 +18,11 @@ class SystemSettings(Document): | |||
def on_update(self): | |||
for df in self.meta.get("fields"): | |||
if df.fieldtype in ("Select", "Data"): | |||
if df.fieldtype in ("Select", "Data", "Check"): | |||
frappe.db.set_default(df.fieldname, self.get(df.fieldname)) | |||
set_default_language(self.language) | |||
if self.language: | |||
set_default_language(self.language) | |||
@frappe.whitelist() | |||
def load(): | |||
@@ -39,7 +40,7 @@ def load(): | |||
languages.sort() | |||
return { | |||
"timezones": pytz.all_timezones, | |||
"timezones": get_all_timezones(), | |||
"languages": [""] + languages, | |||
"defaults": defaults | |||
} |
@@ -1,13 +1,23 @@ | |||
// bind events | |||
frappe.ui.form.on("ToDo", "refresh", function(frm) { | |||
frm.add_custom_button((frm.doc.status=="Open" ? __("Close") : __("Re-open")), function() { | |||
frm.set_value("status", frm.doc.status=="Open" ? "Closed" : "Open"); | |||
frm.save(); | |||
}); | |||
if(frm.doc.reference_type && frm.doc.reference_name) { | |||
frm.set_intro('Reference: <a href="#Form/'+frm.doc.reference_type+'/'+frm.doc.reference_name+'">' | |||
+ frm.doc.reference_name + '</a>'); | |||
frm.add_custom_button(__(frm.doc.reference_name), function() { | |||
frappe.set_route("Form", frm.doc.reference_type, frm.doc.reference_name); | |||
}, frappe.boot.doctype_icons[frm.doc.reference_type]); | |||
} | |||
}); | |||
if (!frm.doc.__islocal) { | |||
if(frm.doc.status=="Open") { | |||
frm.add_custom_button(__("Close"), function() { | |||
frm.set_value("status", "Closed"); | |||
frm.save(); | |||
}, "icon-ok", "btn-success"); | |||
} else { | |||
frm.add_custom_button(__("Re-open"), function() { | |||
frm.set_value("status", "Open"); | |||
frm.save(); | |||
}, null, "btn-default"); | |||
} | |||
} | |||
}); |
@@ -1,9 +1,8 @@ | |||
{ | |||
"allow_attach": 0, | |||
"allow_copy": 0, | |||
"allow_copy": 0, | |||
"allow_rename": 0, | |||
"autoname": "TDI.########", | |||
"creation": "2012-07-03 13:30:35.000000", | |||
"creation": "2012-07-03 13:30:35", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
@@ -82,6 +81,16 @@ | |||
"reqd": 0, | |||
"search_index": 0 | |||
}, | |||
{ | |||
"fieldname": "owner", | |||
"fieldtype": "Link", | |||
"ignore_user_permissions": 1, | |||
"in_list_view": 0, | |||
"label": "Assigned To", | |||
"options": "User", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "section_break_6", | |||
"fieldtype": "Section Break", | |||
@@ -91,13 +100,14 @@ | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "reference_type", | |||
"fieldtype": "Data", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"in_filter": 0, | |||
"label": "Reference Type", | |||
"no_copy": 0, | |||
"oldfieldname": "reference_type", | |||
"oldfieldtype": "Data", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"report_hide": 0, | |||
@@ -107,13 +117,14 @@ | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "reference_name", | |||
"fieldtype": "Data", | |||
"fieldtype": "Dynamic Link", | |||
"hidden": 0, | |||
"in_filter": 0, | |||
"label": "Reference Name", | |||
"no_copy": 0, | |||
"oldfieldname": "reference_name", | |||
"oldfieldtype": "Data", | |||
"options": "reference_type", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"report_hide": 0, | |||
@@ -145,6 +156,7 @@ | |||
{ | |||
"fieldname": "assigned_by", | |||
"fieldtype": "Link", | |||
"ignore_user_permissions": 1, | |||
"label": "Assigned By", | |||
"options": "User", | |||
"permlevel": 0 | |||
@@ -158,14 +170,14 @@ | |||
"in_dialog": 0, | |||
"issingle": 0, | |||
"max_attachments": 0, | |||
"modified": "2014-03-12 17:06:46.000000", | |||
"modified": "2014-06-30 05:40:15.471434", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "ToDo", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"cancel": 0, | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"delete": 0, | |||
"email": 1, | |||
@@ -174,7 +186,6 @@ | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"restricted": 1, | |||
"role": "All", | |||
"submit": 0, | |||
"write": 1 | |||
@@ -194,5 +205,6 @@ | |||
], | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"search_fields": "description, reference_type, reference_name", | |||
"title_field": "description" | |||
} |
@@ -3,46 +3,84 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
import json | |||
from frappe.model.document import Document | |||
class ToDo(Document): | |||
def validate(self): | |||
if self.is_new(): | |||
self.add_comment(frappe._("Assignment Added")) | |||
self.add_comment(frappe._("Assigned to {0}").format(self.owner), "Assigned") | |||
else: | |||
cur_status = frappe.db.get_value("ToDo", self.name, "status") | |||
if cur_status != self.status: | |||
self.add_comment(frappe._("Assignment Status Changed")) | |||
def add_comment(self, text): | |||
self.add_comment(frappe._("Assignment Status Changed"), "Assignment Completed") | |||
def on_update(self): | |||
self.update_in_reference() | |||
def on_trash(self): | |||
self.update_in_reference() | |||
def add_comment(self, text, comment_type): | |||
if not self.reference_type and self.reference_name: | |||
return | |||
comment = frappe.get_doc({ | |||
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": """<div>{text}: | |||
"comment": """<div>{text}: | |||
<a href='#Form/ToDo/{name}'>{status}: {description}</a></div>""".format(text=text, | |||
status = frappe._(self.status), | |||
name = self.name, | |||
description = self.description) | |||
}).insert(ignore_permissions=True) | |||
# todo is viewable if either owner or assigned_to or System Manager in roles | |||
def get_permission_query_conditions(): | |||
if "System Manager" in frappe.get_roles(): | |||
def update_in_reference(self): | |||
if not (self.reference_type and self.reference_name): | |||
return | |||
try: | |||
assignments = [d[0] for d in frappe.get_list("ToDo", | |||
filters={ | |||
"reference_type": self.reference_type, | |||
"reference_name": self.reference_name, | |||
"status": "Open" | |||
}, | |||
fields=["owner"], ignore_permissions=True, as_list=True)] | |||
assignments.reverse() | |||
frappe.db.set_value(self.reference_type, self.reference_name, | |||
"_assign", json.dumps(assignments)) | |||
except Exception, e: | |||
if e.args[0] == 1146 and frappe.flags.in_install: | |||
# no table | |||
return | |||
elif e.args[0]==1054: | |||
from frappe.model.db_schema import add_column | |||
add_column(self.reference_type, "_assign", "Text") | |||
self.update_in_reference() | |||
else: | |||
raise | |||
# NOTE: todo is viewable if either owner or assigned_to or System Manager in roles | |||
def get_permission_query_conditions(user): | |||
if not user: user = frappe.session.user | |||
if "System Manager" in frappe.get_roles(user): | |||
return None | |||
else: | |||
return """(tabToDo.owner = '{user}' or tabToDo.assigned_by = '{user}')""".format(user=frappe.session.user) | |||
def has_permission(doc): | |||
if "System Manager" in frappe.get_roles(): | |||
return """(tabToDo.owner = '{user}' or tabToDo.assigned_by = '{user}')""".format(user=user) | |||
def has_permission(doc, user): | |||
if "System Manager" in frappe.get_roles(user): | |||
return True | |||
else: | |||
return doc.owner==frappe.session.user or doc.assigned_by==frappe.session.user | |||
return doc.owner==user or doc.assigned_by==user |
@@ -0,0 +1,29 @@ | |||
<div class="row" style="max-height: 32px; margin-bottom: 3px;"> | |||
<div class="col-xs-11"> | |||
<div class="text-ellipsis"> | |||
{%= list.get_avatar_and_id(doc) %} | |||
<span class="label label-{%= frappe.utils.guess_style(doc.status) %} filterable" | |||
data-filter="status,=,{%= doc.status %}">{%= __(doc.status) %}</span> | |||
<span class="label label-{%= frappe.utils.guess_style(doc.priority) %} | |||
filterable" | |||
data-filter="priority,=,{%= doc.priority %}">{%= __(doc.priority) %}</span> | |||
{% if (doc.reference_name) { %} | |||
{% var reference_href = ("#Form/" + encodeURIComponent(doc.reference_type) + "/" | |||
+ encodeURIComponent(doc.reference_name)) %} | |||
<a href="{%= reference_href %}" style="text-decoration: none;"> | |||
<span class="label label-default"> | |||
<i class="{%= frappe.boot.doctype_icons[doc.reference_type] %}"></i> | |||
{%= doc.reference_name %} | |||
</span> | |||
</a> | |||
{% } %} | |||
</div> | |||
</div> | |||
<div class="col-xs-1 text-right" style="margin-bottom: 3px;"> | |||
<span class="filterable" data-filter="owner,=,{%= doc.owner %}"> | |||
{%= frappe.avatar(doc.assigned_to) %} | |||
</span> | |||
</div> | |||
</div> |