@@ -14,6 +14,8 @@ install: | |||||
- sudo apt-get update | - sudo apt-get update | ||||
- sudo apt-get purge -y mysql-common | - sudo apt-get purge -y mysql-common | ||||
- sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev | - 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 | - CFLAGS=-O0 pip install -r requirements.txt | ||||
- pip install --editable . | - pip install --editable . | ||||
@@ -23,10 +25,12 @@ script: | |||||
- frappe --use test_site | - frappe --use test_site | ||||
- frappe --reinstall | - frappe --reinstall | ||||
- frappe -b | - frappe -b | ||||
- frappe --build_website | |||||
- frappe --serve_test & | - frappe --serve_test & | ||||
- frappe --verbose --run_tests | - frappe --verbose --run_tests | ||||
before_script: | before_script: | ||||
- mysql -e 'create database test_frappe' | - 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;\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 *.ico | ||||
recursive-include frappe *.less | recursive-include frappe *.less | ||||
recursive-include frappe *.txt | recursive-include frappe *.txt | ||||
recursive-include frappe/public * | |||||
recursive-exclude * *.pyc | recursive-exclude * *.pyc |
@@ -1,41 +1,17 @@ | |||||
## frappe [](https://travis-ci.org/frappe/frappe) | ## 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 | globals attached to frappe module | ||||
+ some utility functions that should probably be moved | + some utility functions that should probably be moved | ||||
""" | """ | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
from werkzeug.local import Local, release_local | 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 * | from .exceptions import * | ||||
__version__ = "4.0.1" | |||||
from .utils.jinja import get_jenv, get_template, render_template | |||||
local = Local() | local = Local() | ||||
@@ -68,7 +64,6 @@ response = local("response") | |||||
session = local("session") | session = local("session") | ||||
user = local("user") | user = local("user") | ||||
flags = local("flags") | flags = local("flags") | ||||
restrictions = local("restrictions") | |||||
error_log = local("error_log") | error_log = local("error_log") | ||||
debug_log = local("debug_log") | debug_log = local("debug_log") | ||||
@@ -84,30 +79,37 @@ def init(site, sites_path=None): | |||||
sites_path = '.' | sites_path = '.' | ||||
local.error_log = [] | local.error_log = [] | ||||
local.message_log = [] | |||||
local.debug_log = [] | |||||
local.flags = _dict({}) | |||||
local.rollback_observers = [] | |||||
local.test_objects = {} | |||||
local.site = site | local.site = site | ||||
local.sites_path = sites_path | local.sites_path = sites_path | ||||
local.site_path = os.path.join(sites_path, site) | 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_method = request.method if request else None | ||||
local.request_ip = None | |||||
local.response = _dict({"docs":[]}) | local.response = _dict({"docs":[]}) | ||||
local.conf = _dict(get_site_config()) | local.conf = _dict(get_site_config()) | ||||
local.lang = local.conf.lang or "en" | local.lang = local.conf.lang or "en" | ||||
local.initialised = True | |||||
local.flags = _dict({}) | |||||
local.rollback_observers = [] | |||||
local.module_app = None | local.module_app = None | ||||
local.app_modules = None | local.app_modules = None | ||||
local.user = None | local.user = None | ||||
local.restrictions = None | |||||
local.user_perms = {} | |||||
local.test_objects = {} | |||||
local.role_permissions = {} | |||||
local.jenv = None | local.jenv = None | ||||
local.jloader =None | local.jloader =None | ||||
local.cache = {} | local.cache = {} | ||||
setup_module_map() | setup_module_map() | ||||
local.initialised = True | |||||
def connect(site=None, db_name=None): | def connect(site=None, db_name=None): | ||||
from database import Database | from database import Database | ||||
if site: | if site: | ||||
@@ -158,7 +160,7 @@ def get_traceback(): | |||||
def errprint(msg): | def errprint(msg): | ||||
from utils import cstr | from utils import cstr | ||||
if not request: | |||||
if not request or (not "cmd" in local.form_dict): | |||||
print cstr(msg) | print cstr(msg) | ||||
error_log.append(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): | def throw(msg, exc=ValidationError): | ||||
msgprint(msg, raise_exception=exc) | 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): | def set_user(username): | ||||
from frappe.utils.user import User | from frappe.utils.user import User | ||||
local.session.user = username | local.session.user = username | ||||
local.session.sid = username | local.session.sid = username | ||||
local.cache = {} | local.cache = {} | ||||
local.form_dict = _dict() | |||||
local.jenv = None | |||||
local.session.data = {} | local.session.data = {} | ||||
local.user = User(username) | local.user = User(username) | ||||
local.restrictions = None | |||||
local.user_perms = {} | |||||
local.role_permissions = {} | |||||
def get_request_header(key, default=None): | def get_request_header(key, default=None): | ||||
return request.headers.get(key, default) | return request.headers.get(key, default) | ||||
def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", | 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: | if bulk: | ||||
import frappe.utils.email_lib.bulk | import frappe.utils.email_lib.bulk | ||||
frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender, | 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: | else: | ||||
import frappe.utils.email_lib | import frappe.utils.email_lib | ||||
if as_markdown: | 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: | 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 | logger = None | ||||
whitelisted = [] | whitelisted = [] | ||||
@@ -276,18 +288,27 @@ def clear_cache(user=None, doctype=None): | |||||
translate.clear_cache() | translate.clear_cache() | ||||
reset_metadata_version() | reset_metadata_version() | ||||
for fn in frappe.get_hooks("clear_cache"): | |||||
get_attr(fn)() | |||||
frappe.local.role_permissions = {} | |||||
def get_roles(username=None): | def get_roles(username=None): | ||||
from frappe.utils.user import User | |||||
if not local.session: | if not local.session: | ||||
return ["Guest"] | 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: | 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 | 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): | def is_table(doctype): | ||||
tables = cache().get_value("is_table") | tables = cache().get_value("is_table") | ||||
@@ -300,6 +321,9 @@ def clear_perms(doctype): | |||||
db.sql("""delete from tabDocPerm where parent=%s""", doctype) | db.sql("""delete from tabDocPerm where parent=%s""", doctype) | ||||
def reset_perms(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) | clear_perms(doctype) | ||||
reload_doc(db.get_value("DocType", doctype, "module"), | reload_doc(db.get_value("DocType", doctype, "module"), | ||||
"DocType", doctype, force=True) | "DocType", doctype, force=True) | ||||
@@ -307,7 +331,8 @@ def reset_perms(doctype): | |||||
def generate_hash(txt=None): | def generate_hash(txt=None): | ||||
"""Generates random hash for session id""" | """Generates random hash for session id""" | ||||
import hashlib, time | 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(): | def reset_metadata_version(): | ||||
v = generate_hash() | 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): | def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False): | ||||
import frappe.model.delete_doc | 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): | def delete_doc_if_exists(doctype, name): | ||||
if db.exists(doctype, name): | if db.exists(doctype, name): | ||||
@@ -362,7 +379,7 @@ def get_module(modulename): | |||||
return importlib.import_module(modulename) | return importlib.import_module(modulename) | ||||
def scrub(txt): | def scrub(txt): | ||||
return txt.replace(' ','_').replace('-', '_').replace('/', '_').lower() | |||||
return txt.replace(' ','_').replace('-', '_').lower() | |||||
def unscrub(txt): | def unscrub(txt): | ||||
return txt.replace('_',' ').replace('-', ' ').title() | 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 | return apps | ||||
def get_installed_apps(): | def get_installed_apps(): | ||||
if flags.in_install_db: | |||||
if getattr(flags, "in_install_db", True): | |||||
return [] | return [] | ||||
installed = json.loads(db.get_global("installed_apps") or "[]") | installed = json.loads(db.get_global("installed_apps") or "[]") | ||||
return installed | 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 get_hooks(hook=None, default=None, app_name=None): | ||||
def load_app_hooks(app_name=None): | def load_app_hooks(app_name=None): | ||||
hooks = {} | hooks = {} | ||||
@@ -449,6 +481,7 @@ def setup_module_map(): | |||||
if app=="webnotes": app="frappe" | if app=="webnotes": app="frappe" | ||||
local.app_modules.setdefault(app, []) | local.app_modules.setdefault(app, []) | ||||
for module in get_module_list(app): | for module in get_module_list(app): | ||||
module = scrub(module) | |||||
local.module_app[module] = app | local.module_app[module] = app | ||||
local.app_modules[app].append(module) | 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("app_modules", local.app_modules) | ||||
_cache.set_value("module_app", local.module_app) | _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) | content = read_file(path, raise_not_found=raise_not_found) | ||||
if content: | 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: | else: | ||||
return [] | return [] | ||||
@@ -494,9 +530,9 @@ def call(fn, *args, **kwargs): | |||||
newargs[a] = kwargs.get(a) | newargs[a] = kwargs.get(a) | ||||
return fn(*args, **newargs) | return fn(*args, **newargs) | ||||
def make_property_setter(args): | |||||
def make_property_setter(args, ignore_validate=False): | |||||
args = _dict(args) | args = _dict(args) | ||||
get_doc({ | |||||
ps = get_doc({ | |||||
'doctype': "Property Setter", | 'doctype': "Property Setter", | ||||
'doctype_or_field': args.doctype_or_field or "DocField", | 'doctype_or_field': args.doctype_or_field or "DocField", | ||||
'doc_type': args.doctype, | 'doc_type': args.doctype, | ||||
@@ -505,13 +541,16 @@ def make_property_setter(args): | |||||
'value': args.value, | 'value': args.value, | ||||
'property_type': args.property_type or "Data", | 'property_type': args.property_type or "Data", | ||||
'__islocal': 1 | '__islocal': 1 | ||||
}).save() | |||||
}) | |||||
ps.ignore_validate = ignore_validate | |||||
ps.insert() | |||||
def import_doc(path, ignore_links=False, ignore_insert=False, insert=False): | def import_doc(path, ignore_links=False, ignore_insert=False, insert=False): | ||||
from frappe.core.page.data_import_tool import data_import_tool | from frappe.core.page.data_import_tool import data_import_tool | ||||
data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert) | data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert) | ||||
def copy_doc(doc): | def copy_doc(doc): | ||||
""" No_copy fields also get copied.""" | |||||
import copy | import copy | ||||
if not isinstance(doc, dict): | if not isinstance(doc, dict): | ||||
d = doc.as_dict() | d = doc.as_dict() | ||||
@@ -523,6 +562,8 @@ def copy_doc(doc): | |||||
newdoc.set("__islocal", 1) | newdoc.set("__islocal", 1) | ||||
newdoc.owner = None | newdoc.owner = None | ||||
newdoc.creation = None | newdoc.creation = None | ||||
newdoc.amended_from = None | |||||
newdoc.amendment_date = None | |||||
for d in newdoc.get_all_children(): | for d in newdoc.get_all_children(): | ||||
d.name = None | d.name = None | ||||
d.parent = 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 = html | ||||
local.message_success = success | local.message_success = success | ||||
local.response['type'] = 'page' | local.response['type'] = 'page' | ||||
local.response['page_name'] = 'message.html' | |||||
local.response['page_name'] = 'message' | |||||
if http_status_code: | if http_status_code: | ||||
local.response['http_status_code'] = 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, | 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, | 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 | import frappe.model.db_query | ||||
return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters, | return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters, | ||||
fields=fields, docstatus=docstatus, or_filters=or_filters, | fields=fields, docstatus=docstatus, or_filters=or_filters, | ||||
group_by=group_by, order_by=order_by, limit_start=limit_start, | group_by=group_by, order_by=order_by, limit_start=limit_start, | ||||
limit_page_length=limit_page_length, as_list=as_list, debug=debug, | 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 | 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): | def add_version(doc): | ||||
get_doc({ | get_doc({ | ||||
"doctype": "Version", | "doctype": "Version", | ||||
@@ -630,3 +617,37 @@ def get_test_records(doctype): | |||||
return json.loads(f.read()) | return json.loads(f.read()) | ||||
else: | else: | ||||
return [] | 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 | DELETE will delete | ||||
/api/resource/{doctype}/{name}?run_method={method} will run a whitelisted controller method | /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 | call = doctype = name = None | ||||
if len(parts) > 1: | if len(parts) > 1: | ||||
@@ -43,18 +43,20 @@ def handle(): | |||||
elif call=="resource": | elif call=="resource": | ||||
if "run_method" in frappe.local.form_dict: | if "run_method" in frappe.local.form_dict: | ||||
method = frappe.local.form_dict.pop("run_method") | |||||
doc = frappe.get_doc(doctype, name) | 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 frappe.local.request.method=="GET": | ||||
if not doc.has_permission("read"): | if not doc.has_permission("read"): | ||||
frappe.throw(_("Not permitted"), frappe.PermissionError) | 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 frappe.local.request.method=="POST": | ||||
if not doc.has_permission("write"): | if not doc.has_permission("write"): | ||||
frappe.throw(_("Not permitted"), frappe.PermissionError) | 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() | frappe.db.commit() | ||||
else: | else: | ||||
@@ -76,7 +78,6 @@ def handle(): | |||||
frappe.db.commit() | frappe.db.commit() | ||||
if frappe.local.request.method=="DELETE": | if frappe.local.request.method=="DELETE": | ||||
doc.update(data) | |||||
# Not checking permissions here because it's checked in delete_doc | # Not checking permissions here because it's checked in delete_doc | ||||
frappe.delete_doc(doctype, name) | frappe.delete_doc(doctype, name) | ||||
frappe.local.response.http_status_code = 202 | frappe.local.response.http_status_code = 202 | ||||
@@ -3,6 +3,7 @@ | |||||
import sys, os | import sys, os | ||||
import json | import json | ||||
import logging | |||||
from werkzeug.wrappers import Request, Response | from werkzeug.wrappers import Request, Response | ||||
from werkzeug.local import LocalManager | from werkzeug.local import LocalManager | ||||
@@ -25,6 +26,8 @@ local_manager = LocalManager([frappe.local]) | |||||
_site = None | _site = None | ||||
_sites_path = os.environ.get("SITES_PATH", ".") | _sites_path = os.environ.get("SITES_PATH", ".") | ||||
logger = frappe.get_logger() | |||||
@Request.application | @Request.application | ||||
def application(request): | def application(request): | ||||
frappe.local.request = request | frappe.local.request = request | ||||
@@ -36,7 +39,7 @@ def application(request): | |||||
init_site(request) | init_site(request) | ||||
if frappe.local.conf.get('maintainance_mode'): | |||||
if frappe.local.conf.get('maintenance_mode'): | |||||
raise frappe.SessionStopped | raise frappe.SessionStopped | ||||
make_form_dict(request) | make_form_dict(request) | ||||
@@ -63,28 +66,35 @@ def application(request): | |||||
except frappe.SessionStopped, e: | except frappe.SessionStopped, e: | ||||
response = frappe.utils.response.handle_session_stopped() | 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: | 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: | 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 e.__class__ == frappe.AuthenticationError: | ||||
if hasattr(frappe.local, "login_manager"): | if hasattr(frappe.local, "login_manager"): | ||||
frappe.local.login_manager.clear_cookies() | frappe.local.login_manager.clear_cookies() | ||||
if http_status_code==500: | |||||
logger.error('Request Error') | |||||
else: | else: | ||||
if frappe.local.request.method in ("POST", "PUT") and frappe.db: | if frappe.local.request.method in ("POST", "PUT") and frappe.db: | ||||
frappe.db.commit() | frappe.db.commit() | ||||
rollback = False | 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: | finally: | ||||
if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback: | if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback: | ||||
frappe.db.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, | 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.sessions import Session, clear_sessions, delete_session | ||||
from frappe.modules.patch_handler import check_session_stopped | from frappe.modules.patch_handler import check_session_stopped | ||||
from urllib import quote | |||||
class HTTPRequest: | class HTTPRequest: | ||||
def __init__(self): | def __init__(self): | ||||
# Get Environment variables | # Get Environment variables | ||||
@@ -20,6 +22,8 @@ class HTTPRequest: | |||||
if self.domain and self.domain.startswith('www.'): | if self.domain and self.domain.startswith('www.'): | ||||
self.domain = self.domain[4:] | 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 | # language | ||||
self.set_lang(frappe.get_request_header('HTTP_ACCEPT_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] | ip_list = [i.strip() for i in ip_list] | ||||
for ip 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 | return | ||||
frappe.throw(_("Not allowed from this IP Address"), frappe.AuthenticationError) | frappe.throw(_("Not allowed from this IP Address"), frappe.AuthenticationError) | ||||
@@ -223,7 +227,8 @@ class CookieManager: | |||||
def flush_cookies(self, response): | def flush_cookies(self, response): | ||||
for key, opts in self.cookies.items(): | 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 yesterday! | ||||
expires = datetime.datetime.now() + datetime.timedelta(days=-1) | expires = datetime.datetime.now() + datetime.timedelta(days=-1) | ||||
@@ -9,6 +9,7 @@ bootstrap client session | |||||
import frappe | import frappe | ||||
import frappe.defaults | import frappe.defaults | ||||
import frappe.widgets.page | import frappe.widgets.page | ||||
from frappe.utils import get_gravatar | |||||
def get_bootinfo(): | def get_bootinfo(): | ||||
"""build and return boot info""" | """build and return boot info""" | ||||
@@ -23,8 +24,6 @@ def get_bootinfo(): | |||||
# system info | # system info | ||||
bootinfo['sysdefaults'] = frappe.defaults.get_defaults() | bootinfo['sysdefaults'] = frappe.defaults.get_defaults() | ||||
bootinfo['server_date'] = frappe.utils.nowdate() | 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': | if frappe.session['user'] != 'Guest': | ||||
bootinfo['user_info'] = get_fullnames() | bootinfo['user_info'] = get_fullnames() | ||||
@@ -37,6 +36,8 @@ def get_bootinfo(): | |||||
bootinfo.modules.update(frappe.get_attr(app + ".config.desktop.get_data")() or {}) | bootinfo.modules.update(frappe.get_attr(app + ".config.desktop.get_data")() or {}) | ||||
except ImportError: | except ImportError: | ||||
pass | pass | ||||
except AttributeError: | |||||
pass | |||||
bootinfo.module_app = frappe.local.module_app | bootinfo.module_app = frappe.local.module_app | ||||
bootinfo.hidden_modules = frappe.db.get_global("hidden_modules") | bootinfo.hidden_modules = frappe.db.get_global("hidden_modules") | ||||
@@ -48,7 +49,9 @@ def get_bootinfo(): | |||||
add_home_page(bootinfo, doclist) | add_home_page(bootinfo, doclist) | ||||
add_allowed_pages(bootinfo) | add_allowed_pages(bootinfo) | ||||
load_translations(bootinfo) | load_translations(bootinfo) | ||||
add_timezone_info(bootinfo) | |||||
load_conf_settings(bootinfo) | load_conf_settings(bootinfo) | ||||
load_print(bootinfo, doclist) | |||||
# ipinfo | # ipinfo | ||||
if frappe.session['data'].get('ipinfo'): | if frappe.session['data'].get('ipinfo'): | ||||
@@ -63,6 +66,8 @@ def get_bootinfo(): | |||||
if bootinfo.lang: | if bootinfo.lang: | ||||
bootinfo.lang = unicode(bootinfo.lang) | bootinfo.lang = unicode(bootinfo.lang) | ||||
bootinfo.error_report_email = frappe.get_hooks("error_report_email") | |||||
return bootinfo | return bootinfo | ||||
def load_conf_settings(bootinfo): | def load_conf_settings(bootinfo): | ||||
@@ -72,14 +77,23 @@ def load_conf_settings(bootinfo): | |||||
def add_allowed_pages(bootinfo): | def add_allowed_pages(bootinfo): | ||||
roles = frappe.get_roles() | 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 | # 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 | from `tabPage` where | ||||
(select count(*) from `tabPage Role` | (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): | def load_translations(bootinfo): | ||||
if frappe.local.lang != 'en': | if frappe.local.lang != 'en': | ||||
@@ -90,18 +104,15 @@ def get_fullnames(): | |||||
"""map of user fullnames""" | """map of user fullnames""" | ||||
ret = frappe.db.sql("""select name, | ret = frappe.db.sql("""select name, | ||||
concat(ifnull(first_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 = {} | d = {} | ||||
for r in ret: | 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 | return d | ||||
@@ -113,16 +124,13 @@ def get_startup_js(): | |||||
def get_user(bootinfo): | def get_user(bootinfo): | ||||
"""get user info""" | """get user info""" | ||||
bootinfo['user'] = frappe.user.load_user() | |||||
bootinfo.user = frappe.user.load_user() | |||||
def add_home_page(bootinfo, docs): | def add_home_page(bootinfo, docs): | ||||
"""load home page""" | """load home page""" | ||||
if frappe.session.user=="Guest": | if frappe.session.user=="Guest": | ||||
return | return | ||||
home_page = frappe.db.get_default("desktop:home_page") | home_page = frappe.db.get_default("desktop:home_page") | ||||
try: | try: | ||||
page = frappe.widgets.page.get(home_page) | page = frappe.widgets.page.get(home_page) | ||||
except (frappe.DoesNotExistError, frappe.PermissionError): | except (frappe.DoesNotExistError, frappe.PermissionError): | ||||
@@ -131,3 +139,22 @@ def add_home_page(bootinfo, docs): | |||||
bootinfo['home_page'] = page.name | bootinfo['home_page'] = page.name | ||||
docs.append(page) | 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 | # 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 __future__ import unicode_literals | ||||
from frappe.utils.minify import JavascriptMinify | from frappe.utils.minify import JavascriptMinify | ||||
@@ -11,12 +11,12 @@ Build the `public` folders and setup languages | |||||
import os, sys, frappe, json, shutil | import os, sys, frappe, json, shutil | ||||
from cssmin import cssmin | from cssmin import cssmin | ||||
def bundle(no_compress, make_copy=False): | |||||
def bundle(no_compress, make_copy=False, verbose=False): | |||||
"""concat / minify js files""" | """concat / minify js files""" | ||||
# build js files | # build js files | ||||
make_asset_dirs(make_copy=make_copy) | make_asset_dirs(make_copy=make_copy) | ||||
build(no_compress) | |||||
build(no_compress, verbose) | |||||
def watch(no_compress): | def watch(no_compress): | ||||
"""watch and rebuild if necessary""" | """watch and rebuild if necessary""" | ||||
import time | import time | ||||
@@ -25,18 +25,18 @@ def watch(no_compress): | |||||
while True: | while True: | ||||
if files_dirty(): | if files_dirty(): | ||||
build(no_compress=True) | build(no_compress=True) | ||||
time.sleep(3) | time.sleep(3) | ||||
def make_asset_dirs(make_copy=False): | def make_asset_dirs(make_copy=False): | ||||
assets_path = os.path.join(frappe.local.sites_path, "assets") | assets_path = os.path.join(frappe.local.sites_path, "assets") | ||||
for dir_path in [ | for dir_path in [ | ||||
os.path.join(assets_path, 'js'), | |||||
os.path.join(assets_path, 'js'), | |||||
os.path.join(assets_path, 'css')]: | os.path.join(assets_path, 'css')]: | ||||
if not os.path.exists(dir_path): | if not os.path.exists(dir_path): | ||||
os.makedirs(dir_path) | os.makedirs(dir_path) | ||||
# symlink app/public > assets/app | # symlink app/public > assets/app | ||||
for app_name in frappe.get_all_apps(True): | for app_name in frappe.get_all_apps(True): | ||||
pymodule = frappe.get_module(app_name) | pymodule = frappe.get_module(app_name) | ||||
@@ -49,11 +49,11 @@ def make_asset_dirs(make_copy=False): | |||||
else: | else: | ||||
os.symlink(os.path.abspath(source), target) | 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") | assets_path = os.path.join(frappe.local.sites_path, "assets") | ||||
for target, sources in get_build_maps().iteritems(): | 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) | shutil.copy(os.path.join(os.path.dirname(os.path.abspath(frappe.__file__)), 'data', 'languages.txt'), frappe.local.sites_path) | ||||
# reset_app_html() | # reset_app_html() | ||||
@@ -79,39 +79,52 @@ def get_build_maps(): | |||||
else: | else: | ||||
s = os.path.join(app_path, source) | s = os.path.join(app_path, source) | ||||
source_paths.append(s) | source_paths.append(s) | ||||
build_maps[target] = source_paths | build_maps[target] = source_paths | ||||
except Exception, e: | except Exception, e: | ||||
print path | print path | ||||
raise | raise | ||||
return build_maps | return build_maps | ||||
timestamps = {} | timestamps = {} | ||||
def pack(target, sources, no_compress): | |||||
def pack(target, sources, no_compress, verbose): | |||||
from cStringIO import StringIO | from cStringIO import StringIO | ||||
outtype, outtxt = target.split(".")[-1], '' | outtype, outtxt = target.split(".")[-1], '' | ||||
jsm = JavascriptMinify() | jsm = JavascriptMinify() | ||||
for f in sources: | for f in sources: | ||||
suffix = None | suffix = None | ||||
if ':' in f: f, suffix = f.split(':') | 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) | timestamps[f] = os.path.getmtime(f) | ||||
try: | try: | ||||
with open(f, 'r') as sourcefile: | |||||
with open(f, 'r') as sourcefile: | |||||
data = unicode(sourcefile.read(), 'utf-8', errors='ignore') | 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() | tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO() | ||||
jsm.minify(tmpin, tmpout) | 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: | else: | ||||
outtxt += ('\n/*\n *\t%s\n */' % f) | outtxt += ('\n/*\n *\t%s\n */' % f) | ||||
outtxt += '\n' + data + '\n' | outtxt += '\n' + data + '\n' | ||||
except Exception, e: | except Exception, e: | ||||
print "--Error in:" + f + "--" | print "--Error in:" + f + "--" | ||||
print frappe.get_traceback() | print frappe.get_traceback() | ||||
@@ -119,10 +132,10 @@ def pack(target, sources, no_compress): | |||||
if not no_compress and outtype == 'css': | if not no_compress and outtype == 'css': | ||||
pass | pass | ||||
#outtxt = cssmin(outtxt) | #outtxt = cssmin(outtxt) | ||||
with open(target, 'w') as f: | with open(target, 'w') as f: | ||||
f.write(outtxt.encode("utf-8")) | f.write(outtxt.encode("utf-8")) | ||||
print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))) | print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))) | ||||
def files_dirty(): | def files_dirty(): | ||||
@@ -135,4 +148,4 @@ def files_dirty(): | |||||
return True | return True | ||||
else: | else: | ||||
return False | return False | ||||
@@ -7,6 +7,7 @@ from __future__ import unicode_literals | |||||
import os | import os | ||||
import subprocess | import subprocess | ||||
import frappe | import frappe | ||||
from frappe.utils import cint | |||||
site_arg_optional = ['serve', 'build', 'watch', 'celery', 'resize_images'] | site_arg_optional = ['serve', 'build', 'watch', 'celery', 'resize_images'] | ||||
@@ -35,7 +36,10 @@ def main(): | |||||
args = parsed_args.copy() | args = parsed_args.copy() | ||||
args["site"] = site | args["site"] = site | ||||
frappe.init(site, sites_path=sites_path) | 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: | else: | ||||
site = get_site(parsed_args) | site = get_site(parsed_args) | ||||
if fn not in site_arg_optional and not site: | 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") | help="Make a new application with boilerplate") | ||||
parser.add_argument("--install", metavar="DB-NAME", nargs=1, | parser.add_argument("--install", metavar="DB-NAME", nargs=1, | ||||
help="Install a new db") | 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, | parser.add_argument("--sites_path", metavar="SITES_PATH", nargs=1, | ||||
help="path to directory with sites") | help="path to directory with sites") | ||||
parser.add_argument("--install_app", metavar="APP-NAME", nargs=1, | parser.add_argument("--install_app", metavar="APP-NAME", nargs=1, | ||||
help="Install a new app") | help="Install a new app") | ||||
parser.add_argument("--add_to_installed_apps", metavar="APP-NAME", nargs="*", | parser.add_argument("--add_to_installed_apps", metavar="APP-NAME", nargs="*", | ||||
help="Add these app(s) to Installed Apps") | 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", | parser.add_argument("--reinstall", default=False, action="store_true", | ||||
help="Install a fresh app in db_name specified in conf.py") | help="Install a fresh app in db_name specified in conf.py") | ||||
parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2, | parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2, | ||||
help="Restore from an sql file") | 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="+", | parser.add_argument("--add_system_manager", nargs="+", | ||||
metavar=("EMAIL", "[FIRST-NAME] [LAST-NAME]"), help="Add a user with all roles") | 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"), | parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"), | ||||
help="Create new conf.py file") | help="Create new conf.py file") | ||||
parser.add_argument("--make_custom_server_script", nargs=1, metavar="DOCTYPE", | 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") | help="Set administrator password") | ||||
parser.add_argument("--request", metavar='URL-ARGS', nargs=1, help="Run request as admin") | 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") | parser.add_argument("--mysql", action="store_true", help="get mysql shell for a site") | ||||
@@ -224,8 +232,8 @@ def setup_utilities(parser): | |||||
# clear | # clear | ||||
parser.add_argument("--clear_web", default=False, action="store_true", | parser.add_argument("--clear_web", default=False, action="store_true", | ||||
help="Clear website cache") | 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", | parser.add_argument("--sync_statics", default=False, action="store_true", | ||||
help="Sync files from templates/statics to Web Pages") | help="Sync files from templates/statics to Web Pages") | ||||
parser.add_argument("--clear_cache", default=False, action="store_true", | 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, | parser.add_argument("--run_scheduler_event", nargs=1, | ||||
metavar="all | daily | weekly | monthly", | metavar="all | daily | weekly | monthly", | ||||
help="Run a scheduler event") | 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 | # replace | ||||
parser.add_argument("--replace", nargs=3, | parser.add_argument("--replace", nargs=3, | ||||
@@ -280,21 +293,52 @@ def use(sites_path): | |||||
sitefile.write(frappe.local.site) | sitefile.write(frappe.local.site) | ||||
# install | # 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 | from frappe.installer import install_db, install_app, make_site_dirs | ||||
import frappe.utils.scheduler | |||||
verbose = not quiet | 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, | 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) | admin_password = admin_password, verbose=verbose, force=force, site_config=site_config, reinstall=reinstall) | ||||
make_site_dirs() | make_site_dirs() | ||||
install_app("frappe", verbose=verbose, set_as_patched=not source_sql) | install_app("frappe", verbose=verbose, set_as_patched=not source_sql) | ||||
if frappe.conf.get("install_apps"): | if frappe.conf.get("install_apps"): | ||||
for app in frappe.conf.install_apps: | for app in frappe.conf.install_apps: | ||||
install_app(app, verbose=verbose, set_as_patched=not source_sql) | 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() | 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 | @cmd | ||||
def install_app(app_name, quiet=False): | def install_app(app_name, quiet=False): | ||||
verbose = not quiet | verbose = not quiet | ||||
@@ -310,7 +354,7 @@ def add_to_installed_apps(*apps): | |||||
all_apps = frappe.get_all_apps(with_frappe=True) | all_apps = frappe.get_all_apps(with_frappe=True) | ||||
for each in apps: | for each in apps: | ||||
if each in all_apps: | if each in all_apps: | ||||
add_to_installed_apps(each, rebuild_sitemap=False) | |||||
add_to_installed_apps(each, rebuild_website=False) | |||||
frappe.destroy() | frappe.destroy() | ||||
@cmd | @cmd | ||||
@@ -318,18 +362,27 @@ def reinstall(quiet=False): | |||||
verbose = not quiet | verbose = not quiet | ||||
try: | try: | ||||
frappe.connect() | frappe.connect() | ||||
installed = frappe.get_installed_apps() | |||||
frappe.clear_cache() | frappe.clear_cache() | ||||
except: | except: | ||||
pass | |||||
installed = [] | |||||
finally: | finally: | ||||
frappe.db.close() | 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 | @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 | @cmd | ||||
def add_system_manager(email, first_name=None, last_name=None): | 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()) | subprocess.check_output("killall -HUP gunicorn".split()) | ||||
@cmd | @cmd | ||||
def latest(rebuild_website_config=True, quiet=False): | |||||
def latest(rebuild_website=True, quiet=False): | |||||
import frappe.modules.patch_handler | import frappe.modules.patch_handler | ||||
import frappe.model.sync | import frappe.model.sync | ||||
from frappe.website import rebuild_config | |||||
from frappe.utils.fixtures import sync_fixtures | from frappe.utils.fixtures import sync_fixtures | ||||
import frappe.translate | import frappe.translate | ||||
from frappe.website import statics | |||||
from frappe.core.doctype.notification_count.notification_count import clear_notifications | |||||
verbose = not quiet | verbose = not quiet | ||||
@@ -370,16 +422,13 @@ def latest(rebuild_website_config=True, quiet=False): | |||||
frappe.modules.patch_handler.run_all() | frappe.modules.patch_handler.run_all() | ||||
# sync | # sync | ||||
frappe.model.sync.sync_all(verbose=verbose) | frappe.model.sync.sync_all(verbose=verbose) | ||||
frappe.translate.clear_cache() | |||||
sync_fixtures() | 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: | finally: | ||||
frappe.destroy() | frappe.destroy() | ||||
@@ -419,10 +468,10 @@ def reload_doc(module, doctype, docname, force=False): | |||||
frappe.destroy() | frappe.destroy() | ||||
@cmd | @cmd | ||||
def build(make_copy=False): | |||||
def build(make_copy=False, verbose=False): | |||||
import frappe.build | import frappe.build | ||||
import frappe | import frappe | ||||
frappe.build.bundle(False, make_copy=make_copy) | |||||
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose) | |||||
@cmd | @cmd | ||||
def watch(): | def watch(): | ||||
@@ -487,12 +536,19 @@ def make_custom_server_script(doctype): | |||||
make_custom_server_script_file(doctype) | make_custom_server_script_file(doctype) | ||||
frappe.destroy() | frappe.destroy() | ||||
@cmd | |||||
def init_list(doctype): | |||||
import frappe.core.doctype.doctype.doctype | |||||
frappe.core.doctype.doctype.doctype.init_list(doctype) | |||||
# clear | # clear | ||||
@cmd | @cmd | ||||
def clear_cache(): | def clear_cache(): | ||||
import frappe.sessions | import frappe.sessions | ||||
from frappe.core.doctype.notification_count.notification_count import clear_notifications | |||||
frappe.connect() | frappe.connect() | ||||
frappe.clear_cache() | frappe.clear_cache() | ||||
clear_notifications() | |||||
frappe.destroy() | frappe.destroy() | ||||
@cmd | @cmd | ||||
@@ -511,17 +567,19 @@ def clear_all_sessions(): | |||||
frappe.destroy() | frappe.destroy() | ||||
@cmd | @cmd | ||||
def build_sitemap(): | |||||
from frappe.website import rebuild_config | |||||
def build_website(verbose=False): | |||||
from frappe.website import render, statics | |||||
frappe.connect() | frappe.connect() | ||||
rebuild_config() | |||||
render.clear_cache() | |||||
statics.sync(verbose=verbose).start() | |||||
frappe.db.commit() | |||||
frappe.destroy() | frappe.destroy() | ||||
@cmd | @cmd | ||||
def sync_statics(): | |||||
def sync_statics(force=False): | |||||
from frappe.website import statics | from frappe.website import statics | ||||
frappe.connect() | frappe.connect() | ||||
statics.sync_statics() | |||||
statics.sync_statics(rebuild = force) | |||||
frappe.db.commit() | frappe.db.commit() | ||||
frappe.destroy() | frappe.destroy() | ||||
@@ -558,6 +616,24 @@ def run_scheduler_event(event, force=False): | |||||
frappe.utils.scheduler.trigger(frappe.local.site, event, now=force) | frappe.utils.scheduler.trigger(frappe.local.site, event, now=force) | ||||
frappe.destroy() | 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 | # replace | ||||
@cmd | @cmd | ||||
def replace(search_regex, replacement, extn, force=False): | def replace(search_regex, replacement, extn, force=False): | ||||
@@ -665,8 +741,13 @@ def checkout(branch): | |||||
git(("checkout", branch)) | git(("checkout", branch)) | ||||
@cmd | @cmd | ||||
def set_admin_password(admin_password): | |||||
def set_admin_password(admin_password=None): | |||||
import frappe | import frappe | ||||
import getpass | |||||
while not admin_password: | |||||
admin_password = getpass.getpass("Administrator's password: ") | |||||
frappe.connect() | frappe.connect() | ||||
frappe.db.sql("""update __Auth set `password`=password(%s) | frappe.db.sql("""update __Auth set `password`=password(%s) | ||||
where user='Administrator'""", (admin_password,)) | where user='Administrator'""", (admin_password,)) | ||||
@@ -706,7 +787,7 @@ def smtp_debug_server(): | |||||
os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"]) | os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"]) | ||||
@cmd | @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 | import frappe.test_runner | ||||
from frappe.utils import sel | from frappe.utils import sel | ||||
@@ -715,7 +796,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driv | |||||
ret = 1 | ret = 1 | ||||
try: | try: | ||||
ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose, | 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: | if len(ret.failures) == 0 and len(ret.errors) == 0: | ||||
ret = 0 | ret = 0 | ||||
finally: | finally: | ||||
@@ -42,7 +42,11 @@ def set_value(doctype, name, fieldname, value): | |||||
child.set(fieldname, value) | child.set(fieldname, value) | ||||
else: | else: | ||||
doc = frappe.get_doc(doctype, name) | 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() | doc.save() | ||||
@@ -88,7 +92,7 @@ def submit(doclist): | |||||
doclistobj = frappe.get_doc(doclist) | doclistobj = frappe.get_doc(doclist) | ||||
doclistobj.submit() | doclistobj.submit() | ||||
return doclist.as_dict() | |||||
return doclistobj.as_dict() | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def cancel(doctype, name): | def cancel(doctype, name): | ||||
@@ -108,8 +112,9 @@ def set_default(key, value, parent=None): | |||||
frappe.clear_cache(user=frappe.session.user) | frappe.clear_cache(user=frappe.session.user) | ||||
@frappe.whitelist() | @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": | if doc["doctype"]=="Property Setter" and doc["property"]=="width": | ||||
frappe.get_doc(doc).insert(ignore_permissions = True) | frappe.get_doc(doc).insert(ignore_permissions = True) | ||||
@@ -139,6 +144,8 @@ def has_permission(doctype, docname, perm_type="read"): | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def get_js(src): | def get_js(src): | ||||
if src[0]=="/": | |||||
src = src[1:] | |||||
contentpath = os.path.join(frappe.local.sites_path, src) | contentpath = os.path.join(frappe.local.sites_path, src) | ||||
with open(contentpath, "r") as srcfile: | with open(contentpath, "r") as srcfile: | ||||
code = frappe.utils.cstr(srcfile.read()) | code = frappe.utils.cstr(srcfile.read()) | ||||
@@ -20,17 +20,25 @@ def get_data(): | |||||
{ | { | ||||
"type": "page", | "type": "page", | ||||
"name": "permission-manager", | "name": "permission-manager", | ||||
"label": "Permission Manager", | |||||
"label": _("Role Permissions Manager"), | |||||
"icon": "icon-lock", | "icon": "icon-lock", | ||||
"description": _("Set Permissions on Document Types and Roles") | "description": _("Set Permissions on Document Types and Roles") | ||||
}, | }, | ||||
{ | { | ||||
"type": "page", | "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", | "name": "Outgoing Email Settings", | ||||
"description": _("Set outgoing mail server.") | "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"), | "label": _("Printing and Branding"), | ||||
"icon": "icon-print", | "icon": "icon-print", | ||||
"items": [ | "items": [ | ||||
{ | |||||
"type": "doctype", | |||||
"name": "Print Settings", | |||||
"description": _("Set default format, page size, print style etc.") | |||||
}, | |||||
{ | { | ||||
"type": "doctype", | "type": "doctype", | ||||
"name": "Print Format", | "name": "Print Format", | ||||
@@ -168,11 +191,6 @@ def get_data(): | |||||
"description": _("Send download link of a recent backup to System Managers"), | "description": _("Send download link of a recent backup to System Managers"), | ||||
"hide_count": True | "hide_count": True | ||||
}, | }, | ||||
{ | |||||
"type": "doctype", | |||||
"name": "Social Login Keys", | |||||
"description": _("Enter keys to enable login via Facebook, Google, GitHub."), | |||||
}, | |||||
{ | { | ||||
"type": "doctype", | "type": "doctype", | ||||
"name": "Backup Manager", | "name": "Backup Manager", | ||||
@@ -16,6 +16,11 @@ def get_data(): | |||||
"name": "Blog Post", | "name": "Blog Post", | ||||
"description": _("Single Post (article)."), | "description": _("Single Post (article)."), | ||||
}, | }, | ||||
{ | |||||
"type": "doctype", | |||||
"name": "Web Form", | |||||
"description": _("User editable form on Website."), | |||||
}, | |||||
{ | { | ||||
"type": "doctype", | "type": "doctype", | ||||
"name": "Blogger", | "name": "Blogger", | ||||
@@ -47,13 +52,6 @@ def get_data(): | |||||
"name": "Website Settings", | "name": "Website Settings", | ||||
"description": _("Setup of top navigation bar, footer and logo."), | "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", | "type": "doctype", | ||||
"name": "Style Settings", | "name": "Style Settings", | ||||
@@ -89,6 +87,11 @@ def get_data(): | |||||
"name": "Website Page Permission", | "name": "Website Page Permission", | ||||
"description": _("Define read, write, admin permissions for a Website Page."), | "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.", | "description": "Bulk Email records.", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
@@ -8,30 +9,35 @@ | |||||
{ | { | ||||
"fieldname": "sender", | "fieldname": "sender", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Sender", | "label": "Sender", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "recipient", | "fieldname": "recipient", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Recipient", | "label": "Recipient", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "message", | "fieldname": "message", | ||||
"fieldtype": "Long Text", | "fieldtype": "Long Text", | ||||
"in_list_view": 1, | |||||
"label": "Message", | "label": "Message", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "status", | "fieldname": "status", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Status", | "label": "Status", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "error", | "fieldname": "error", | ||||
"fieldtype": "Text", | "fieldtype": "Text", | ||||
"in_list_view": 1, | |||||
"label": "Error", | "label": "Error", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
@@ -56,7 +62,7 @@ | |||||
"icon": "icon-envelope", | "icon": "icon-envelope", | ||||
"idx": 1, | "idx": 1, | ||||
"in_create": 1, | "in_create": 1, | ||||
"modified": "2014-02-12 21:11:05.000000", | |||||
"modified": "2014-06-03 02:22:18.860832", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Bulk Email", | "name": "Bulk Email", | ||||
@@ -67,8 +73,9 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print": 1, | "print": 1, | ||||
"read": 1, | "read": 1, | ||||
"report": 1, | |||||
"role": "System Manager" | "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, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "comment", | "fieldname": "comment", | ||||
"fieldtype": "Text", | "fieldtype": "Text", | ||||
"in_list_view": 1, | |||||
"label": "Comment", | "label": "Comment", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment", | "oldfieldname": "comment", | ||||
"oldfieldtype": "Text", | "oldfieldtype": "Text", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"reqd": 1, | |||||
"search_index": 0 | "search_index": 0 | ||||
}, | }, | ||||
{ | |||||
"fieldname": "comment_type", | |||||
"fieldtype": "Data", | |||||
"label": "Comment Type", | |||||
"permlevel": 0 | |||||
}, | |||||
{ | { | ||||
"fieldname": "comment_by", | "fieldname": "comment_by", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Comment By", | "label": "Comment By", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment_by", | "oldfieldname": "comment_by", | ||||
@@ -27,6 +36,7 @@ | |||||
{ | { | ||||
"fieldname": "comment_by_fullname", | "fieldname": "comment_by_fullname", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Comment By Fullname", | "label": "Comment By Fullname", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment_by_fullname", | "oldfieldname": "comment_by_fullname", | ||||
@@ -37,6 +47,7 @@ | |||||
{ | { | ||||
"fieldname": "comment_date", | "fieldname": "comment_date", | ||||
"fieldtype": "Date", | "fieldtype": "Date", | ||||
"in_list_view": 1, | |||||
"label": "Comment Date", | "label": "Comment Date", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment_date", | "oldfieldname": "comment_date", | ||||
@@ -47,6 +58,7 @@ | |||||
{ | { | ||||
"fieldname": "comment_time", | "fieldname": "comment_time", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Comment Time", | "label": "Comment Time", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment_time", | "oldfieldname": "comment_time", | ||||
@@ -94,7 +106,7 @@ | |||||
"icon": "icon-comments", | "icon": "icon-comments", | ||||
"idx": 1, | "idx": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"modified": "2014-01-24 13:00:20.000000", | |||||
"modified": "2014-08-22 05:24:28.072749", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Comment", | "name": "Comment", | ||||
@@ -19,7 +19,7 @@ class Comment(Document): | |||||
self.update_comment_in_doc() | self.update_comment_in_doc() | ||||
def update_comment_in_doc(self): | 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: | try: | ||||
_comments = self.get_comments_from_parent() | _comments = self.get_comments_from_parent() | ||||
updated = False | updated = False | ||||
@@ -59,14 +59,14 @@ class Comment(Document): | |||||
frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (self.comment_doctype, | frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (self.comment_doctype, | ||||
"%s", "%s"), (json.dumps(_comments), self.comment_docname)) | "%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): | def on_trash(self): | ||||
if (self.comment_type or "Comment") != "Comment": | |||||
frappe.only_for("System Manager") | |||||
_comments = self.get_comments_from_parent() | _comments = self.get_comments_from_parent() | ||||
for c in _comments: | for c in _comments: | ||||
if c.get("name")==self.name: | 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:", | "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", | "description": "Keep a track of all communications", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
@@ -122,6 +121,7 @@ | |||||
"default": "__user", | "default": "__user", | ||||
"fieldname": "user", | "fieldname": "user", | ||||
"fieldtype": "Link", | "fieldtype": "Link", | ||||
"ignore_user_permissions": 1, | |||||
"label": "User", | "label": "User", | ||||
"options": "User", | "options": "User", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -154,7 +154,7 @@ | |||||
"idx": 1, | "idx": 1, | ||||
"in_dialog": 0, | "in_dialog": 0, | ||||
"issingle": 0, | "issingle": 0, | ||||
"modified": "2014-01-24 13:01:25.000000", | |||||
"modified": "2014-08-14 09:39:23.219125", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Communication", | "name": "Communication", | ||||
@@ -162,7 +162,7 @@ | |||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"amend": 0, | "amend": 0, | ||||
"cancel": 0, | |||||
"apply_user_permissions": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -176,7 +176,6 @@ | |||||
}, | }, | ||||
{ | { | ||||
"amend": 0, | "amend": 0, | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -190,7 +189,7 @@ | |||||
}, | }, | ||||
{ | { | ||||
"amend": 0, | "amend": 0, | ||||
"cancel": 0, | |||||
"apply_user_permissions": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -203,7 +202,6 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -216,7 +214,6 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -16,14 +16,17 @@ from frappe import _ | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class Communication(Document): | class Communication(Document): | ||||
def validate(self): | |||||
if not self.parentfield: | |||||
self.parentfield = "communications" | |||||
def get_parent_doc(self): | def get_parent_doc(self): | ||||
return frappe.get_doc(self.parenttype, self.parent) | return frappe.get_doc(self.parenttype, self.parent) | ||||
def update_parent(self): | def update_parent(self): | ||||
"""update status of parent Lead or Contact based on who is replying""" | """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): | def on_update(self): | ||||
self.update_parent() | self.update_parent() | ||||
@@ -31,7 +34,7 @@ class Communication(Document): | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | ||||
sender=None, recipients=None, communication_medium="Email", send_email=False, | 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): | 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( | 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, | _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, | 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) | date=date) | ||||
def _make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | def _make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | ||||
sender=None, recipients=None, communication_medium="Email", send_email=False, | 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 | # add to Communication | ||||
sent_via = None | sent_via = None | ||||
@@ -86,7 +89,7 @@ def _make(doctype=None, name=None, content=None, subject=None, sent_or_received | |||||
if send_email: | if send_email: | ||||
d = comm | 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() | @frappe.whitelist() | ||||
def get_customer_supplier(args=None): | def get_customer_supplier(args=None): | ||||
@@ -107,9 +110,10 @@ def get_customer_supplier(args=None): | |||||
} | } | ||||
return {} | 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 | footer = None | ||||
if sent_via: | if sent_via: | ||||
if hasattr(sent_via, "get_sender"): | if hasattr(sent_via, "get_sender"): | ||||
d.sender = sent_via.get_sender(d) or d.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"): | if hasattr(sent_via, "get_content"): | ||||
d.content = sent_via.get_content(d) | 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, | 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: | if send_me_a_copy: | ||||
mail.cc.append(frappe.db.get_value("User", frappe.session.user, "email")) | 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): | for a in json.loads(attachments): | ||||
try: | try: | ||||
@@ -142,10 +141,31 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s | |||||
send(mail) | 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): | def set_portal_link(sent_via, comm): | ||||
"""set portal link in footer""" | """set portal link in footer""" | ||||
footer = None | |||||
footer = "" | |||||
if is_signup_enabled() and hasattr(sent_via, "get_portal_page"): | if is_signup_enabled() and hasattr(sent_via, "get_portal_page"): | ||||
portal_page = 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 | sent_via.get("contact_email")) in comm.recipients | ||||
if is_valid_recipient: | if is_valid_recipient: | ||||
url = "%s/%s?name=%s" % (get_url(), portal_page, urllib.quote(sent_via.name)) | 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 | 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) { | 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", | "description": "Adds a custom field to a DocType", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
@@ -57,11 +57,21 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "fieldtype", | "oldfieldname": "fieldtype", | ||||
"oldfieldtype": "Select", | "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, | "permlevel": 0, | ||||
"reqd": 1, | "reqd": 1, | ||||
"search_index": 0 | "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", | "fieldname": "options_help", | ||||
"fieldtype": "HTML", | "fieldtype": "HTML", | ||||
@@ -143,9 +153,9 @@ | |||||
}, | }, | ||||
{ | { | ||||
"depends_on": "eval:doc.fieldtype===\"Link\"", | "depends_on": "eval:doc.fieldtype===\"Link\"", | ||||
"fieldname": "ignore_restrictions", | |||||
"fieldname": "ignore_user_permissions", | |||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"label": "Ignore Restrictions", | |||||
"label": "Ignore User Permissions", | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
@@ -257,7 +267,7 @@ | |||||
], | ], | ||||
"icon": "icon-glass", | "icon": "icon-glass", | ||||
"idx": 1, | "idx": 1, | ||||
"modified": "2014-01-20 17:48:31.000000", | |||||
"modified": "2014-09-05 07:41:13.076820", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Custom Field", | "name": "Custom Field", | ||||
@@ -33,10 +33,10 @@ class CustomField(Document): | |||||
# validate field | # validate field | ||||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype | from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype | ||||
validate_fields_for_doctype(self.dt) | |||||
frappe.clear_cache(doctype=self.dt) | frappe.clear_cache(doctype=self.dt) | ||||
validate_fields_for_doctype(self.dt) | |||||
# create property setter to emulate insert after | # create property setter to emulate insert after | ||||
self.create_property_setter() | self.create_property_setter() | ||||
@@ -78,22 +78,27 @@ class CustomField(Document): | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def get_fields_label(doctype=None): | 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): | def create_custom_field_if_values_exist(doctype, df): | ||||
df = frappe._dict(df) | df = frappe._dict(df) | ||||
if df.fieldname in frappe.db.get_table_columns(doctype) and \ | if df.fieldname in frappe.db.get_table_columns(doctype) and \ | ||||
frappe.db.sql("""select count(*) from `tab{doctype}` | 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.####", | "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", | "description": "Adds a custom script (client or server) to a DocType", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
@@ -8,6 +8,7 @@ | |||||
{ | { | ||||
"fieldname": "dt", | "fieldname": "dt", | ||||
"fieldtype": "Link", | "fieldtype": "Link", | ||||
"in_list_view": 1, | |||||
"label": "DocType", | "label": "DocType", | ||||
"oldfieldname": "dt", | "oldfieldname": "dt", | ||||
"oldfieldtype": "Link", | "oldfieldtype": "Link", | ||||
@@ -19,6 +20,7 @@ | |||||
"fieldname": "script_type", | "fieldname": "script_type", | ||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"hidden": 1, | "hidden": 1, | ||||
"in_list_view": 1, | |||||
"label": "Script Type", | "label": "Script Type", | ||||
"oldfieldname": "script_type", | "oldfieldname": "script_type", | ||||
"oldfieldtype": "Select", | "oldfieldtype": "Select", | ||||
@@ -29,16 +31,24 @@ | |||||
{ | { | ||||
"fieldname": "script", | "fieldname": "script", | ||||
"fieldtype": "Code", | "fieldtype": "Code", | ||||
"in_list_view": 1, | |||||
"label": "Script", | "label": "Script", | ||||
"oldfieldname": "script", | "oldfieldname": "script", | ||||
"oldfieldtype": "Code", | "oldfieldtype": "Code", | ||||
"options": "Script", | "options": "Script", | ||||
"permlevel": 0 | "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", | "icon": "icon-glass", | ||||
"idx": 1, | "idx": 1, | ||||
"modified": "2014-01-20 17:48:31.000000", | |||||
"modified": "2014-06-19 06:55:02.522204", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Custom Script", | "name": "Custom Script", | ||||
@@ -58,11 +58,11 @@ frappe.ui.form.on("Customize Form", "refresh", function(frm) { | |||||
frm.add_custom_button('Refresh Form', function() { | frm.add_custom_button('Refresh Form', function() { | ||||
frm.script_manager.trigger("doc_type"); | frm.script_manager.trigger("doc_type"); | ||||
}, "icon-refresh"); | |||||
}, "icon-refresh", "btn-default"); | |||||
frm.add_custom_button('Reset to defaults', function() { | frm.add_custom_button('Reset to defaults', function() { | ||||
frappe.customize_form.confirm(__('Remove all customizations?'), frm); | frappe.customize_form.confirm(__('Remove all customizations?'), frm); | ||||
}, "icon-eraser"); | |||||
}, "icon-eraser", "btn-default"); | |||||
} | } | ||||
// if(!frm.doc.doc_type) { | // if(!frm.doc.doc_type) { | ||||
@@ -73,8 +73,10 @@ frappe.ui.form.on("Customize Form", "refresh", function(frm) { | |||||
// } | // } | ||||
if(frappe.route_options) { | 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({ | var d = new frappe.ui.Dialog({ | ||||
title: 'Reset To Defaults', | 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; | frappe.customize_form.confirm.dialog = d; | ||||
d.show(); | d.show(); | ||||
} | } | ||||
@@ -159,7 +146,7 @@ frappe.customize_form.add_fields_help = function(frm) { | |||||
<td><b>Perm Level</b></td>\ | <td><b>Perm Level</b></td>\ | ||||
<td>\ | <td>\ | ||||
Assign a permission level to the field.<br />\ | 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>\ | </td>\ | ||||
</tr>\ | </tr>\ | ||||
<tr>\ | <tr>\ | ||||
@@ -63,22 +63,6 @@ | |||||
"fieldtype": "Column Break", | "fieldtype": "Column Break", | ||||
"permlevel": 0 | "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", | "fieldname": "allow_copy", | ||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
@@ -88,16 +72,7 @@ | |||||
"search_index": 0 | "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", | "fieldname": "max_attachments", | ||||
"fieldtype": "Int", | "fieldtype": "Int", | ||||
"label": "Max Attachments", | "label": "Max Attachments", | ||||
@@ -126,7 +101,7 @@ | |||||
"icon": "icon-glass", | "icon": "icon-glass", | ||||
"idx": 1, | "idx": 1, | ||||
"issingle": 1, | "issingle": 1, | ||||
"modified": "2014-05-08 09:27:44.167026", | |||||
"modified": "2014-08-22 05:42:45.083260", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Customize Form", | "name": "Customize Form", | ||||
@@ -18,7 +18,6 @@ class CustomizeForm(Document): | |||||
'sort_order': 'Data', | 'sort_order': 'Data', | ||||
'default_print_format': 'Data', | 'default_print_format': 'Data', | ||||
'read_only_onload': 'Check', | 'read_only_onload': 'Check', | ||||
'allow_attach': 'Check', | |||||
'allow_copy': 'Check', | 'allow_copy': 'Check', | ||||
'max_attachments': 'Int' | 'max_attachments': 'Int' | ||||
} | } | ||||
@@ -32,7 +31,7 @@ class CustomizeForm(Document): | |||||
'width': 'Data', | 'width': 'Data', | ||||
'print_width': 'Data', | 'print_width': 'Data', | ||||
'reqd': 'Check', | 'reqd': 'Check', | ||||
'ignore_restrictions': 'Check', | |||||
'ignore_user_permissions': 'Check', | |||||
'in_filter': 'Check', | 'in_filter': 'Check', | ||||
'in_list_view': 'Check', | 'in_list_view': 'Check', | ||||
'hidden': 'Check', | 'hidden': 'Check', | ||||
@@ -41,11 +40,12 @@ class CustomizeForm(Document): | |||||
'allow_on_submit': 'Check', | 'allow_on_submit': 'Check', | ||||
'depends_on': 'Data', | 'depends_on': 'Data', | ||||
'description': 'Text', | '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): | def on_update(self): | ||||
frappe.db.sql("delete from tabSingles where doctype='Customize Form'") | frappe.db.sql("delete from tabSingles where doctype='Customize Form'") | ||||
@@ -187,6 +187,7 @@ class CustomizeForm(Document): | |||||
return | return | ||||
# create a new property setter | # create a new property setter | ||||
# ignore validation becuase it will be done at end | |||||
frappe.make_property_setter({ | frappe.make_property_setter({ | ||||
"doctype": self.doc_type, | "doctype": self.doc_type, | ||||
"doctype_or_field": "DocField" if fieldname else "DocType", | "doctype_or_field": "DocField" if fieldname else "DocType", | ||||
@@ -194,7 +195,7 @@ class CustomizeForm(Document): | |||||
"property": property, | "property": property, | ||||
"value": value, | "value": value, | ||||
"property_type": property_type | "property_type": property_type | ||||
}) | |||||
}, ignore_validate=True) | |||||
def delete_existing_property_setter(self, property, fieldname=None): | def delete_existing_property_setter(self, property, fieldname=None): | ||||
# first delete existing property setter | # first delete existing property setter | ||||
@@ -204,14 +205,14 @@ class CustomizeForm(Document): | |||||
if existing_property_setter: | if existing_property_setter: | ||||
frappe.delete_doc("Property Setter", 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! | # check if there is any need to make property setter! | ||||
if fieldname: | if fieldname: | ||||
property_value = frappe.db.get_value("DocField", {"parent": self.doc_type, | property_value = frappe.db.get_value("DocField", {"parent": self.doc_type, | ||||
"fieldname": fieldname}, property) | |||||
"fieldname": fieldname}, property_name) | |||||
else: | else: | ||||
try: | 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: | except Exception, e: | ||||
if e.args[0]==1054: | if e.args[0]==1054: | ||||
property_value = None | property_value = None | ||||
@@ -221,18 +222,19 @@ class CustomizeForm(Document): | |||||
return property_value | return property_value | ||||
def validate_fieldtype_change(self, df, old_value, new_value): | def validate_fieldtype_change(self, df, old_value, new_value): | ||||
allowed = False | |||||
for allowed_changes in self.allowed_fieldtype_change: | 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): | def reset_to_defaults(self): | ||||
if not self.doc_type: | if not self.doc_type: | ||||
return | 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) | frappe.clear_cache(doctype=self.doc_type) | ||||
self.fetch_to_customize() | self.fetch_to_customize() | ||||
@@ -47,7 +47,7 @@ class TestCustomizeForm(unittest.TestCase): | |||||
d = self.get_customize_form("User") | d = self.get_customize_form("User") | ||||
self.assertEquals(d.doc_type, "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")[-1].fieldname, "test_custom_field") | ||||
self.assertEquals(d.get("customize_form_fields", {"fieldname": "location"})[0].in_list_view, 1) | 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): | def test_save_customization_custom_field_property(self): | ||||
d = self.get_customize_form("User") | 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 = d.get("customize_form_fields", {"fieldname": "test_custom_field"})[0] | ||||
custom_field.reqd = 1 | custom_field.reqd = 1 | ||||
@@ -1,10 +1,17 @@ | |||||
{ | { | ||||
"allow_copy": 0, | "allow_copy": 0, | ||||
"autoname": "DLF.#####", | "autoname": "DLF.#####", | ||||
"creation": "2013-02-22 01:27:32.000000", | |||||
"creation": "2013-02-22 01:27:32", | |||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"fields": [ | "fields": [ | ||||
{ | |||||
"fieldname": "label_and_type", | |||||
"fieldtype": "Section Break", | |||||
"label": "Label and Type", | |||||
"permlevel": 0, | |||||
"precision": "" | |||||
}, | |||||
{ | { | ||||
"fieldname": "label", | "fieldname": "label", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
@@ -19,6 +26,7 @@ | |||||
"search_index": 1 | "search_index": 1 | ||||
}, | }, | ||||
{ | { | ||||
"default": "Data", | |||||
"fieldname": "fieldtype", | "fieldname": "fieldtype", | ||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"hidden": 0, | "hidden": 0, | ||||
@@ -26,7 +34,7 @@ | |||||
"label": "Type", | "label": "Type", | ||||
"oldfieldname": "fieldtype", | "oldfieldname": "fieldtype", | ||||
"oldfieldtype": "Select", | "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, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"reqd": 1, | "reqd": 1, | ||||
@@ -46,6 +54,42 @@ | |||||
"reqd": 0, | "reqd": 0, | ||||
"search_index": 1 | "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", | "description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", | ||||
"fieldname": "options", | "fieldname": "options", | ||||
@@ -60,6 +104,25 @@ | |||||
"reqd": 0, | "reqd": 0, | ||||
"search_index": 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", | "default": "0", | ||||
"fieldname": "permlevel", | "fieldname": "permlevel", | ||||
@@ -75,13 +138,12 @@ | |||||
"search_index": 0 | "search_index": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "width", | |||||
"fieldtype": "Data", | |||||
"fieldname": "hidden", | |||||
"fieldtype": "Check", | |||||
"hidden": 0, | "hidden": 0, | ||||
"in_list_view": 1, | |||||
"label": "Width", | |||||
"oldfieldname": "width", | |||||
"oldfieldtype": "Data", | |||||
"label": "Hidden", | |||||
"oldfieldname": "hidden", | |||||
"oldfieldtype": "Check", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_width": "50px", | "print_width": "50px", | ||||
@@ -90,111 +152,76 @@ | |||||
"width": "50px" | "width": "50px" | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "print_width", | |||||
"fieldtype": "Data", | |||||
"label": "Print Width", | |||||
"fieldname": "column_break_14", | |||||
"fieldtype": "Column Break", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_width": "50px", | |||||
"width": "50px" | |||||
"precision": "" | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "reqd", | |||||
"fieldname": "ignore_user_permissions", | |||||
"fieldtype": "Check", | "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 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "in_filter", | |||||
"fieldname": "allow_on_submit", | |||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"hidden": 0, | "hidden": 0, | ||||
"label": "In Filter", | |||||
"oldfieldname": "in_filter", | |||||
"label": "Allow on Submit", | |||||
"oldfieldname": "allow_on_submit", | |||||
"oldfieldtype": "Check", | "oldfieldtype": "Check", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 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", | "fieldtype": "Check", | ||||
"hidden": 0, | "hidden": 0, | ||||
"label": "Hidden", | |||||
"oldfieldname": "hidden", | |||||
"label": "Report Hide", | |||||
"oldfieldname": "report_hide", | |||||
"oldfieldtype": "Check", | "oldfieldtype": "Check", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 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, | "permlevel": 0, | ||||
"print_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0 | |||||
"precision": "" | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "report_hide", | |||||
"fieldtype": "Check", | |||||
"fieldname": "default", | |||||
"fieldtype": "Text", | |||||
"hidden": 0, | "hidden": 0, | ||||
"label": "Report Hide", | |||||
"oldfieldname": "report_hide", | |||||
"oldfieldtype": "Check", | |||||
"label": "Default", | |||||
"oldfieldname": "default", | |||||
"oldfieldtype": "Text", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"reqd": 0 | |||||
"reqd": 0, | |||||
"search_index": 0 | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "allow_on_submit", | |||||
"fieldname": "in_filter", | |||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"hidden": 0, | "hidden": 0, | ||||
"label": "Allow on Submit", | |||||
"oldfieldname": "allow_on_submit", | |||||
"label": "In Filter", | |||||
"oldfieldname": "in_filter", | |||||
"oldfieldtype": "Check", | "oldfieldtype": "Check", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 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, | "permlevel": 0, | ||||
"print_hide": 0, | |||||
"reqd": 0 | |||||
"precision": "" | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "description", | "fieldname": "description", | ||||
@@ -210,16 +237,40 @@ | |||||
"width": "300px" | "width": "300px" | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "default", | |||||
"fieldtype": "Text", | |||||
"fieldname": "print_hide", | |||||
"fieldtype": "Check", | |||||
"hidden": 0, | "hidden": 0, | ||||
"label": "Default", | |||||
"oldfieldname": "default", | |||||
"oldfieldtype": "Text", | |||||
"label": "Print Hide", | |||||
"oldfieldname": "print_hide", | |||||
"oldfieldtype": "Check", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
"search_index": 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, | "hide_heading": 0, | ||||
@@ -227,10 +278,11 @@ | |||||
"idx": 1, | "idx": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 1, | "istable": 1, | ||||
"modified": "2013-12-23 16:12:45.000000", | |||||
"modified": "2014-09-05 07:41:29.641454", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Customize Form Field", | "name": "Customize Form Field", | ||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [], | |||||
"read_only": 0 | "read_only": 0 | ||||
} | } |
@@ -34,7 +34,7 @@ | |||||
"label": "Type", | "label": "Type", | ||||
"oldfieldname": "fieldtype", | "oldfieldname": "fieldtype", | ||||
"oldfieldtype": "Select", | "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, | "permlevel": 0, | ||||
"reqd": 1, | "reqd": 1, | ||||
"search_index": 1 | "search_index": 1 | ||||
@@ -91,6 +91,16 @@ | |||||
"fieldtype": "Column Break", | "fieldtype": "Column Break", | ||||
"permlevel": 0 | "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", | "description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", | ||||
"fieldname": "options", | "fieldname": "options", | ||||
@@ -166,10 +176,10 @@ | |||||
"permlevel": 0 | "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", | "fieldtype": "Check", | ||||
"label": "Ignore Restrictions", | |||||
"label": "Ignore User Permissions", | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
@@ -304,7 +314,7 @@ | |||||
"in_dialog": 1, | "in_dialog": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 1, | "istable": 1, | ||||
"modified": "2014-04-24 15:56:23.561687", | |||||
"modified": "2014-09-05 07:41:05.956027", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "DocField", | "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, | "search_index": 0, | ||||
"width": "150px" | "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", | "fieldname": "column_break_2", | ||||
"fieldtype": "Column Break", | "fieldtype": "Column Break", | ||||
@@ -46,6 +53,15 @@ | |||||
"search_index": 0, | "search_index": 0, | ||||
"width": "40px" | "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", | "fieldname": "section_break_4", | ||||
"fieldtype": "Section Break", | "fieldtype": "Section Break", | ||||
@@ -171,15 +187,10 @@ | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "print", | |||||
"description": "This role update User Permissions for a user", | |||||
"fieldname": "set_user_permissions", | |||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"label": "Print", | |||||
"permlevel": 0 | |||||
}, | |||||
{ | |||||
"fieldname": "email", | |||||
"fieldtype": "Check", | |||||
"label": "Email", | |||||
"label": "Set User Permissions", | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
@@ -188,17 +199,15 @@ | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"description": "Only restricted users can access", | |||||
"fieldname": "restricted", | |||||
"fieldname": "print", | |||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"label": "Only Restricted Documents", | |||||
"label": "Print", | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"description": "This role can restrict users for accessing the record.", | |||||
"fieldname": "restrict", | |||||
"fieldname": "email", | |||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"label": "Can Restrict Others", | |||||
"label": "Email", | |||||
"permlevel": 0 | "permlevel": 0 | ||||
} | } | ||||
], | ], | ||||
@@ -207,7 +216,7 @@ | |||||
"idx": 1, | "idx": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 1, | "istable": 1, | ||||
"modified": "2014-05-01 05:20:48.162224", | |||||
"modified": "2014-08-26 01:43:31.499363", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "DocPerm", | "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 | # For license information, please see license.txt | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
@@ -6,4 +6,4 @@ import frappe | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class {classname}(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) { | cur_frm.cscript.refresh = function(doc, cdt, cdn) { | ||||
if(in_list(user_roles, 'System Manager') && !in_list(user_roles, 'Administrator')) { | if(in_list(user_roles, 'System Manager') && !in_list(user_roles, 'Administrator')) { | ||||
// make the document read-only | // make the document read-only | ||||
@@ -1,6 +1,5 @@ | |||||
{ | { | ||||
"allow_attach": 0, | |||||
"allow_copy": 0, | |||||
"allow_copy": 0, | |||||
"autoname": "Prompt", | "autoname": "Prompt", | ||||
"creation": "2013-02-18 13:36:19", | "creation": "2013-02-18 13:36:19", | ||||
"custom": 0, | "custom": 0, | ||||
@@ -276,18 +275,10 @@ | |||||
"oldfieldtype": "Check", | "oldfieldtype": "Check", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | |||||
"fieldname": "allow_attach", | |||||
"fieldtype": "Check", | |||||
"label": "Allow Attach", | |||||
"oldfieldname": "allow_attach", | |||||
"oldfieldtype": "Check", | |||||
"permlevel": 0 | |||||
}, | |||||
{ | { | ||||
"fieldname": "max_attachments", | "fieldname": "max_attachments", | ||||
"fieldtype": "Int", | "fieldtype": "Int", | ||||
"hidden": 1, | |||||
"hidden": 0, | |||||
"label": "Max Attachments", | "label": "Max Attachments", | ||||
"oldfieldname": "max_attachments", | "oldfieldname": "max_attachments", | ||||
"oldfieldtype": "Int", | "oldfieldtype": "Int", | ||||
@@ -345,7 +336,7 @@ | |||||
"idx": 1, | "idx": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"modified": "2014-05-08 09:23:56.952829", | |||||
"modified": "2014-08-22 05:33:03.067964", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "DocType", | "name": "DocType", | ||||
@@ -5,12 +5,18 @@ from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
from frappe import _ | from frappe import _ | ||||
import os | |||||
from frappe.utils import now, cint | from frappe.utils import now, cint | ||||
from frappe.model import no_value_fields | from frappe.model import no_value_fields | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.model.db_schema import type_map | 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): | class DocType(Document): | ||||
def validate(self): | def validate(self): | ||||
@@ -22,7 +28,7 @@ class DocType(Document): | |||||
self.validate_series() | self.validate_series() | ||||
self.scrub_field_names() | self.scrub_field_names() | ||||
self.validate_title_field() | self.validate_title_field() | ||||
validate_fields(self.get("fields")) | |||||
validate_fields(self) | |||||
if self.istable: | if self.istable: | ||||
# no permission records for child table | # 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])) | frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0])) | ||||
def scrub_field_names(self): | 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") | 'parentfield','parenttype',"file_list") | ||||
for d in self.get("fields"): | for d in self.get("fields"): | ||||
if d.fieldtype: | if d.fieldtype: | ||||
@@ -66,8 +72,11 @@ class DocType(Document): | |||||
if not autoname and self.get("fields", {"fieldname":"naming_series"}): | if not autoname and self.get("fields", {"fieldname":"naming_series"}): | ||||
self.autoname = "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] | prefix = autoname.split('.')[0] | ||||
used_in = frappe.db.sql('select name from tabDocType where substring_index(autoname, ".", 1) = %s and name!=%s', (prefix, name)) | used_in = frappe.db.sql('select name from tabDocType where substring_index(autoname, ".", 1) = %s and name!=%s', (prefix, name)) | ||||
if used_in: | if used_in: | ||||
@@ -81,7 +90,7 @@ class DocType(Document): | |||||
make_module_and_roles(self) | make_module_and_roles(self) | ||||
from frappe import conf | 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.export_doc() | ||||
self.make_controller_template() | self.make_controller_template() | ||||
@@ -91,13 +100,9 @@ class DocType(Document): | |||||
module = load_doctype_module(self.name, self.module) | module = load_doctype_module(self.name, self.module) | ||||
if hasattr(module, "on_doctype_update"): | if hasattr(module, "on_doctype_update"): | ||||
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): | def before_rename(self, old, new, merge=False): | ||||
if merge: | if merge: | ||||
@@ -109,6 +114,29 @@ class DocType(Document): | |||||
else: | else: | ||||
frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new)) | 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): | def export_doc(self): | ||||
from frappe.modules.export_file import export_to_files | from frappe.modules.export_file import export_to_files | ||||
export_to_files(record_list=[['DocType', self.name]]) | 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]]) | import_from_files(record_list=[[self.module, 'doctype', self.name]]) | ||||
def make_controller_template(self): | 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): | def make_amendable(self): | ||||
""" | """ | ||||
@@ -158,9 +175,10 @@ class DocType(Document): | |||||
return max_idx and max_idx[0][0] or 0 | return max_idx and max_idx[0][0] or 0 | ||||
def validate_fields_for_doctype(doctype): | 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): | def check_illegal_characters(fieldname): | ||||
for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$', | 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))) | frappe.throw(_("Fieldname {0} appears multiple times in rows {1}").format(fieldname, ", ".join(duplicates))) | ||||
def check_illegal_mandatory(d): | 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)) | frappe.throw(_("Field {0} of type {1} cannot be mandatory").format(d.label, d.fieldtype)) | ||||
def check_link_table_options(d): | 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): | 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)) | 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: | for d in fields: | ||||
if not d.permlevel: d.permlevel = 0 | if not d.permlevel: d.permlevel = 0 | ||||
if not d.fieldname: | if not d.fieldname: | ||||
@@ -211,13 +270,28 @@ def validate_fields(fields): | |||||
check_unique_fieldname(d.fieldname) | check_unique_fieldname(d.fieldname) | ||||
check_illegal_mandatory(d) | check_illegal_mandatory(d) | ||||
check_link_table_options(d) | check_link_table_options(d) | ||||
check_dynamic_link_options(d) | |||||
check_hidden_and_mandatory(d) | check_hidden_and_mandatory(d) | ||||
check_in_list_view(d) | check_in_list_view(d) | ||||
check_illegal_default(d) | |||||
check_min_items_in_list(fields) | check_min_items_in_list(fields) | ||||
check_fold(fields) | |||||
check_search_fields(meta) | |||||
def validate_permissions_for_doctype(doctype, for_remove=False): | 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): | def validate_permissions(doctype, for_remove=False): | ||||
permissions = doctype.get("permissions") | permissions = doctype.get("permissions") | ||||
@@ -239,12 +313,13 @@ def validate_permissions(doctype, for_remove=False): | |||||
def check_double(d): | def check_double(d): | ||||
has_similar = False | has_similar = False | ||||
for p in permissions: | 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 | has_similar = True | ||||
break | break | ||||
if has_similar: | 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): | def check_level_zero_is_set(d): | ||||
if cint(d.permlevel) > 0 and d.role != 'All': | 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("import", 0) | ||||
d.set("export", 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): | def check_if_submittable(d): | ||||
if d.submit and not issubmittable: | if d.submit and not issubmittable: | ||||
@@ -331,3 +409,9 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): | |||||
pass | pass | ||||
else: | else: | ||||
raise | 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 | // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | ||||
// MIT License. See license.txt | // 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) { | cur_frm.cscript.repeat_on = function(doc, cdt, cdn) { | ||||
if(doc.repeat_on==="Every Day") { | if(doc.repeat_on==="Every Day") { | ||||
$.each(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"], function(i,v) { | $.each(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"], function(i,v) { | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"autoname": "EV.#####", | "autoname": "EV.#####", | ||||
"creation": "2013-06-10 13:17:47.000000", | |||||
"creation": "2013-06-10 13:17:47", | |||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"fields": [ | "fields": [ | ||||
@@ -218,24 +218,26 @@ | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "ref_type", | "fieldname": "ref_type", | ||||
"fieldtype": "Data", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | "hidden": 0, | ||||
"label": "Ref Type", | "label": "Ref Type", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "ref_type", | "oldfieldname": "ref_type", | ||||
"oldfieldtype": "Data", | "oldfieldtype": "Data", | ||||
"options": "DocType", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"read_only": 1, | |||||
"read_only": 0, | |||||
"search_index": 0 | "search_index": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "ref_name", | "fieldname": "ref_name", | ||||
"fieldtype": "Data", | |||||
"fieldtype": "Dynamic Link", | |||||
"hidden": 0, | "hidden": 0, | ||||
"label": "Ref Name", | "label": "Ref Name", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "ref_name", | "oldfieldname": "ref_name", | ||||
"oldfieldtype": "Data", | "oldfieldtype": "Data", | ||||
"options": "ref_type", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"read_only": 1, | "read_only": 1, | ||||
"search_index": 0 | "search_index": 0 | ||||
@@ -244,14 +246,14 @@ | |||||
"icon": "icon-calendar", | "icon": "icon-calendar", | ||||
"idx": 1, | "idx": 1, | ||||
"in_create": 1, | "in_create": 1, | ||||
"modified": "2014-01-24 13:00:01.000000", | |||||
"modified": "2014-06-20 06:40:05.415405", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Event", | "name": "Event", | ||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"cancel": 0, | |||||
"apply_user_permissions": 1, | |||||
"create": 1, | "create": 1, | ||||
"delete": 0, | "delete": 0, | ||||
"email": 1, | "email": 1, | ||||
@@ -264,7 +266,6 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 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: | 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) | 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' | return """(tabEvent.event_type='Public' or tabEvent.owner='%(user)s' | ||||
or exists(select * from `tabEvent User` where | or exists(select * from `tabEvent User` where | ||||
`tabEvent User`.parent=tabEvent.name and `tabEvent User`.person='%(user)s') | `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 | `tabEvent Role`.parent=tabEvent.name | ||||
and `tabEvent Role`.role in ('%(roles)s'))) | 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 | return True | ||||
if doc.get("event_individuals", {"person":frappe.session.user}): | |||||
if doc.get("event_individuals", {"person": user}): | |||||
return True | 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 True | ||||
return False | return False | ||||
@@ -1,11 +1,12 @@ | |||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | # 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 | ||||
import frappe.defaults | import frappe.defaults | ||||
import unittest | import unittest | ||||
import json | |||||
test_records = frappe.get_test_records('Event') | 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 1" in subjects) | ||||
self.assertTrue("_Test Event 3" in subjects) | self.assertTrue("_Test Event 3" in subjects) | ||||
self.assertFalse("_Test Event 2" 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": [ | "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": [ | "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 | "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 | # 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 __future__ import unicode_literals | ||||
""" | """ | ||||
@@ -16,20 +16,20 @@ from frappe.utils.file_manager import delete_file_data_content | |||||
class FileData(Document): | class FileData(Document): | ||||
def before_insert(self): | def before_insert(self): | ||||
frappe.local.rollback_observers.append(self) | frappe.local.rollback_observers.append(self) | ||||
def on_update(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): | def on_trash(self): | ||||
if self.attached_to_name: | if self.attached_to_name: | ||||
@@ -37,9 +37,9 @@ class FileData(Document): | |||||
try: | try: | ||||
if not getattr(self, 'ignore_permissions', False) and \ | if not getattr(self, 'ignore_permissions', False) and \ | ||||
not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name): | not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name): | ||||
frappe.msgprint(frappe._("No permission to write / remove."), raise_exception=True) | frappe.msgprint(frappe._("No permission to write / remove."), raise_exception=True) | ||||
except frappe.DoesNotExistError: | except frappe.DoesNotExistError: | ||||
pass | 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", | "creation": "2012-11-22 17:45:46", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
@@ -35,7 +34,8 @@ | |||||
"label": "Is Default", | "label": "Is Default", | ||||
"oldfieldname": "is_default", | "oldfieldname": "is_default", | ||||
"oldfieldtype": "Check", | "oldfieldtype": "Check", | ||||
"permlevel": 0 | |||||
"permlevel": 0, | |||||
"search_index": 1 | |||||
}, | }, | ||||
{ | { | ||||
"depends_on": "letter_head_name", | "depends_on": "letter_head_name", | ||||
@@ -52,14 +52,13 @@ | |||||
"icon": "icon-font", | "icon": "icon-font", | ||||
"idx": 1, | "idx": 1, | ||||
"max_attachments": 3, | "max_attachments": 3, | ||||
"modified": "2014-05-07 06:03:07.760995", | |||||
"modified": "2014-07-21 05:57:56.052191", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Letter Head", | "name": "Letter Head", | ||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -72,6 +71,7 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"apply_user_permissions": 1, | |||||
"delete": 0, | "delete": 0, | ||||
"email": 0, | "email": 0, | ||||
"permlevel": 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_rename": 1, | ||||
"allow_trash": null, | |||||
"autoname": "field:module_name", | "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", | "creation": "2013-01-10 16:34:03", | ||||
"custom": null, | |||||
"default_print_format": null, | |||||
"description": null, | |||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": null, | |||||
"dt_template": null, | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"allow_on_submit": null, | |||||
"default": null, | |||||
"depends_on": null, | |||||
"description": null, | |||||
"fieldname": "module_name", | "fieldname": "module_name", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"hidden": null, | |||||
"ignore_restrictions": null, | |||||
"in_filter": null, | |||||
"in_list_view": 1, | "in_list_view": 1, | ||||
"label": "Module Name", | "label": "Module Name", | ||||
"no_column": null, | |||||
"no_copy": null, | |||||
"oldfieldname": "module_name", | "oldfieldname": "module_name", | ||||
"oldfieldtype": "Data", | "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", | "fieldname": "app_name", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"hidden": null, | |||||
"ignore_restrictions": null, | |||||
"in_filter": null, | |||||
"in_list_view": 1, | "in_list_view": 1, | ||||
"label": "App Name", | "label": "App Name", | ||||
"no_column": null, | |||||
"no_copy": null, | |||||
"oldfieldname": null, | |||||
"oldfieldtype": null, | |||||
"options": null, | |||||
"permlevel": 0, | "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", | "icon": "icon-sitemap", | ||||
"idx": 1, | "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", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Module Def", | "name": "Module Def", | ||||
"name_case": null, | |||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"parent": null, | |||||
"parent_node": null, | |||||
"parentfield": null, | |||||
"parenttype": null, | |||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"amend": 0, | "amend": 0, | ||||
@@ -109,54 +37,18 @@ | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
"export": null, | |||||
"import": null, | |||||
"match": null, | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print": 1, | "print": 1, | ||||
"read": 1, | "read": 1, | ||||
"report": 1, | "report": 1, | ||||
"restrict": null, | |||||
"restricted": null, | |||||
"role": "Administrator", | "role": "Administrator", | ||||
"submit": 0, | "submit": 0, | ||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"amend": null, | |||||
"cancel": null, | |||||
"create": 1, | |||||
"delete": 1, | |||||
"email": null, | |||||
"export": null, | |||||
"import": null, | |||||
"match": null, | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print": null, | |||||
"read": 1, | "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): | class ModuleDef(Document): | ||||
def validate(self): | def validate(self): | ||||
if not frappe.conf.get("developer_mode"): | |||||
return | |||||
modules = None | 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: | with open(frappe.get_app_path(self.app_name, "modules.txt"), "r") as f: | ||||
content = f.read() | 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 = filter(None, content.splitlines()) | ||||
modules.append(frappe.scrub(self.name)) | |||||
modules.append(self.name) | |||||
if modules: | if modules: | ||||
with open(frappe.get_app_path(self.app_name, "modules.txt"), "w") as f: | with open(frappe.get_app_path(self.app_name, "modules.txt"), "w") as f: | ||||
@@ -5,9 +5,11 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
import MySQLdb | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
logger = frappe.get_logger() | |||||
class NotificationCount(Document): | class NotificationCount(Document): | ||||
pass | pass | ||||
@@ -15,6 +17,7 @@ class NotificationCount(Document): | |||||
def get_notifications(): | def get_notifications(): | ||||
if frappe.flags.in_install_app: | if frappe.flags.in_install_app: | ||||
return | return | ||||
config = get_notification_config() | config = get_notification_config() | ||||
can_read = frappe.user.get_can_read() | can_read = frappe.user.get_can_read() | ||||
open_count_doctype = {} | open_count_doctype = {} | ||||
@@ -32,34 +35,63 @@ def get_notifications(): | |||||
open_count_doctype[d] = notification_count[d] | open_count_doctype[d] = notification_count[d] | ||||
else: | else: | ||||
result = frappe.get_list(d, fields=["count(*)"], | 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 | 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: | for m in config.for_module: | ||||
if m in notification_count: | if m in notification_count: | ||||
open_count_module[m] = notification_count[m] | open_count_module[m] = notification_count[m] | ||||
else: | else: | ||||
open_count_module[m] = frappe.get_attr(config.for_module[m])() | 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 { | return { | ||||
"open_count_doctype": open_count_doctype, | "open_count_doctype": open_count_doctype, | ||||
"open_count_module": open_count_module | "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): | def delete_notification_count_for(doctype): | ||||
if frappe.flags.in_import: return | if frappe.flags.in_import: return | ||||
frappe.db.sql("""delete from `tabNotification Count` where for_doctype = %s""", (doctype,)) | 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: | if frappe.flags.in_import: | ||||
return | return | ||||
@@ -1,14 +1,28 @@ | |||||
{ | { | ||||
"allow_copy": 1, | "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.", | "description": "Email Settings for Outgoing and Incoming Emails.", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"fields": [ | "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)", | "description": "SMTP Server (e.g. smtp.gmail.com)", | ||||
"fieldname": "mail_server", | "fieldname": "mail_server", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Outgoing Mail Server", | "label": "Outgoing Mail Server", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
@@ -16,6 +30,7 @@ | |||||
"description": "<a href=\"https://en.wikipedia.org/wiki/Transport_Layer_Security\" target=\"_blank\">[?]</a>", | "description": "<a href=\"https://en.wikipedia.org/wiki/Transport_Layer_Security\" target=\"_blank\">[?]</a>", | ||||
"fieldname": "use_ssl", | "fieldname": "use_ssl", | ||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"in_list_view": 1, | |||||
"label": "Use TLS", | "label": "Use TLS", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
@@ -23,6 +38,7 @@ | |||||
"description": "If non standard port (e.g. 587)", | "description": "If non standard port (e.g. 587)", | ||||
"fieldname": "mail_port", | "fieldname": "mail_port", | ||||
"fieldtype": "Int", | "fieldtype": "Int", | ||||
"in_list_view": 1, | |||||
"label": "Port", | "label": "Port", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
@@ -35,6 +51,7 @@ | |||||
"description": "Set Login and Password if authentication is required.", | "description": "Set Login and Password if authentication is required.", | ||||
"fieldname": "mail_login", | "fieldname": "mail_login", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Login Id", | "label": "Login Id", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
@@ -59,19 +76,25 @@ | |||||
"permlevel": 0 | "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 | "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", | "icon": "icon-cog", | ||||
"idx": 1, | "idx": 1, | ||||
"in_create": 1, | "in_create": 1, | ||||
"issingle": 1, | "issingle": 1, | ||||
"modified": "2014-03-03 20:20:09.000000", | |||||
"modified": "2014-07-17 08:08:00.483391", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Outgoing Email Settings", | "name": "Outgoing Email Settings", | ||||
@@ -5,21 +5,28 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
from frappe import _, throw | |||||
from frappe.utils import validate_email_add | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class OutgoingEmailSettings(Document): | class OutgoingEmailSettings(Document): | ||||
def validate(self): | 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 import cint | ||||
from frappe.utils.email_lib.smtp import SMTPServer | from frappe.utils.email_lib.smtp import SMTPServer | ||||
smtpserver = SMTPServer(login = self.mail_login, | smtpserver = SMTPServer(login = self.mail_login, | ||||
password = self.mail_password, | password = self.mail_password, | ||||
server = self.mail_server, | server = self.mail_server, | ||||
port = cint(self.mail_port), | port = cint(self.mail_port), | ||||
use_ssl = self.use_ssl | |||||
use_ssl = cint(self.use_ssl) | |||||
) | ) | ||||
# exceptions are handled in session connect | # 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_copy": 0, | ||||
"allow_rename": 1, | "allow_rename": 1, | ||||
"autoname": "field:page_name", | "autoname": "field:page_name", | ||||
"creation": "2012-12-20 17:16:49.000000", | |||||
"creation": "2012-12-20 17:16:49", | |||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"fields": [ | "fields": [ | ||||
@@ -16,6 +16,7 @@ | |||||
{ | { | ||||
"fieldname": "page_name", | "fieldname": "page_name", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Page Name", | "label": "Page Name", | ||||
"oldfieldname": "page_name", | "oldfieldname": "page_name", | ||||
"oldfieldtype": "Data", | "oldfieldtype": "Data", | ||||
@@ -25,12 +26,14 @@ | |||||
{ | { | ||||
"fieldname": "title", | "fieldname": "title", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Title", | "label": "Title", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "icon", | "fieldname": "icon", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "icon", | "label": "icon", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
@@ -84,14 +87,13 @@ | |||||
"idx": 1, | "idx": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"modified": "2013-12-30 13:48:02.000000", | |||||
"modified": "2014-05-27 03:49:14.476843", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Page", | "name": "Page", | ||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"email": 1, | "email": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -102,6 +104,7 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"apply_user_permissions": 1, | |||||
"email": 1, | "email": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print": 1, | "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 (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 { | } 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, | "allow_rename": 0, | ||||
"autoname": "Prompt", | "autoname": "Prompt", | ||||
"creation": "2013-01-23 19:54:43", | "creation": "2013-01-23 19:54:43", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"fields": [ | "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, | "allow_on_submit": 0, | ||||
"fieldname": "module", | "fieldname": "module", | ||||
@@ -25,18 +36,6 @@ | |||||
"reqd": 1, | "reqd": 1, | ||||
"search_index": 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", | "fieldname": "column_break_3", | ||||
"fieldtype": "Column Break", | "fieldtype": "Column Break", | ||||
@@ -63,11 +62,20 @@ | |||||
"search_index": 1 | "search_index": 1 | ||||
}, | }, | ||||
{ | { | ||||
"default": "Server", | |||||
"description": "Client-side formats are now deprecated", | |||||
"fieldname": "print_format_type", | "fieldname": "print_format_type", | ||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"in_list_view": 1, | "in_list_view": 1, | ||||
"label": "Print Format Type", | "label": "Print Format Type", | ||||
"options": "Client\nServer", | |||||
"options": "Server\nClient", | |||||
"permlevel": 0, | |||||
"read_only": 0 | |||||
}, | |||||
{ | |||||
"fieldname": "disabled", | |||||
"fieldtype": "Check", | |||||
"label": "Disabled", | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
@@ -77,7 +85,7 @@ | |||||
}, | }, | ||||
{ | { | ||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"depends_on": "eval:doc.print_format_type!=\"Server\"", | |||||
"depends_on": "", | |||||
"fieldname": "html", | "fieldname": "html", | ||||
"fieldtype": "Code", | "fieldtype": "Code", | ||||
"hidden": 0, | "hidden": 0, | ||||
@@ -92,6 +100,13 @@ | |||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
"search_index": 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, | "hide_heading": 0, | ||||
@@ -103,7 +118,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2014-05-09 02:12:39.540952", | |||||
"modified": "2014-07-31 03:39:35.898711", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Print Format", | "name": "Print Format", | ||||
@@ -2,11 +2,8 @@ | |||||
# MIT License. See license.txt | # MIT License. See license.txt | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe, os | |||||
import frappe | |||||
import frappe.utils | import frappe.utils | ||||
from frappe.modules import get_doc_path | |||||
standard_format = "templates/print_formats/standard.html" | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -38,76 +35,3 @@ class PrintFormat(Document): | |||||
if self.doc_type: | if self.doc_type: | ||||
frappe.clear_cache(doctype=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", | "description": "Property Setter overrides a standard DocType or Field property", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
@@ -20,6 +20,7 @@ | |||||
"depends_on": "eval:doc.__islocal", | "depends_on": "eval:doc.__islocal", | ||||
"fieldname": "doctype_or_field", | "fieldname": "doctype_or_field", | ||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"in_list_view": 1, | |||||
"label": "DocType or Field", | "label": "DocType or Field", | ||||
"options": "\nDocField\nDocType", | "options": "\nDocField\nDocType", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -29,6 +30,7 @@ | |||||
"description": "New value to be set", | "description": "New value to be set", | ||||
"fieldname": "value", | "fieldname": "value", | ||||
"fieldtype": "Text", | "fieldtype": "Text", | ||||
"in_list_view": 1, | |||||
"label": "Set Value", | "label": "Set Value", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
@@ -83,7 +85,7 @@ | |||||
], | ], | ||||
"icon": "icon-glass", | "icon": "icon-glass", | ||||
"idx": 1, | "idx": 1, | ||||
"modified": "2014-01-20 17:49:03.000000", | |||||
"modified": "2014-07-24 02:04:26.838056", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Property Setter", | "name": "Property Setter", | ||||
@@ -116,5 +118,5 @@ | |||||
"write": 1 | "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 | # 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 __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
@@ -7,7 +7,6 @@ import frappe | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class PropertySetter(Document): | class PropertySetter(Document): | ||||
def autoname(self): | def autoname(self): | ||||
self.name = self.doc_type + "-" \ | self.name = self.doc_type + "-" \ | ||||
+ (self.field_name and (self.field_name + "-") or "") \ | + (self.field_name and (self.field_name + "-") or "") \ | ||||
@@ -21,47 +20,52 @@ class PropertySetter(Document): | |||||
and doc_type = %(doc_type)s | and doc_type = %(doc_type)s | ||||
and ifnull(field_name,'') = ifnull(%(field_name)s, '') | and ifnull(field_name,'') = ifnull(%(field_name)s, '') | ||||
and property = %(property)s""", self.get_valid_dict()) | and property = %(property)s""", self.get_valid_dict()) | ||||
# clear cache | # clear cache | ||||
frappe.clear_cache(doctype = self.doc_type) | frappe.clear_cache(doctype = self.doc_type) | ||||
def get_property_list(self, dt): | def get_property_list(self, dt): | ||||
return frappe.db.sql("""select fieldname, label, fieldtype | |||||
return frappe.db.sql("""select fieldname, label, fieldtype | |||||
from tabDocField | from tabDocField | ||||
where parent=%s | 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, '') != '' | and ifnull(fieldname, '') != '' | ||||
order by label asc""", dt, as_dict=1) | order by label asc""", dt, as_dict=1) | ||||
def get_setup_data(self): | def get_setup_data(self): | ||||
return { | return { | ||||
'doctypes': [d[0] for d in frappe.db.sql("select name from tabDocType")], | 'doctypes': [d[0] for d in frappe.db.sql("select name from tabDocType")], | ||||
'dt_properties': self.get_property_list('DocType'), | 'dt_properties': self.get_property_list('DocType'), | ||||
'df_properties': self.get_property_list('DocField') | 'df_properties': self.get_property_list('DocField') | ||||
} | } | ||||
def get_field_ids(self): | 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) | return frappe.db.sql("select name, fieldtype, label, fieldname from tabDocField where parent=%s", self.doc_type, as_dict = 1) | ||||
def get_defaults(self): | def get_defaults(self): | ||||
if not self.field_name: | if not self.field_name: | ||||
return frappe.db.sql("select * from `tabDocType` where name=%s", self.doc_type, as_dict = 1)[0] | return frappe.db.sql("select * from `tabDocType` where name=%s", self.doc_type, as_dict = 1)[0] | ||||
else: | 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] | (self.field_name, self.doc_type), as_dict = 1)[0] | ||||
def on_update(self): | 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); | frappe.set_route("query-report", doc.name); | ||||
break; | 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(""); | cur_frm.set_intro(""); | ||||
switch(doc.report_type) { | switch(doc.report_type) { | ||||
@@ -61,6 +61,15 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"read_only": 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", | "fieldname": "section_break_6", | ||||
"fieldtype": "Section Break", | "fieldtype": "Section Break", | ||||
@@ -101,14 +110,13 @@ | |||||
], | ], | ||||
"icon": "icon-table", | "icon": "icon-table", | ||||
"idx": 1, | "idx": 1, | ||||
"modified": "2014-05-12 17:08:04.185601", | |||||
"modified": "2014-06-03 07:25:41.509885", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Report", | "name": "Report", | ||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -121,7 +129,6 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -134,7 +141,6 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
@@ -147,7 +153,7 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"cancel": 0, | |||||
"apply_user_permissions": 1, | |||||
"delete": 0, | "delete": 0, | ||||
"email": 1, | "email": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -3,35 +3,50 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
from frappe import conf, _ | |||||
from frappe import _ | |||||
from frappe.utils import cint | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.modules.export_file import export_to_files | |||||
from frappe.modules import make_boilerplate | |||||
class Report(Document): | class Report(Document): | ||||
def validate(self): | def validate(self): | ||||
"""only administrator can save standard report""" | """only administrator can save standard report""" | ||||
if not self.module: | if not self.module: | ||||
self.module = frappe.db.get_value("DocType", self.ref_doctype, "module") | self.module = frappe.db.get_value("DocType", self.ref_doctype, "module") | ||||
if not self.is_standard: | if not self.is_standard: | ||||
self.is_standard = "No" | 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" | self.is_standard = "Yes" | ||||
if self.is_standard == "Yes" and frappe.session.user!="Administrator": | 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) | raise_exception=True) | ||||
if self.report_type in ("Query Report", "Script Report") \ | if self.report_type in ("Query Report", "Script Report") \ | ||||
and frappe.session.user!="Administrator": | and frappe.session.user!="Administrator": | ||||
frappe.msgprint(_("Only Administrator allowed to create Query / Script Reports"), | frappe.msgprint(_("Only Administrator allowed to create Query / Script Reports"), | ||||
raise_exception=True) | raise_exception=True) | ||||
def on_update(self): | def on_update(self): | ||||
self.export_doc() | self.export_doc() | ||||
def export_doc(self): | 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) | 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 | // MIT License. See license.txt | ||||
cur_frm.cscript.refresh = function(doc) { | 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.route_options = {"role": doc.name}; | ||||
frappe.set_route("permission-manager"); | frappe.set_route("permission-manager"); | ||||
}); | }); | ||||
@@ -23,7 +23,7 @@ | |||||
"idx": 1, | "idx": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"modified": "2014-05-02 06:35:09.528809", | |||||
"modified": "2014-08-05 05:24:42.185395", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Role", | "name": "Role", | ||||
@@ -31,7 +31,6 @@ | |||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"amend": 0, | "amend": 0, | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"email": 1, | "email": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -43,8 +42,8 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | |||||
"email": 1, | "email": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print": 1, | "print": 1, | ||||
@@ -55,6 +54,7 @@ | |||||
"write": 1 | "write": 1 | ||||
}, | }, | ||||
{ | { | ||||
"apply_user_permissions": 1, | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"read": 1, | "read": 1, | ||||
"role": "All" | "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": [ | "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": [ | "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 | "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 | # For license information, please see license.txt | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe, pytz | |||||
import frappe | |||||
from frappe import _ | from frappe import _ | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.translate import get_lang_dict, set_default_language | from frappe.translate import get_lang_dict, set_default_language | ||||
from frappe.utils import cint | from frappe.utils import cint | ||||
from frappe.utils.momentjs import get_all_timezones | |||||
class SystemSettings(Document): | class SystemSettings(Document): | ||||
def validate(self): | def validate(self): | ||||
@@ -17,11 +18,11 @@ class SystemSettings(Document): | |||||
def on_update(self): | def on_update(self): | ||||
for df in self.meta.get("fields"): | 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)) | 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() | @frappe.whitelist() | ||||
def load(): | def load(): | ||||
@@ -39,7 +40,7 @@ def load(): | |||||
languages.sort() | languages.sort() | ||||
return { | return { | ||||
"timezones": pytz.all_timezones, | |||||
"timezones": get_all_timezones(), | |||||
"languages": [""] + languages, | "languages": [""] + languages, | ||||
"defaults": defaults | "defaults": defaults | ||||
} | } |
@@ -1,13 +1,23 @@ | |||||
// bind events | // bind events | ||||
frappe.ui.form.on("ToDo", "refresh", function(frm) { | 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) { | 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, | "allow_rename": 0, | ||||
"autoname": "TDI.########", | "autoname": "TDI.########", | ||||
"creation": "2012-07-03 13:30:35.000000", | |||||
"creation": "2012-07-03 13:30:35", | |||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"fields": [ | "fields": [ | ||||
@@ -82,6 +81,16 @@ | |||||
"reqd": 0, | "reqd": 0, | ||||
"search_index": 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", | "fieldname": "section_break_6", | ||||
"fieldtype": "Section Break", | "fieldtype": "Section Break", | ||||
@@ -91,13 +100,14 @@ | |||||
{ | { | ||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"fieldname": "reference_type", | "fieldname": "reference_type", | ||||
"fieldtype": "Data", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | "hidden": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"label": "Reference Type", | "label": "Reference Type", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "reference_type", | "oldfieldname": "reference_type", | ||||
"oldfieldtype": "Data", | "oldfieldtype": "Data", | ||||
"options": "DocType", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
@@ -107,13 +117,14 @@ | |||||
{ | { | ||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"fieldname": "reference_name", | "fieldname": "reference_name", | ||||
"fieldtype": "Data", | |||||
"fieldtype": "Dynamic Link", | |||||
"hidden": 0, | "hidden": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"label": "Reference Name", | "label": "Reference Name", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "reference_name", | "oldfieldname": "reference_name", | ||||
"oldfieldtype": "Data", | "oldfieldtype": "Data", | ||||
"options": "reference_type", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
@@ -145,6 +156,7 @@ | |||||
{ | { | ||||
"fieldname": "assigned_by", | "fieldname": "assigned_by", | ||||
"fieldtype": "Link", | "fieldtype": "Link", | ||||
"ignore_user_permissions": 1, | |||||
"label": "Assigned By", | "label": "Assigned By", | ||||
"options": "User", | "options": "User", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
@@ -158,14 +170,14 @@ | |||||
"in_dialog": 0, | "in_dialog": 0, | ||||
"issingle": 0, | "issingle": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2014-03-12 17:06:46.000000", | |||||
"modified": "2014-06-30 05:40:15.471434", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "ToDo", | "name": "ToDo", | ||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"cancel": 0, | |||||
"apply_user_permissions": 1, | |||||
"create": 1, | "create": 1, | ||||
"delete": 0, | "delete": 0, | ||||
"email": 1, | "email": 1, | ||||
@@ -174,7 +186,6 @@ | |||||
"print": 1, | "print": 1, | ||||
"read": 1, | "read": 1, | ||||
"report": 1, | "report": 1, | ||||
"restricted": 1, | |||||
"role": "All", | "role": "All", | ||||
"submit": 0, | "submit": 0, | ||||
"write": 1 | "write": 1 | ||||
@@ -194,5 +205,6 @@ | |||||
], | ], | ||||
"read_only": 0, | "read_only": 0, | ||||
"read_only_onload": 0, | "read_only_onload": 0, | ||||
"search_fields": "description, reference_type, reference_name", | |||||
"title_field": "description" | "title_field": "description" | ||||
} | } |
@@ -3,46 +3,84 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
import json | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class ToDo(Document): | class ToDo(Document): | ||||
def validate(self): | def validate(self): | ||||
if self.is_new(): | if self.is_new(): | ||||
self.add_comment(frappe._("Assignment Added")) | |||||
self.add_comment(frappe._("Assigned to {0}").format(self.owner), "Assigned") | |||||
else: | else: | ||||
cur_status = frappe.db.get_value("ToDo", self.name, "status") | cur_status = frappe.db.get_value("ToDo", self.name, "status") | ||||
if cur_status != self.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: | if not self.reference_type and self.reference_name: | ||||
return | return | ||||
comment = frappe.get_doc({ | |||||
frappe.get_doc({ | |||||
"doctype":"Comment", | "doctype":"Comment", | ||||
"comment_by": frappe.session.user, | "comment_by": frappe.session.user, | ||||
"comment_type": comment_type, | |||||
"comment_doctype": self.reference_type, | "comment_doctype": self.reference_type, | ||||
"comment_docname": self.reference_name, | "comment_docname": self.reference_name, | ||||
"comment": """<div>{text}: | |||||
"comment": """<div>{text}: | |||||
<a href='#Form/ToDo/{name}'>{status}: {description}</a></div>""".format(text=text, | <a href='#Form/ToDo/{name}'>{status}: {description}</a></div>""".format(text=text, | ||||
status = frappe._(self.status), | status = frappe._(self.status), | ||||
name = self.name, | name = self.name, | ||||
description = self.description) | description = self.description) | ||||
}).insert(ignore_permissions=True) | }).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 | return None | ||||
else: | 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 | return True | ||||
else: | 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> |