diff --git a/.travis.yml b/.travis.yml index 9fb42a9218..230a97ffdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,8 @@ install: - sudo apt-get update - sudo apt-get purge -y mysql-common - sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev + - wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.1/wkhtmltox-0.12.1_linux-precise-amd64.deb + - sudo dpkg -i wkhtmltox-0.12.1_linux-precise-amd64.deb - CFLAGS=-O0 pip install -r requirements.txt - pip install --editable . @@ -23,10 +25,12 @@ script: - frappe --use test_site - frappe --reinstall - frappe -b + - frappe --build_website - frappe --serve_test & - frappe --verbose --run_tests before_script: - mysql -e 'create database test_frappe' - echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root + - echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..a29e0badee --- /dev/null +++ b/CONTRIBUTING.md @@ -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 diff --git a/MANIFEST.in b/MANIFEST.in index 0f85939e09..30ca6761a2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -25,4 +25,5 @@ recursive-include frappe *.csv recursive-include frappe *.ico recursive-include frappe *.less recursive-include frappe *.txt +recursive-include frappe/public * recursive-exclude * *.pyc diff --git a/README.md b/README.md index 28dd978089..d2010dd1bd 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,17 @@ ## frappe [![Build Status](https://travis-ci.org/frappe/frappe.png)](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 diff --git a/frappe/__init__.py b/frappe/__init__.py index 89d71ce19e..24dbb5030c 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -4,19 +4,15 @@ globals attached to frappe module + some utility functions that should probably be moved """ - from __future__ import unicode_literals from werkzeug.local import Local, release_local -from werkzeug.exceptions import NotFound -from MySQLdb import ProgrammingError as SQLError - -import os, sys, importlib, inspect -import json +import os, importlib, inspect, logging, json +# public +from frappe.__version__ import __version__ from .exceptions import * - -__version__ = "4.0.1" +from .utils.jinja import get_jenv, get_template, render_template local = Local() @@ -68,7 +64,6 @@ response = local("response") session = local("session") user = local("user") flags = local("flags") -restrictions = local("restrictions") error_log = local("error_log") debug_log = local("debug_log") @@ -84,30 +79,37 @@ def init(site, sites_path=None): sites_path = '.' local.error_log = [] + local.message_log = [] + local.debug_log = [] + local.flags = _dict({}) + local.rollback_observers = [] + local.test_objects = {} + local.site = site local.sites_path = sites_path local.site_path = os.path.join(sites_path, site) - local.message_log = [] - local.debug_log = [] + local.request_method = request.method if request else None + local.request_ip = None local.response = _dict({"docs":[]}) + local.conf = _dict(get_site_config()) local.lang = local.conf.lang or "en" - local.initialised = True - local.flags = _dict({}) - local.rollback_observers = [] + local.module_app = None local.app_modules = None + local.user = None - local.restrictions = None - local.user_perms = {} - local.test_objects = {} + local.role_permissions = {} + local.jenv = None local.jloader =None local.cache = {} setup_module_map() + local.initialised = True + def connect(site=None, db_name=None): from database import Database if site: @@ -158,7 +160,7 @@ def get_traceback(): def errprint(msg): from utils import cstr - if not request: + if not request or (not "cmd" in local.form_dict): print cstr(msg) error_log.append(cstr(msg)) @@ -199,36 +201,46 @@ def msgprint(msg, small=0, raise_exception=0, as_table=False): def throw(msg, exc=ValidationError): msgprint(msg, raise_exception=exc) -def create_folder(path): - if not os.path.exists(path): os.makedirs(path) +def create_folder(path, with_init=False): + from frappe.utils import touch_file + if not os.path.exists(path): + os.makedirs(path) + + if with_init: + touch_file(os.path.join(path, "__init__.py")) def set_user(username): from frappe.utils.user import User local.session.user = username local.session.sid = username local.cache = {} + local.form_dict = _dict() + local.jenv = None local.session.data = {} local.user = User(username) - local.restrictions = None - local.user_perms = {} + local.role_permissions = {} def get_request_header(key, default=None): return request.headers.get(key, default) def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", - as_markdown=False, bulk=False): + as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None, + add_unsubscribe_link=False, attachments=None): if bulk: import frappe.utils.email_lib.bulk frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender, - subject=subject, message=message, add_unsubscribe_link=False) + subject=subject, message=message, ref_doctype = ref_doctype, + ref_docname = ref_docname, add_unsubscribe_link=add_unsubscribe_link, attachments=attachments) else: import frappe.utils.email_lib if as_markdown: - frappe.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message) + frappe.utils.email_lib.sendmail_md(recipients, sender=sender, + subject=subject, msg=message, attachments=attachments) else: - frappe.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message) + frappe.utils.email_lib.sendmail(recipients, sender=sender, + subject=subject, msg=message, attachments=attachments) logger = None whitelisted = [] @@ -276,18 +288,27 @@ def clear_cache(user=None, doctype=None): translate.clear_cache() reset_metadata_version() + for fn in frappe.get_hooks("clear_cache"): + get_attr(fn)() + + frappe.local.role_permissions = {} + def get_roles(username=None): - from frappe.utils.user import User if not local.session: return ["Guest"] - elif not username or username==local.session.user: - return local.user.get_roles() + + return get_user(username).get_roles() + +def get_user(username): + from frappe.utils.user import User + if not username or username == local.session.user: + return local.user else: - return User(username).get_roles() + return User(username) -def has_permission(doctype, ptype="read", doc=None): +def has_permission(doctype, ptype="read", doc=None, user=None): import frappe.permissions - return frappe.permissions.has_permission(doctype, ptype, doc) + return frappe.permissions.has_permission(doctype, ptype, doc, user=user) def is_table(doctype): tables = cache().get_value("is_table") @@ -300,6 +321,9 @@ def clear_perms(doctype): db.sql("""delete from tabDocPerm where parent=%s""", doctype) def reset_perms(doctype): + from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for + delete_notification_count_for(doctype) + clear_perms(doctype) reload_doc(db.get_value("DocType", doctype, "module"), "DocType", doctype, force=True) @@ -307,7 +331,8 @@ def reset_perms(doctype): def generate_hash(txt=None): """Generates random hash for session id""" import hashlib, time - return hashlib.sha224((txt or "") + repr(time.time())).hexdigest() + from .utils import random_string + return hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest() def reset_metadata_version(): v = generate_hash() @@ -332,15 +357,7 @@ def get_meta(doctype, cached=True): def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False): import frappe.model.delete_doc - - if not ignore_doctypes: - ignore_doctypes = [] - - if isinstance(name, list): - for n in name: - frappe.model.delete_doc.delete_doc(doctype, n, force, ignore_doctypes, for_reload, ignore_permissions) - else: - frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions) + frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions) def delete_doc_if_exists(doctype, name): if db.exists(doctype, name): @@ -362,7 +379,7 @@ def get_module(modulename): return importlib.import_module(modulename) def scrub(txt): - return txt.replace(' ','_').replace('-', '_').replace('/', '_').lower() + return txt.replace(' ','_').replace('-', '_').lower() def unscrub(txt): return txt.replace('_',' ').replace('-', ' ').title() @@ -396,11 +413,26 @@ def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None): return apps def get_installed_apps(): - if flags.in_install_db: + if getattr(flags, "in_install_db", True): return [] installed = json.loads(db.get_global("installed_apps") or "[]") return installed +@whitelist() +def get_versions(): + versions = {} + for app in get_installed_apps(): + versions[app] = { + "title": get_hooks("app_title", app_name=app), + "description": get_hooks("app_description", app_name=app) + } + try: + versions[app]["version"] = get_attr(app + ".__version__") + except AttributeError: + versions[app]["version"] = '0.0.1' + + return versions + def get_hooks(hook=None, default=None, app_name=None): def load_app_hooks(app_name=None): hooks = {} @@ -449,6 +481,7 @@ def setup_module_map(): if app=="webnotes": app="frappe" local.app_modules.setdefault(app, []) for module in get_module_list(app): + module = scrub(module) local.module_app[module] = app local.app_modules[app].append(module) @@ -456,10 +489,13 @@ def setup_module_map(): _cache.set_value("app_modules", local.app_modules) _cache.set_value("module_app", local.module_app) -def get_file_items(path, raise_not_found=False): +def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): content = read_file(path, raise_not_found=raise_not_found) if content: - return [p.strip() for p in content.splitlines() if p.strip() and not p.startswith("#")] + # \ufeff is no-width-break, \u200b is no-width-space + content = content.replace("\ufeff", "").replace("\u200b", "").strip() + + return [p.strip() for p in content.splitlines() if (not ignore_empty_lines) or (p.strip() and not p.startswith("#"))] else: return [] @@ -494,9 +530,9 @@ def call(fn, *args, **kwargs): newargs[a] = kwargs.get(a) return fn(*args, **newargs) -def make_property_setter(args): +def make_property_setter(args, ignore_validate=False): args = _dict(args) - get_doc({ + ps = get_doc({ 'doctype': "Property Setter", 'doctype_or_field': args.doctype_or_field or "DocField", 'doc_type': args.doctype, @@ -505,13 +541,16 @@ def make_property_setter(args): 'value': args.value, 'property_type': args.property_type or "Data", '__islocal': 1 - }).save() + }) + ps.ignore_validate = ignore_validate + ps.insert() def import_doc(path, ignore_links=False, ignore_insert=False, insert=False): from frappe.core.page.data_import_tool import data_import_tool data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert) def copy_doc(doc): + """ No_copy fields also get copied.""" import copy if not isinstance(doc, dict): d = doc.as_dict() @@ -523,6 +562,8 @@ def copy_doc(doc): newdoc.set("__islocal", 1) newdoc.owner = None newdoc.creation = None + newdoc.amended_from = None + newdoc.amendment_date = None for d in newdoc.get_all_children(): d.name = None d.parent = None @@ -540,7 +581,7 @@ def respond_as_web_page(title, html, success=None, http_status_code=None): local.message = html local.message_success = success local.response['type'] = 'page' - local.response['page_name'] = 'message.html' + local.response['page_name'] = 'message' if http_status_code: local.response['http_status_code'] = http_status_code @@ -550,70 +591,16 @@ def build_match_conditions(doctype, as_condition=True): def get_list(doctype, filters=None, fields=None, or_filters=None, docstatus=None, group_by=None, order_by=None, limit_start=0, limit_page_length=None, - as_list=False, debug=False, ignore_permissions=False): + as_list=False, debug=False, ignore_permissions=False, user=None): import frappe.model.db_query return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters, fields=fields, docstatus=docstatus, or_filters=or_filters, group_by=group_by, order_by=order_by, limit_start=limit_start, limit_page_length=limit_page_length, as_list=as_list, debug=debug, - ignore_permissions=ignore_permissions) + ignore_permissions=ignore_permissions, user=user) run_query = get_list -def get_jenv(): - if not local.jenv: - from jinja2 import Environment, DebugUndefined - import frappe.utils - - # frappe will be loaded last, so app templates will get precedence - jenv = Environment(loader = get_jloader(), undefined=DebugUndefined) - set_filters(jenv) - - jenv.globals.update({ - "frappe": sys.modules[__name__], - "frappe.utils": frappe.utils, - "_": _ - }) - - local.jenv = jenv - - return local.jenv - -def get_jloader(): - if not local.jloader: - from jinja2 import ChoiceLoader, PackageLoader - - apps = get_installed_apps() - apps.remove("frappe") - - local.jloader = ChoiceLoader([PackageLoader(app, ".") \ - for app in apps + ["frappe"]]) - - return local.jloader - -def set_filters(jenv): - from frappe.utils import global_date_format - from frappe.website.utils import get_hex_shade - from markdown2 import markdown - from json import dumps - - jenv.filters["global_date_format"] = global_date_format - jenv.filters["markdown"] = markdown - jenv.filters["json"] = dumps - jenv.filters["get_hex_shade"] = get_hex_shade - - # load jenv_filters from hooks.txt - for app in get_all_apps(True): - for jenv_filter in (get_hooks(app_name=app).jenv_filter or []): - filter_name, filter_function = jenv_filter.split(":") - jenv.filters[filter_name] = get_attr(filter_function) - -def get_template(path): - return get_jenv().get_template(path) - -def get_website_route(doctype, name): - return db.get_value("Website Route", {"ref_doctype": doctype, "docname": name}) - def add_version(doc): get_doc({ "doctype": "Version", @@ -630,3 +617,37 @@ def get_test_records(doctype): return json.loads(f.read()) else: return [] + +def format_value(value, df, doc=None, currency=None): + import frappe.utils.formatters + return frappe.utils.formatters.format_value(value, df, doc, currency=currency) + +def get_print_format(doctype, name, print_format=None, style=None, as_pdf=False): + from frappe.website.render import build_page + local.form_dict.doctype = doctype + local.form_dict.name = name + local.form_dict.format = print_format + local.form_dict.style = style + + html = build_page("print") + + if as_pdf: + print_settings = db.get_singles_dict("Print Settings") + if int(print_settings.send_print_as_pdf or 0): + from utils.pdf import get_pdf + return get_pdf(html, {"page-size": print_settings.pdf_page_size}) + else: + return html + else: + return html + +logging_setup_complete = False +def get_logger(module=None): + from frappe.setup_logging import setup_logging + global logging_setup_complete + + if not logging_setup_complete: + setup_logging() + logging_setup_complete = True + + return logging.getLogger(module or "frappe") diff --git a/frappe/__version__.py b/frappe/__version__.py new file mode 100644 index 0000000000..111dc9172a --- /dev/null +++ b/frappe/__version__.py @@ -0,0 +1 @@ +__version__ = "4.3.0" diff --git a/frappe/api.py b/frappe/api.py index cfa525eff6..f16c73b172 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -25,7 +25,7 @@ def handle(): DELETE will delete /api/resource/{doctype}/{name}?run_method={method} will run a whitelisted controller method """ - parts = frappe.request.path[1:].split("/") + parts = frappe.request.path[1:].split("/",3) call = doctype = name = None if len(parts) > 1: @@ -43,18 +43,20 @@ def handle(): elif call=="resource": if "run_method" in frappe.local.form_dict: + method = frappe.local.form_dict.pop("run_method") doc = frappe.get_doc(doctype, name) - doc.is_whitelisted(frappe.local.form_dict.run_method) + doc.is_whitelisted(method) if frappe.local.request.method=="GET": if not doc.has_permission("read"): frappe.throw(_("Not permitted"), frappe.PermissionError) - doc.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict) + doc.run_method(method, **frappe.local.form_dict) if frappe.local.request.method=="POST": if not doc.has_permission("write"): frappe.throw(_("Not permitted"), frappe.PermissionError) - doc.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict) + + doc.run_method(method, **frappe.local.form_dict) frappe.db.commit() else: @@ -76,7 +78,6 @@ def handle(): frappe.db.commit() if frappe.local.request.method=="DELETE": - doc.update(data) # Not checking permissions here because it's checked in delete_doc frappe.delete_doc(doctype, name) frappe.local.response.http_status_code = 202 diff --git a/frappe/app.py b/frappe/app.py index 3fbca54857..8d97dae8e7 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -3,6 +3,7 @@ import sys, os import json +import logging from werkzeug.wrappers import Request, Response from werkzeug.local import LocalManager @@ -25,6 +26,8 @@ local_manager = LocalManager([frappe.local]) _site = None _sites_path = os.environ.get("SITES_PATH", ".") +logger = frappe.get_logger() + @Request.application def application(request): frappe.local.request = request @@ -36,7 +39,7 @@ def application(request): init_site(request) - if frappe.local.conf.get('maintainance_mode'): + if frappe.local.conf.get('maintenance_mode'): raise frappe.SessionStopped make_form_dict(request) @@ -63,28 +66,35 @@ def application(request): except frappe.SessionStopped, e: response = frappe.utils.response.handle_session_stopped() - except (frappe.AuthenticationError, - frappe.PermissionError, - frappe.DoesNotExistError, - frappe.NameError, - frappe.OutgoingEmailError, - frappe.ValidationError, - frappe.UnsupportedMediaType), e: + except Exception, e: + http_status_code = getattr(e, "http_status_code", 500) if frappe.local.is_ajax: - response = frappe.utils.response.report_error(e.http_status_code) + response = frappe.utils.response.report_error(http_status_code) else: - response = frappe.website.render.render("error", e.http_status_code) + frappe.respond_as_web_page("Server Error", + "
"+frappe.get_traceback()+"
", + http_status_code=http_status_code) + response = frappe.website.render.render("message", http_status_code=http_status_code) if e.__class__ == frappe.AuthenticationError: if hasattr(frappe.local, "login_manager"): frappe.local.login_manager.clear_cookies() + if http_status_code==500: + logger.error('Request Error') + else: if frappe.local.request.method in ("POST", "PUT") and frappe.db: frappe.db.commit() rollback = False + # update session + if getattr(frappe.local, "session_obj", None): + updated_in_db = frappe.local.session_obj.update() + if updated_in_db: + frappe.db.commit() + finally: if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback: frappe.db.rollback() @@ -131,4 +141,4 @@ def serve(port=8000, profile=False, site=None, sites_path='.'): }) run_simple('0.0.0.0', int(port), application, use_reloader=True, - use_debugger=True, use_evalex=True) + use_debugger=True, use_evalex=True, threaded=True) diff --git a/frappe/auth.py b/frappe/auth.py index 0597e7d59e..2f4ae51f24 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -13,6 +13,8 @@ from frappe import conf from frappe.sessions import Session, clear_sessions, delete_session from frappe.modules.patch_handler import check_session_stopped +from urllib import quote + class HTTPRequest: def __init__(self): # Get Environment variables @@ -20,6 +22,8 @@ class HTTPRequest: if self.domain and self.domain.startswith('www.'): self.domain = self.domain[4:] + frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') \ + or frappe.get_request_header('X-Forwarded-For') or '127.0.0.1' # language self.set_lang(frappe.get_request_header('HTTP_ACCEPT_LANGUAGE')) @@ -157,7 +161,7 @@ class LoginManager: ip_list = [i.strip() for i in ip_list] for ip in ip_list: - if frappe.get_request_header('REMOTE_ADDR', '').startswith(ip) or frappe.get_request_header('X-Forwarded-For', '').startswith(ip): + if frappe.local.request_ip.startswith(ip): return frappe.throw(_("Not allowed from this IP Address"), frappe.AuthenticationError) @@ -223,7 +227,8 @@ class CookieManager: def flush_cookies(self, response): for key, opts in self.cookies.items(): - response.set_cookie(key, opts.get("value"), expires=opts.get("expires")) + response.set_cookie(key, quote((opts.get("value") or "").encode('utf-8')), + expires=opts.get("expires")) # expires yesterday! expires = datetime.datetime.now() + datetime.timedelta(days=-1) diff --git a/frappe/boot.py b/frappe/boot.py index 2bb61eefc4..6623abcc4d 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -9,6 +9,7 @@ bootstrap client session import frappe import frappe.defaults import frappe.widgets.page +from frappe.utils import get_gravatar def get_bootinfo(): """build and return boot info""" @@ -23,8 +24,6 @@ def get_bootinfo(): # system info bootinfo['sysdefaults'] = frappe.defaults.get_defaults() bootinfo['server_date'] = frappe.utils.nowdate() - bootinfo["send_print_in_body_and_attachment"] = frappe.db.get_value("Outgoing Email Settings", - None, "send_print_in_body_and_attachment") if frappe.session['user'] != 'Guest': bootinfo['user_info'] = get_fullnames() @@ -37,6 +36,8 @@ def get_bootinfo(): bootinfo.modules.update(frappe.get_attr(app + ".config.desktop.get_data")() or {}) except ImportError: pass + except AttributeError: + pass bootinfo.module_app = frappe.local.module_app bootinfo.hidden_modules = frappe.db.get_global("hidden_modules") @@ -48,7 +49,9 @@ def get_bootinfo(): add_home_page(bootinfo, doclist) add_allowed_pages(bootinfo) load_translations(bootinfo) + add_timezone_info(bootinfo) load_conf_settings(bootinfo) + load_print(bootinfo, doclist) # ipinfo if frappe.session['data'].get('ipinfo'): @@ -63,6 +66,8 @@ def get_bootinfo(): if bootinfo.lang: bootinfo.lang = unicode(bootinfo.lang) + bootinfo.error_report_email = frappe.get_hooks("error_report_email") + return bootinfo def load_conf_settings(bootinfo): @@ -72,14 +77,23 @@ def load_conf_settings(bootinfo): def add_allowed_pages(bootinfo): roles = frappe.get_roles() - bootinfo.page_info = dict(frappe.db.sql("""select distinct parent, modified from `tabPage Role` - where role in (%s)""" % ', '.join(['%s']*len(roles)), roles)) + bootinfo.page_info = {} + for p in frappe.db.sql("""select distinct + tabPage.name, tabPage.modified, tabPage.title + from `tabPage Role`, `tabPage` + where `tabPage Role`.role in (%s) + and `tabPage Role`.parent = `tabPage`.name""" % ', '.join(['%s']*len(roles)), + roles, as_dict=True): + + bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title} # pages where role is not set are also allowed - bootinfo.page_info.update(dict(frappe.db.sql("""select parent, modified + for p in frappe.db.sql("""select name, modified, title from `tabPage` where (select count(*) from `tabPage Role` - where `tabPage Role`.parent=tabPage.name) = 0"""))) + where `tabPage Role`.parent=tabPage.name) = 0""", as_dict=1): + + bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title} def load_translations(bootinfo): if frappe.local.lang != 'en': @@ -90,18 +104,15 @@ def get_fullnames(): """map of user fullnames""" ret = frappe.db.sql("""select name, concat(ifnull(first_name, ''), - if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')), - user_image, gender, email - from tabUser where ifnull(enabled, 0)=1""", as_list=1) + if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')) as fullname, + user_image as image, gender, email + from tabUser where ifnull(enabled, 0)=1""", as_dict=1) + d = {} for r in ret: - if not r[2]: - r[2] = '/assets/frappe/images/ui/avatar.png' - else: - r[2] = r[2] - - d[r[0]]= {'fullname': r[1], 'image': r[2], 'gender': r[3], - 'email': r[4] or r[0]} + if not r.image: + r.image = get_gravatar() + d[r.name] = r return d @@ -113,16 +124,13 @@ def get_startup_js(): def get_user(bootinfo): """get user info""" - bootinfo['user'] = frappe.user.load_user() + bootinfo.user = frappe.user.load_user() def add_home_page(bootinfo, docs): """load home page""" - if frappe.session.user=="Guest": return - home_page = frappe.db.get_default("desktop:home_page") - try: page = frappe.widgets.page.get(home_page) except (frappe.DoesNotExistError, frappe.PermissionError): @@ -131,3 +139,22 @@ def add_home_page(bootinfo, docs): bootinfo['home_page'] = page.name docs.append(page) + +def add_timezone_info(bootinfo): + user = bootinfo.user.get("time_zone") + system = bootinfo.sysdefaults.get("time_zone") + if user and user != system: + import frappe.utils.momentjs + bootinfo.timezone_info = {"zones":{}, "rules":{}, "links":{}} + + frappe.utils.momentjs.update(user, bootinfo.timezone_info) + frappe.utils.momentjs.update(system, bootinfo.timezone_info) + +def load_print(bootinfo, doclist): + print_settings = frappe.db.get_singles_dict("Print Settings") + print_settings.doctype = ":Print Settings" + doclist.append(print_settings) + load_print_css(bootinfo, print_settings) + +def load_print_css(bootinfo, print_settings): + bootinfo.print_css = frappe.get_attr("frappe.templates.pages.print.get_print_style")(print_settings.print_style or "Modern") diff --git a/frappe/build.py b/frappe/build.py index 3f49530dfa..13373ae897 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -1,5 +1,5 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# MIT License. See license.txt from __future__ import unicode_literals from frappe.utils.minify import JavascriptMinify @@ -11,12 +11,12 @@ Build the `public` folders and setup languages import os, sys, frappe, json, shutil from cssmin import cssmin -def bundle(no_compress, make_copy=False): +def bundle(no_compress, make_copy=False, verbose=False): """concat / minify js files""" # build js files make_asset_dirs(make_copy=make_copy) - build(no_compress) - + build(no_compress, verbose) + def watch(no_compress): """watch and rebuild if necessary""" import time @@ -25,18 +25,18 @@ def watch(no_compress): while True: if files_dirty(): build(no_compress=True) - + time.sleep(3) def make_asset_dirs(make_copy=False): assets_path = os.path.join(frappe.local.sites_path, "assets") for dir_path in [ - os.path.join(assets_path, 'js'), + os.path.join(assets_path, 'js'), os.path.join(assets_path, 'css')]: - + if not os.path.exists(dir_path): os.makedirs(dir_path) - + # symlink app/public > assets/app for app_name in frappe.get_all_apps(True): pymodule = frappe.get_module(app_name) @@ -49,11 +49,11 @@ def make_asset_dirs(make_copy=False): else: os.symlink(os.path.abspath(source), target) -def build(no_compress=False): +def build(no_compress=False, verbose=False): assets_path = os.path.join(frappe.local.sites_path, "assets") for target, sources in get_build_maps().iteritems(): - pack(os.path.join(assets_path, target), sources, no_compress) + pack(os.path.join(assets_path, target), sources, no_compress, verbose) shutil.copy(os.path.join(os.path.dirname(os.path.abspath(frappe.__file__)), 'data', 'languages.txt'), frappe.local.sites_path) # reset_app_html() @@ -79,39 +79,52 @@ def get_build_maps(): else: s = os.path.join(app_path, source) source_paths.append(s) - + build_maps[target] = source_paths except Exception, e: print path raise - + return build_maps timestamps = {} -def pack(target, sources, no_compress): +def pack(target, sources, no_compress, verbose): from cStringIO import StringIO - + outtype, outtxt = target.split(".")[-1], '' jsm = JavascriptMinify() - + for f in sources: suffix = None if ':' in f: f, suffix = f.split(':') - if not os.path.exists(f) or os.path.isdir(f): continue + if not os.path.exists(f) or os.path.isdir(f): + print "did not find " + f + continue timestamps[f] = os.path.getmtime(f) try: - with open(f, 'r') as sourcefile: + with open(f, 'r') as sourcefile: data = unicode(sourcefile.read(), 'utf-8', errors='ignore') - - if outtype=="js" and (not no_compress) and suffix!="concat" and (".min." not in f): + + extn = f.rsplit(".", 1)[1] + + if outtype=="js" and extn=="js" and (not no_compress) and suffix!="concat" and (".min." not in f): tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO() jsm.minify(tmpin, tmpout) - outtxt += unicode(tmpout.getvalue() or '', 'utf-8').strip('\n') + ';' + minified = tmpout.getvalue() + outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';' + + if verbose: + print "{0}: {1}k".format(f, int(len(minified) / 1024)) + elif outtype=="js" and extn=="html": + # add to frappe.templates + content = data.replace("\n", " ").replace("'", "\'") + outtxt += """frappe.templates["{key}"] = '{content}';\n""".format(\ + key=f.rsplit("/", 1)[1][:-5], content=content) else: outtxt += ('\n/*\n *\t%s\n */' % f) outtxt += '\n' + data + '\n' - + except Exception, e: print "--Error in:" + f + "--" print frappe.get_traceback() @@ -119,10 +132,10 @@ def pack(target, sources, no_compress): if not no_compress and outtype == 'css': pass #outtxt = cssmin(outtxt) - + with open(target, 'w') as f: f.write(outtxt.encode("utf-8")) - + print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))) def files_dirty(): @@ -135,4 +148,4 @@ def files_dirty(): return True else: return False - + diff --git a/frappe/cli.py b/frappe/cli.py index e961a83b77..b42e887a98 100755 --- a/frappe/cli.py +++ b/frappe/cli.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import os import subprocess import frappe +from frappe.utils import cint site_arg_optional = ['serve', 'build', 'watch', 'celery', 'resize_images'] @@ -35,7 +36,10 @@ def main(): args = parsed_args.copy() args["site"] = site frappe.init(site, sites_path=sites_path) - return run(fn, args) + ret = run(fn, args) + if ret: + # if there's a return value, it's an error, so quit + return ret else: site = get_site(parsed_args) if fn not in site_arg_optional and not site: @@ -128,18 +132,20 @@ def setup_install(parser): help="Make a new application with boilerplate") parser.add_argument("--install", metavar="DB-NAME", nargs=1, help="Install a new db") + parser.add_argument("--root_password", metavar="ROOT-PASSWD", + help="MariaDB root password") parser.add_argument("--sites_path", metavar="SITES_PATH", nargs=1, help="path to directory with sites") parser.add_argument("--install_app", metavar="APP-NAME", nargs=1, help="Install a new app") parser.add_argument("--add_to_installed_apps", metavar="APP-NAME", nargs="*", help="Add these app(s) to Installed Apps") - parser.add_argument("--root-password", nargs=1, - help="Root password for new app") parser.add_argument("--reinstall", default=False, action="store_true", help="Install a fresh app in db_name specified in conf.py") parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2, help="Restore from an sql file") + parser.add_argument("--with_scheduler_enabled", default=False, action="store_true", + help="Enable scheduler on restore") parser.add_argument("--add_system_manager", nargs="+", metavar=("EMAIL", "[FIRST-NAME] [LAST-NAME]"), help="Add a user with all roles") @@ -201,8 +207,10 @@ def setup_utilities(parser): parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"), help="Create new conf.py file") parser.add_argument("--make_custom_server_script", nargs=1, metavar="DOCTYPE", - help="Create new conf.py file") - parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs=1, + help="Create new custome server script") + parser.add_argument("--init_list", nargs=1, metavar="DOCTYPE", + help="Create new list.js and list.html files") + parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs="*", help="Set administrator password") parser.add_argument("--request", metavar='URL-ARGS', nargs=1, help="Run request as admin") parser.add_argument("--mysql", action="store_true", help="get mysql shell for a site") @@ -224,8 +232,8 @@ def setup_utilities(parser): # clear parser.add_argument("--clear_web", default=False, action="store_true", help="Clear website cache") - parser.add_argument("--build_sitemap", default=False, action="store_true", - help="Build Website Route") + parser.add_argument("--build_website", default=False, action="store_true", + help="Sync statics and clear cache") parser.add_argument("--sync_statics", default=False, action="store_true", help="Sync files from templates/statics to Web Pages") parser.add_argument("--clear_cache", default=False, action="store_true", @@ -242,6 +250,11 @@ def setup_utilities(parser): parser.add_argument("--run_scheduler_event", nargs=1, metavar="all | daily | weekly | monthly", help="Run a scheduler event") + parser.add_argument("--enable_scheduler", default=False, action="store_true", + help="Enable scheduler") + parser.add_argument("--disable_scheduler", default=False, action="store_true", + help="Disable scheduler") + # replace parser.add_argument("--replace", nargs=3, @@ -280,21 +293,52 @@ def use(sites_path): sitefile.write(frappe.local.site) # install -@cmd -def install(db_name, root_login="root", root_password=None, source_sql=None, - admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False): +def _install(db_name, root_login="root", root_password=None, source_sql=None, + admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False, install_apps=None): + from frappe.installer import install_db, install_app, make_site_dirs + import frappe.utils.scheduler + verbose = not quiet + # enable scheduler post install? + enable_scheduler = _is_scheduler_enabled() + install_db(root_login=root_login, root_password=root_password, db_name=db_name, source_sql=source_sql, admin_password = admin_password, verbose=verbose, force=force, site_config=site_config, reinstall=reinstall) make_site_dirs() install_app("frappe", verbose=verbose, set_as_patched=not source_sql) + if frappe.conf.get("install_apps"): for app in frappe.conf.install_apps: install_app(app, verbose=verbose, set_as_patched=not source_sql) + + if install_apps: + for app in install_apps: + install_app(app, verbose=verbose, set_as_patched=not source_sql) + + frappe.utils.scheduler.toggle_scheduler(enable_scheduler) + scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled" + print "*** Scheduler is", scheduler_status, "***" + +@cmd +def install(db_name, root_login="root", root_password=None, source_sql=None, + admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False, install_apps=None): + _install(db_name, root_login, root_password, source_sql, admin_password, force, site_config, reinstall, quiet, install_apps) frappe.destroy() +def _is_scheduler_enabled(): + enable_scheduler = False + try: + frappe.connect() + enable_scheduler = cint(frappe.db.get_default("enable_scheduler")) + except: + pass + finally: + frappe.db.close() + + return enable_scheduler + @cmd def install_app(app_name, quiet=False): verbose = not quiet @@ -310,7 +354,7 @@ def add_to_installed_apps(*apps): all_apps = frappe.get_all_apps(with_frappe=True) for each in apps: if each in all_apps: - add_to_installed_apps(each, rebuild_sitemap=False) + add_to_installed_apps(each, rebuild_website=False) frappe.destroy() @cmd @@ -318,18 +362,27 @@ def reinstall(quiet=False): verbose = not quiet try: frappe.connect() + installed = frappe.get_installed_apps() frappe.clear_cache() except: - pass + installed = [] finally: frappe.db.close() - install(db_name=frappe.conf.db_name, verbose=verbose, force=True, reinstall=True) + install(db_name=frappe.conf.db_name, verbose=verbose, force=True, reinstall=True, install_apps=installed) @cmd -def restore(db_name, source_sql, force=False, quiet=False): - verbose = not quiet - install(db_name, source_sql=source_sql, verbose=verbose, force=force) +def restore(db_name, source_sql, force=False, quiet=False, with_scheduler_enabled=False): + import frappe.utils.scheduler + _install(db_name, source_sql=source_sql, quiet=quiet, force=force) + + try: + frappe.connect() + frappe.utils.scheduler.toggle_scheduler(with_scheduler_enabled) + scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled" + print "*** Scheduler is", scheduler_status, "***" + finally: + frappe.destroy() @cmd def add_system_manager(email, first_name=None, last_name=None): @@ -353,13 +406,12 @@ def update(remote=None, branch=None, reload_gunicorn=False): subprocess.check_output("killall -HUP gunicorn".split()) @cmd -def latest(rebuild_website_config=True, quiet=False): +def latest(rebuild_website=True, quiet=False): import frappe.modules.patch_handler import frappe.model.sync - from frappe.website import rebuild_config from frappe.utils.fixtures import sync_fixtures import frappe.translate - from frappe.website import statics + from frappe.core.doctype.notification_count.notification_count import clear_notifications verbose = not quiet @@ -370,16 +422,13 @@ def latest(rebuild_website_config=True, quiet=False): frappe.modules.patch_handler.run_all() # sync frappe.model.sync.sync_all(verbose=verbose) + frappe.translate.clear_cache() sync_fixtures() - statics.sync().start() - # build website config if any changes in templates etc. - if rebuild_website_config: - rebuild_config() - - - frappe.translate.clear_cache() + clear_notifications() + if rebuild_website: + build_website() finally: frappe.destroy() @@ -419,10 +468,10 @@ def reload_doc(module, doctype, docname, force=False): frappe.destroy() @cmd -def build(make_copy=False): +def build(make_copy=False, verbose=False): import frappe.build import frappe - frappe.build.bundle(False, make_copy=make_copy) + frappe.build.bundle(False, make_copy=make_copy, verbose=verbose) @cmd def watch(): @@ -487,12 +536,19 @@ def make_custom_server_script(doctype): make_custom_server_script_file(doctype) frappe.destroy() +@cmd +def init_list(doctype): + import frappe.core.doctype.doctype.doctype + frappe.core.doctype.doctype.doctype.init_list(doctype) + # clear @cmd def clear_cache(): import frappe.sessions + from frappe.core.doctype.notification_count.notification_count import clear_notifications frappe.connect() frappe.clear_cache() + clear_notifications() frappe.destroy() @cmd @@ -511,17 +567,19 @@ def clear_all_sessions(): frappe.destroy() @cmd -def build_sitemap(): - from frappe.website import rebuild_config +def build_website(verbose=False): + from frappe.website import render, statics frappe.connect() - rebuild_config() + render.clear_cache() + statics.sync(verbose=verbose).start() + frappe.db.commit() frappe.destroy() @cmd -def sync_statics(): +def sync_statics(force=False): from frappe.website import statics frappe.connect() - statics.sync_statics() + statics.sync_statics(rebuild = force) frappe.db.commit() frappe.destroy() @@ -558,6 +616,24 @@ def run_scheduler_event(event, force=False): frappe.utils.scheduler.trigger(frappe.local.site, event, now=force) frappe.destroy() +@cmd +def enable_scheduler(): + import frappe.utils.scheduler + frappe.connect() + frappe.utils.scheduler.enable_scheduler() + frappe.db.commit() + print "Enabled" + frappe.destroy() + +@cmd +def disable_scheduler(): + import frappe.utils.scheduler + frappe.connect() + frappe.utils.scheduler.disable_scheduler() + frappe.db.commit() + print "Disabled" + frappe.destroy() + # replace @cmd def replace(search_regex, replacement, extn, force=False): @@ -665,8 +741,13 @@ def checkout(branch): git(("checkout", branch)) @cmd -def set_admin_password(admin_password): +def set_admin_password(admin_password=None): import frappe + import getpass + + while not admin_password: + admin_password = getpass.getpass("Administrator's password: ") + frappe.connect() frappe.db.sql("""update __Auth set `password`=password(%s) where user='Administrator'""", (admin_password,)) @@ -706,7 +787,7 @@ def smtp_debug_server(): os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"]) @cmd -def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None): +def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None, force=False): import frappe.test_runner from frappe.utils import sel @@ -715,7 +796,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driv ret = 1 try: ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose, - tests=tests) + tests=tests, force=force) if len(ret.failures) == 0 and len(ret.errors) == 0: ret = 0 finally: diff --git a/frappe/client.py b/frappe/client.py index a5add41e9c..dd80dc6026 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -42,7 +42,11 @@ def set_value(doctype, name, fieldname, value): child.set(fieldname, value) else: doc = frappe.get_doc(doctype, name) - doc.set(fieldname, value) + df = doc.meta.get_field(fieldname) + if df.fieldtype == "Read Only" or df.read_only: + frappe.throw(_("Can not edit Read Only fields")) + else: + doc.set(fieldname, value) doc.save() @@ -88,7 +92,7 @@ def submit(doclist): doclistobj = frappe.get_doc(doclist) doclistobj.submit() - return doclist.as_dict() + return doclistobj.as_dict() @frappe.whitelist() def cancel(doctype, name): @@ -108,8 +112,9 @@ def set_default(key, value, parent=None): frappe.clear_cache(user=frappe.session.user) @frappe.whitelist() -def make_width_property_setter(): - doc = json.loads(frappe.form_dict) +def make_width_property_setter(doc): + if isinstance(doc, basestring): + doc = json.loads(doc) if doc["doctype"]=="Property Setter" and doc["property"]=="width": frappe.get_doc(doc).insert(ignore_permissions = True) @@ -139,6 +144,8 @@ def has_permission(doctype, docname, perm_type="read"): @frappe.whitelist() def get_js(src): + if src[0]=="/": + src = src[1:] contentpath = os.path.join(frappe.local.sites_path, src) with open(contentpath, "r") as srcfile: code = frappe.utils.cstr(srcfile.read()) diff --git a/frappe/config/setup.py b/frappe/config/setup.py index 9a72577d42..389757e632 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -20,17 +20,25 @@ def get_data(): { "type": "page", "name": "permission-manager", - "label": "Permission Manager", + "label": _("Role Permissions Manager"), "icon": "icon-lock", "description": _("Set Permissions on Document Types and Roles") }, { "type": "page", - "name": "user-properties", - "label": _("User Permission Restrictions"), - "icon": "icon-user", - "description": _("Set Defaults and Restrictions for Users") - }, + "name": "user-permissions", + "label": _("User Permissions Manager"), + "icon": "icon-shield", + "description": _("Set Permissions per User") + }, + { + "type": "report", + "is_query_report": True, + "doctype": "User", + "icon": "icon-eye-open", + "name": "Permitted Documents For User", + "description": _("Check which Documents are readable by a User") + } ] }, { @@ -113,12 +121,27 @@ def get_data(): "name": "Outgoing Email Settings", "description": _("Set outgoing mail server.") }, + { + "type": "doctype", + "name": "Email Alert", + "description": _("Setup Email Alert based on various criteria.") + }, + { + "type": "doctype", + "name": "Standard Reply", + "description": _("Standard replies to common queries.") + }, ] }, { "label": _("Printing and Branding"), "icon": "icon-print", "items": [ + { + "type": "doctype", + "name": "Print Settings", + "description": _("Set default format, page size, print style etc.") + }, { "type": "doctype", "name": "Print Format", @@ -168,11 +191,6 @@ def get_data(): "description": _("Send download link of a recent backup to System Managers"), "hide_count": True }, - { - "type": "doctype", - "name": "Social Login Keys", - "description": _("Enter keys to enable login via Facebook, Google, GitHub."), - }, { "type": "doctype", "name": "Backup Manager", diff --git a/frappe/config/website.py b/frappe/config/website.py index 4adc17afd9..ad2fba13b1 100644 --- a/frappe/config/website.py +++ b/frappe/config/website.py @@ -16,6 +16,11 @@ def get_data(): "name": "Blog Post", "description": _("Single Post (article)."), }, + { + "type": "doctype", + "name": "Web Form", + "description": _("User editable form on Website."), + }, { "type": "doctype", "name": "Blogger", @@ -47,13 +52,6 @@ def get_data(): "name": "Website Settings", "description": _("Setup of top navigation bar, footer and logo."), }, - { - "type": "page", - "name":"sitemap-browser", - "label": _("Sitemap Browser"), - "description": _("View or manage Website Route tree."), - "icon": "icon-sitemap" - }, { "type": "doctype", "name": "Style Settings", @@ -89,6 +87,11 @@ def get_data(): "name": "Website Page Permission", "description": _("Define read, write, admin permissions for a Website Page."), }, + { + "type": "doctype", + "name": "Social Login Keys", + "description": _("Enter keys to enable login via Facebook, Google, GitHub."), + } ] }, ] diff --git a/frappe/core/doctype/bulk_email/bulk_email.json b/frappe/core/doctype/bulk_email/bulk_email.json index 782686f625..7622229816 100644 --- a/frappe/core/doctype/bulk_email/bulk_email.json +++ b/frappe/core/doctype/bulk_email/bulk_email.json @@ -1,5 +1,6 @@ { - "creation": "2012-08-02 15:17:28.000000", + "autoname": "hash", + "creation": "2012-08-02 15:17:28", "description": "Bulk Email records.", "docstatus": 0, "doctype": "DocType", @@ -8,30 +9,35 @@ { "fieldname": "sender", "fieldtype": "Data", + "in_list_view": 1, "label": "Sender", "permlevel": 0 }, { "fieldname": "recipient", "fieldtype": "Data", + "in_list_view": 1, "label": "Recipient", "permlevel": 0 }, { "fieldname": "message", "fieldtype": "Long Text", + "in_list_view": 1, "label": "Message", "permlevel": 0 }, { "fieldname": "status", "fieldtype": "Data", + "in_list_view": 1, "label": "Status", "permlevel": 0 }, { "fieldname": "error", "fieldtype": "Text", + "in_list_view": 1, "label": "Error", "permlevel": 0 }, @@ -56,7 +62,7 @@ "icon": "icon-envelope", "idx": 1, "in_create": 1, - "modified": "2014-02-12 21:11:05.000000", + "modified": "2014-06-03 02:22:18.860832", "modified_by": "Administrator", "module": "Core", "name": "Bulk Email", @@ -67,8 +73,9 @@ "permlevel": 0, "print": 1, "read": 1, + "report": 1, "role": "System Manager" } ], - "read_only": 1 + "read_only": 0 } \ No newline at end of file diff --git a/frappe/core/doctype/comment/comment.json b/frappe/core/doctype/comment/comment.json index 9dcff41adb..ab96742429 100644 --- a/frappe/core/doctype/comment/comment.json +++ b/frappe/core/doctype/comment/comment.json @@ -1,22 +1,31 @@ { - "autoname": "CWR/.#####", - "creation": "2012-08-08 10:40:11.000000", + "autoname": "hash", + "creation": "2012-08-08 10:40:11", "docstatus": 0, "doctype": "DocType", "fields": [ { "fieldname": "comment", "fieldtype": "Text", + "in_list_view": 1, "label": "Comment", "no_copy": 0, "oldfieldname": "comment", "oldfieldtype": "Text", "permlevel": 0, + "reqd": 1, "search_index": 0 }, + { + "fieldname": "comment_type", + "fieldtype": "Data", + "label": "Comment Type", + "permlevel": 0 + }, { "fieldname": "comment_by", "fieldtype": "Data", + "in_list_view": 1, "label": "Comment By", "no_copy": 0, "oldfieldname": "comment_by", @@ -27,6 +36,7 @@ { "fieldname": "comment_by_fullname", "fieldtype": "Data", + "in_list_view": 1, "label": "Comment By Fullname", "no_copy": 0, "oldfieldname": "comment_by_fullname", @@ -37,6 +47,7 @@ { "fieldname": "comment_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Comment Date", "no_copy": 0, "oldfieldname": "comment_date", @@ -47,6 +58,7 @@ { "fieldname": "comment_time", "fieldtype": "Data", + "in_list_view": 1, "label": "Comment Time", "no_copy": 0, "oldfieldname": "comment_time", @@ -94,7 +106,7 @@ "icon": "icon-comments", "idx": 1, "issingle": 0, - "modified": "2014-01-24 13:00:20.000000", + "modified": "2014-08-22 05:24:28.072749", "modified_by": "Administrator", "module": "Core", "name": "Comment", diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index 7d64602ca9..3cd6b3de11 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -19,7 +19,7 @@ class Comment(Document): self.update_comment_in_doc() def update_comment_in_doc(self): - if self.comment_doctype and self.comment_docname and self.comment: + if self.comment_doctype and self.comment_docname and self.comment and self.comment_type=="Comment": try: _comments = self.get_comments_from_parent() updated = False @@ -59,14 +59,14 @@ class Comment(Document): frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (self.comment_doctype, "%s", "%s"), (json.dumps(_comments), self.comment_docname)) - # clear parent cache if route exists: - route = frappe.db.get_value("Website Route", {"ref_doctype": self.comment_doctype, - "docname": self.comment_docname}) - - if route: - clear_cache(route) + comment_doc = frappe.get_doc(self.comment_doctype, self.comment_docname) + if getattr(comment_doc, "get_route", None): + clear_cache(comment_doc.get_route()) def on_trash(self): + if (self.comment_type or "Comment") != "Comment": + frappe.only_for("System Manager") + _comments = self.get_comments_from_parent() for c in _comments: if c.get("name")==self.name: diff --git a/frappe/core/doctype/comment/test_records.json b/frappe/core/doctype/comment/test_records.json new file mode 100644 index 0000000000..5552a3fcec --- /dev/null +++ b/frappe/core/doctype/comment/test_records.json @@ -0,0 +1,7 @@ +[ + { + "doctype": "Comment", + "name": "_Test Comment 1", + "comment": "test comment" + } +] diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index 508d44c62a..3d66f81f02 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -1,8 +1,7 @@ { - "allow_attach": 1, - "allow_import": 1, + "allow_import": 1, "autoname": "naming_series:", - "creation": "2013-01-29 10:47:14.000000", + "creation": "2013-01-29 10:47:14", "description": "Keep a track of all communications", "docstatus": 0, "doctype": "DocType", @@ -122,6 +121,7 @@ "default": "__user", "fieldname": "user", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "User", "options": "User", "permlevel": 0, @@ -154,7 +154,7 @@ "idx": 1, "in_dialog": 0, "issingle": 0, - "modified": "2014-01-24 13:01:25.000000", + "modified": "2014-08-14 09:39:23.219125", "modified_by": "Administrator", "module": "Core", "name": "Communication", @@ -162,7 +162,7 @@ "permissions": [ { "amend": 0, - "cancel": 0, + "apply_user_permissions": 0, "create": 1, "delete": 1, "email": 1, @@ -176,7 +176,6 @@ }, { "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, @@ -190,7 +189,7 @@ }, { "amend": 0, - "cancel": 0, + "apply_user_permissions": 0, "create": 1, "delete": 1, "email": 1, @@ -203,7 +202,6 @@ "write": 1 }, { - "cancel": 0, "create": 1, "delete": 1, "email": 1, @@ -216,7 +214,6 @@ "write": 1 }, { - "cancel": 0, "create": 1, "delete": 1, "email": 1, diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index d55d89cdea..47546c221b 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -16,14 +16,17 @@ from frappe import _ from frappe.model.document import Document class Communication(Document): + def validate(self): + if not self.parentfield: + self.parentfield = "communications" + def get_parent_doc(self): return frappe.get_doc(self.parenttype, self.parent) def update_parent(self): """update status of parent Lead or Contact based on who is replying""" - observer = getattr(self.get_parent_doc(), "on_communication", None) - if observer: - observer() + parent_doc = self.get_parent_doc() + parent_doc.run_method("on_communication") def on_update(self): self.update_parent() @@ -31,7 +34,7 @@ class Communication(Document): @frappe.whitelist() def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, recipients=None, communication_medium="Email", send_email=False, - print_html=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): + print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): if doctype and name and not frappe.has_permission(doctype, "email", name): raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format( @@ -39,12 +42,12 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = _make(doctype=doctype, name=name, content=content, subject=subject, sent_or_received=sent_or_received, sender=sender, recipients=recipients, communication_medium=communication_medium, send_email=send_email, - print_html=print_html, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead, + print_html=print_html, print_format=print_format, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead, date=date) def _make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, recipients=None, communication_medium="Email", send_email=False, - print_html=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): + print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None): # add to Communication sent_via = None @@ -86,7 +89,7 @@ def _make(doctype=None, name=None, content=None, subject=None, sent_or_received if send_email: d = comm - send_comm_email(d, name, sent_via, print_html, attachments, send_me_a_copy) + send_comm_email(d, name, sent_via, print_html, print_format, attachments, send_me_a_copy) @frappe.whitelist() def get_customer_supplier(args=None): @@ -107,9 +110,10 @@ def get_customer_supplier(args=None): } return {} -def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', send_me_a_copy=False): +def send_comm_email(d, name, sent_via=None, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False): footer = None + if sent_via: if hasattr(sent_via, "get_sender"): d.sender = sent_via.get_sender(d) or d.sender @@ -118,21 +122,16 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s if hasattr(sent_via, "get_content"): d.content = sent_via.get_content(d) - footer = set_portal_link(sent_via, d) - - send_print_in_body = frappe.db.get_value("Outgoing Email Settings", None, "send_print_in_body_and_attachment") - if print_html and not send_print_in_body: - d.content += "

Please see attachment for document details.

" + footer = "
" + set_portal_link(sent_via, d) mail = get_email(d.recipients, sender=d.sender, subject=d.subject, - msg=d.content, footer=footer, print_html=print_html if send_print_in_body else None) + msg=d.content, footer=footer) if send_me_a_copy: mail.cc.append(frappe.db.get_value("User", frappe.session.user, "email")) - if print_html: - print_html = scrub_urls(print_html) - mail.add_attachment(name.replace(' ','').replace('/','-') + '.html', print_html) + if print_html or print_format: + attach_print(mail, sent_via, print_html, print_format) for a in json.loads(attachments): try: @@ -142,10 +141,31 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s send(mail) +def attach_print(mail, sent_via, print_html, print_format): + name = sent_via.name + if not print_html and print_format: + print_html = frappe.get_print_format(sent_via.doctype, sent_via.name, print_format) + + print_settings = frappe.db.get_singles_dict("Print Settings") + send_print_as_pdf = cint(print_settings.send_print_as_pdf) + + if send_print_as_pdf: + try: + mail.add_pdf_attachment(name.replace(' ','').replace('/','-') + '.pdf', print_html) + except Exception: + frappe.msgprint(_("Error generating PDF, attachment sent as HTML")) + frappe.errprint(frappe.get_traceback()) + send_print_as_pdf = 0 + + if not send_print_as_pdf: + print_html = scrub_urls(print_html) + mail.add_attachment(name.replace(' ','').replace('/','-') + '.html', + print_html, 'text/html') + def set_portal_link(sent_via, comm): """set portal link in footer""" - footer = None + footer = "" if is_signup_enabled() and hasattr(sent_via, "get_portal_page"): portal_page = sent_via.get_portal_page() @@ -154,7 +174,7 @@ def set_portal_link(sent_via, comm): sent_via.get("contact_email")) in comm.recipients if is_valid_recipient: url = "%s/%s?name=%s" % (get_url(), portal_page, urllib.quote(sent_via.name)) - footer = """
- View this on our website""" % url + footer = """ +

View this on our website

""" % url return footer diff --git a/frappe/core/doctype/communication/communication_list.html b/frappe/core/doctype/communication/communication_list.html new file mode 100644 index 0000000000..14ec357e46 --- /dev/null +++ b/frappe/core/doctype/communication/communication_list.html @@ -0,0 +1,37 @@ +
+
+
+ {%= list.get_avatar_and_id(doc) %} + + + + + + + + + + +
+
+
+
+ + {%= doc.recipients %} + +
+
+
diff --git a/frappe/core/doctype/communication/communication_list.js b/frappe/core/doctype/communication/communication_list.js new file mode 100644 index 0000000000..c1695e7778 --- /dev/null +++ b/frappe/core/doctype/communication/communication_list.js @@ -0,0 +1,3 @@ +frappe.listview_settings['Communication'] = { + add_fields: ["sent_or_received", "recipients", "subject", "communication_medium"] +}; diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py new file mode 100644 index 0000000000..e3c73f7eb8 --- /dev/null +++ b/frappe/core/doctype/communication/test_communication.py @@ -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 diff --git a/frappe/core/doctype/communication/test_records.json b/frappe/core/doctype/communication/test_records.json new file mode 100644 index 0000000000..a69d3e9570 --- /dev/null +++ b/frappe/core/doctype/communication/test_records.json @@ -0,0 +1,10 @@ +[ + { + "doctype": "Communication", + "name": "_Test Communication 1", + "subject": "Test Subject", + "sent_or_received": "Received", + "parenttype": "User", + "parent": "Administrator" + } +] diff --git a/frappe/core/doctype/custom_field/custom_field.js b/frappe/core/doctype/custom_field/custom_field.js index 040cb10616..ec4eea4809 100644 --- a/frappe/core/doctype/custom_field/custom_field.js +++ b/frappe/core/doctype/custom_field/custom_field.js @@ -47,9 +47,18 @@ cur_frm.fields_dict['dt'].get_query = function(doc, dt, dn) { } cur_frm.cscript.fieldtype = function(doc, dt, dn) { - if(doc.fieldtype == 'Link') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter name of the document you want this field to be linked to in Options.
Eg.: Customer'; - else if(doc.fieldtype == 'Select') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter values in Options, with each option on a new line.
Eg.: Field: Country
Options:
China
India
United States

'; - 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.:
Option 1
Option 2
Option 3
'); + } 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 = ''; + } } diff --git a/frappe/core/doctype/custom_field/custom_field.json b/frappe/core/doctype/custom_field/custom_field.json index cb0c3ad2c1..712ba856f7 100644 --- a/frappe/core/doctype/custom_field/custom_field.json +++ b/frappe/core/doctype/custom_field/custom_field.json @@ -1,5 +1,5 @@ { - "creation": "2013-01-10 16:34:01.000000", + "creation": "2013-01-10 16:34:01", "description": "Adds a custom field to a DocType", "docstatus": 0, "doctype": "DocType", @@ -57,11 +57,21 @@ "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", + "options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", "permlevel": 0, "reqd": 1, "search_index": 0 }, + { + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n1\n2\n3\n4\n5\n6", + "permlevel": 0, + "precision": "" + }, { "fieldname": "options_help", "fieldtype": "HTML", @@ -143,9 +153,9 @@ }, { "depends_on": "eval:doc.fieldtype===\"Link\"", - "fieldname": "ignore_restrictions", + "fieldname": "ignore_user_permissions", "fieldtype": "Check", - "label": "Ignore Restrictions", + "label": "Ignore User Permissions", "permlevel": 0 }, { @@ -257,7 +267,7 @@ ], "icon": "icon-glass", "idx": 1, - "modified": "2014-01-20 17:48:31.000000", + "modified": "2014-09-05 07:41:13.076820", "modified_by": "Administrator", "module": "Core", "name": "Custom Field", diff --git a/frappe/core/doctype/custom_field/custom_field.py b/frappe/core/doctype/custom_field/custom_field.py index 46b44d0e7d..852786f914 100644 --- a/frappe/core/doctype/custom_field/custom_field.py +++ b/frappe/core/doctype/custom_field/custom_field.py @@ -33,10 +33,10 @@ class CustomField(Document): # validate field from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype - validate_fields_for_doctype(self.dt) - frappe.clear_cache(doctype=self.dt) + validate_fields_for_doctype(self.dt) + # create property setter to emulate insert after self.create_property_setter() @@ -78,22 +78,27 @@ class CustomField(Document): @frappe.whitelist() def get_fields_label(doctype=None): - return [{"value": df.fieldname, "label": _(df.label)} for df in frappe.get_meta(doctype).get("fields")] + return [{"value": df.fieldname or "", "label": _(df.label or "")} for df in frappe.get_meta(doctype).get("fields")] def create_custom_field_if_values_exist(doctype, df): df = frappe._dict(df) if df.fieldname in frappe.db.get_table_columns(doctype) and \ frappe.db.sql("""select count(*) from `tab{doctype}` - where ifnull({fieldname},'')!=''""".format(doctype=doctype, fieldname=df.fieldname))[0][0] and \ - not frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df.fieldname}): - frappe.get_doc({ - "doctype":"Custom Field", - "dt": doctype, - "permlevel": df.permlevel or 0, - "label": df.label, - "fieldname": df.fieldname, - "fieldtype": df.fieldtype, - "options": df.options, - "insert_after": df.insert_after - }).insert() - + where ifnull({fieldname},'')!=''""".format(doctype=doctype, fieldname=df.fieldname))[0][0]: + + create_custom_field(doctype, df) + + +def create_custom_field(doctype, df): + if not frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df.fieldname}): + frappe.get_doc({ + "doctype":"Custom Field", + "dt": doctype, + "permlevel": df.get("permlevel") or 0, + "label": df.get("label"), + "fieldname": df.get("fieldname"), + "fieldtype": df.get("fieldtype"), + "options": df.get("options"), + "insert_after": df.get("insert_after"), + "print_hide": df.get("print_hide") + }).insert() diff --git a/frappe/core/doctype/custom_field/test_custom_field.py b/frappe/core/doctype/custom_field/test_custom_field.py new file mode 100644 index 0000000000..ad4def6175 --- /dev/null +++ b/frappe/core/doctype/custom_field/test_custom_field.py @@ -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 diff --git a/frappe/core/doctype/custom_field/test_records.json b/frappe/core/doctype/custom_field/test_records.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/frappe/core/doctype/custom_field/test_records.json @@ -0,0 +1 @@ +[] diff --git a/frappe/core/doctype/custom_script/custom_script.json b/frappe/core/doctype/custom_script/custom_script.json index 95c302f1f3..8ad35d8c86 100644 --- a/frappe/core/doctype/custom_script/custom_script.json +++ b/frappe/core/doctype/custom_script/custom_script.json @@ -1,6 +1,6 @@ { "autoname": "CustomScript.####", - "creation": "2013-01-10 16:34:01.000000", + "creation": "2013-01-10 16:34:01", "description": "Adds a custom script (client or server) to a DocType", "docstatus": 0, "doctype": "DocType", @@ -8,6 +8,7 @@ { "fieldname": "dt", "fieldtype": "Link", + "in_list_view": 1, "label": "DocType", "oldfieldname": "dt", "oldfieldtype": "Link", @@ -19,6 +20,7 @@ "fieldname": "script_type", "fieldtype": "Select", "hidden": 1, + "in_list_view": 1, "label": "Script Type", "oldfieldname": "script_type", "oldfieldtype": "Select", @@ -29,16 +31,24 @@ { "fieldname": "script", "fieldtype": "Code", + "in_list_view": 1, "label": "Script", "oldfieldname": "script", "oldfieldtype": "Code", "options": "Script", "permlevel": 0 + }, + { + "fieldname": "sample", + "fieldtype": "HTML", + "label": "Sample", + "options": "

Custom Script Help

\n

Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started

\n
\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\n
", + "permlevel": 0 } ], "icon": "icon-glass", "idx": 1, - "modified": "2014-01-20 17:48:31.000000", + "modified": "2014-06-19 06:55:02.522204", "modified_by": "Administrator", "module": "Core", "name": "Custom Script", diff --git a/frappe/core/doctype/customize_form/customize_form.js b/frappe/core/doctype/customize_form/customize_form.js index c3f7f15d3a..cdd0c72b00 100644 --- a/frappe/core/doctype/customize_form/customize_form.js +++ b/frappe/core/doctype/customize_form/customize_form.js @@ -58,11 +58,11 @@ frappe.ui.form.on("Customize Form", "refresh", function(frm) { frm.add_custom_button('Refresh Form', function() { frm.script_manager.trigger("doc_type"); - }, "icon-refresh"); + }, "icon-refresh", "btn-default"); frm.add_custom_button('Reset to defaults', function() { frappe.customize_form.confirm(__('Remove all customizations?'), frm); - }, "icon-eraser"); + }, "icon-eraser", "btn-default"); } // if(!frm.doc.doc_type) { @@ -73,8 +73,10 @@ frappe.ui.form.on("Customize Form", "refresh", function(frm) { // } if(frappe.route_options) { - frappe.model.set_value("Customize Form", null, "doc_type", frappe.route_options.doctype) - frappe.route_options = null; + setTimeout(function() { + frm.set_value("doc_type", frappe.route_options.doctype); + frappe.route_options = null; + }, 1000); } }); @@ -83,40 +85,25 @@ frappe.customize_form.confirm = function(msg, frm) { var d = new frappe.ui.Dialog({ title: 'Reset To Defaults', - width: 500 - }); - - $y(d.body, {padding: '32px', textAlign: 'center'}); - - $a(d.body, 'div', '', '', msg); - - var button_wrapper = $a(d.body, 'div'); - $y(button_wrapper, {paddingTop: '15px'}); - - var proceed_btn = $btn(button_wrapper, 'Proceed', function() { - return frm.call({ - doc: frm.doc, - method: "reset_to_defaults", - callback: function(r) { - if(r.exc) { - msgprint(r.exc); - } else { - frappe.customize_form.confirm.dialog.hide(); - frappe.customize_form.clear_locals_and_refresh(frm); + fields: [ + {fieldtype:"HTML", options:__("All customizations will be removed. Please confirm.")}, + ], + primary_action: function() { + return frm.call({ + doc: frm.doc, + method: "reset_to_defaults", + callback: function(r) { + if(r.exc) { + msgprint(r.exc); + } else { + d.hide(); + frappe.customize_form.clear_locals_and_refresh(frm); + } } - } - }); - }); - - $y(proceed_btn, {marginRight: '20px', fontWeight: 'bold'}); - - var cancel_btn = $btn(button_wrapper, 'Cancel', function() { - frappe.customize_form.confirm.dialog.hide(); + }); + } }); - $(cancel_btn).addClass('btn-small btn-info'); - $y(cancel_btn, {fontWeight: 'bold'}); - frappe.customize_form.confirm.dialog = d; d.show(); } @@ -159,7 +146,7 @@ frappe.customize_form.add_fields_help = function(frm) { Perm Level\ \ Assign a permission level to the field.
\ - (Permissions can be managed via Setup > Permission Manager)\ + (Permissions can be managed via Setup > Role Permissions Manager)\ \ \ \ diff --git a/frappe/core/doctype/customize_form/customize_form.json b/frappe/core/doctype/customize_form/customize_form.json index a95988f208..af2df37574 100644 --- a/frappe/core/doctype/customize_form/customize_form.json +++ b/frappe/core/doctype/customize_form/customize_form.json @@ -63,22 +63,6 @@ "fieldtype": "Column Break", "permlevel": 0 }, - { - "fieldname": "allow_print", - "fieldtype": "Check", - "label": "Hide Print", - "no_copy": 0, - "permlevel": 0, - "search_index": 0 - }, - { - "fieldname": "allow_email", - "fieldtype": "Check", - "label": "Hide Email", - "no_copy": 0, - "permlevel": 0, - "search_index": 0 - }, { "fieldname": "allow_copy", "fieldtype": "Check", @@ -88,16 +72,7 @@ "search_index": 0 }, { - "description": "Note: maximum attachment size = 1mb", - "fieldname": "allow_attach", - "fieldtype": "Check", - "label": "Allow Attach", - "no_copy": 0, - "permlevel": 0, - "search_index": 0 - }, - { - "depends_on": "eval:cint(doc.allow_attach)", + "depends_on": "", "fieldname": "max_attachments", "fieldtype": "Int", "label": "Max Attachments", @@ -126,7 +101,7 @@ "icon": "icon-glass", "idx": 1, "issingle": 1, - "modified": "2014-05-08 09:27:44.167026", + "modified": "2014-08-22 05:42:45.083260", "modified_by": "Administrator", "module": "Core", "name": "Customize Form", diff --git a/frappe/core/doctype/customize_form/customize_form.py b/frappe/core/doctype/customize_form/customize_form.py index 0e0eefdcfa..8f9f7f8a9d 100644 --- a/frappe/core/doctype/customize_form/customize_form.py +++ b/frappe/core/doctype/customize_form/customize_form.py @@ -18,7 +18,6 @@ class CustomizeForm(Document): 'sort_order': 'Data', 'default_print_format': 'Data', 'read_only_onload': 'Check', - 'allow_attach': 'Check', 'allow_copy': 'Check', 'max_attachments': 'Int' } @@ -32,7 +31,7 @@ class CustomizeForm(Document): 'width': 'Data', 'print_width': 'Data', 'reqd': 'Check', - 'ignore_restrictions': 'Check', + 'ignore_user_permissions': 'Check', 'in_filter': 'Check', 'in_list_view': 'Check', 'hidden': 'Check', @@ -41,11 +40,12 @@ class CustomizeForm(Document): 'allow_on_submit': 'Check', 'depends_on': 'Data', 'description': 'Text', - 'default': 'Text' + 'default': 'Text', + 'precision': 'Select' } - allowed_fieldtype_change = (('Currency', 'Float'), ('Small Text', 'Data'), - ('Text', 'Text Editor', 'Code')) + allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), + ('Text', 'Text Editor', 'Code'), ('Data', 'Select'), ('Text', 'Small Text')) def on_update(self): frappe.db.sql("delete from tabSingles where doctype='Customize Form'") @@ -187,6 +187,7 @@ class CustomizeForm(Document): return # create a new property setter + # ignore validation becuase it will be done at end frappe.make_property_setter({ "doctype": self.doc_type, "doctype_or_field": "DocField" if fieldname else "DocType", @@ -194,7 +195,7 @@ class CustomizeForm(Document): "property": property, "value": value, "property_type": property_type - }) + }, ignore_validate=True) def delete_existing_property_setter(self, property, fieldname=None): # first delete existing property setter @@ -204,14 +205,14 @@ class CustomizeForm(Document): if existing_property_setter: frappe.delete_doc("Property Setter", existing_property_setter) - def get_existing_property_value(self, property, fieldname=None): + def get_existing_property_value(self, property_name, fieldname=None): # check if there is any need to make property setter! if fieldname: property_value = frappe.db.get_value("DocField", {"parent": self.doc_type, - "fieldname": fieldname}, property) + "fieldname": fieldname}, property_name) else: try: - property_value = frappe.db.get_value("DocType", self.doc_type, property) + property_value = frappe.db.get_value("DocType", self.doc_type, property_name) except Exception, e: if e.args[0]==1054: property_value = None @@ -221,18 +222,19 @@ class CustomizeForm(Document): return property_value def validate_fieldtype_change(self, df, old_value, new_value): + allowed = False for allowed_changes in self.allowed_fieldtype_change: - if ((old_value in allowed_changes and new_value in allowed_changes) - or (old_value not in allowed_changes and new_value not in allowed_changes)): - continue - else: - frappe.throw(_("Fieldtype must be one of {0} in row {1}").format(", ".join([_(fieldtype) for fieldtype in allowed_changes]), df.idx)) + if (old_value in allowed_changes and new_value in allowed_changes): + allowed = True + if not allowed: + frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx)) def reset_to_defaults(self): if not self.doc_type: return - frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s""", self.doc_type) + frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s + and ifnull(field_name, '')!='naming_series'""", self.doc_type) frappe.clear_cache(doctype=self.doc_type) self.fetch_to_customize() diff --git a/frappe/core/doctype/customize_form/test_customize_form.py b/frappe/core/doctype/customize_form/test_customize_form.py index f6167f6dce..7a1e4e6b4d 100644 --- a/frappe/core/doctype/customize_form/test_customize_form.py +++ b/frappe/core/doctype/customize_form/test_customize_form.py @@ -47,7 +47,7 @@ class TestCustomizeForm(unittest.TestCase): d = self.get_customize_form("User") self.assertEquals(d.doc_type, "User") - self.assertEquals(len(d.get("customize_form_fields")), 53) + self.assertEquals(len(d.get("customize_form_fields")), 55) self.assertEquals(d.get("customize_form_fields")[-1].fieldname, "test_custom_field") self.assertEquals(d.get("customize_form_fields", {"fieldname": "location"})[0].in_list_view, 1) @@ -112,7 +112,7 @@ class TestCustomizeForm(unittest.TestCase): def test_save_customization_custom_field_property(self): d = self.get_customize_form("User") - self.assertEquals(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), None) + self.assertEquals(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 0) custom_field = d.get("customize_form_fields", {"fieldname": "test_custom_field"})[0] custom_field.reqd = 1 diff --git a/frappe/core/doctype/customize_form_field/customize_form_field.json b/frappe/core/doctype/customize_form_field/customize_form_field.json index e1cf79090f..0c540044bc 100644 --- a/frappe/core/doctype/customize_form_field/customize_form_field.json +++ b/frappe/core/doctype/customize_form_field/customize_form_field.json @@ -1,10 +1,17 @@ { "allow_copy": 0, "autoname": "DLF.#####", - "creation": "2013-02-22 01:27:32.000000", + "creation": "2013-02-22 01:27:32", "docstatus": 0, "doctype": "DocType", "fields": [ + { + "fieldname": "label_and_type", + "fieldtype": "Section Break", + "label": "Label and Type", + "permlevel": 0, + "precision": "" + }, { "fieldname": "label", "fieldtype": "Data", @@ -19,6 +26,7 @@ "search_index": 1 }, { + "default": "Data", "fieldname": "fieldtype", "fieldtype": "Select", "hidden": 0, @@ -26,7 +34,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", + "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", "permlevel": 0, "print_hide": 0, "reqd": 1, @@ -46,6 +54,42 @@ "reqd": 0, "search_index": 1 }, + { + "fieldname": "reqd", + "fieldtype": "Check", + "hidden": 0, + "label": "Mandatory", + "oldfieldname": "reqd", + "oldfieldtype": "Check", + "permlevel": 0, + "print_hide": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, + "width": "50px" + }, + { + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View", + "permlevel": 0 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n1\n2\n3\n4\n5\n6", + "permlevel": 0, + "precision": "" + }, { "description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", "fieldname": "options", @@ -60,6 +104,25 @@ "reqd": 0, "search_index": 0 }, + { + "fieldname": "permissions", + "fieldtype": "Section Break", + "label": "Permissions", + "permlevel": 0, + "precision": "" + }, + { + "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples):
\nmyfield\neval:doc.myfield=='My Value'
\neval:doc.age>18", + "fieldname": "depends_on", + "fieldtype": "Data", + "hidden": 0, + "label": "Depends On", + "oldfieldname": "depends_on", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "reqd": 0 + }, { "default": "0", "fieldname": "permlevel", @@ -75,13 +138,12 @@ "search_index": 0 }, { - "fieldname": "width", - "fieldtype": "Data", + "fieldname": "hidden", + "fieldtype": "Check", "hidden": 0, - "in_list_view": 1, - "label": "Width", - "oldfieldname": "width", - "oldfieldtype": "Data", + "label": "Hidden", + "oldfieldname": "hidden", + "oldfieldtype": "Check", "permlevel": 0, "print_hide": 0, "print_width": "50px", @@ -90,111 +152,76 @@ "width": "50px" }, { - "fieldname": "print_width", - "fieldtype": "Data", - "label": "Print Width", + "fieldname": "column_break_14", + "fieldtype": "Column Break", "permlevel": 0, - "print_width": "50px", - "width": "50px" + "precision": "" }, { - "fieldname": "reqd", + "fieldname": "ignore_user_permissions", "fieldtype": "Check", - "hidden": 0, - "label": "Reqd", - "oldfieldname": "reqd", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, - "width": "50px" - }, - { - "fieldname": "ignore_restrictions", - "fieldtype": "Check", - "label": "Ignore Restrictions", + "label": "Ignore User Permissions", "permlevel": 0 }, { - "fieldname": "in_filter", + "fieldname": "allow_on_submit", "fieldtype": "Check", "hidden": 0, - "label": "In Filter", - "oldfieldname": "in_filter", + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", "oldfieldtype": "Check", "permlevel": 0, "print_hide": 0, - "print_width": "50px", - "reqd": 0, - "width": "50px" - }, - { - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View", - "permlevel": 0 + "reqd": 0 }, { - "fieldname": "hidden", + "fieldname": "report_hide", "fieldtype": "Check", "hidden": 0, - "label": "Hidden", - "oldfieldname": "hidden", + "label": "Report Hide", + "oldfieldname": "report_hide", "oldfieldtype": "Check", "permlevel": 0, "print_hide": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, - "width": "50px" + "reqd": 0 }, { - "fieldname": "print_hide", - "fieldtype": "Check", - "hidden": 0, - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check", + "fieldname": "display", + "fieldtype": "Section Break", + "label": "Display", "permlevel": 0, - "print_hide": 0, - "reqd": 0, - "search_index": 0 + "precision": "" }, { - "fieldname": "report_hide", - "fieldtype": "Check", + "fieldname": "default", + "fieldtype": "Text", "hidden": 0, - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check", + "label": "Default", + "oldfieldname": "default", + "oldfieldtype": "Text", "permlevel": 0, "print_hide": 0, - "reqd": 0 + "reqd": 0, + "search_index": 0 }, { - "fieldname": "allow_on_submit", + "fieldname": "in_filter", "fieldtype": "Check", "hidden": 0, - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", + "label": "In Filter", + "oldfieldname": "in_filter", "oldfieldtype": "Check", "permlevel": 0, "print_hide": 0, - "reqd": 0 + "print_width": "50px", + "reqd": 0, + "width": "50px" }, { - "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples):
\nmyfield\neval:doc.myfield=='My Value'
\neval:doc.age>18", - "fieldname": "depends_on", - "fieldtype": "Data", - "hidden": 0, - "label": "Depends On", - "oldfieldname": "depends_on", - "oldfieldtype": "Data", + "fieldname": "column_break_21", + "fieldtype": "Column Break", "permlevel": 0, - "print_hide": 0, - "reqd": 0 + "precision": "" }, { "fieldname": "description", @@ -210,16 +237,40 @@ "width": "300px" }, { - "fieldname": "default", - "fieldtype": "Text", + "fieldname": "print_hide", + "fieldtype": "Check", "hidden": 0, - "label": "Default", - "oldfieldname": "default", - "oldfieldtype": "Text", + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check", "permlevel": 0, "print_hide": 0, "reqd": 0, "search_index": 0 + }, + { + "description": "Print Width of the field, if the field is a column in a table", + "fieldname": "print_width", + "fieldtype": "Data", + "label": "Print Width", + "permlevel": 0, + "print_width": "50px", + "width": "50px" + }, + { + "fieldname": "width", + "fieldtype": "Data", + "hidden": 0, + "in_list_view": 1, + "label": "Width", + "oldfieldname": "width", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, + "width": "50px" } ], "hide_heading": 0, @@ -227,10 +278,11 @@ "idx": 1, "issingle": 0, "istable": 1, - "modified": "2013-12-23 16:12:45.000000", + "modified": "2014-09-05 07:41:29.641454", "modified_by": "Administrator", "module": "Core", "name": "Customize Form Field", "owner": "Administrator", + "permissions": [], "read_only": 0 } \ No newline at end of file diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 2a07ba0cc7..1bb0d71a57 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -34,7 +34,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", + "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", "permlevel": 0, "reqd": 1, "search_index": 1 @@ -91,6 +91,16 @@ "fieldtype": "Column Break", "permlevel": 0 }, + { + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n1\n2\n3\n4\n5\n6", + "permlevel": 0, + "print_hide": 1 + }, { "description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma", "fieldname": "options", @@ -166,10 +176,10 @@ "permlevel": 0 }, { - "description": "User restrictions should not apply for this Link", - "fieldname": "ignore_restrictions", + "description": "User permissions should not apply for this Link", + "fieldname": "ignore_user_permissions", "fieldtype": "Check", - "label": "Ignore Restrictions", + "label": "Ignore User Permissions", "permlevel": 0 }, { @@ -304,7 +314,7 @@ "in_dialog": 1, "issingle": 0, "istable": 1, - "modified": "2014-04-24 15:56:23.561687", + "modified": "2014-09-05 07:41:05.956027", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/core/doctype/docperm/README.md b/frappe/core/doctype/docperm/README.md deleted file mode 100644 index c3cac80740..0000000000 --- a/frappe/core/doctype/docperm/README.md +++ /dev/null @@ -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. \ No newline at end of file diff --git a/frappe/core/doctype/docperm/docperm.json b/frappe/core/doctype/docperm/docperm.json index f4a46de3ce..4a57765d3b 100644 --- a/frappe/core/doctype/docperm/docperm.json +++ b/frappe/core/doctype/docperm/docperm.json @@ -26,6 +26,13 @@ "search_index": 0, "width": "150px" }, + { + "description": "Filter records based on User Permissions defined for a user", + "fieldname": "apply_user_permissions", + "fieldtype": "Check", + "label": "Apply User Permissions", + "permlevel": 0 + }, { "fieldname": "column_break_2", "fieldtype": "Column Break", @@ -46,6 +53,15 @@ "search_index": 0, "width": "40px" }, + { + "depends_on": "", + "description": "JSON list of DocTypes used to apply User Permissions. If empty, all linked DocTypes will be used to apply User Permissions.", + "fieldname": "user_permission_doctypes", + "fieldtype": "Text", + "label": "User Permission DocTypes", + "permlevel": 0, + "read_only": 1 + }, { "fieldname": "section_break_4", "fieldtype": "Section Break", @@ -171,15 +187,10 @@ "permlevel": 0 }, { - "fieldname": "print", + "description": "This role update User Permissions for a user", + "fieldname": "set_user_permissions", "fieldtype": "Check", - "label": "Print", - "permlevel": 0 - }, - { - "fieldname": "email", - "fieldtype": "Check", - "label": "Email", + "label": "Set User Permissions", "permlevel": 0 }, { @@ -188,17 +199,15 @@ "permlevel": 0 }, { - "description": "Only restricted users can access", - "fieldname": "restricted", + "fieldname": "print", "fieldtype": "Check", - "label": "Only Restricted Documents", + "label": "Print", "permlevel": 0 }, { - "description": "This role can restrict users for accessing the record.", - "fieldname": "restrict", + "fieldname": "email", "fieldtype": "Check", - "label": "Can Restrict Others", + "label": "Email", "permlevel": 0 } ], @@ -207,7 +216,7 @@ "idx": 1, "issingle": 0, "istable": 1, - "modified": "2014-05-01 05:20:48.162224", + "modified": "2014-08-26 01:43:31.499363", "modified_by": "Administrator", "module": "Core", "name": "DocPerm", diff --git a/frappe/core/doctype/doctype/doctype_template.py b/frappe/core/doctype/doctype/boilerplate/controller.py similarity index 74% rename from frappe/core/doctype/doctype/doctype_template.py rename to frappe/core/doctype/doctype/boilerplate/controller.py index 369bd592d8..1d5ae0895d 100644 --- a/frappe/core/doctype/doctype/doctype_template.py +++ b/frappe/core/doctype/doctype/boilerplate/controller.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013, {app_publisher} +# Copyright (c) 2013, {app_publisher} and contributors # For license information, please see license.txt from __future__ import unicode_literals @@ -6,4 +6,4 @@ import frappe from frappe.model.document import Document class {classname}(Document): - pass \ No newline at end of file + pass diff --git a/frappe/core/doctype/doctype/boilerplate/controller_list.html b/frappe/core/doctype/doctype/boilerplate/controller_list.html new file mode 100644 index 0000000000..0ad19e561b --- /dev/null +++ b/frappe/core/doctype/doctype/boilerplate/controller_list.html @@ -0,0 +1,34 @@ +
+
+
+ {{%= list.get_avatar_and_id(doc) %}} + + + + {{%= doc.text %}} + + + {{% if(doc.check) {{ %}} + + + + {{% }} %}} + + + + {{%= doc.status %}} + +
+
+ +
+ {{% var completed = doc.completed, title = __("Completed") %}} + {{% include "templates/form_grid/includes/progress.html" %}} +
+
diff --git a/frappe/core/doctype/doctype/boilerplate/controller_list.js b/frappe/core/doctype/doctype/boilerplate/controller_list.js new file mode 100644 index 0000000000..7f4915b301 --- /dev/null +++ b/frappe/core/doctype/doctype/boilerplate/controller_list.js @@ -0,0 +1,4 @@ +frappe.listview_settings['{doctype}'] = {{ + add_fields: ["status"], + filters:[["status","=", "Open"]] +}}; diff --git a/frappe/core/doctype/doctype/boilerplate/test_controller.py b/frappe/core/doctype/doctype/boilerplate/test_controller.py new file mode 100644 index 0000000000..cc12c9b9c8 --- /dev/null +++ b/frappe/core/doctype/doctype/boilerplate/test_controller.py @@ -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 diff --git a/frappe/core/doctype/doctype/boilerplate/test_records.json b/frappe/core/doctype/doctype/boilerplate/test_records.json new file mode 100644 index 0000000000..6df1b54abc --- /dev/null +++ b/frappe/core/doctype/doctype/boilerplate/test_records.json @@ -0,0 +1,6 @@ +[ + {{ + "doctype": "{doctype}", + "name": "_Test {doctype} 1" + }} +] diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 9ae14e4a8a..1f862bb26b 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -11,18 +11,6 @@ $(cur_frm.wrapper).on("grid-row-render", function(e, grid_row) { } }) -cur_frm.cscript.allow_attach = function(doc, cdt, cdn) { - if(doc.allow_attach) { - unhide_field('max_attachments'); - } else { - hide_field('max_attachments'); - } -} - -cur_frm.cscript.onload = function(doc, cdt, cdn) { - this.allow_attach(doc, cdt, cdn); -} - cur_frm.cscript.refresh = function(doc, cdt, cdn) { if(in_list(user_roles, 'System Manager') && !in_list(user_roles, 'Administrator')) { // make the document read-only diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 646600a471..53af79a699 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -1,6 +1,5 @@ { - "allow_attach": 0, - "allow_copy": 0, + "allow_copy": 0, "autoname": "Prompt", "creation": "2013-02-18 13:36:19", "custom": 0, @@ -276,18 +275,10 @@ "oldfieldtype": "Check", "permlevel": 0 }, - { - "fieldname": "allow_attach", - "fieldtype": "Check", - "label": "Allow Attach", - "oldfieldname": "allow_attach", - "oldfieldtype": "Check", - "permlevel": 0 - }, { "fieldname": "max_attachments", "fieldtype": "Int", - "hidden": 1, + "hidden": 0, "label": "Max Attachments", "oldfieldname": "max_attachments", "oldfieldtype": "Int", @@ -345,7 +336,7 @@ "idx": 1, "issingle": 0, "istable": 0, - "modified": "2014-05-08 09:23:56.952829", + "modified": "2014-08-22 05:33:03.067964", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 69e66017f0..9f2c0419a6 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -5,12 +5,18 @@ from __future__ import unicode_literals import frappe from frappe import _ -import os from frappe.utils import now, cint from frappe.model import no_value_fields from frappe.model.document import Document from frappe.model.db_schema import type_map +from frappe.core.doctype.property_setter.property_setter import make_property_setter +from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for +from frappe.modules import make_boilerplate + +form_grid_templates = { + "fields": "templates/form_grid/fields.html" +} class DocType(Document): def validate(self): @@ -22,7 +28,7 @@ class DocType(Document): self.validate_series() self.scrub_field_names() self.validate_title_field() - validate_fields(self.get("fields")) + validate_fields(self) if self.istable: # no permission records for child table @@ -41,7 +47,7 @@ class DocType(Document): frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0])) def scrub_field_names(self): - restricted = ('name','parent','idx','owner','creation','modified','modified_by', + restricted = ('name','parent','creation','modified','modified_by', 'parentfield','parenttype',"file_list") for d in self.get("fields"): if d.fieldtype: @@ -66,8 +72,11 @@ class DocType(Document): if not autoname and self.get("fields", {"fieldname":"naming_series"}): self.autoname = "naming_series:" - if autoname and (not autoname.startswith('field:')) and (not autoname.startswith('eval:')) \ - and (not autoname=='Prompt') and (not autoname.startswith('naming_series:')): + if autoname and (not autoname.startswith('field:')) \ + and (not autoname.startswith('eval:')) \ + and (not autoname in ('Prompt', 'hash')) \ + and (not autoname.startswith('naming_series:')): + prefix = autoname.split('.')[0] used_in = frappe.db.sql('select name from tabDocType where substring_index(autoname, ".", 1) = %s and name!=%s', (prefix, name)) if used_in: @@ -81,7 +90,7 @@ class DocType(Document): make_module_and_roles(self) from frappe import conf - if (not frappe.flags.in_import) and conf.get('developer_mode') or 0: + if not (frappe.flags.in_import or frappe.flags.in_test) and conf.get('developer_mode') or 0: self.export_doc() self.make_controller_template() @@ -91,13 +100,9 @@ class DocType(Document): module = load_doctype_module(self.name, self.module) if hasattr(module, "on_doctype_update"): module.on_doctype_update() - frappe.clear_cache(doctype=self.name) - def on_trash(self): - frappe.db.sql("delete from `tabCustom Field` where dt = %s", self.name) - frappe.db.sql("delete from `tabCustom Script` where dt = %s", self.name) - frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", self.name) - frappe.db.sql("delete from `tabReport` where ref_doctype=%s", self.name) + delete_notification_count_for(doctype=self.name) + frappe.clear_cache(doctype=self.name) def before_rename(self, old, new, merge=False): if merge: @@ -109,6 +114,29 @@ class DocType(Document): else: frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new)) + def before_reload(self): + if not (self.issingle and self.istable): + self.preserve_naming_series_options_in_property_setter() + + def preserve_naming_series_options_in_property_setter(self): + """preserve naming_series as property setter if it does not exist""" + naming_series = self.get("fields", {"fieldname": "naming_series"}) + + if not naming_series: + return + + # check if atleast 1 record exists + if not (frappe.db.table_exists("tab" + self.name) and frappe.db.sql("select name from `tab{}` limit 1".format(self.name))): + return + + existing_property_setter = frappe.db.get_value("Property Setter", {"doc_type": self.name, + "property": "options", "field_name": "naming_series"}) + + if not existing_property_setter: + make_property_setter(self.name, "naming_series", "options", naming_series[0].options, "Text", validate_fields_for_doctype=False) + if naming_series[0].default: + make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text", validate_fields_for_doctype=False) + def export_doc(self): from frappe.modules.export_file import export_to_files export_to_files(record_list=[['DocType', self.name]]) @@ -118,22 +146,11 @@ class DocType(Document): import_from_files(record_list=[[self.module, 'doctype', self.name]]) def make_controller_template(self): - from frappe.modules import get_doc_path, get_module_path, scrub - - pypath = os.path.join(get_doc_path(self.module, - self.doctype, self.name), scrub(self.name) + '.py') - - if not os.path.exists(pypath): - # get app publisher for copyright - app = frappe.local.module_app[frappe.scrub(self.module)] - if not app: - frappe.throw(_("App not found")) - app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0] + make_boilerplate("controller.py", self) - with open(pypath, 'w') as pyfile: - with open(os.path.join(get_module_path("core"), "doctype", "doctype", - "doctype_template.py"), 'r') as srcfile: - pyfile.write(srcfile.read().format(app_publisher=app_publisher, classname=self.name.replace(" ", ""))) + if not (self.istable or self.issingle): + make_boilerplate("test_controller.py", self) + make_boilerplate("test_records.json", self) def make_amendable(self): """ @@ -158,9 +175,10 @@ class DocType(Document): return max_idx and max_idx[0][0] or 0 def validate_fields_for_doctype(doctype): - validate_fields(frappe.get_meta(doctype).get("fields")) + validate_fields(frappe.get_meta(doctype)) -def validate_fields(fields): +# this is separate because it is also called via custom field +def validate_fields(meta): def check_illegal_characters(fieldname): for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$', '(', ')', '[', ']', '/']: @@ -173,7 +191,7 @@ def validate_fields(fields): frappe.throw(_("Fieldname {0} appears multiple times in rows {1}").format(fieldname, ", ".join(duplicates))) def check_illegal_mandatory(d): - if d.fieldtype in ('HTML', 'Button', 'Section Break', 'Column Break') and d.reqd: + if (d.fieldtype in no_value_fields) and d.fieldtype!="Table" and d.reqd: frappe.throw(_("Field {0} of type {1} cannot be mandatory").format(d.label, d.fieldtype)) def check_link_table_options(d): @@ -203,6 +221,47 @@ def validate_fields(fields): if d.in_list_view and d.fieldtype!="Image" and (d.fieldtype in no_value_fields): frappe.throw(_("'In List View' not allowed for type {0} in row {1}").format(d.fieldtype, d.idx)) + def check_dynamic_link_options(d): + if d.fieldtype=="Dynamic Link": + doctype_pointer = filter(lambda df: df.fieldname==d.options, fields) + if not doctype_pointer or (doctype_pointer[0].fieldtype!="Link") \ + or (doctype_pointer[0].options!="DocType"): + frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'")) + + def check_illegal_default(d): + if d.fieldtype == "Check" and d.default and d.default not in ('0', '1'): + frappe.throw(_("Default for 'Check' type of field must be either '0' or '1'")) + + def check_precision(d): + if d.fieldtype in ("Currency", "Float", "Percent") and d.precision is not None and not (1 <= cint(d.precision) <= 6): + frappe.throw(_("Precision should be between 1 and 6")) + + def check_fold(fields): + fold_exists = False + for i, f in enumerate(fields): + if f.fieldtype=="Fold": + if fold_exists: + frappe.throw(_("There can be only one Fold in a form")) + fold_exists = True + if i < len(fields)-1: + nxt = fields[i+1] + if nxt.fieldtype != "Section Break" \ + or (nxt.fieldtype=="Section Break" and not nxt.label): + frappe.throw(_("Fold must come before a labelled Section Break")) + else: + frappe.throw(_("Fold can not be at the end of the form")) + + def check_search_fields(meta): + if not meta.search_fields: + return + + fieldname_list = [d.fieldname for d in fields] + for fieldname in (meta.search_fields or "").split(","): + fieldname = fieldname.strip() + if fieldname not in fieldname_list: + frappe.throw(_("Search Fields should contain valid fieldnames")) + + fields = meta.get("fields") for d in fields: if not d.permlevel: d.permlevel = 0 if not d.fieldname: @@ -211,13 +270,28 @@ def validate_fields(fields): check_unique_fieldname(d.fieldname) check_illegal_mandatory(d) check_link_table_options(d) + check_dynamic_link_options(d) check_hidden_and_mandatory(d) check_in_list_view(d) + check_illegal_default(d) check_min_items_in_list(fields) + check_fold(fields) + check_search_fields(meta) def validate_permissions_for_doctype(doctype, for_remove=False): - validate_permissions(frappe.get_doc("DocType", doctype), for_remove) + doctype = frappe.get_doc("DocType", doctype) + + if frappe.conf.developer_mode and not frappe.flags.in_test: + # save doctype + doctype.save() + + else: + validate_permissions(doctype, for_remove) + + # save permissions + for perm in doctype.get("permissions"): + perm.db_update() def validate_permissions(doctype, for_remove=False): permissions = doctype.get("permissions") @@ -239,12 +313,13 @@ def validate_permissions(doctype, for_remove=False): def check_double(d): has_similar = False for p in permissions: - if p.role==d.role and p.permlevel==d.permlevel and p.match==d.match and p!=d: + if (p.role==d.role and p.permlevel==d.permlevel + and p.apply_user_permissions==d.apply_user_permissions and p!=d): has_similar = True break if has_similar: - frappe.throw(_("{0}: Only one rule allowed at a Role and Level").format(get_txt(d))) + frappe.throw(_("{0}: Only one rule allowed with the same Role, Level and Apply User Permissions").format(get_txt(d))) def check_level_zero_is_set(d): if cint(d.permlevel) > 0 and d.role != 'All': @@ -281,9 +356,12 @@ def validate_permissions(doctype, for_remove=False): d.set("import", 0) d.set("export", 0) - if d.restrict: - frappe.msgprint(_("Restrict cannot be set for Single types")) - d.restrict = 0 + for ptype, label in ( + ("set_user_permissions", _("Set User Permissions")), + ("apply_user_permissions", _("Apply User Permissions"))): + if d.get(ptype): + d.set(ptype, 0) + frappe.msgprint(_("{0} cannot be set for Single types").format(label)) def check_if_submittable(d): if d.submit and not issubmittable: @@ -331,3 +409,9 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): pass else: raise + +def init_list(doctype): + doc = frappe.get_meta(doctype) + make_boilerplate("controller_list.js", doc) + make_boilerplate("controller_list.html", doc) + diff --git a/frappe/core/doctype/social_login_keys/__init__.py b/frappe/core/doctype/email_alert/__init__.py similarity index 100% rename from frappe/core/doctype/social_login_keys/__init__.py rename to frappe/core/doctype/email_alert/__init__.py diff --git a/frappe/core/doctype/email_alert/email_alert.js b/frappe/core/doctype/email_alert/email_alert.js new file mode 100644 index 0000000000..9bad01d3b3 --- /dev/null +++ b/frappe/core/doctype/email_alert/email_alert.js @@ -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); +}); diff --git a/frappe/core/doctype/email_alert/email_alert.json b/frappe/core/doctype/email_alert/email_alert.json new file mode 100644 index 0000000000..9d06dd4ced --- /dev/null +++ b/frappe/core/doctype/email_alert/email_alert.json @@ -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": "

Condition Examples:

\n
doc.status==\"Open\"\ndoc.due_date==nowdate()\ndoc.total > 40000\n
\n

Hints:

\n
    \n
  1. To check for an event every day, select \"Date Change\" in Event
  2. \n
  3. To send an alert if a particular value changes, select \"Value Change\"
  4. \n
", + "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": "
Message Example (Markdown)
\n
Transaction {{ doc.name }} has exceeded Due Date. Please take relevant action\n\n#### Details\n\nCustomer: {{ doc.customer }}\nAmount: {{ doc.total_amount }}
", + "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" +} \ No newline at end of file diff --git a/frappe/core/doctype/email_alert/email_alert.py b/frappe/core/doctype/email_alert/email_alert.py new file mode 100644 index 0000000000..ea5021db8b --- /dev/null +++ b/frappe/core/doctype/email_alert/email_alert.py @@ -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 = """
+This Email Alert {{alert.subject}} was autogenerated for +{{ doc.doctype }} {{doc.name}}. +To update, modify it, go to Setup > Email > Email Alert +""" diff --git a/frappe/core/doctype/email_alert/test_email_alert.py b/frappe/core/doctype/email_alert/test_email_alert.py new file mode 100644 index 0000000000..c53bfcae71 --- /dev/null +++ b/frappe/core/doctype/email_alert/test_email_alert.py @@ -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"})) diff --git a/frappe/core/doctype/email_alert/test_records.json b/frappe/core/doctype/email_alert/test_records.json new file mode 100644 index 0000000000..dfe87aafe0 --- /dev/null +++ b/frappe/core/doctype/email_alert/test_records.json @@ -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" } + ] + } +] diff --git a/frappe/website/doctype/website_route/__init__.py b/frappe/core/doctype/email_alert_recipient/__init__.py similarity index 100% rename from frappe/website/doctype/website_route/__init__.py rename to frappe/core/doctype/email_alert_recipient/__init__.py diff --git a/frappe/core/doctype/email_alert_recipient/email_alert_recipient.json b/frappe/core/doctype/email_alert_recipient/email_alert_recipient.json new file mode 100644 index 0000000000..e2434b1eb8 --- /dev/null +++ b/frappe/core/doctype/email_alert_recipient/email_alert_recipient.json @@ -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" +} \ No newline at end of file diff --git a/frappe/core/doctype/email_alert_recipient/email_alert_recipient.py b/frappe/core/doctype/email_alert_recipient/email_alert_recipient.py new file mode 100644 index 0000000000..6721834e8f --- /dev/null +++ b/frappe/core/doctype/email_alert_recipient/email_alert_recipient.py @@ -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 \ No newline at end of file diff --git a/frappe/core/doctype/event/event.js b/frappe/core/doctype/event/event.js index 5503e565e8..2d0eb91cb7 100644 --- a/frappe/core/doctype/event/event.js +++ b/frappe/core/doctype/event/event.js @@ -1,6 +1,15 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt +frappe.ui.form.on("Event", "refresh", function(frm) { + if(frm.doc.ref_type && frm.doc.ref_name) { + frm.add_custom_button(__(frm.doc.ref_name), function() { + frappe.set_route("Form", frm.doc.ref_type, frm.doc.ref_name); + }, frappe.boot.doctype_icons[frm.doc.ref_type]); + } +}); + + cur_frm.cscript.repeat_on = function(doc, cdt, cdn) { if(doc.repeat_on==="Every Day") { $.each(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"], function(i,v) { diff --git a/frappe/core/doctype/event/event.json b/frappe/core/doctype/event/event.json index 7c32762656..de46143f73 100644 --- a/frappe/core/doctype/event/event.json +++ b/frappe/core/doctype/event/event.json @@ -1,6 +1,6 @@ { "autoname": "EV.#####", - "creation": "2013-06-10 13:17:47.000000", + "creation": "2013-06-10 13:17:47", "docstatus": 0, "doctype": "DocType", "fields": [ @@ -218,24 +218,26 @@ }, { "fieldname": "ref_type", - "fieldtype": "Data", + "fieldtype": "Link", "hidden": 0, "label": "Ref Type", "no_copy": 0, "oldfieldname": "ref_type", "oldfieldtype": "Data", + "options": "DocType", "permlevel": 0, - "read_only": 1, + "read_only": 0, "search_index": 0 }, { "fieldname": "ref_name", - "fieldtype": "Data", + "fieldtype": "Dynamic Link", "hidden": 0, "label": "Ref Name", "no_copy": 0, "oldfieldname": "ref_name", "oldfieldtype": "Data", + "options": "ref_type", "permlevel": 0, "read_only": 1, "search_index": 0 @@ -244,14 +246,14 @@ "icon": "icon-calendar", "idx": 1, "in_create": 1, - "modified": "2014-01-24 13:00:01.000000", + "modified": "2014-06-20 06:40:05.415405", "modified_by": "Administrator", "module": "Core", "name": "Event", "owner": "Administrator", "permissions": [ { - "cancel": 0, + "apply_user_permissions": 1, "create": 1, "delete": 0, "email": 1, @@ -264,7 +266,6 @@ "write": 1 }, { - "cancel": 0, "create": 1, "delete": 1, "email": 1, diff --git a/frappe/core/doctype/event/event.py b/frappe/core/doctype/event/event.py index ebed036f47..b154256c23 100644 --- a/frappe/core/doctype/event/event.py +++ b/frappe/core/doctype/event/event.py @@ -15,7 +15,8 @@ class Event(Document): if self.starts_on and self.ends_on and self.starts_on > self.ends_on: frappe.msgprint(frappe._("Event end must be after start"), raise_exception=True) -def get_permission_query_conditions(): +def get_permission_query_conditions(user): + if not user: user = frappe.session.user return """(tabEvent.event_type='Public' or tabEvent.owner='%(user)s' or exists(select * from `tabEvent User` where `tabEvent User`.parent=tabEvent.name and `tabEvent User`.person='%(user)s') @@ -23,18 +24,18 @@ def get_permission_query_conditions(): `tabEvent Role`.parent=tabEvent.name and `tabEvent Role`.role in ('%(roles)s'))) """ % { - "user": frappe.session.user, - "roles": "', '".join(frappe.get_roles(frappe.session.user)) + "user": user, + "roles": "', '".join(frappe.get_roles(user)) } -def has_permission(doc): - if doc.event_type=="Public" or doc.owner==frappe.session.user: +def has_permission(doc, user): + if doc.event_type=="Public" or doc.owner==user: return True - if doc.get("event_individuals", {"person":frappe.session.user}): + if doc.get("event_individuals", {"person": user}): return True - if doc.get("event_roles", {"role":("in", frappe.get_roles())}): + if doc.get("event_roles", {"role":("in", frappe.get_roles(user))}): return True return False diff --git a/frappe/core/doctype/event/test_event.py b/frappe/core/doctype/event/test_event.py index 7bed6eb843..ab9260b0c5 100644 --- a/frappe/core/doctype/event/test_event.py +++ b/frappe/core/doctype/event/test_event.py @@ -1,11 +1,12 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -"""Use blog post test to test permission restriction logic""" +"""Use blog post test to test user permissions logic""" import frappe import frappe.defaults import unittest +import json test_records = frappe.get_test_records('Event') @@ -40,3 +41,56 @@ class TestEvent(unittest.TestCase): self.assertTrue("_Test Event 1" in subjects) self.assertTrue("_Test Event 3" in subjects) self.assertFalse("_Test Event 2" in subjects) + + def test_revert_logic(self): + ev = frappe.get_doc(test_records[0]).insert() + name = ev.name + + frappe.delete_doc("Event", ev.name) + + # insert again + ev = frappe.get_doc(test_records[0]).insert() + + # the name should be same! + self.assertEquals(ev.name, name) + + def test_assign(self): + from frappe.widgets.form.assign_to import add + + ev = frappe.get_doc(test_records[0]).insert() + + add({ + "assign_to": "test@example.com", + "doctype": "Event", + "name": ev.name, + "description": "Test Assignment" + }) + + ev = frappe.get_doc("Event", ev.name) + + self.assertEquals(ev._assign, json.dumps(["test@example.com"])) + + # add another one + add({ + "assign_to": "test1@example.com", + "doctype": "Event", + "name": ev.name, + "description": "Test Assignment" + }) + + ev = frappe.get_doc("Event", ev.name) + + self.assertEquals(ev._assign, json.dumps(["test@example.com", "test1@example.com"])) + + # close an assignment + todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name, + "owner": "test1@example.com"}) + todo.status = "Closed" + todo.save() + + ev = frappe.get_doc("Event", ev.name) + self.assertEquals(ev._assign, json.dumps(["test@example.com"])) + + # cleanup + ev.delete() + diff --git a/frappe/core/doctype/file_data/file_data.json b/frappe/core/doctype/file_data/file_data.json index c52506c032..b8f27b2681 100644 --- a/frappe/core/doctype/file_data/file_data.json +++ b/frappe/core/doctype/file_data/file_data.json @@ -1,255 +1,83 @@ { - "_last_update": null, - "_user_tags": null, - "allow_attach": null, - "allow_copy": null, - "allow_email": null, - "allow_import": 1, - "allow_print": null, - "allow_rename": null, - "allow_trash": null, - "autoname": "File.######", - "change_log": null, - "client_script": null, - "client_script_core": null, - "client_string": null, - "colour": null, - "creation": "2012-12-12 11:19:22", - "custom": null, - "default_print_format": null, - "description": null, - "docstatus": 0, - "doctype": "DocType", - "document_type": null, - "dt_template": null, + "allow_import": 1, + "autoname": "File.######", + "creation": "2012-12-12 11:19:22", + "docstatus": 0, + "doctype": "DocType", "fields": [ { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "file_name", - "fieldtype": "Data", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": 1, - "label": "File Name", - "no_column": null, - "no_copy": null, - "oldfieldname": "file_name", - "oldfieldtype": "Data", - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": 1, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "file_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "File Name", + "oldfieldname": "file_name", + "oldfieldtype": "Data", + "permlevel": 0, + "read_only": 1 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "file_url", - "fieldtype": "Data", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": 1, - "label": "File URL", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": 1, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "file_url", + "fieldtype": "Data", + "in_list_view": 1, + "label": "File URL", + "permlevel": 0, + "read_only": 1 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "attached_to_doctype", - "fieldtype": "Link", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": 1, - "label": "Attached To DocType", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": "DocType", - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": 1, - "report_hide": null, - "reqd": null, - "search_index": 1, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "attached_to_doctype", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Attached To DocType", + "options": "DocType", + "permlevel": 0, + "read_only": 1, + "search_index": 1 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "attached_to_name", - "fieldtype": "Data", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": 1, - "label": "Attached To Name", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": 1, - "report_hide": null, - "reqd": null, - "search_index": 1, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "attached_to_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Attached To Name", + "permlevel": 0, + "read_only": 1, + "search_index": 1 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "file_size", - "fieldtype": "Int", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": 1, - "label": "File Size", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": 1, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "file_size", + "fieldtype": "Int", + "in_list_view": 1, + "label": "File Size", + "permlevel": 0, + "read_only": 1 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "content_hash", - "fieldtype": "Data", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Content Hash", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": null, - "search_index": 1, - "set_only_once": null, - "trigger": null, - "width": null + "fieldname": "content_hash", + "fieldtype": "Data", + "label": "Content Hash", + "permlevel": 0, + "search_index": 1 } - ], - "hide_heading": null, - "hide_toolbar": null, - "icon": "icon-file", - "idx": 1, - "in_create": 1, - "in_dialog": null, - "is_submittable": null, - "is_transaction_doc": null, - "issingle": null, - "istable": null, - "max_attachments": null, - "menu_index": null, - "modified": "2014-04-16 09:28:02.979858", - "modified_by": "Administrator", - "module": "Core", - "name": "File Data", - "name_case": null, - "owner": "Administrator", - "parent": null, - "parent_node": null, - "parentfield": null, - "parenttype": null, + ], + "icon": "icon-file", + "idx": 1, + "in_create": 1, + "modified": "2014-05-26 03:35:40.362967", + "modified_by": "Administrator", + "module": "Core", + "name": "File Data", + "owner": "Administrator", "permissions": [ { - "amend": null, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": null, - "import": null, - "match": null, - "permlevel": 0, - "print": 1, - "read": 1, - "report": null, - "restrict": null, - "restricted": null, - "role": "System Manager", - "submit": null, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "role": "System Manager", "write": 1 } - ], - "plugin": null, - "print_outline": null, - "read_only": 0, - "read_only_onload": null, - "search_fields": null, - "section_style": null, - "server_code": null, - "server_code_compiled": null, - "server_code_core": null, - "server_code_error": null, - "show_in_menu": null, - "smallicon": null, - "subject": null, - "tag_fields": null, - "title_field": null, - "use_template": null, - "version": null -} \ No newline at end of file + ], + "read_only": 0 +} diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py index 4d88a6adca..ae0971d79e 100644 --- a/frappe/core/doctype/file_data/file_data.py +++ b/frappe/core/doctype/file_data/file_data.py @@ -1,5 +1,5 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# MIT License. See license.txt from __future__ import unicode_literals """ @@ -16,20 +16,20 @@ from frappe.utils.file_manager import delete_file_data_content class FileData(Document): def before_insert(self): frappe.local.rollback_observers.append(self) - + def on_update(self): - # check duplicate assignement - n_records = frappe.db.sql("""select name from `tabFile Data` - where content_hash=%s - and name!=%s - and attached_to_doctype=%s - and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype, - self.attached_to_name)) - if len(n_records) > 0: - self.duplicate_entry = n_records[0][0] - frappe.msgprint(frappe._("Same file has already been attached to the record")) - frappe.db.rollback() - raise frappe.DuplicateEntryError + if not getattr(self, "ignore_duplicate_entry_error", False): + # check duplicate assignement + n_records = frappe.db.sql("""select name from `tabFile Data` + where content_hash=%s + and name!=%s + and attached_to_doctype=%s + and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype, + self.attached_to_name)) + if len(n_records) > 0: + self.duplicate_entry = n_records[0][0] + frappe.msgprint(frappe._("Same file has already been attached to the record")) + raise frappe.DuplicateEntryError def on_trash(self): if self.attached_to_name: @@ -37,9 +37,9 @@ class FileData(Document): try: if not getattr(self, 'ignore_permissions', False) and \ not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name): - + frappe.msgprint(frappe._("No permission to write / remove."), raise_exception=True) - + except frappe.DoesNotExistError: pass diff --git a/frappe/core/doctype/letter_head/letter_head.json b/frappe/core/doctype/letter_head/letter_head.json index 115570898b..ba04b34611 100644 --- a/frappe/core/doctype/letter_head/letter_head.json +++ b/frappe/core/doctype/letter_head/letter_head.json @@ -1,6 +1,5 @@ { - "allow_attach": 1, - "autoname": "field:letter_head_name", + "autoname": "field:letter_head_name", "creation": "2012-11-22 17:45:46", "docstatus": 0, "doctype": "DocType", @@ -35,7 +34,8 @@ "label": "Is Default", "oldfieldname": "is_default", "oldfieldtype": "Check", - "permlevel": 0 + "permlevel": 0, + "search_index": 1 }, { "depends_on": "letter_head_name", @@ -52,14 +52,13 @@ "icon": "icon-font", "idx": 1, "max_attachments": 3, - "modified": "2014-05-07 06:03:07.760995", + "modified": "2014-07-21 05:57:56.052191", "modified_by": "Administrator", "module": "Core", "name": "Letter Head", "owner": "Administrator", "permissions": [ { - "cancel": 0, "create": 1, "delete": 1, "email": 1, @@ -72,6 +71,7 @@ "write": 1 }, { + "apply_user_permissions": 1, "delete": 0, "email": 0, "permlevel": 0, diff --git a/frappe/core/doctype/module_def/module_def.json b/frappe/core/doctype/module_def/module_def.json index b046c7057b..49dc29c61b 100644 --- a/frappe/core/doctype/module_def/module_def.json +++ b/frappe/core/doctype/module_def/module_def.json @@ -1,107 +1,35 @@ { - "_last_update": null, - "_user_tags": null, - "allow_attach": null, - "allow_copy": null, - "allow_email": null, - "allow_import": null, - "allow_print": null, "allow_rename": 1, - "allow_trash": null, "autoname": "field:module_name", - "change_log": null, - "client_script": null, - "client_script_core": null, - "client_string": null, - "colour": null, "creation": "2013-01-10 16:34:03", - "custom": null, - "default_print_format": null, - "description": null, "docstatus": 0, "doctype": "DocType", - "document_type": null, - "dt_template": null, "fields": [ { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, "fieldname": "module_name", "fieldtype": "Data", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, "in_list_view": 1, "label": "Module Name", - "no_column": null, - "no_copy": null, "oldfieldname": "module_name", "oldfieldtype": "Data", - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null + "permlevel": 0 }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, "fieldname": "app_name", "fieldtype": "Data", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, "in_list_view": 1, "label": "App Name", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": 1, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null + "reqd": 1 } ], - "hide_heading": null, - "hide_toolbar": null, "icon": "icon-sitemap", "idx": 1, - "in_create": null, - "in_dialog": null, - "is_submittable": null, - "is_transaction_doc": null, - "issingle": null, - "istable": null, - "max_attachments": null, - "menu_index": null, - "modified": "2014-04-07 13:00:27.894115", + "modified": "2014-06-12 01:00:52.304755", "modified_by": "Administrator", "module": "Core", "name": "Module Def", - "name_case": null, "owner": "Administrator", - "parent": null, - "parent_node": null, - "parentfield": null, - "parenttype": null, "permissions": [ { "amend": 0, @@ -109,54 +37,18 @@ "create": 1, "delete": 1, "email": 1, - "export": null, - "import": null, - "match": null, "permlevel": 0, "print": 1, "read": 1, "report": 1, - "restrict": null, - "restricted": null, "role": "Administrator", "submit": 0, "write": 1 }, { - "amend": null, - "cancel": null, - "create": 1, - "delete": 1, - "email": null, - "export": null, - "import": null, - "match": null, "permlevel": 0, - "print": null, "read": 1, - "report": null, - "restrict": null, - "restricted": null, - "role": "System Manager", - "submit": null, - "write": 1 + "role": "System Manager" } - ], - "plugin": null, - "print_outline": null, - "read_only": null, - "read_only_onload": null, - "search_fields": null, - "section_style": null, - "server_code": null, - "server_code_compiled": null, - "server_code_core": null, - "server_code_error": null, - "show_in_menu": null, - "smallicon": null, - "subject": null, - "tag_fields": null, - "title_field": null, - "use_template": null, - "version": null + ] } \ No newline at end of file diff --git a/frappe/core/doctype/module_def/module_def.py b/frappe/core/doctype/module_def/module_def.py index 6758a4ae0d..b1c790a696 100644 --- a/frappe/core/doctype/module_def/module_def.py +++ b/frappe/core/doctype/module_def/module_def.py @@ -8,13 +8,16 @@ from frappe.model.document import Document class ModuleDef(Document): def validate(self): + if not frappe.conf.get("developer_mode"): + return + modules = None - if not frappe.local.module_app.get(self.name): + if not frappe.local.module_app.get(frappe.scrub(self.name)): with open(frappe.get_app_path(self.app_name, "modules.txt"), "r") as f: content = f.read() - if not frappe.scrub(self.name) in content.splitlines(): + if not self.name in content.splitlines(): modules = filter(None, content.splitlines()) - modules.append(frappe.scrub(self.name)) + modules.append(self.name) if modules: with open(frappe.get_app_path(self.app_name, "modules.txt"), "w") as f: diff --git a/frappe/core/doctype/notification_count/notification_count.py b/frappe/core/doctype/notification_count/notification_count.py index 8796d747b8..28bd0e2ea6 100644 --- a/frappe/core/doctype/notification_count/notification_count.py +++ b/frappe/core/doctype/notification_count/notification_count.py @@ -5,9 +5,11 @@ from __future__ import unicode_literals import frappe - +import MySQLdb from frappe.model.document import Document +logger = frappe.get_logger() + class NotificationCount(Document): pass @@ -15,6 +17,7 @@ class NotificationCount(Document): def get_notifications(): if frappe.flags.in_install_app: return + config = get_notification_config() can_read = frappe.user.get_can_read() open_count_doctype = {} @@ -32,34 +35,63 @@ def get_notifications(): open_count_doctype[d] = notification_count[d] else: result = frappe.get_list(d, fields=["count(*)"], - filters=[[d, key, "=", condition[key]]], as_list=True, limit_page_length=1)[0][0] - - frappe.get_doc({"doctype":"Notification Count", "for_doctype":d, - "open_count":result}).insert(ignore_permissions=True) + filters=[[d, key, "=", condition[key]]], as_list=True)[0][0] open_count_doctype[d] = result + try: + frappe.get_doc({"doctype":"Notification Count", "for_doctype":d, + "open_count":result}).insert(ignore_permissions=True) + + except MySQLdb.OperationalError, e: + if e.args[0] != 1213: + raise + + logger.error("Deadlock") + for m in config.for_module: if m in notification_count: open_count_module[m] = notification_count[m] else: open_count_module[m] = frappe.get_attr(config.for_module[m])() - frappe.get_doc({"doctype":"Notification Count", "for_doctype":m, - "open_count":open_count_module[m]}).insert(ignore_permissions=True) + + try: + frappe.get_doc({"doctype":"Notification Count", "for_doctype":m, + "open_count":open_count_module[m]}).insert(ignore_permissions=True) + + except MySQLdb.OperationalError, e: + if e.args[0] != 1213: + raise + + logger.error("Deadlock") + return { "open_count_doctype": open_count_doctype, "open_count_module": open_count_module } +def clear_notifications(user=None): + if frappe.flags.in_install_app=="frappe": + return + + try: + if user: + frappe.db.sql("""delete from `tabNotification Count` where owner=%s""", (user,)) + else: + frappe.db.sql("""delete from `tabNotification Count`""") + + except MySQLdb.OperationalError, e: + if e.args[0] != 1213: + raise + + logger.error("Deadlock") + def delete_notification_count_for(doctype): if frappe.flags.in_import: return frappe.db.sql("""delete from `tabNotification Count` where for_doctype = %s""", (doctype,)) -def delete_event_notification_count(): - delete_notification_count_for("Event") - -def clear_doctype_notifications(doc, method=None): +def clear_doctype_notifications(doc, method=None, *args, **kwargs): if frappe.flags.in_import: return diff --git a/frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json b/frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json index c4460d34c9..7ae14e6256 100644 --- a/frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json +++ b/frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json @@ -1,14 +1,28 @@ { "allow_copy": 1, - "creation": "2014-03-03 19:48:01.000000", + "creation": "2014-03-03 19:48:01", "description": "Email Settings for Outgoing and Incoming Emails.", "docstatus": 0, "doctype": "DocType", "fields": [ + { + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled", + "permlevel": 0 + }, + { + "depends_on": "eval:doc.enabled", + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "label": "Server & Credentials", + "permlevel": 0 + }, { "description": "SMTP Server (e.g. smtp.gmail.com)", "fieldname": "mail_server", "fieldtype": "Data", + "in_list_view": 1, "label": "Outgoing Mail Server", "permlevel": 0 }, @@ -16,6 +30,7 @@ "description": "[?]", "fieldname": "use_ssl", "fieldtype": "Check", + "in_list_view": 1, "label": "Use TLS", "permlevel": 0 }, @@ -23,6 +38,7 @@ "description": "If non standard port (e.g. 587)", "fieldname": "mail_port", "fieldtype": "Int", + "in_list_view": 1, "label": "Port", "permlevel": 0 }, @@ -35,6 +51,7 @@ "description": "Set Login and Password if authentication is required.", "fieldname": "mail_login", "fieldtype": "Data", + "in_list_view": 1, "label": "Login Id", "permlevel": 0 }, @@ -59,19 +76,25 @@ "permlevel": 0 }, { - "default": "1", - "description": "If checked, an email with an attached HTML format will be added to part of the EMail body as well as attachment. To only send as attachment, uncheck this.", - "fieldname": "send_print_in_body_and_attachment", - "fieldtype": "Check", - "label": "Send Print in Body and Attachment", + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "label": "Email Footer", "permlevel": 0 + }, + { + "default": "
Sent via \n\tFrappe
", + "fieldname": "footer", + "fieldtype": "Text Editor", + "label": "", + "permlevel": 0, + "reqd": 0 } ], "icon": "icon-cog", "idx": 1, "in_create": 1, "issingle": 1, - "modified": "2014-03-03 20:20:09.000000", + "modified": "2014-07-17 08:08:00.483391", "modified_by": "Administrator", "module": "Core", "name": "Outgoing Email Settings", diff --git a/frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py b/frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py index 5ee88d31a8..dd61262559 100644 --- a/frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py +++ b/frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py @@ -5,21 +5,28 @@ from __future__ import unicode_literals import frappe - +from frappe import _, throw +from frappe.utils import validate_email_add from frappe.model.document import Document class OutgoingEmailSettings(Document): - + def validate(self): - if self.mail_server: + if self.auto_email_id and not validate_email_add(self.auto_email_id): + throw(_("{0} is not a valid email id").format(self.auto_email_id), frappe.InvalidEmailAddressError) + + if self.mail_server and not frappe.local.flags.in_patch: from frappe.utils import cint from frappe.utils.email_lib.smtp import SMTPServer smtpserver = SMTPServer(login = self.mail_login, password = self.mail_password, server = self.mail_server, port = cint(self.mail_port), - use_ssl = self.use_ssl + use_ssl = cint(self.use_ssl) ) - + # exceptions are handled in session connect - sess = smtpserver.sess \ No newline at end of file + sess = smtpserver.sess + +def get_mail_footer(): + return frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "footer") or "" diff --git a/frappe/core/doctype/page/page.json b/frappe/core/doctype/page/page.json index 1ea08691f7..b8aab04f73 100644 --- a/frappe/core/doctype/page/page.json +++ b/frappe/core/doctype/page/page.json @@ -2,7 +2,7 @@ "allow_copy": 0, "allow_rename": 1, "autoname": "field:page_name", - "creation": "2012-12-20 17:16:49.000000", + "creation": "2012-12-20 17:16:49", "docstatus": 0, "doctype": "DocType", "fields": [ @@ -16,6 +16,7 @@ { "fieldname": "page_name", "fieldtype": "Data", + "in_list_view": 1, "label": "Page Name", "oldfieldname": "page_name", "oldfieldtype": "Data", @@ -25,12 +26,14 @@ { "fieldname": "title", "fieldtype": "Data", + "in_list_view": 1, "label": "Title", "permlevel": 0 }, { "fieldname": "icon", "fieldtype": "Data", + "in_list_view": 1, "label": "icon", "permlevel": 0 }, @@ -84,14 +87,13 @@ "idx": 1, "issingle": 0, "istable": 0, - "modified": "2013-12-30 13:48:02.000000", + "modified": "2014-05-27 03:49:14.476843", "modified_by": "Administrator", "module": "Core", "name": "Page", "owner": "Administrator", "permissions": [ { - "cancel": 0, "create": 1, "email": 1, "permlevel": 0, @@ -102,6 +104,7 @@ "write": 1 }, { + "apply_user_permissions": 1, "email": 1, "permlevel": 0, "print": 1, diff --git a/frappe/core/doctype/print_format/print_format.js b/frappe/core/doctype/print_format/print_format.js index d09f46c261..7bf03bfce6 100644 --- a/frappe/core/doctype/print_format/print_format.js +++ b/frappe/core/doctype/print_format/print_format.js @@ -1,13 +1,21 @@ -cur_frm.cscript.refresh = function (doc) { +frappe.ui.form.on("Print Format", "onload", function(frm) { + frm.add_fetch("doc_type", "module", "module"); +}); + +frappe.ui.form.on("Print Format", "refresh", function(frm) { + frm.set_intro(""); if (user!="Administrator") { - if (doc.standard == 'Yes') { - cur_frm.toggle_enable(["html", "doc_type", "module"], false); - cur_frm.disable_save(); + if (frm.doc.standard == 'Yes') { + frm.toggle_enable(["html", "doc_type", "module"], false); + frm.disable_save(); } else { - cur_frm.toggle_enable(["html", "doc_type", "module"], true); - cur_frm.enable_save(); + frm.toggle_enable(["html", "doc_type", "module"], true); + frm.enable_save(); + } + frm.toggle_enable("standard", false); + } else { + if(frm.doc.standard==="Yes") { + frm.set_intro(__("This is a standard format. To make changes, please copy it make make a new format.")) } - - cur_frm.toggle_enable("standard", false); } -} +}) diff --git a/frappe/core/doctype/print_format/print_format.json b/frappe/core/doctype/print_format/print_format.json index a8bcffd22e..ef2306509a 100644 --- a/frappe/core/doctype/print_format/print_format.json +++ b/frappe/core/doctype/print_format/print_format.json @@ -1,12 +1,23 @@ { - "allow_attach": 0, - "allow_copy": 0, + "allow_copy": 0, "allow_rename": 0, "autoname": "Prompt", "creation": "2013-01-23 19:54:43", "docstatus": 0, "doctype": "DocType", "fields": [ + { + "description": "Belongs to", + "fieldname": "doc_type", + "fieldtype": "Link", + "in_filter": 1, + "in_list_view": 1, + "label": "DocType", + "options": "DocType", + "permlevel": 0, + "reqd": 1, + "search_index": 0 + }, { "allow_on_submit": 0, "fieldname": "module", @@ -25,18 +36,6 @@ "reqd": 1, "search_index": 1 }, - { - "description": "Belongs to", - "fieldname": "doc_type", - "fieldtype": "Link", - "in_filter": 1, - "in_list_view": 1, - "label": "DocType", - "options": "DocType", - "permlevel": 0, - "reqd": 0, - "search_index": 0 - }, { "fieldname": "column_break_3", "fieldtype": "Column Break", @@ -63,11 +62,20 @@ "search_index": 1 }, { + "default": "Server", + "description": "Client-side formats are now deprecated", "fieldname": "print_format_type", "fieldtype": "Select", "in_list_view": 1, "label": "Print Format Type", - "options": "Client\nServer", + "options": "Server\nClient", + "permlevel": 0, + "read_only": 0 + }, + { + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled", "permlevel": 0 }, { @@ -77,7 +85,7 @@ }, { "allow_on_submit": 0, - "depends_on": "eval:doc.print_format_type!=\"Server\"", + "depends_on": "", "fieldname": "html", "fieldtype": "Code", "hidden": 0, @@ -92,6 +100,13 @@ "report_hide": 0, "reqd": 0, "search_index": 0 + }, + { + "fieldname": "print_format_help", + "fieldtype": "HTML", + "label": "Print Format Help", + "options": "

Print Format Help

\n
\n

Introduction

\n

Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.

\n

For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.

\n
\n

References

\n
    \n\t
  1. Jinja Tempalting Language: Reference
  2. \n\t
  3. Bootstrap CSS Framework
  4. \n
\n
\n

Example

\n
<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>
\n
\n

Common Functions

\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
doc.get_formatted(\"[fieldname]\", [parent_doc])Get document value formatted as Date, Currency etc. Pass parent doc for curreny type fields.
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")Get value from another document.
\n", + "permlevel": 0 } ], "hide_heading": 0, @@ -103,7 +118,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2014-05-09 02:12:39.540952", + "modified": "2014-07-31 03:39:35.898711", "modified_by": "Administrator", "module": "Core", "name": "Print Format", diff --git a/frappe/core/doctype/print_format/print_format.py b/frappe/core/doctype/print_format/print_format.py index c666487c37..a770062147 100644 --- a/frappe/core/doctype/print_format/print_format.py +++ b/frappe/core/doctype/print_format/print_format.py @@ -2,11 +2,8 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe, os +import frappe import frappe.utils -from frappe.modules import get_doc_path - -standard_format = "templates/print_formats/standard.html" from frappe.model.document import Document @@ -38,76 +35,3 @@ class PrintFormat(Document): if self.doc_type: frappe.clear_cache(doctype=self.doc_type) -def get_args(): - if not frappe.form_dict.format: - frappe.form_dict.format = standard_format - if not frappe.form_dict.doctype or not frappe.form_dict.name: - return { - "body": """

Error

-

Parameters doctype, name and format required

-
%s
""" % 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": """

Error

-

No {ptype} permission

""".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() diff --git a/frappe/core/doctype/print_format/styles/classic.css b/frappe/core/doctype/print_format/styles/classic.css deleted file mode 100644 index d6a08f06d8..0000000000 --- a/frappe/core/doctype/print_format/styles/classic.css +++ /dev/null @@ -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; -} diff --git a/frappe/core/doctype/print_format/styles/modern.css b/frappe/core/doctype/print_format/styles/modern.css deleted file mode 100644 index e9f9a95501..0000000000 --- a/frappe/core/doctype/print_format/styles/modern.css +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/frappe/core/doctype/print_format/styles/standard.css b/frappe/core/doctype/print_format/styles/standard.css deleted file mode 100644 index 17b18bb050..0000000000 --- a/frappe/core/doctype/print_format/styles/standard.css +++ /dev/null @@ -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; -} diff --git a/frappe/core/doctype/print_format/test_print_format.py b/frappe/core/doctype/print_format/test_print_format.py new file mode 100644 index 0000000000..a6b9981b0f --- /dev/null +++ b/frappe/core/doctype/print_format/test_print_format.py @@ -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("" in print_html) + self.assertTrue(re.findall('
[\s]*Administrator[\s]*
', 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) diff --git a/frappe/core/doctype/print_format/test_records.json b/frappe/core/doctype/print_format/test_records.json new file mode 100644 index 0000000000..aab3b96884 --- /dev/null +++ b/frappe/core/doctype/print_format/test_records.json @@ -0,0 +1,8 @@ +[ + { + "doctype": "Print Format", + "name": "_Test Print Format 1", + "module": "core", + "doc_type": "User" + } +] diff --git a/frappe/website/doctype/website_template/__init__.py b/frappe/core/doctype/print_settings/__init__.py similarity index 100% rename from frappe/website/doctype/website_template/__init__.py rename to frappe/core/doctype/print_settings/__init__.py diff --git a/frappe/core/doctype/print_settings/print_settings.js b/frappe/core/doctype/print_settings/print_settings.js new file mode 100644 index 0000000000..207be09fcd --- /dev/null +++ b/frappe/core/doctype/print_settings/print_settings.js @@ -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(''); +}); + +frappe.ui.form.on("Print Settings", "onload", function (frm) { + frm.script_manager.trigger("print_style"); +}); diff --git a/frappe/core/doctype/print_settings/print_settings.json b/frappe/core/doctype/print_settings/print_settings.json new file mode 100644 index 0000000000..87daa526cf --- /dev/null +++ b/frappe/core/doctype/print_settings/print_settings.json @@ -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" +} \ No newline at end of file diff --git a/frappe/core/doctype/print_settings/print_settings.py b/frappe/core/doctype/print_settings/print_settings.py new file mode 100644 index 0000000000..d027cdf1e0 --- /dev/null +++ b/frappe/core/doctype/print_settings/print_settings.py @@ -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() diff --git a/frappe/core/doctype/property_setter/property_setter.json b/frappe/core/doctype/property_setter/property_setter.json index 3606bc325a..47691926aa 100644 --- a/frappe/core/doctype/property_setter/property_setter.json +++ b/frappe/core/doctype/property_setter/property_setter.json @@ -1,5 +1,5 @@ { - "creation": "2013-01-10 16:34:04.000000", + "creation": "2013-01-10 16:34:04", "description": "Property Setter overrides a standard DocType or Field property", "docstatus": 0, "doctype": "DocType", @@ -20,6 +20,7 @@ "depends_on": "eval:doc.__islocal", "fieldname": "doctype_or_field", "fieldtype": "Select", + "in_list_view": 1, "label": "DocType or Field", "options": "\nDocField\nDocType", "permlevel": 0, @@ -29,6 +30,7 @@ "description": "New value to be set", "fieldname": "value", "fieldtype": "Text", + "in_list_view": 1, "label": "Set Value", "permlevel": 0 }, @@ -83,7 +85,7 @@ ], "icon": "icon-glass", "idx": 1, - "modified": "2014-01-20 17:49:03.000000", + "modified": "2014-07-24 02:04:26.838056", "modified_by": "Administrator", "module": "Core", "name": "Property Setter", @@ -116,5 +118,5 @@ "write": 1 } ], - "search_fields": "doc_name,property" + "search_fields": "doc_type,property" } \ No newline at end of file diff --git a/frappe/core/doctype/property_setter/property_setter.py b/frappe/core/doctype/property_setter/property_setter.py index af529b84d6..07d9a348af 100644 --- a/frappe/core/doctype/property_setter/property_setter.py +++ b/frappe/core/doctype/property_setter/property_setter.py @@ -1,5 +1,5 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# MIT License. See license.txt from __future__ import unicode_literals import frappe @@ -7,7 +7,6 @@ import frappe from frappe.model.document import Document class PropertySetter(Document): - def autoname(self): self.name = self.doc_type + "-" \ + (self.field_name and (self.field_name + "-") or "") \ @@ -21,47 +20,52 @@ class PropertySetter(Document): and doc_type = %(doc_type)s and ifnull(field_name,'') = ifnull(%(field_name)s, '') and property = %(property)s""", self.get_valid_dict()) - + # clear cache frappe.clear_cache(doctype = self.doc_type) - + def get_property_list(self, dt): - return frappe.db.sql("""select fieldname, label, fieldtype + return frappe.db.sql("""select fieldname, label, fieldtype from tabDocField where parent=%s - and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table') + and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table', 'Fold') and ifnull(fieldname, '') != '' order by label asc""", dt, as_dict=1) - + def get_setup_data(self): return { 'doctypes': [d[0] for d in frappe.db.sql("select name from tabDocType")], 'dt_properties': self.get_property_list('DocType'), 'df_properties': self.get_property_list('DocField') } - + def get_field_ids(self): return frappe.db.sql("select name, fieldtype, label, fieldname from tabDocField where parent=%s", self.doc_type, as_dict = 1) - + def get_defaults(self): if not self.field_name: return frappe.db.sql("select * from `tabDocType` where name=%s", self.doc_type, as_dict = 1)[0] else: - return frappe.db.sql("select * from `tabDocField` where fieldname=%s and parent=%s", + return frappe.db.sql("select * from `tabDocField` where fieldname=%s and parent=%s", (self.field_name, self.doc_type), as_dict = 1)[0] - + def on_update(self): - from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype - validate_fields_for_doctype(self.doc_type) - -def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False): - return frappe.get_doc({ - "doctype":"Property Setter", - "doctype_or_field": for_doctype and "DocType" or "DocField", - "doc_type": doctype, - "field_name": fieldname, - "property": property, - "value": value, - "property_type": property_type - }).insert() - \ No newline at end of file + 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 diff --git a/frappe/core/doctype/report/boilerplate/controller.js b/frappe/core/doctype/report/boilerplate/controller.js new file mode 100644 index 0000000000..cb82227747 --- /dev/null +++ b/frappe/core/doctype/report/boilerplate/controller.js @@ -0,0 +1,8 @@ +// Copyright (c) 2013, {app_publisher} and contributors +// For license information, please see license.txt + +frappe.query_reports["{name}"] = {{ + "filters": [ + + ] +}} diff --git a/frappe/core/doctype/report/boilerplate/controller.py b/frappe/core/doctype/report/boilerplate/controller.py new file mode 100644 index 0000000000..5287ecc691 --- /dev/null +++ b/frappe/core/doctype/report/boilerplate/controller.py @@ -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 diff --git a/frappe/core/doctype/report/report.js b/frappe/core/doctype/report/report.js index d8d4bf90cc..47cc2c4706 100644 --- a/frappe/core/doctype/report/report.js +++ b/frappe/core/doctype/report/report.js @@ -11,7 +11,22 @@ cur_frm.cscript.refresh = function(doc) { frappe.set_route("query-report", doc.name); break; } - }, "icon-table") + }, "icon-table"); + + if (doc.is_standard === "Yes") { + cur_frm.add_custom_button(doc.disabled ? __("Enable Report") : __("Disable Report"), function() { + $.ajax({ + url: "/api/resource/Report/" + encodeURIComponent(doc.name), + type: "POST", + data: { + run_method: "toggle_disable", + disable: doc.disabled ? 0 : 1 + } + }).always(function() { + cur_frm.reload_doc(); + }); + }, doc.disabled ? "icon-ok" : "icon-off"); + } cur_frm.set_intro(""); switch(doc.report_type) { diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index 62e81b08b9..3851d07c40 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -61,6 +61,15 @@ "permlevel": 0, "read_only": 0 }, + { + "default": "1", + "depends_on": "eval:[\"Query Report\", \"Script Report\"].indexOf(doc.report_type)!==-1", + "fieldname": "apply_user_permissions", + "fieldtype": "Check", + "in_list_view": 0, + "label": "Apply User Permissions", + "permlevel": 0 + }, { "fieldname": "section_break_6", "fieldtype": "Section Break", @@ -101,14 +110,13 @@ ], "icon": "icon-table", "idx": 1, - "modified": "2014-05-12 17:08:04.185601", + "modified": "2014-06-03 07:25:41.509885", "modified_by": "Administrator", "module": "Core", "name": "Report", "owner": "Administrator", "permissions": [ { - "cancel": 0, "create": 1, "delete": 1, "email": 1, @@ -121,7 +129,6 @@ "write": 1 }, { - "cancel": 0, "create": 1, "delete": 1, "email": 1, @@ -134,7 +141,6 @@ "write": 1 }, { - "cancel": 0, "create": 1, "delete": 1, "email": 1, @@ -147,7 +153,7 @@ "write": 1 }, { - "cancel": 0, + "apply_user_permissions": 1, "delete": 0, "email": 1, "permlevel": 0, diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 26c646529c..69b66ba0c7 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -3,35 +3,50 @@ from __future__ import unicode_literals import frappe -from frappe import conf, _ - +from frappe import _ +from frappe.utils import cint from frappe.model.document import Document +from frappe.modules.export_file import export_to_files +from frappe.modules import make_boilerplate class Report(Document): def validate(self): """only administrator can save standard report""" if not self.module: self.module = frappe.db.get_value("DocType", self.ref_doctype, "module") - + if not self.is_standard: self.is_standard = "No" - if frappe.session.user=="Administrator" and getattr(conf, 'developer_mode',0)==1: + if frappe.session.user=="Administrator" and getattr(frappe.local.conf, 'developer_mode',0)==1: self.is_standard = "Yes" if self.is_standard == "Yes" and frappe.session.user!="Administrator": - frappe.msgprint(_("Only Administrator can save a standard report. Please rename and save."), + frappe.msgprint(_("Only Administrator can save a standard report. Please rename and save."), raise_exception=True) if self.report_type in ("Query Report", "Script Report") \ and frappe.session.user!="Administrator": frappe.msgprint(_("Only Administrator allowed to create Query / Script Reports"), raise_exception=True) - + def on_update(self): self.export_doc() - + def export_doc(self): - from frappe.modules.export_file import export_to_files - if self.is_standard == 'Yes' and (conf.get('developer_mode') or 0) == 1: - export_to_files(record_list=[['Report', self.name]], + if frappe.flags.in_import: + return + + if self.is_standard == 'Yes' and (frappe.local.conf.get('developer_mode') or 0) == 1: + export_to_files(record_list=[['Report', self.name]], record_module=self.module) + + self.create_report_py() + + def create_report_py(self): + if self.report_type == "Script Report": + make_boilerplate("controller.py", self, {"name": self.name}) + make_boilerplate("controller.js", self, {"name": self.name}) + + @Document.whitelist + def toggle_disable(self, disable): + self.db_set("disabled", cint(disable)) diff --git a/frappe/core/doctype/role/role.js b/frappe/core/doctype/role/role.js index f05a2709c4..3ce8f32bdf 100644 --- a/frappe/core/doctype/role/role.js +++ b/frappe/core/doctype/role/role.js @@ -2,7 +2,7 @@ // MIT License. See license.txt cur_frm.cscript.refresh = function(doc) { - cur_frm.permission_manager = cur_frm.add_custom_button("Permission Manager", function() { + cur_frm.permission_manager = cur_frm.add_custom_button("Role Permissions Manager", function() { frappe.route_options = {"role": doc.name}; frappe.set_route("permission-manager"); }); diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json index aba7cfe0eb..6d664f4d77 100644 --- a/frappe/core/doctype/role/role.json +++ b/frappe/core/doctype/role/role.json @@ -23,7 +23,7 @@ "idx": 1, "issingle": 0, "istable": 0, - "modified": "2014-05-02 06:35:09.528809", + "modified": "2014-08-05 05:24:42.185395", "modified_by": "Administrator", "module": "Core", "name": "Role", @@ -31,7 +31,6 @@ "permissions": [ { "amend": 0, - "cancel": 0, "create": 1, "email": 1, "permlevel": 0, @@ -43,8 +42,8 @@ "write": 1 }, { - "cancel": 0, "create": 1, + "delete": 1, "email": 1, "permlevel": 0, "print": 1, @@ -55,6 +54,7 @@ "write": 1 }, { + "apply_user_permissions": 1, "permlevel": 0, "read": 1, "role": "All" diff --git a/frappe/core/doctype/social_login_keys/social_login_keys.json b/frappe/core/doctype/social_login_keys/social_login_keys.json deleted file mode 100644 index 98c0190754..0000000000 --- a/frappe/core/doctype/social_login_keys/social_login_keys.json +++ /dev/null @@ -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 - } - ] -} \ No newline at end of file diff --git a/frappe/website/page/sitemap_browser/__init__.py b/frappe/core/doctype/standard_reply/__init__.py similarity index 100% rename from frappe/website/page/sitemap_browser/__init__.py rename to frappe/core/doctype/standard_reply/__init__.py diff --git a/frappe/core/doctype/standard_reply/standard_reply.json b/frappe/core/doctype/standard_reply/standard_reply.json new file mode 100644 index 0000000000..bbb7c45546 --- /dev/null +++ b/frappe/core/doctype/standard_reply/standard_reply.json @@ -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" +} \ No newline at end of file diff --git a/frappe/core/doctype/standard_reply/standard_reply.py b/frappe/core/doctype/standard_reply/standard_reply.py new file mode 100644 index 0000000000..54937b3a58 --- /dev/null +++ b/frappe/core/doctype/standard_reply/standard_reply.py @@ -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 \ No newline at end of file diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index df30c669e9..dbc9eaead8 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -1,339 +1,100 @@ { - "_last_update": null, - "_user_tags": null, - "allow_attach": null, - "allow_copy": null, - "allow_email": null, - "allow_import": null, - "allow_print": null, - "allow_rename": null, - "allow_trash": null, - "autoname": null, - "change_log": null, - "client_script": null, - "client_script_core": null, - "client_string": null, - "colour": null, - "creation": "2014-04-17 16:53:52.640856", - "custom": null, - "default_print_format": null, - "description": null, - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "dt_template": null, + "creation": "2014-04-17 16:53:52.640856", + "docstatus": 0, + "doctype": "DocType", + "document_type": "System", "fields": [ { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "localization", - "fieldtype": "Section Break", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Localization", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "localization", + "fieldtype": "Section Break", + "label": "Localization", + "permlevel": 0 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "language", - "fieldtype": "Select", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": 1, - "label": "Language", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": "Loading...", - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": 1, - "search_index": 0, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "language", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Language", + "options": "Loading...", + "permlevel": 0, + "reqd": 1, + "search_index": 0 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "time_zone", - "fieldtype": "Select", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Time Zone", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": 1, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "time_zone", + "fieldtype": "Select", + "label": "Time Zone", + "permlevel": 0, + "reqd": 1 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "date_and_number_format", - "fieldtype": "Section Break", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Date and Number Format", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "date_and_number_format", + "fieldtype": "Section Break", + "label": "Date and Number Format", + "permlevel": 0 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "date_format", - "fieldtype": "Select", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Date Format", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\nmm/dd/yyyy\nmm-dd-yyyy", - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": 1, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "date_format", + "fieldtype": "Select", + "label": "Date Format", + "options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\ndd.mm.yyyy\nmm/dd/yyyy\nmm-dd-yyyy", + "permlevel": 0, + "reqd": 1 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "number_format", - "fieldtype": "Select", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Number Format", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": "#,###.##\n#.###,##\n# ###.##\n#,###.###\n#,##,###.##\n#.###\n#,###", - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": 1, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "number_format", + "fieldtype": "Select", + "label": "Number Format", + "options": "#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###", + "permlevel": 0, + "reqd": 1 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "float_precision", - "fieldtype": "Select", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Float Precision", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": "\n2\n3\n4\n5\n6", - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "float_precision", + "fieldtype": "Select", + "label": "Float Precision", + "options": "\n2\n3\n4\n5\n6", + "permlevel": 0 + }, { - "allow_on_submit": null, - "default": null, - "depends_on": null, - "description": null, - "fieldname": "security", - "fieldtype": "Section Break", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Security", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": null, - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null - }, + "fieldname": "security", + "fieldtype": "Section Break", + "label": "Security", + "permlevel": 0 + }, { - "allow_on_submit": null, - "default": "06:00", - "depends_on": null, - "description": "Session Expiry in Hours e.g. 06:00", - "fieldname": "session_expiry", - "fieldtype": "Data", - "hidden": null, - "ignore_restrictions": null, - "in_filter": null, - "in_list_view": null, - "label": "Session Expiry", - "no_column": null, - "no_copy": null, - "oldfieldname": null, - "oldfieldtype": null, - "options": "", - "permlevel": 0, - "print_hide": null, - "print_width": null, - "read_only": null, - "report_hide": null, - "reqd": null, - "search_index": null, - "set_only_once": null, - "trigger": null, - "width": null + "default": "06:00", + "description": "Session Expiry in Hours e.g. 06:00", + "fieldname": "session_expiry", + "fieldtype": "Data", + "label": "Session Expiry", + "options": "", + "permlevel": 0 + }, + { + "description": "Run scheduled jobs only if checked", + "fieldname": "enable_scheduler", + "fieldtype": "Check", + "in_list_view": 0, + "label": "Enable Scheduled Jobs", + "permlevel": 0 } - ], - "hide_heading": null, - "hide_toolbar": null, - "icon": "icon-cog", - "idx": null, - "in_create": null, - "in_dialog": null, - "is_submittable": null, - "is_transaction_doc": null, - "issingle": 1, - "istable": null, - "max_attachments": null, - "menu_index": null, - "modified": "2014-04-17 17:52:27.046530", - "modified_by": "Administrator", - "module": "Core", - "name": "System Settings", - "name_case": "", - "owner": "Administrator", - "parent": null, - "parent_node": null, - "parentfield": null, - "parenttype": null, + ], + "icon": "icon-cog", + "issingle": 1, + "modified": "2014-06-18 02:09:03.623094", + "modified_by": "Administrator", + "module": "Core", + "name": "System Settings", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "amend": null, - "cancel": null, - "create": 1, - "delete": null, - "email": null, - "export": null, - "import": null, - "match": null, - "permlevel": 0, - "print": null, - "read": 1, - "report": null, - "restrict": null, - "restricted": null, - "role": "System Manager", - "submit": null, + "create": 1, + "permlevel": 0, + "read": 1, + "role": "System Manager", "write": 1 } - ], - "plugin": null, - "print_outline": null, - "read_only": null, - "read_only_onload": null, - "search_fields": null, - "section_style": null, - "server_code": null, - "server_code_compiled": null, - "server_code_core": null, - "server_code_error": null, - "show_in_menu": null, - "smallicon": null, - "subject": null, - "tag_fields": null, - "title_field": null, - "use_template": null, - "version": null -} \ No newline at end of file + ] +} diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 2491a4a9b0..df497e732b 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -2,11 +2,12 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, pytz +import frappe from frappe import _ from frappe.model.document import Document from frappe.translate import get_lang_dict, set_default_language from frappe.utils import cint +from frappe.utils.momentjs import get_all_timezones class SystemSettings(Document): def validate(self): @@ -17,11 +18,11 @@ class SystemSettings(Document): def on_update(self): for df in self.meta.get("fields"): - if df.fieldtype in ("Select", "Data"): + if df.fieldtype in ("Select", "Data", "Check"): frappe.db.set_default(df.fieldname, self.get(df.fieldname)) - set_default_language(self.language) - + if self.language: + set_default_language(self.language) @frappe.whitelist() def load(): @@ -39,7 +40,7 @@ def load(): languages.sort() return { - "timezones": pytz.all_timezones, + "timezones": get_all_timezones(), "languages": [""] + languages, "defaults": defaults } diff --git a/frappe/core/doctype/todo/todo.js b/frappe/core/doctype/todo/todo.js index fa43b43fff..9835e16538 100644 --- a/frappe/core/doctype/todo/todo.js +++ b/frappe/core/doctype/todo/todo.js @@ -1,13 +1,23 @@ // bind events frappe.ui.form.on("ToDo", "refresh", function(frm) { - frm.add_custom_button((frm.doc.status=="Open" ? __("Close") : __("Re-open")), function() { - frm.set_value("status", frm.doc.status=="Open" ? "Closed" : "Open"); - frm.save(); - }); - if(frm.doc.reference_type && frm.doc.reference_name) { - frm.set_intro('Reference: ' - + frm.doc.reference_name + ''); + 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]); } -}); \ No newline at end of file + + 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"); + } + } +}); diff --git a/frappe/core/doctype/todo/todo.json b/frappe/core/doctype/todo/todo.json index c525a98347..e1cc5fa782 100644 --- a/frappe/core/doctype/todo/todo.json +++ b/frappe/core/doctype/todo/todo.json @@ -1,9 +1,8 @@ { - "allow_attach": 0, - "allow_copy": 0, + "allow_copy": 0, "allow_rename": 0, "autoname": "TDI.########", - "creation": "2012-07-03 13:30:35.000000", + "creation": "2012-07-03 13:30:35", "docstatus": 0, "doctype": "DocType", "fields": [ @@ -82,6 +81,16 @@ "reqd": 0, "search_index": 0 }, + { + "fieldname": "owner", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 0, + "label": "Assigned To", + "options": "User", + "permlevel": 0, + "reqd": 1 + }, { "fieldname": "section_break_6", "fieldtype": "Section Break", @@ -91,13 +100,14 @@ { "allow_on_submit": 0, "fieldname": "reference_type", - "fieldtype": "Data", + "fieldtype": "Link", "hidden": 0, "in_filter": 0, "label": "Reference Type", "no_copy": 0, "oldfieldname": "reference_type", "oldfieldtype": "Data", + "options": "DocType", "permlevel": 0, "print_hide": 0, "report_hide": 0, @@ -107,13 +117,14 @@ { "allow_on_submit": 0, "fieldname": "reference_name", - "fieldtype": "Data", + "fieldtype": "Dynamic Link", "hidden": 0, "in_filter": 0, "label": "Reference Name", "no_copy": 0, "oldfieldname": "reference_name", "oldfieldtype": "Data", + "options": "reference_type", "permlevel": 0, "print_hide": 0, "report_hide": 0, @@ -145,6 +156,7 @@ { "fieldname": "assigned_by", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Assigned By", "options": "User", "permlevel": 0 @@ -158,14 +170,14 @@ "in_dialog": 0, "issingle": 0, "max_attachments": 0, - "modified": "2014-03-12 17:06:46.000000", + "modified": "2014-06-30 05:40:15.471434", "modified_by": "Administrator", "module": "Core", "name": "ToDo", "owner": "Administrator", "permissions": [ { - "cancel": 0, + "apply_user_permissions": 1, "create": 1, "delete": 0, "email": 1, @@ -174,7 +186,6 @@ "print": 1, "read": 1, "report": 1, - "restricted": 1, "role": "All", "submit": 0, "write": 1 @@ -194,5 +205,6 @@ ], "read_only": 0, "read_only_onload": 0, + "search_fields": "description, reference_type, reference_name", "title_field": "description" } \ No newline at end of file diff --git a/frappe/core/doctype/todo/todo.py b/frappe/core/doctype/todo/todo.py index 936e676c07..5d67314780 100644 --- a/frappe/core/doctype/todo/todo.py +++ b/frappe/core/doctype/todo/todo.py @@ -3,46 +3,84 @@ from __future__ import unicode_literals import frappe +import json from frappe.model.document import Document class ToDo(Document): def validate(self): if self.is_new(): - self.add_comment(frappe._("Assignment Added")) + self.add_comment(frappe._("Assigned to {0}").format(self.owner), "Assigned") else: cur_status = frappe.db.get_value("ToDo", self.name, "status") if cur_status != self.status: - self.add_comment(frappe._("Assignment Status Changed")) - - def add_comment(self, text): + self.add_comment(frappe._("Assignment Status Changed"), "Assignment Completed") + + def on_update(self): + self.update_in_reference() + + def on_trash(self): + self.update_in_reference() + + def add_comment(self, text, comment_type): if not self.reference_type and self.reference_name: return - - comment = frappe.get_doc({ + + frappe.get_doc({ "doctype":"Comment", "comment_by": frappe.session.user, + "comment_type": comment_type, "comment_doctype": self.reference_type, "comment_docname": self.reference_name, - "comment": """
{text}: + "comment": """
{text}: {status}: {description}
""".format(text=text, status = frappe._(self.status), name = self.name, description = self.description) }).insert(ignore_permissions=True) - - -# todo is viewable if either owner or assigned_to or System Manager in roles -def get_permission_query_conditions(): - if "System Manager" in frappe.get_roles(): + def update_in_reference(self): + if not (self.reference_type and self.reference_name): + return + + try: + assignments = [d[0] for d in frappe.get_list("ToDo", + filters={ + "reference_type": self.reference_type, + "reference_name": self.reference_name, + "status": "Open" + }, + fields=["owner"], ignore_permissions=True, as_list=True)] + + assignments.reverse() + frappe.db.set_value(self.reference_type, self.reference_name, + "_assign", json.dumps(assignments)) + + except Exception, e: + if e.args[0] == 1146 and frappe.flags.in_install: + # no table + return + + elif e.args[0]==1054: + from frappe.model.db_schema import add_column + add_column(self.reference_type, "_assign", "Text") + self.update_in_reference() + + else: + raise + +# NOTE: todo is viewable if either owner or assigned_to or System Manager in roles + +def get_permission_query_conditions(user): + if not user: user = frappe.session.user + + if "System Manager" in frappe.get_roles(user): return None else: - return """(tabToDo.owner = '{user}' or tabToDo.assigned_by = '{user}')""".format(user=frappe.session.user) - -def has_permission(doc): - if "System Manager" in frappe.get_roles(): + return """(tabToDo.owner = '{user}' or tabToDo.assigned_by = '{user}')""".format(user=user) + +def has_permission(doc, user): + if "System Manager" in frappe.get_roles(user): return True else: - return doc.owner==frappe.session.user or doc.assigned_by==frappe.session.user - \ No newline at end of file + return doc.owner==user or doc.assigned_by==user diff --git a/frappe/core/doctype/todo/todo_list.html b/frappe/core/doctype/todo/todo_list.html new file mode 100644 index 0000000000..18b9dea848 --- /dev/null +++ b/frappe/core/doctype/todo/todo_list.html @@ -0,0 +1,29 @@ +
+
+
+ {%= list.get_avatar_and_id(doc) %} + {%= __(doc.status) %} + + {%= __(doc.priority) %} + + {% if (doc.reference_name) { %} + {% var reference_href = ("#Form/" + encodeURIComponent(doc.reference_type) + "/" + + encodeURIComponent(doc.reference_name)) %} + + + + {%= doc.reference_name %} + + + {% } %} +
+
+
+ + {%= frappe.avatar(doc.assigned_to) %} + +
+
diff --git a/frappe/core/doctype/todo/todo_list.js b/frappe/core/doctype/todo/todo_list.js index 0c6c9d684a..0f747b62e9 100644 --- a/frappe/core/doctype/todo/todo_list.js +++ b/frappe/core/doctype/todo/todo_list.js @@ -1,18 +1,9 @@ frappe.listview_settings['ToDo'] = { onload: function(me) { - $(frappe.container.page) - .find(".layout-main-section") - .css({ - "background-color":"cornsilk", - "min-height": "400px" - }) - if(user_roles.indexOf("System Manager")!==-1) { - frappe.route_options = { - "owner": user - } - } + frappe.route_options = { + "owner": user, + "status": "Open" + }; }, - set_title_left: function() { - frappe.set_route(); - } + add_fields: ["reference_type", "reference_name"], } diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index e4657ff7dc..201d84dc6f 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -13,6 +13,7 @@ cur_frm.cscript.onload = function(doc, dt, dn) { cur_frm.cscript.before_load = function(doc, dt, dn, callback) { var update_language_select = function(user_language) { cur_frm.set_df_property("language", "options", frappe.languages || ["", "English"]); + cur_frm.set_df_property("time_zone", "options", [""].concat(frappe.all_timezones)); callback && callback(); } @@ -20,7 +21,8 @@ cur_frm.cscript.before_load = function(doc, dt, dn, callback) { frappe.call({ method: "frappe.core.doctype.user.user.get_languages", callback: function(r) { - frappe.languages = r.message; + frappe.languages = r.message.languages; + frappe.all_timezones = r.message.timezones; update_language_select(); } }); @@ -45,12 +47,12 @@ cur_frm.cscript.refresh = function(doc) { cur_frm.toggle_display(['sb1', 'sb3'], false); if(!doc.__islocal){ - cur_frm.add_custom_button("Set Properties", function() { + cur_frm.add_custom_button("Set User Permissions", function() { frappe.route_options = { "user": doc.name }; - frappe.set_route("user-properties"); - }) + frappe.set_route("user-permissions"); + }, null, "btn-default") if(has_common(user_roles, ["Administrator", "System Manager"])) { cur_frm.toggle_display(['sb1', 'sb3'], true); @@ -223,6 +225,7 @@ frappe.RoleEditor = Class.extend({ $body.append('' + '' + '' + + '' + '' + '' + '' @@ -235,8 +238,7 @@ frappe.RoleEditor = Class.extend({ // + '' // + '' // + '' - + '' - + '' + + '' + '
' + __('Document Type') + '' + __('Level') + '' + __('Apply User Permissions') + '' + __('Read') + '' + __('Write') + '' + __('Create') + '' + __('Export') + '' + __('Print') + '' + __('Email') + '' + __('Only Restricted Documents') + '' + __('Can Restrict Others') + '' + __('Set User Permissions') + '
'); for(var i=0, l=r.message.length; i\ %(parent)s\ %(permlevel)s\ + %(apply_user_permissions)s\ %(read)s\ %(write)s\ %(create)s\ @@ -268,8 +271,7 @@ frappe.RoleEditor = Class.extend({ // %(export)s\ // %(print)s\ // %(email)s' - + '%(restricted)s\ - %(restrict)s\ + + '%(set_user_permissions)s\ ', perm)) } @@ -280,8 +282,9 @@ frappe.RoleEditor = Class.extend({ }, make_perm_dialog: function() { this.perm_dialog = new frappe.ui.Dialog({ - title:'Role Permissions', - width: "800px" + title:'Role Permissions' }); + + this.perm_dialog.$wrapper.find('.modal-dialog').css("width", "800px"); } }); diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 4003b57872..5690fb2515 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -1,9 +1,8 @@ { - "allow_attach": 1, "allow_copy": 0, "allow_import": 1, "allow_rename": 1, - "creation": "2014-03-11 14:55:00.000000", + "creation": "2014-03-11 14:55:00", "description": "Represents a User in the system.", "docstatus": 0, "doctype": "DocType", @@ -21,6 +20,7 @@ "fieldtype": "Check", "in_list_view": 1, "label": "Enabled", + "no_copy": 0, "oldfieldname": "enabled", "oldfieldtype": "Check", "permlevel": 0, @@ -31,6 +31,7 @@ "fieldtype": "Check", "hidden": 1, "label": "Unsubscribed", + "no_copy": 1, "permlevel": 0 }, { @@ -38,6 +39,7 @@ "fieldtype": "Data", "hidden": 0, "label": "Email", + "no_copy": 1, "oldfieldname": "email", "oldfieldtype": "Data", "permlevel": 0, @@ -49,6 +51,7 @@ "fieldtype": "Data", "in_list_view": 1, "label": "First Name", + "no_copy": 0, "oldfieldname": "first_name", "oldfieldtype": "Data", "permlevel": 0, @@ -58,6 +61,7 @@ "fieldname": "middle_name", "fieldtype": "Data", "label": "Middle Name (Optional)", + "no_copy": 0, "oldfieldname": "middle_name", "oldfieldtype": "Data", "permlevel": 0 @@ -87,6 +91,13 @@ "options": "Loading...", "permlevel": 0 }, + { + "description": "Default is system timezone", + "fieldname": "time_zone", + "fieldtype": "Select", + "label": "Timezone", + "permlevel": 0 + }, { "fieldname": "change_password", "fieldtype": "Section Break", @@ -97,6 +108,7 @@ "fieldname": "new_password", "fieldtype": "Password", "label": "New Password", + "no_copy": 1, "permlevel": 0 }, { @@ -104,6 +116,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Reset Password Key", + "no_copy": 1, "permlevel": 0, "print_hide": 1, "read_only": 1 @@ -116,10 +129,12 @@ "permlevel": 0 }, { + "description": "Get your globally recognized avatar from Gravatar.com", "fieldname": "user_image", "fieldtype": "Attach", "hidden": 0, "label": "User Image", + "no_copy": 1, "permlevel": 0 }, { @@ -127,6 +142,15 @@ "fieldtype": "Attach", "hidden": 0, "label": "Background Image", + "no_copy": 1, + "permlevel": 0 + }, + { + "default": "Fill Screen", + "fieldname": "background_style", + "fieldtype": "Select", + "label": "Background Style", + "options": "Fill Screen\nTile", "permlevel": 0 }, { @@ -161,6 +185,7 @@ "fieldname": "birth_date", "fieldtype": "Date", "label": "Birth Date", + "no_copy": 1, "oldfieldname": "birth_date", "oldfieldtype": "Date", "permlevel": 0 @@ -169,6 +194,7 @@ "fieldname": "location", "fieldtype": "Data", "label": "Location", + "no_copy": 1, "permlevel": 0 }, { @@ -180,7 +206,9 @@ "fieldname": "bio", "fieldtype": "Small Text", "label": "Bio", - "permlevel": 0 + "no_copy": 1, + "permlevel": 0, + "set_only_once": 0 }, { "description": "Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.", @@ -212,6 +240,7 @@ "fieldname": "email_signature", "fieldtype": "Small Text", "label": "Email Signature", + "no_copy": 1, "permlevel": 0 }, { @@ -225,6 +254,7 @@ "fieldtype": "Check", "hidden": 1, "label": "Sync Inbox", + "no_copy": 1, "permlevel": 0 }, { @@ -233,6 +263,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Email Host", + "no_copy": 1, "permlevel": 0 }, { @@ -240,6 +271,7 @@ "fieldtype": "Check", "hidden": 1, "label": "Email Use SSL", + "no_copy": 1, "permlevel": 0 }, { @@ -247,6 +279,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Email Login", + "no_copy": 1, "permlevel": 0 }, { @@ -254,6 +287,7 @@ "fieldtype": "Password", "hidden": 1, "label": "Email Password", + "no_copy": 1, "permlevel": 0 }, { @@ -274,6 +308,7 @@ "fieldtype": "Table", "hidden": 1, "label": "User Defaults", + "no_copy": 1, "options": "DefaultValue", "permlevel": 0 }, @@ -335,6 +370,7 @@ "fieldtype": "Read Only", "hidden": 0, "label": "Last Login", + "no_copy": 1, "oldfieldname": "last_login", "oldfieldtype": "Read Only", "permlevel": 0, @@ -346,6 +382,7 @@ "fieldname": "last_ip", "fieldtype": "Read Only", "label": "Last IP", + "no_copy": 1, "oldfieldname": "last_ip", "oldfieldtype": "Read Only", "permlevel": 0, @@ -381,6 +418,7 @@ "fieldname": "fb_username", "fieldtype": "Data", "label": "Facebook Username", + "no_copy": 1, "permlevel": 0, "read_only": 1 }, @@ -388,6 +426,7 @@ "fieldname": "fb_userid", "fieldtype": "Data", "label": "Facebook User ID", + "no_copy": 1, "permlevel": 0, "read_only": 1 }, @@ -395,6 +434,7 @@ "fieldname": "google_userid", "fieldtype": "Data", "label": "Google User ID", + "no_copy": 1, "permlevel": 0, "read_only": 1 }, @@ -402,6 +442,7 @@ "fieldname": "github_userid", "fieldtype": "Data", "label": "Github User ID", + "no_copy": 1, "permlevel": 0, "read_only": 1 }, @@ -409,6 +450,7 @@ "fieldname": "github_username", "fieldtype": "Data", "label": "Github Username", + "no_copy": 1, "permlevel": 0, "read_only": 1 } @@ -420,16 +462,15 @@ "issingle": 0, "istable": 0, "max_attachments": 5, - "modified": "2014-03-11 16:04:13.000000", + "modified": "2014-09-08 06:07:20.157915", "modified_by": "Administrator", "module": "Core", "name": "User", "owner": "Administrator", "permissions": [ { - "cancel": 0, "create": 1, - "delete": 0, + "delete": 1, "email": 1, "permlevel": 0, "print": 1, @@ -440,15 +481,25 @@ "write": 1 }, { + "apply_user_permissions": 1, "cancel": 0, "create": 0, - "delete": 0, "email": 1, "permlevel": 0, - "print": 1, "read": 1, - "report": 1, - "restricted": 1, + "role": "All", + "write": 1 + }, + { + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, "role": "All", "submit": 0, "write": 0 diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 8080a35376..3b1f94dc20 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -3,9 +3,10 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, now +from frappe.utils import cint, now, get_gravatar from frappe import throw, msgprint, _ from frappe.auth import _update_password +from frappe.core.doctype.notification_count.notification_count import clear_notifications import frappe.permissions STANDARD_USERS = ("Guest", "Administrator") @@ -13,7 +14,6 @@ STANDARD_USERS = ("Guest", "Administrator") from frappe.model.document import Document class User(Document): - def autoname(self): """set name as email id""" if self.name not in STANDARD_USERS: @@ -27,7 +27,10 @@ class User(Document): self.add_system_manager_role() self.check_enable_disable() self.update_gravatar() + self.ensure_unique_roles() self.remove_all_roles_for_guest() + if self.language == "Loading...": + self.language = None def check_enable_disable(self): # do not allow disabling administrator/guest @@ -38,7 +41,7 @@ class User(Document): self.a_system_manager_should_exist() # clear sessions if disabled - if not cint(self.enabled) and getattr(frappe, "login_manager", None): + if not cint(self.enabled) and getattr(frappe.local, "login_manager", None): frappe.local.login_manager.logout(user=self.name) def add_system_manager_role(self): @@ -69,6 +72,7 @@ class User(Document): new_password = self.new_password self.db_set("new_password", "") + clear_notifications(user=self.name) frappe.clear_cache(user=self.name) try: @@ -88,10 +92,8 @@ class User(Document): pass # email server not set, don't send email def update_gravatar(self): - import md5 if not self.user_image: - self.user_image = "https://secure.gravatar.com/avatar/" + md5.md5(self.name).hexdigest() \ - + "?d=retro" + self.user_image = get_gravatar(self.name) @Document.hook def validate_reset_password(self): @@ -105,11 +107,12 @@ class User(Document): self.password_reset_mail(get_url("/update-password?key=" + key)) def get_other_system_managers(self): - return frappe.db.sql("""select distinct parent from tabUserRole user_role - where role='System Manager' and docstatus<2 - and parent not in ('Administrator', %s) and exists - (select * from `tabUser` user - where user.name=user_role.parent and enabled=1)""", (self.name,)) + return frappe.db.sql("""select distinct user.name from tabUserRole user_role, tabUser user + where user_role.role='System Manager' + and user.docstatus<2 + and ifnull(user.enabled,0)=1 + and user_role.parent = user.name + and user_role.parent not in ('Administrator', %s) limit 1""", (self.name,)) def get_fullname(self): """get first_name space last_name""" @@ -160,7 +163,7 @@ class User(Document): def a_system_manager_should_exist(self): if not self.get_other_system_managers(): - throw(_("Hey! There should remain at least one System Manager")) + throw(_("There should remain at least one System Manager")) def on_trash(self): frappe.clear_cache(user=self.name) @@ -243,16 +246,36 @@ class User(Document): self.save() + def remove_roles(self, *roles): + existing_roles = dict((d.role, d) for d in self.get("user_roles")) + for role in roles: + if role in existing_roles: + self.get("user_roles").remove(existing_roles[role]) + + self.save() + def remove_all_roles_for_guest(self): if self.name == "Guest": self.set("user_roles", list(set(d for d in self.get("user_roles") if d.role == "Guest"))) + def ensure_unique_roles(self): + exists = [] + for i, d in enumerate(self.get("user_roles")): + if (not d.role) or (d.role in exists): + self.get("user_roles").remove(d) + else: + exists.append(d.role) + @frappe.whitelist() def get_languages(): from frappe.translate import get_lang_dict + import pytz languages = get_lang_dict().keys() languages.sort() - return [""] + languages + return { + "languages": [""] + languages, + "timezones": pytz.all_timezones + } @frappe.whitelist() def get_all_roles(arg=None): @@ -268,9 +291,8 @@ def get_user_roles(arg=None): @frappe.whitelist() def get_perm_info(arg=None): """get permission info""" - return frappe.db.sql("""select parent, permlevel, `{}` from tabDocPerm where role=%s - and docstatus<2 order by parent, permlevel""".format("`, `".join(frappe.permissions.rights)), - (frappe.form_dict['role'],), as_dict=1) + return frappe.db.sql("""select * from tabDocPerm where role=%s + and docstatus<2 order by parent, permlevel""", (frappe.form_dict['role'],), as_dict=1) @frappe.whitelist(allow_guest=True) def update_password(new_password, key=None, old_password=None): @@ -391,3 +413,18 @@ def get_active_website_users(): where enabled = 1 and user_type = 'Website User' and hour(timediff(now(), last_login)) < 72""")[0][0] +def get_permission_query_conditions(user): + if user=="Administrator": + return "" + + else: + return """(`tabUser`.name not in ({standard_users}))""".format( + standard_users='"' + '", "'.join(STANDARD_USERS) + '"') + +def has_permission(doc, user): + if (user != "Administrator") and (doc.name in STANDARD_USERS): + # dont allow non Administrator user to view / edit Administrator user + return False + + else: + return True diff --git a/frappe/core/doctype/user/user_list.html b/frappe/core/doctype/user/user_list.html new file mode 100644 index 0000000000..b4ecb01340 --- /dev/null +++ b/frappe/core/doctype/user/user_list.html @@ -0,0 +1,31 @@ +
+
+
+ {%= list.get_avatar_and_id(doc) %} + + + {%= doc.first_name %} + + {%= doc.last_name %} + + {% if(doc.enabled) { %} + + + + {% } %} + + {% if(doc.user_type==="System User") { %} + + + + {% } %} + +
+
+
diff --git a/frappe/core/doctype/user/user_list.js b/frappe/core/doctype/user/user_list.js new file mode 100644 index 0000000000..e75b32844a --- /dev/null +++ b/frappe/core/doctype/user/user_list.js @@ -0,0 +1,10 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.listview_settings['User'] = { + add_fields: ["enabled", "first_name", "last_name", "user_type"], + filters: [["enabled","=","Yes"], ["user_type","=","System User"]], + prepare_data: function(data) { + data["user_for_avatar"] = data["name"]; + } +}; diff --git a/frappe/core/doctype/userrole/userrole.json b/frappe/core/doctype/userrole/userrole.json index fb9094cb33..b0f5f373ed 100644 --- a/frappe/core/doctype/userrole/userrole.json +++ b/frappe/core/doctype/userrole/userrole.json @@ -1,7 +1,7 @@ { "allow_copy": 0, "autoname": "UR.#####", - "creation": "2013-02-06 11:30:13.000000", + "creation": "2013-02-06 11:30:13", "docstatus": 0, "doctype": "DocType", "fields": [ @@ -16,7 +16,7 @@ "options": "Role", "permlevel": 0, "print_width": "200px", - "reqd": 0, + "reqd": 1, "search_index": 0, "width": "200px" } @@ -26,10 +26,11 @@ "idx": 1, "issingle": 0, "istable": 1, - "modified": "2013-12-20 19:21:54.000000", + "modified": "2014-06-17 06:56:28.816283", "modified_by": "Administrator", "module": "Core", "name": "UserRole", "owner": "Administrator", + "permissions": [], "read_only": 0 } \ No newline at end of file diff --git a/frappe/core/doctype/version/test_records.json b/frappe/core/doctype/version/test_records.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/frappe/core/doctype/version/test_records.json @@ -0,0 +1 @@ +[] diff --git a/frappe/core/doctype/version/test_version.py b/frappe/core/doctype/version/test_version.py new file mode 100644 index 0000000000..5bc8781581 --- /dev/null +++ b/frappe/core/doctype/version/test_version.py @@ -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('Version') + +class TestVersion(unittest.TestCase): + pass diff --git a/frappe/core/doctype/version/version.js b/frappe/core/doctype/version/version.js index 0b62fc0fa0..f815cd444f 100644 --- a/frappe/core/doctype/version/version.js +++ b/frappe/core/doctype/version/version.js @@ -11,5 +11,5 @@ frappe.ui.form.on("Version", "refresh", function(frm) { } } }) - }) + }); }) diff --git a/frappe/core/doctype/version/version.json b/frappe/core/doctype/version/version.json index 87a967e28d..b0f4a8586d 100644 --- a/frappe/core/doctype/version/version.json +++ b/frappe/core/doctype/version/version.json @@ -1,6 +1,6 @@ { "autoname": "_VER.######", - "creation": "2014-02-20 17:22:37.000000", + "creation": "2014-02-20 17:22:37", "docstatus": 0, "doctype": "DocType", "document_type": "Master", @@ -8,6 +8,7 @@ { "fieldname": "ref_doctype", "fieldtype": "Link", + "in_list_view": 1, "label": "Ref DocType", "options": "DocType", "permlevel": 0, @@ -16,6 +17,7 @@ { "fieldname": "docname", "fieldtype": "Data", + "in_list_view": 1, "label": "Docname", "permlevel": 0, "reqd": 1 @@ -23,13 +25,15 @@ { "fieldname": "doclist_json", "fieldtype": "Code", + "in_list_view": 1, "label": "Doclist JSON", "permlevel": 0, "reqd": 1 } ], + "icon": "icon-copy", "idx": 1, - "modified": "2014-02-20 17:22:38.000000", + "modified": "2014-08-05 01:23:37.541856", "modified_by": "Administrator", "module": "Core", "name": "Version", diff --git a/frappe/core/doctype/workflow/workflow.js b/frappe/core/doctype/workflow/workflow.js index 7cbfd6b715..d0cfc9434d 100644 --- a/frappe/core/doctype/workflow/workflow.js +++ b/frappe/core/doctype/workflow/workflow.js @@ -6,26 +6,24 @@ frappe.core.Workflow = frappe.ui.form.Controller.extend({ if(doc.is_active) { this.frm.set_intro("This Workflow is active."); } - this.load_document_type(doc); + this.update_field_options(doc); }, document_type: function(doc) { - this.load_document_type(doc); + this.update_field_options(doc); }, - load_document_type: function(doc) { + update_field_options: function(doc) { var me = this; - if(doc.document_type && !locals.DocType[doc.document_type]) { - frappe.model.with_doctype(doc.document_type, function() { - me.update_field_options(); + if(doc.document_type) { + frappe.model.with_doctype(doc.document_type, function() { + var fields = $.map(frappe.get_doc("DocType", + me.frm.doc.document_type).fields, function(d) { + return frappe.model.no_value_type.indexOf(d.fieldtype)===-1 ? d.fieldname : null; + }) + frappe.meta.get_docfield("Workflow Document State", "update_field", me.frm.doc.name).options + = [""].concat(fields); }); } - }, - update_field_options: function() { - var fields = $.map(frappe.get_doc("DocType", this.frm.doc.document_type).fields, function(d) { - return frappe.model.no_value_type.indexOf(d.fieldtype)===-1 ? d.fieldname : null; - }) - frappe.meta.get_docfield("Workflow Document State", "update_field", this.frm.doc.name).options - = [""].concat(fields); } }); -cur_frm.cscript = new frappe.core.Workflow({frm:cur_frm}); \ No newline at end of file +cur_frm.cscript = new frappe.core.Workflow({frm:cur_frm}); diff --git a/frappe/core/doctype/workflow/workflow.py b/frappe/core/doctype/workflow/workflow.py index 629b63b9d1..d34b7126a4 100644 --- a/frappe/core/doctype/workflow/workflow.py +++ b/frappe/core/doctype/workflow/workflow.py @@ -13,6 +13,7 @@ class Workflow(Document): self.set_active() self.create_custom_field_for_workflow_state() self.update_default_workflow_status() + self.validate_docstatus() def on_update(self): frappe.clear_cache(doctype=self.document_type) @@ -46,6 +47,27 @@ class Workflow(Document): '%s', self.workflow_state_field, "%s"), (d.state, d.doc_status)) docstatus_map[d.doc_status] = d.state + def validate_docstatus(self): + def get_state(state): + for s in self.workflow_document_states: + if s.state==state: + return s + + frappe.throw(frappe._("{0} not a valid State").format(state)) + + for t in self.workflow_transitions: + state = get_state(t.state) + next_state = get_state(t.next_state) + + if state.doc_status=="2": + frappe.throw(frappe._("Cannot change state of Cancelled Document. Transition row {0}").format(t.idx)) + + if state.doc_status=="1" and next_state.doc_status=="0": + frappe.throw(frappe._("Submitted Document cannot be converted back to draft. Transition row {0}").format(t.idx)) + + if state.doc_status=="0" and next_state.doc_status=="2": + frappe.throw(frappe._("Cannot cancel before submitting. See Transition {0}").format(t.idx)) + def set_active(self): if int(self.is_active or 0): # clear all other diff --git a/frappe/core/doctype/workflow_document_state/workflow_document_state.json b/frappe/core/doctype/workflow_document_state/workflow_document_state.json index 67fb37028a..dfe287074d 100644 --- a/frappe/core/doctype/workflow_document_state/workflow_document_state.json +++ b/frappe/core/doctype/workflow_document_state/workflow_document_state.json @@ -1,6 +1,6 @@ { "allow_import": 1, - "creation": "2013-02-22 01:27:36.000000", + "creation": "2013-02-22 01:27:36", "description": "Represents the states allowed in one document and role assigned to change the state.", "docstatus": 0, "doctype": "DocType", @@ -18,6 +18,7 @@ "width": "160px" }, { + "description": "0 - Draft; 1 - Submitted; 2 - Cancelled", "fieldname": "doc_status", "fieldtype": "Select", "in_list_view": 1, @@ -65,9 +66,10 @@ ], "idx": 1, "istable": 1, - "modified": "2013-12-20 19:21:55.000000", + "modified": "2014-05-27 06:35:01.070157", "modified_by": "Administrator", "module": "Core", "name": "Workflow Document State", - "owner": "Administrator" + "owner": "Administrator", + "permissions": [] } \ No newline at end of file diff --git a/frappe/core/notifications.py b/frappe/core/notifications.py index 9a8c236555..330e5ece43 100644 --- a/frappe/core/notifications.py +++ b/frappe/core/notifications.py @@ -20,11 +20,12 @@ def get_notification_config(): def get_things_todo(): """Returns a count of incomplete todos""" - incomplete_todos = frappe.db.sql("""\ - SELECT COUNT(*) FROM `tabToDo` - WHERE status="Open" - AND (owner = %s or assigned_by=%s)""", (frappe.session.user, frappe.session.user)) - return incomplete_todos[0][0] + return frappe.get_list("ToDo", + fields=["count(*)"], + filters=[["ToDo", "status", "=", "Open"]], + or_filters=[["ToDo", "owner", "=", frappe.session.user], + ["ToDo", "assigned_by", "=", frappe.session.user]], + as_list=True)[0][0] def get_todays_events(): """Returns a count of todays events in calendar""" @@ -41,4 +42,4 @@ def get_unread_messages(): WHERE comment_doctype IN ('My Company', 'Message') AND comment_docname = %s AND ifnull(docstatus,0)=0 - """, (frappe.user.name,))[0][0] \ No newline at end of file + """, (frappe.user.name,))[0][0] diff --git a/frappe/core/page/applications/applications.js b/frappe/core/page/applications/applications.js index 14c5941900..8d3b9abb0f 100644 --- a/frappe/core/page/applications/applications.js +++ b/frappe/core/page/applications/applications.js @@ -23,13 +23,14 @@ frappe.pages['applications'].onload = function(wrapper) { \
\

').appendTo($main).find(".app-search").on("keyup", function() { - var val = $(this).val(); + var val = ($(this).val() || "").toLowerCase(); $main.find(".app-listing").each(function() { $(this).toggle($(this).attr("data-title").toLowerCase().indexOf(val)!==-1); }); - }) + }); - $.each(r.message, function(app_key, app) { + $.each(Object.keys(r.message).sort(), function(i, app_key) { + var app = r.message[app_key]; frappe.modules[app_key] = { label: app.app_title, icon: app.app_icon, diff --git a/frappe/core/page/applications/applications.py b/frappe/core/page/applications/applications.py index 0162bf4530..545e303bc1 100644 --- a/frappe/core/page/applications/applications.py +++ b/frappe/core/page/applications/applications.py @@ -13,7 +13,7 @@ def get_app_list(): for app in frappe.get_all_apps(True): app_hooks = frappe.get_hooks(app_name=app) - if app_hooks.get('hide_in_installer'): + if app not in installed and app_hooks.get('hide_in_installer'): continue out[app] = {} diff --git a/frappe/core/page/data_import_tool/data_import_tool.js b/frappe/core/page/data_import_tool/data_import_tool.js index 6dd953ad17..41d15b4bcd 100644 --- a/frappe/core/page/data_import_tool/data_import_tool.js +++ b/frappe/core/page/data_import_tool/data_import_tool.js @@ -5,8 +5,6 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { icon: "icon-upload" }); - wrapper.appframe.set_title_left(function() { frappe.set_route(frappe.get_module("Setup").link); }); - // check permission for import if(!((frappe.boot.user.can_import && frappe.boot.user.can_import.length) || user_roles.indexOf("System Manager")!==-1)) { @@ -14,17 +12,17 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { return false; } - $(wrapper).find('.layout-main-section').append('

1. Download Template

\ + $(wrapper).find('.layout-main-section').append('

1. ' + __("Download Template") + '

\
\ -

Download a template for importing a table.

\ +

' + __("Download a template for importing a table.") + '

\
\
\

\ \ -

Export all rows in CSV fields for re-upload. This is ideal for bulk-editing.

\ +

' + __("Export all rows in CSV fields for re-upload. This is ideal for bulk-editing.") + '

\
\
\
\ @@ -32,23 +30,23 @@ frappe.pages['data-import-tool'].onload = function(wrapper) {
\
\
\ -

2. Import Data

\ -

Attach .csv file to import data

\ +

2. '+ __("Import Data") + '

\ +

' + __("Attach .csv file to import data") + '

\

\ \

\
\ -

Help: Importing non-English data in Microsoft Excel

\ -

While uploading non English files ensure that the encoding is UTF-8.

\ +

' + __("Help: Importing non-English data in Microsoft Excel") + '

\ +

' + __("While uploading non English files ensure that the encoding is UTF-8.") + '

\
    \ -
  1. In Excel, save the file in CSV (Comma Delimited) format
  2. \ -
  3. Open this saved file in Notepad
  4. \ -
  5. Click on File -> Save As
  6. \ -
  7. File Name: <your filename>.csv
    \ +
  8. ' + __("In Excel, save the file in CSV (Comma Delimited) format") + '
  9. \ +
  10. ' + __("Open this saved file in Notepad") + '
  11. \ +
  12. ' + __("Click on File -> Save As") + '
  13. \ +
  14. ' + __("File Name: <your filename>.csv
    \ Save as type: Text Documents (*.txt)
    \ - Encoding: UTF-8\ + Encoding: UTF-8") + '\
  15. \ -
  16. Click on Save
  17. \ +
  18. ' + __("Click on Save") + '
  19. \
\

\
'); @@ -56,13 +54,13 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { $select = $(wrapper).find('[name="dit-doctype"]'); frappe.messages.waiting($(wrapper).find(".dit-progress-area").toggle(false), - "Performing hardcore import process....", 100); + __("Performing hardcore import process")+ "....", 100); // load doctypes frappe.call({ method: 'frappe.core.page.data_import_tool.data_import_tool.get_doctypes', callback: function(r) { - $select.add_options(['Select...'].concat(r.message)); + $select.add_options([__('Select') + '...'].concat(r.message)); wrapper.doctypes = r.message; wrapper.set_route_options(); } @@ -128,10 +126,11 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { // get options return frappe.call({ + btn: this, method: 'frappe.core.page.data_import_tool.data_import_tool.get_doctype_options', args: {doctype: val}, callback: function(r) { - $('

Download

').appendTo('#dit-download'); + $('

' + __("Download") + '

').appendTo('#dit-download'); var with_data = $('[name="dit-with-data"]:checked').length ? 'Yes' : 'No'; // download link $.each(r.message, function(i, v) { @@ -145,7 +144,7 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { }); if(r.message.length > 1) { - $('
All Tables (Main + Child Tables):
').appendTo('#dit-download'); + $('
' + __("All Tables (Main + Child Tables)") + ':
').appendTo('#dit-download'); var link = wrapper .add_template_download_link(r.message[0]) .data('all_doctypes', "Yes") @@ -161,7 +160,8 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { $("#dit-output").empty(); $.each(r.messages, function(i, v) { - var $p = $('

').html(v).appendTo('#dit-output'); + var $p = $('

').html(frappe.markdown(v)).appendTo('#dit-output'); + $("
").appendTo('#dit-output'); if(v.substr(0,5)=='Error') { $p.css('color', 'red'); } else if(v.substr(0,8)=='Inserted') { @@ -175,6 +175,7 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { } var onerror = function(r) { + $(wrapper).find(".dit-progress-area").toggle(false); r.messages = $.map(r.message.messages, function(v) { var msg = v.replace("Inserted", "Valid") .replace("Updated", "Valid").split("<"); @@ -186,7 +187,7 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { return v; }); - r.messages = ["

Import Failed!

"] + r.messages = ["

" + __("Import Failed!") + "

"] .concat(r.messages); write_messages(r); @@ -204,7 +205,7 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { onerror(r); } else { // replace links if error has occured - r.messages = ["

Import Successful!

"]. + r.messages = ["

" + __("Import Successful!") + "

"]. concat(r.message.messages) write_messages(r); @@ -216,18 +217,18 @@ frappe.pages['data-import-tool'].onload = function(wrapper) { var $submit_btn = $('#dit-upload-area button.btn-upload') .html(' ' + __("Upload and Import")); - $('\ -

If you are uploading a child table (for example Item Price), the all the entries of that table will be deleted (for that parent record) and new entries will be made.


') + $('\ +

' + __("If you are uploading a child table (for example Item Price), the all the entries of that table will be deleted (for that parent record) and new entries will be made.") + '


') .insertBefore($submit_btn); // add submit option $('\ -

If you are inserting new records (overwrite not checked) \ - and if you have submit permission, the record will be submitted.


') +

' + __("If you are inserting new records (overwrite not checked) \ + and if you have submit permission, the record will be submitted.") + '


') .insertBefore($submit_btn); // add ignore option - $('

') + $('

') .insertBefore($submit_btn); // rename button diff --git a/frappe/core/page/data_import_tool/data_import_tool.py b/frappe/core/page/data_import_tool/data_import_tool.py index 4309213597..6f1d56cc47 100644 --- a/frappe/core/page/data_import_tool/data_import_tool.py +++ b/frappe/core/page/data_import_tool/data_import_tool.py @@ -28,7 +28,7 @@ def get_doctype_options(): return [doctype] + [d.options for d in frappe.get_meta(doctype).get_table_fields()] def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False): - from frappe.utils.datautils import read_csv_content + from frappe.utils.csvutils import read_csv_content from frappe.core.page.data_import_tool.importer import upload print "Importing " + path with open(path, "r") as infile: diff --git a/frappe/core/page/data_import_tool/exporter.py b/frappe/core/page/data_import_tool/exporter.py index ab711e171b..c1ef21e410 100644 --- a/frappe/core/page/data_import_tool/exporter.py +++ b/frappe/core/page/data_import_tool/exporter.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, json, os import frappe.permissions -from frappe.utils.datautils import UnicodeWriter +from frappe.utils.csvutils import UnicodeWriter from frappe.utils import cstr, cint, flt from frappe.core.page.data_import_tool.data_import_tool import data_keys diff --git a/frappe/core/page/data_import_tool/importer.py b/frappe/core/page/data_import_tool/importer.py index 36034b01bf..1b2c863e64 100644 --- a/frappe/core/page/data_import_tool/importer.py +++ b/frappe/core/page/data_import_tool/importer.py @@ -8,7 +8,7 @@ import frappe.permissions from frappe import _ -from frappe.utils.datautils import getlink +from frappe.utils.csvutils import getlink from frappe.utils.dateutils import parse_date from frappe.utils import cint, cstr, flt @@ -27,7 +27,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, if params.get("ignore_encoding_errors"): ignore_encoding_errors = True - from frappe.utils.datautils import read_csv_content_from_uploaded_file + from frappe.utils.csvutils import read_csv_content_from_uploaded_file def bad_template(): frappe.throw(_("Please do not change the rows above {0}").format(data_keys.data_separator)) @@ -103,7 +103,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, d[fieldname] = rows[idx][column_idx] if fieldtype in ("Int", "Check"): d[fieldname] = cint(d[fieldname]) - elif fieldtype in ("Float", "Currency"): + elif fieldtype in ("Float", "Currency", "Percent"): d[fieldname] = flt(d[fieldname]) elif fieldtype == "Date": d[fieldname] = parse_date(d[fieldname]) if d[fieldname] else None @@ -172,8 +172,12 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite = params.get('overwrite') # delete child rows (if parenttype) - if parenttype and overwrite: - delete_child_rows(data, doctype) + parentfield = None + if parenttype: + parentfield = get_parent_field(doctype, parenttype) + + if overwrite: + delete_child_rows(data, doctype) ret = [] error = False @@ -188,13 +192,12 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, doc = get_doc(row_idx) try: frappe.local.message_log = [] - if doc.get("parentfield"): - parent = frappe.get_doc(doc["parenttype"], doc["parentfield"]) - parent.append(doc) + if parentfield: + parent = frappe.get_doc(parenttype, doc["parent"]) + doc = parent.append(parentfield, doc) parent.save() ret.append('Inserted row for %s at #%s' % (getlink(parenttype, doc.parent), unicode(doc.idx))) - else: if overwrite and frappe.db.exists(doctype, doc["name"]): original = frappe.get_doc(doctype, doc["name"]) @@ -214,7 +217,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, error = True if doc: frappe.errprint(doc if isinstance(doc, dict) else doc.as_dict()) - err_msg = frappe.local.message_log and "
".join(frappe.local.message_log) or cstr(e) + err_msg = frappe.local.message_log and "\n\n".join(frappe.local.message_log) or cstr(e) ret.append('Error for row (#%d) %s : %s' % (row_idx + 1, len(row)>1 and row[1] or "", err_msg)) frappe.errprint(frappe.get_traceback()) diff --git a/frappe/core/page/desktop/desktop.css b/frappe/core/page/desktop/desktop.css index 710882b873..d77a04f3b0 100644 --- a/frappe/core/page/desktop/desktop.css +++ b/frappe/core/page/desktop/desktop.css @@ -20,7 +20,7 @@ @media (max-width: 768px) { .case-wrapper { - margin: 12px; + margin: 9px; width: 70px; height: 80px; } @@ -77,4 +77,6 @@ padding-top: 14px; padding-bottom: 50px; margin-bottom: -50px; + border: 0px; + background-color: transparent; } diff --git a/frappe/core/page/desktop/desktop.js b/frappe/core/page/desktop/desktop.js index 78012c9fec..13249f8c8e 100644 --- a/frappe/core/page/desktop/desktop.js +++ b/frappe/core/page/desktop/desktop.js @@ -11,8 +11,7 @@ frappe.pages['desktop'].refresh = function(wrapper) { frappe.ui.toolbar.add_dropdown_button("File", __("All Applications"), function() { frappe.desktop.show_all_modules(); }, 'icon-th'); - -} +}; frappe.desktop.refresh = function() { frappe.desktop.render(); @@ -104,21 +103,22 @@ frappe.desktop.render = function() { $(document).on("notification-update", function() { frappe.desktop.show_pending_notifications(); - }) + }); + $(document).trigger("desktop-render"); } frappe.desktop.show_all_modules = function() { if(!frappe.desktop.all_modules_dialog) { var d = new frappe.ui.Dialog({ - title: ' All Applications' + title: ' '+ __("All Applications") }); - var desktop_items = frappe.user.get_desktop_items(); + var desktop_items = frappe.user.get_desktop_items(true); var user_desktop_items = frappe.user.get_user_desktop_items(); $('') + type="text" placeholder="' + __("Search Filter") +'>') .appendTo(d.body) .on("keyup", function() { var val = $(this).val(); @@ -131,10 +131,12 @@ frappe.desktop.show_all_modules = function() { $wrapper = $('
').appendTo(d.body); // list of applications (frappe.user.get_desktop_items()) - $.each(keys(frappe.modules).sort(), function(i, m) { + var items = keys(frappe.modules).sort(); + $.each(items, function(i, m) { var module = frappe.get_module(m); if(module.link && desktop_items.indexOf(m)!==-1) { module.app_icon = frappe.ui.app_icon.get_html(m, true); + module.label = __(module.label); $(repl('
\
\ \ @@ -150,16 +152,10 @@ frappe.desktop.show_all_modules = function() { // check shown items $wrapper.find('[type="checkbox"]') .on("click", function() { - // update user_desktop_items (when checked or un-checked) - var user_desktop_items = frappe.user.get_user_desktop_items(); - var module = $(this).attr("data-name"); - if($(this).prop("checked")) { - user_desktop_items.push(module); - } else { - if(user_desktop_items.indexOf(module)!==-1) { - user_desktop_items.splice(user_desktop_items.indexOf(module), 1); - } - } + var user_desktop_items = []; + $wrapper.find('[type="checkbox"]:checked').each(function(i,ele) { + user_desktop_items.push($(ele).attr("data-name")); + }) frappe.defaults.set_default("_user_desktop_items", user_desktop_items); frappe.desktop.refresh(); }) diff --git a/frappe/core/page/messages/messages.css b/frappe/core/page/messages/messages.css deleted file mode 100644 index 88bbde7e86..0000000000 --- a/frappe/core/page/messages/messages.css +++ /dev/null @@ -1,32 +0,0 @@ -#message-post-text { -} - -#message-list { -} - -.message { - padding: 7px; - padding-left: 17px; - border-bottom: 1px solid #ccc; -} - -.message-mark { - margin-left: -17px; - width: 9px; - position: absolute; - height: 30px; -} - -.message .help { - margin-bottom: 0px; - padding-bottom: 0px; - color: #888; - font-size: 11px; -} - -.message-other { -} - -.message-self { - background-color: #eee; -} \ No newline at end of file diff --git a/frappe/core/page/messages/messages.js b/frappe/core/page/messages/messages.js index fac7515f16..ecbf0dc577 100644 --- a/frappe/core/page/messages/messages.js +++ b/frappe/core/page/messages/messages.js @@ -1,5 +1,5 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -// MIT License. See license.txt +// MIT License. See license.txt frappe.provide('frappe.core.pages.messages'); @@ -8,26 +8,31 @@ frappe.pages.messages.onload = function(wrapper) { parent: wrapper, title: "Messages" }); - - $('
\ -
\ -

Everyone

\ -

\ + + $('
\ + \ +

' + __("Everyone") + '

\ +
\
\ - \ -

\ + \ +
\ +
\ +

\
\

').appendTo($(wrapper).find('.layout-main-section')); wrapper.appframe.add_module_icon("Messages"); - + frappe.core.pages.messages = new frappe.core.pages.messages(wrapper); } $(frappe.pages.messages).bind('show', function() { // remove alerts $('#alert-container .alert').remove(); - + frappe.core.pages.messages.show(); setTimeout("frappe.core.pages.messages.refresh()", 5000); }) @@ -38,11 +43,11 @@ frappe.core.pages.messages = Class.extend({ this.show_active_users(); this.make_post_message(); this.make_list(); - //this.update_messages('reset'); //Resets notification icons + //this.update_messages('reset'); //Resets notification icons }, make_post_message: function() { var me = this; - + $('#post-message .btn').click(function() { var txt = $('#post-message textarea').val(); if(txt) { @@ -52,7 +57,8 @@ frappe.core.pages.messages = Class.extend({ method:'post', args: { txt: txt, - contact: me.contact + contact: me.contact, + notify: $('#messages-email').prop("checked") ? 1 : 0 }, callback:function(r,rt) { $('#post-message textarea').val('') @@ -60,7 +66,7 @@ frappe.core.pages.messages = Class.extend({ }, btn: this }); - } + } }); }, show: function() { @@ -72,20 +78,20 @@ frappe.core.pages.messages = Class.extend({ $('#avatar-image').attr("src", frappe.utils.get_file_link(frappe.user_info(contact).image)); $("#show-everyone").toggle(contact!==user); - + $("#post-message button").text(contact==user ? __("Post Publicly") : __("Post to user")) - + this.contact = contact; this.list.opts.args.contact = contact; this.list.run(); - + }, // check for updates every 5 seconds if page is active refresh: function() { setTimeout("frappe.core.pages.messages.refresh()", 5000); - if(frappe.container.page.label != 'Messages') + if(frappe.container.page.label != 'Messages') return; - if(!frappe.session_alive) + if(!frappe.session_alive) return; this.show(); }, @@ -110,18 +116,15 @@ frappe.core.pages.messages = Class.extend({ no_loading: true, render_row: function(wrapper, data) { $(wrapper).removeClass('list-row'); - - data.creation = dateutil.comment_when(data.creation); + + data.creation = comment_when(data.creation); data.comment_by_fullname = frappe.user_info(data.owner).fullname; data.image = frappe.utils.get_file_link(frappe.user_info(data.owner).image); - data.mark_html = ""; + data.info = ""; data.reply_html = ''; if(data.owner==user) { - data.cls = 'message-self'; - data.comment_by_fullname = 'You'; - } else { - data.cls = 'message-other'; + data.comment_by_fullname = 'You'; } // delete @@ -131,18 +134,23 @@ frappe.core.pages.messages = Class.extend({ onclick="frappe.core.pages.messages.delete(this)"\ data-name="%(name)s">×', data); } - + if(data.owner==data.comment_docname && data.parenttype!="Assignment") { - data.mark_html = "
" + data.info = 'Public' } - wrapper.innerHTML = repl('
%(mark_html)s\ - %(comment)s\ - %(delete_html)s\ -
by %(comment_by_fullname)s, %(creation)s
\ -
\ -
', data); + $(wrapper) + .addClass("media").addClass(data.cls); + wrapper.innerHTML = repl('\ + \ + \ +
\ + %(comment)s\ + %(delete_html)s\ +
\ + by %(comment_by_fullname)s, \ + %(creation)s %(info)s
\ +
', data); } }); }, @@ -164,14 +172,14 @@ frappe.core.pages.messages = Class.extend({ method:'get_active_users', callback: function(r,rt) { var $body = $(me.wrapper).find('.layout-side-section'); - $('

Users


\ + $('

' + __("Users") + '


\ \ + ' + __("Messages from everyone") + '
\ ').appendTo($body); $("#show-everyone").toggle(me.contact!==user); - + r.message.sort(function(a, b) { return b.has_session - a.has_session; }); for(var i in r.message) { var p = r.message[i]; @@ -186,7 +194,7 @@ frappe.core.pages.messages = Class.extend({ title="%(status)s">\ %(fullname)s\

', p)) - .appendTo($body); + .appendTo($body); } } } diff --git a/frappe/core/page/messages/messages.py b/frappe/core/page/messages/messages.py index 620e3a0344..7399c4a2a9 100644 --- a/frappe/core/page/messages/messages.py +++ b/frappe/core/page/messages/messages.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for from frappe.core.doctype.user.user import STANDARD_USERS +from frappe.utils import cint @frappe.whitelist() def get_list(arg=None): @@ -19,84 +20,83 @@ def get_list(arg=None): set docstatus = 1 where comment_doctype in ('My Company', 'Message') and comment_docname = %s """, frappe.user.name) - + delete_notification_count_for("Messages") - + frappe.db.commit() if frappe.form_dict['contact'] == frappe.session['user']: # return messages - return frappe.db.sql("""select * from `tabComment` - where (owner=%(contact)s - or comment_docname=%(user)s + return frappe.db.sql("""select * from `tabComment` + where (owner=%(contact)s + or comment_docname=%(user)s or (owner=comment_docname and ifnull(parenttype, "")!="Assignment")) and comment_doctype ='Message' order by creation desc - limit %(limit_start)s, %(limit_page_length)s""", frappe.local.form_dict, as_dict=1) + limit %(limit_start)s, %(limit_page_length)s""", frappe.local.form_dict, as_dict=1) else: - return frappe.db.sql("""select * from `tabComment` + return frappe.db.sql("""select * from `tabComment` where (owner=%(contact)s and comment_docname=%(user)s) or (owner=%(user)s and comment_docname=%(contact)s) or (owner=%(contact)s and comment_docname=%(contact)s) and comment_doctype ='Message' order by creation desc limit %(limit_start)s, %(limit_page_length)s""", frappe.local.form_dict, as_dict=1) - + @frappe.whitelist() def get_active_users(): return frappe.db.sql("""select name, (select count(*) from tabSessions where user=tabUser.name and timediff(now(), lastupdate) < time("01:00:00")) as has_session - from tabUser + from tabUser where ifnull(enabled,0)=1 and - ifnull(user_type, '')!='Website User' and + ifnull(user_type, '')!='Website User' and name not in ({}) order by first_name""".format(", ".join(["%s"]*len(STANDARD_USERS))), STANDARD_USERS, as_dict=1) @frappe.whitelist() -def post(arg=None): +def post(txt, contact, parenttype=None, notify=False, subject=None): import frappe """post message""" - if not arg: - arg = {} - arg.update(frappe.local.form_dict) - - if isinstance(arg, basestring): - import json - arg = json.loads(arg) d = frappe.new_doc('Comment') - d.parenttype = arg.get("parenttype") - d.comment = arg['txt'] - d.comment_docname = arg['contact'] + d.parenttype = parenttype + d.comment = txt + d.comment_docname = contact d.comment_doctype = 'Message' d.insert(ignore_permissions=True) - + delete_notification_count_for("Messages") - import frappe.utils - if frappe.utils.cint(arg.get('notify')): - notify(arg) - + if notify and cint(notify): + if contact==frappe.session.user: + _notify([user.name for user in frappe.get_list("User", + {"user_type":"System User", "enabled": 1}) \ + if user.name not in ("Guest", "Administrator")], txt) + else: + _notify(contact, txt, subject) + @frappe.whitelist() def delete(arg=None): - frappe.db.sql("""delete from `tabComment` where name=%s""", + frappe.db.sql("""delete from `tabComment` where name=%s""", frappe.form_dict['name']); -def notify(arg=None): - from frappe.utils import cstr, get_fullname, get_url - +def _notify(contact, txt, subject=None): + from frappe.utils import get_fullname, get_url + try: + if not isinstance(contact, list): + contact = [frappe.db.get_value("User", contact, "email") or contact] frappe.sendmail(\ - recipients=[frappe.db.get_value("User", arg["contact"], "email") or arg["contact"]], + recipients=contact, sender= frappe.db.get_value("User", frappe.session.user, "email"), - subject="New Message from " + get_fullname(frappe.user.name), + subject=subject or "New Message from " + get_fullname(frappe.user.name), message=frappe.get_template("templates/emails/new_message.html").render({ "from": get_fullname(frappe.user.name), - "message": arg['txt'], + "message": txt, "link": get_url() - }) - ) - except frappe.OutgoingEmailError, e: - pass \ No newline at end of file + }), + bulk=True) + except frappe.OutgoingEmailError: + pass diff --git a/frappe/core/page/modules_setup/modules_setup.js b/frappe/core/page/modules_setup/modules_setup.js index 28ffed4ebe..5ab4a010c8 100644 --- a/frappe/core/page/modules_setup/modules_setup.js +++ b/frappe/core/page/modules_setup/modules_setup.js @@ -1,33 +1,33 @@ -frappe.pages['modules_setup'].onload = function(wrapper) { +frappe.pages['modules_setup'].onload = function(wrapper) { frappe.ui.make_app_page({ parent: wrapper, title: 'Show or Hide Modules', single_column: true }); - + wrapper.appframe.set_title_right("Update", function() { frappe.modules_setup.update(this); }) - + $('
' +__("Select modules to be shown (based on permission). If hidden, they will be hidden for all users.")+'
').appendTo($(wrapper).find(".layout-main")); $('
').appendTo($(wrapper).find(".layout-main")); - + frappe.modules_setup.refresh_page(); } frappe.modules_setup = { refresh_page: function() { $('#modules-list').empty(); - + var wrapper = $('
').appendTo("#modules-list"); $.each(keys(frappe.modules).sort(), function(i, m) { - if(m!="Setup" && frappe.modules[m].link) { + if(m!="Setup") { var row = $('
\ ' - +frappe.ui.app_icon.get_html(m, true) + +frappe.ui.app_icon.get_html(m, true) + " " +m+'
').appendTo("#modules-list"); var $chk = $("") .appendTo(row.find(".check-area")); @@ -42,7 +42,7 @@ frappe.modules_setup = { $('#modules-list [data-module]:checkbox:not(:checked)').each(function() { ml.push($(this).attr('data-module')); }); - + return frappe.call({ method: 'frappe.core.page.modules_setup.modules_setup.update', args: { @@ -54,5 +54,5 @@ frappe.modules_setup = { btn: btn }); } - + } diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js index 4e274c5ed1..36ff5b7e8d 100644 --- a/frappe/core/page/permission_manager/permission_manager.js +++ b/frappe/core/page/permission_manager/permission_manager.js @@ -1,13 +1,14 @@ frappe.pages['permission-manager'].onload = function(wrapper) { frappe.ui.make_app_page({ parent: wrapper, - title: __('Permission Manager'), + title: __('Role Permissions Manager'), icon: "icon-lock", single_column: true }); $(wrapper).find(".layout-main").html("
" + permissions_help); wrapper.permission_engine = new frappe.PermissionEngine(wrapper); + } frappe.pages['permission-manager'].refresh = function(wrapper) { @@ -35,17 +36,18 @@ frappe.PermissionEngine = Class.extend({ me.setup_appframe(); } }); + }, setup_appframe: function() { var me = this; this.doctype_select - = this.wrapper.appframe.add_select("doctypes", + = this.wrapper.appframe.add_select(__("Document Types"), [{value: "", label: __("Select Document Type")+"..."}].concat(this.options.doctypes)) .change(function() { frappe.set_route("permission-manager", $(this).val()); }); this.role_select - = this.wrapper.appframe.add_select("roles", + = this.wrapper.appframe.add_select(__("Roles"), [__("Select Role")+"..."].concat(this.options.roles)) .change(function() { me.refresh(); @@ -85,46 +87,37 @@ frappe.PermissionEngine = Class.extend({ } return false; }, - make_reset_button: function() { + reset_std_permissions: function(data) { var me = this; - me.reset_button = me.wrapper.appframe.set_title_right("Reset Permissions", function() { - me.get_standard_permissions(function(data) { - var d = frappe.confirm(__("Reset Permissions for {0}?", [me.get_doctype()]), function() { - return frappe.call({ - module:"frappe.core", - page:"permission_manager", - method:"reset", - args: { - doctype:me.get_doctype(), - }, - callback: function() { me.refresh(); } - }); - }); + var d = frappe.confirm(__("Reset Permissions for {0}?", [me.get_doctype()]), function() { + return frappe.call({ + module:"frappe.core", + page:"permission_manager", + method:"reset", + args: { + doctype: me.get_doctype(), + }, + callback: function() { me.refresh(); } + }); + }); - // show standard permissions - var $d = $(d.wrapper).find(".msgprint").append("

Standard Permissions

"); - var $wrapper = $("

").appendTo($d); - $.each(data.message, function(i, d) { - d.rights = []; - $.each(me.rights, function(i, r) { - if(d[r]===1) { - if(r==="restrict") { - d.rights.push(__("Can Restrict Others")); - } else if(r==="restricted") { - d.rights.push(__("Only Restricted Documents")); - } else { - d.rights.push(__(toTitle(r))); - } - } - }); - d.rights = d.rights.join(", "); - $wrapper.append(repl('
\ -
%(role)s, Level %(permlevel)s
\ -
%(rights)s
\ -

', d)); - }); + // show standard permissions + var $d = $(d.wrapper).find(".frappe-confirm-message").append("

Standard Permissions

"); + var $wrapper = $("

").appendTo($d); + $.each(data.message, function(i, d) { + d.rights = []; + $.each(me.rights, function(i, r) { + if(d[r]===1) { + d.rights.push(__(toTitle(r.replace("_", " ")))); + } }); - }).toggle(false); + d.rights = d.rights.join(", "); + $wrapper.append(repl('
\ +
%(role)s, Level %(permlevel)s
\ +
%(rights)s
\ +

', d)); + }); + }, get_doctype: function() { var doctype = this.doctype_select.val(); @@ -137,7 +130,7 @@ frappe.PermissionEngine = Class.extend({ refresh: function() { var me = this; if(!me.doctype_select) { - this.body.html("
Loading...
"); + this.body.html("
" + __("Loading") + "...
"); return; } if(!me.get_doctype() && !me.get_role()) { @@ -157,7 +150,6 @@ frappe.PermissionEngine = Class.extend({ me.render(r.message); } }); - me.reset_button.toggle(me.get_doctype() ? true : false); }, render: function(perm_list) { this.body.empty(); @@ -169,8 +161,10 @@ frappe.PermissionEngine = Class.extend({ this.show_permission_table(this.perm_list); } this.show_add_rule(); + this.make_reset_button(); }, show_permission_table: function(perm_list) { + var me = this; this.table = $("
\ \ @@ -179,71 +173,104 @@ frappe.PermissionEngine = Class.extend({
\
").appendTo(this.body); - $.each([["Document Type", 150], ["Role", 150], ["Level", 40], - ["Permissions", 370], ["", 40]], function(i, col) { + $.each([[__("Document Type"), 150], [__("Role"), 170], [__("Level"), 40], + [__("Permissions"), 350], ["", 40]], function(i, col) { $("").html(col[0]).css("width", col[1]+"px") .appendTo(me.table.find("thead tr")); }); - var add_cell = function(row, d, fieldname, is_check) { - return $("").appendTo(row) - .attr("data-fieldname", fieldname) - .html(d[fieldname]); - }; - - var add_check = function(cell, d, fieldname, label) { - if(!label) label = fieldname; - if(d.permlevel > 0 && ["read", "write"].indexOf(fieldname)==-1) { - return; - } - - var checkbox = $("
\ - \ -
").appendTo(cell) - .attr("data-fieldname", fieldname) - .css("text-transform", "capitalize"); - - checkbox.find("input") - .prop("checked", d[fieldname] ? true: false) - .attr("data-ptype", fieldname) - .attr("data-name", d.name) - .attr("data-doctype", d.parent) - }; - $.each(perm_list, function(i, d) { if(!d.permlevel) d.permlevel = 0; var row = $("").appendTo(me.table.find("tbody")); - add_cell(row, d, "parent"); - me.set_show_users(add_cell(row, d, "role"), d.role); + me.add_cell(row, d, "parent"); + var role_cell = me.add_cell(row, d, "role"); + me.set_show_users(role_cell, d.role); + + if (d.permlevel===0) { + me.setup_user_permissions(d, role_cell); + } - var cell = add_cell(row, d, "permlevel"); + var cell = me.add_cell(row, d, "permlevel"); if(d.permlevel==0) { cell.css("font-weight", "bold"); row.addClass("warning"); } - var perm_cell = add_cell(row, d, "permissions").css("padding-top", 0); + var perm_cell = me.add_cell(row, d, "permissions").css("padding-top", 0); var perm_container = $("
").appendTo(perm_cell); $.each(me.rights, function(i, r) { - if(r==="restrict") { - add_check(perm_container, d, "restrict", "Can Restrict Others"); - } else if(r==="restricted") { - add_check(perm_container, d, "restricted", "Only Restricted Documents"); - } else { - add_check(perm_container, d, r); - } - }) + me.add_check(perm_container, d, r); + }); // buttons me.add_delete_button(row, d); }); }, + + add_cell: function(row, d, fieldname) { + return $("").appendTo(row) + .attr("data-fieldname", fieldname) + .html(__(d[fieldname])); + }, + + add_check: function(cell, d, fieldname, label) { + var me = this; + + if(!label) label = toTitle(fieldname.replace(/_/g, " ")); + if(d.permlevel > 0 && ["read", "write"].indexOf(fieldname)==-1) { + return; + } + + var checkbox = $("
\ + " + + (d.help || "") + "
").appendTo(cell) + .attr("data-fieldname", fieldname); + + checkbox.find("input") + .prop("checked", d[fieldname] ? true: false) + .attr("data-ptype", fieldname) + .attr("data-name", d.name) + .attr("data-doctype", d.parent) + + checkbox.find("label") + .css("text-transform", "capitalize"); + + return checkbox; + }, + + setup_user_permissions: function(d, role_cell) { + var me = this; + d.help = frappe.render('', {}); + + var checkbox = this.add_check(role_cell, d, "apply_user_permissions") + .removeClass("col-md-4") + .css({"margin-top": "15px"}); + + checkbox.find(".show-user-permission-doctypes").on("click", function() { + me.show_user_permission_doctypes(d); + }); + + var toggle_user_permissions = function() { + checkbox.find(".user-permission-help").toggleClass("hidden", !checkbox.find("input").prop("checked")); + }; + + toggle_user_permissions(); + checkbox.find("input").on('click', function() { + toggle_user_permissions(); + }); + + d.help = ""; + }, + rights: ["read", "write", "create", "delete", "submit", "cancel", "amend", - "report", "import", "export", "print", "email", "restricted", "restrict"], + "print", "email", "report", "import", "export", "set_user_permissions"], set_show_users: function(cell, role) { - cell.html(""+role+"") + cell.html(""+__(role)+"") .find("a") .attr("data-role", role) .click(function() { @@ -260,7 +287,7 @@ frappe.PermissionEngine = Class.extend({ return $.format('{1}', [p, p]); }) msgprint(__("Users with role {0}:", [role]) - + r.message.join("
")); + + "
" + r.message.join("
")); } }) return false; @@ -293,6 +320,12 @@ frappe.PermissionEngine = Class.extend({ }, add_check_events: function() { var me = this; + + this.body.on("click", ".show-user-permissions", function() { + frappe.route_options = { doctype: me.get_doctype() || "" }; + frappe.set_route("user-permissions"); + }); + this.body.on("click", "input[type='checkbox']", function() { var chk = $(this); var args = { @@ -319,20 +352,21 @@ frappe.PermissionEngine = Class.extend({ }, show_add_rule: function() { var me = this; - $("") - .appendTo($("

").appendTo(this.body)) + $("") + .appendTo($("

").appendTo(this.body)) .click(function() { var d = new frappe.ui.Dialog({ title: __("Add New Permission Rule"), fields: [ - {fieldtype:"Select", label:"Document Type", + {fieldtype:"Select", label:__("Document Type"), options:me.options.doctypes, reqd:1, fieldname:"parent"}, - {fieldtype:"Select", label:"Role", + {fieldtype:"Select", label:__("Role"), options:me.options.roles, reqd:1}, - {fieldtype:"Select", label:"Permission Level", + {fieldtype:"Select", label:__("Permission Level"), options:[0,1,2,3,4,5,6,7,8,9], reqd:1, fieldname: "permlevel", description: __("Level 0 is for document level permissions, higher levels for field level permissions.")}, - {fieldtype:"Button", label:"Add"}, + {fieldtype:"Button", label:__("Add")}, ] }); if(me.get_doctype()) { @@ -367,6 +401,95 @@ frappe.PermissionEngine = Class.extend({ d.show(); }); }, + + show_user_permission_doctypes: function(d) { + if (!d.dialog) { + var fields = []; + for (var i=0, l=d.linked_doctypes.length; i\ + ' + __("Restore Original Permissions") + '') + .appendTo(this.body.find(".permission-toolbar")) + .on("click", function() { + me.get_standard_permissions(function(data) { + me.reset_std_permissions(data); + }); + }) + }, + get_perm: function(name) { return $.map(this.perm_list, function(d) { if(d.name==name) return d; })[0]; }, @@ -383,14 +506,14 @@ frappe.PermissionEngine = Class.extend({ } }) -var permissions_help = ['', +var permissions_help = ['
', '").appendTo(me.table.find("tbody")); + + $("").appendTo(me.table.find("tbody")); - - $("', doc)) +
%(content)s
\ + ', doc)) .appendTo(this.body); - - if(!doc.name) { - comm.find(".show-details").toggle(false); - } - - comm.find(".comm-header") - .css({"cursor":"pointer"}) - .click(function() { - $(this).parent().find(".comm-content").toggle(); - }); - this.comm_list.push(comm); - comm.find(".comm-content .inner").html(doc.content); } }); +frappe.last_edited_communication = {}; +frappe.standard_replies = {}; + frappe.views.CommunicationComposer = Class.extend({ init: function(opts) { $.extend(this, opts) this.make(); - this.dialog.show(); }, make: function() { var me = this; @@ -143,6 +130,8 @@ frappe.views.CommunicationComposer = Class.extend({ description:__("Email addresses, separted by commas")}, {label:__("Subject"), fieldtype:"Data", reqd: 1, fieldname:"subject"}, + {label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply", + fieldname:"standard_reply"}, {label:__("Message"), fieldtype:"Text Editor", reqd: 1, fieldname:"content"}, {label:__("Send As Email"), fieldtype:"Check", @@ -169,6 +158,7 @@ frappe.views.CommunicationComposer = Class.extend({ this.dialog.$wrapper.find("[data-edit='outdent']").remove(); this.dialog.get_input("send").addClass("btn-primary"); + $(document).on("upload_complete", function(event, attachment) { if(me.dialog.display) { var wrapper = $(me.dialog.fields_dict.select_attachments.wrapper); @@ -190,16 +180,75 @@ frappe.views.CommunicationComposer = Class.extend({ } }) this.prepare(); + this.dialog.show(); + }, prepare: function() { this.setup_print(); this.setup_attach(); this.setup_email(); this.setup_autosuggest(); + this.setup_last_edited_communication(); + this.setup_standard_reply(); $(this.dialog.fields_dict.recipients.input).val(this.recipients || "").change(); $(this.dialog.fields_dict.subject.input).val(this.subject || "").change(); this.setup_earlier_reply(); }, + + setup_standard_reply: function() { + var me = this; + this.dialog.get_input("standard_reply").on("change", function() { + var standard_reply = $(this).val(); + var prepend_reply = function() { + var content_field = me.dialog.fields_dict.content; + var content = content_field.get_value() || ""; + content_field.set_input( + frappe.standard_replies[standard_reply] + + "

" + content); + } + if(frappe.standard_replies[standard_reply]) { + prepend_reply(); + } else { + $.ajax({ + url:"/api/resource/Standard Reply/" + standard_reply, + statusCode: { + 200: function(data) { + frappe.standard_replies[standard_reply] = data.data.response; + prepend_reply(); + } + } + }); + } + }); + }, + + setup_last_edited_communication: function() { + var me = this; + this.dialog.onhide = function() { + if(cur_frm && cur_frm.docname) { + if (!frappe.last_edited_communication[cur_frm.doctype]) { + frappe.last_edited_communication[cur_frm.doctype] = {}; + } + frappe.last_edited_communication[cur_frm.doctype][cur_frm.docname] = { + recipients: me.dialog.get_value("recipients"), + subject: me.dialog.get_value("subject"), + content: me.dialog.get_value("content"), + } + } + } + + this.dialog.onshow = function() { + if (cur_frm && cur_frm.docname && + (frappe.last_edited_communication[cur_frm.doctype] || {})[cur_frm.docname]) { + + c = frappe.last_edited_communication[cur_frm.doctype][cur_frm.docname]; + me.dialog.set_value("subject", c.subject || ""); + me.dialog.set_value("recipients", c.recipients || ""); + me.dialog.set_value("content", c.content || ""); + } + } + + }, setup_print: function() { // print formats var fields = this.dialog.fields_dict; @@ -211,13 +260,20 @@ frappe.views.CommunicationComposer = Class.extend({ // select print format $(fields.select_print_format.wrapper).toggle(false); - $(fields.select_print_format.input) - .empty() - .add_options(cur_frm.print_formats) - .val(cur_frm.print_formats[0]); + + if (cur_frm) { + $(fields.select_print_format.input) + .empty() + .add_options(cur_frm.print_preview.print_formats) + .val(cur_frm.print_preview.print_formats[0]); + } else { + $(fields.attach_document_print.wrapper).toggle(false); + } }, setup_attach: function() { + if (!cur_frm) return; + var fields = this.dialog.fields_dict; var attach = $(fields.select_attachments.wrapper); @@ -225,8 +281,11 @@ frappe.views.CommunicationComposer = Class.extend({ if(files.length) { $("

"+__("Add Attachments")+":

").appendTo(attach.empty()); $.each(files, function(i, f) { - $(repl("

%(file)s

", {file:f})).appendTo(attach) + if (!f.file_name) return; + + $(repl("

", f)) + .appendTo(attach) }); } }, @@ -265,20 +324,29 @@ frappe.views.CommunicationComposer = Class.extend({ }) if(form_values.attach_document_print) { - _p.build(form_values.select_print_format || "", function(print_format_html) { - me.send_email(btn, form_values, selected_attachments, print_format_html); - }); + if (cur_frm.print_preview.is_old_style(form_values.select_print_format || "")) { + cur_frm.print_preview.with_old_style({ + format: form_values.select_print_format, + callback: function(print_html) { + me.send_email(btn, form_values, selected_attachments, print_html); + } + }); + } else { + me.send_email(btn, form_values, selected_attachments, null, form_values.select_print_format || ""); + } + } else { me.send_email(btn, form_values, selected_attachments); } }); }, - send_email: function(btn, form_values, selected_attachments, print_html) { + send_email: function(btn, form_values, selected_attachments, print_html, print_format) { var me = this; if(!form_values.attach_document_print) { - print_html = ""; + print_html = null; + print_format = null; } if(form_values.send_email) { @@ -303,6 +371,7 @@ frappe.views.CommunicationComposer = Class.extend({ send_me_a_copy: form_values.send_me_a_copy, send_email: form_values.send_email, print_html: print_html, + print_format: print_format, communication_medium: form_values.communication_medium, sent_or_received: form_values.sent_or_received, attachments: selected_attachments @@ -313,7 +382,13 @@ frappe.views.CommunicationComposer = Class.extend({ if(form_values.send_email) msgprint(__("Email sent to {0}", [form_values.recipients])); me.dialog.hide(); - cur_frm.reload_doc(); + + if (cur_frm) { + if (cur_frm.docname && (frappe.last_edited_communication[cur_frm.doctype] || {})[cur_frm.docname]) { + delete frappe.last_edited_communication[cur_frm.doctype][cur_frm.docname]; + } + cur_frm.reload_doc(); + } } else { msgprint(__("There were errors while sending email. Please try again.")); } @@ -323,7 +398,7 @@ frappe.views.CommunicationComposer = Class.extend({ setup_earlier_reply: function() { var fields = this.dialog.fields_dict; - var comm_list = cur_frm.communication_view + var comm_list = (cur_frm && cur_frm.communication_view) ? cur_frm.communication_view.list : []; var signature = frappe.boot.user.email_signature || ""; @@ -342,7 +417,8 @@ frappe.views.CommunicationComposer = Class.extend({ if(comm_list.length > 0) { fields.content.set_input(reply + "

" - +"-----"+__("In response to")+"-----

" + +"-----"+__("In response to")+"-----" + +"

"+__("Please reply above this line or remove it if you are replying below it")+"



" + comm_list[0].content); } else { fields.content.set_input(reply); diff --git a/frappe/public/js/frappe/views/container.js b/frappe/public/js/frappe/views/container.js index e0f6a0ed6c..b391097e6a 100644 --- a/frappe/public/js/frappe/views/container.js +++ b/frappe/public/js/frappe/views/container.js @@ -15,7 +15,7 @@ frappe.views.Container = Class.extend({ this.pagemargin = 50; }, add_page: function(label, onshow, onhide) { - var page = $('
') + var page = $('
') .attr('id', "page-" + label) .attr("data-page-route", label) .toggle(false) diff --git a/frappe/public/js/frappe/views/doclistview.js b/frappe/public/js/frappe/views/doclistview.js index d4a4d4334f..6385aabab6 100644 --- a/frappe/public/js/frappe/views/doclistview.js +++ b/frappe/public/js/frappe/views/doclistview.js @@ -57,52 +57,57 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
'+__('Loading')+'...
') .appendTo(this.$page.find(".layout-main-section")); - $('
\ -
Show
\ -
\ -
'+__('Drafts')+'
\ -
'+__('Submitted')+'
\ -
'+__('Cancelled')+'
\ -
') - .appendTo(this.$page.find(".layout-side-section")); - this.$page.find(".layout-main-section") .addClass("listview-main-section") .parent().css({"margin-top":"-15px"}); this.appframe = this.page.appframe; var module = locals.DocType[this.doctype].module; - this.appframe.set_title(__(this.doctype) + " " + __("List")); + this.appframe.set_title(__("{0} List", [__(this.doctype)])); this.appframe.add_module_icon(module, this.doctype, null, true); - this.appframe.set_title_left(function() { frappe.set_route(frappe.get_module(module).link); }); + this.appframe.set_title_left(function() { + frappe.set_route(frappe.listview_parent_route[me.doctype] + || frappe.get_module(module).link); + }); this.appframe.set_views_for(this.doctype, "list"); }, setup: function() { + var me = this; this.can_delete = frappe.model.can_delete(this.doctype); this.meta = locals.DocType[this.doctype]; this.$page.find('.frappe-list-area').empty(), this.setup_listview(); - this.setup_docstatus_filter(); this.init_list(false); this.init_stats(); this.init_minbar(); this.show_match_help(); + this.init_listview(); + this.make_help(); + this.setup_filterable(); + this.init_filters(); + this.$page.find(".show_filters").css({"padding":"15px", "margin":"0px -15px"}); + this.$w.on("render-complete", function() { + // if only one record, open the form, if not coming from the form itself + if(me.data.length===1 + && frappe.get_prev_route()[2]!== me.data[0].name) { + frappe.set_route("Form", me.doctype, me.data[0].name); + } + }); + }, + + init_listview: function() { if(this.listview.settings.onload) { this.listview.settings.onload(this); } + if(this.listview.settings.set_title_left) { this.appframe.set_title_left(this.listview.settings.set_title_left); + } else if(this.listview.settings.parent_route) { + this.appframe.set_title_left(function() { + frappe.set_route(me.listview.settings.parent_route); + }); } - this.make_help(); - this.$page.find(".show_filters").css({"padding":"15px", "margin":"0px -15px"}); - var me = this; - // this.$w.on("render-complete", function() { - // me.set_sidebar_height(); - // }); }, set_sidebar_height: function() { @@ -112,28 +117,73 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ this.$page.find(".layout-main-section").css({"min-height": h_side}); }, + setup_filterable: function() { + var me = this; + this.$page.on("click", ".filterable", function(e) { + var filters = $(this).attr("data-filter").split("|"); + var added = false; + $.each(filters, function(i, f) { + f = f.split(","); + if(f[2]==="Today") { + f[2] = frappe.datetime.get_today(); + } else if(f[2]=="User") { + f[2] = user; + } + added = added || me.filter_list.add_filter(me.doctype, + f[0], f[1], f.slice(2).join(",")); + }); + added && me.run(); + }) + }, + + init_filters: function() { + var me = this; + if(this.listview.settings.filters) { + $.each(this.listview.settings.filters, function(i, f) { + if(f.length===3) { + f = [me.doctype, f[0], f[1], f[2]] + } + me.filter_list.add_filter(f[0], f[1], f[2], f[3]); + }); + } + }, + show_match_help: function() { var me = this; - var match_rules = frappe.perm.get_match_rules(this.doctype); + var match_rules_list = frappe.perm.get_match_rules(this.doctype); var perm = frappe.perm.get_perm(this.doctype); - if(keys(match_rules).length) { - var match_text = [] - $.each(match_rules, function(key, values) { - if(values.length==0) { - match_text.push(__(key) + __(" is not set")); - } else if(values.length) { - match_text.push(__(key) + " = " + frappe.utils.comma_or(values)); + if(match_rules_list.length) { + var or_match_text = []; + + $.each(match_rules_list, function(i, match_rules) { + var match_text = [] + $.each(match_rules, function(key, values) { + if(values.length==0) { + match_text.push(__(key) + __(" is not set")); + } else if(values.length) { + match_text.push(__(key) + " = " + frappe.utils.comma_or(values)); + } + }); + + if (match_text.length) { + var txt = "
    " + $.map(match_text, function(txt) { return "
  • "+txt+"
  • " }).join("") + "
"; + or_match_text.push(txt); } }); - if(perm[0].restricted) { - match_text.push(__("Or Created By") + " = " + user); + if (or_match_text.length) { + frappe.utils.set_footnote(this, this.$page.find(".layout-main-section"), + "

" + + __("Additional filters based on User Permissions, having:") + "

" + + or_match_text.join("

" + + __("or") + "

") + + "

" + + __("Note: fields having empty value for above criteria are not filtered out.") + + "

"); + $(this.footnote_area).css({"margin-top":"0px", "margin-bottom":"20px"}); } - frappe.utils.set_footnote(this, this.$page.find(".layout-main-section"), - "

" + __("Showing only for (if not empty)") + ":

    " - + $.map(match_text, function(txt) { return "
  • "+txt+"
  • " }).join("")) + "
"; - $(this.footnote_area).css({"margin-top":"0px", "margin-bottom":"20px"}); } }, make_help: function() { @@ -142,19 +192,6 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ this.appframe.add_help_button(this.meta.description); } }, - setup_docstatus_filter: function() { - var me = this; - this.can_submit = $.map(locals.DocPerm || [], function(d) { - if(d.parent==me.meta.name && d.submit) return 1 - else return null; - }).length; - if(this.can_submit) { - this.$page.find('.show-docstatus').removeClass('hide'); - this.$page.find('.show-docstatus input').click(function() { - me.run(); - }) - } - }, setup_listview: function() { this.listview = frappe.views.get_listview(this.doctype, this); this.wrapper = this.$page.find('.frappe-list-area'); @@ -207,7 +244,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ var me = this; me.filter_list.clear_filters(); $.each(frappe.route_options, function(key, value) { - me.filter_list.add_filter(me.doctype, key, "=", value); + if($.isArray(value)) { + me.filter_list.add_filter(me.doctype, key, value[0], value[1]); + } else { + me.filter_list.add_filter(me.doctype, key, "=", value); + } }); frappe.route_options = null; me.run(); @@ -229,14 +270,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ make_no_result: function() { var new_button = frappe.boot.user.can_create.indexOf(this.doctype)!=-1 ? ('

') + list_view_doc="' + this.doctype + '">'+ + __('Make a new {0}', [__(this.doctype)]) + '

') : ''; - var no_result_message = repl('
\ -

' + __("No") + ' %(doctype_label)s ' + __("found") + '

' + new_button + '
', { - doctype_label: __(this.doctype), - doctype: this.doctype, - }); + var no_result_message = '
\ +

' + __("No {0} found", [__(this.doctype)]) + '

' + new_button + '
'; return no_result_message; }, @@ -245,16 +283,10 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ this.listview.render(row, data, this); }, get_args: function() { - var docstatus_list = this.can_submit ? $.map(this.$page.find('.show-docstatus :checked'), - function(inp) { - return $(inp).attr('data-docstatus'); - }) : [] - var args = { doctype: this.doctype, fields: this.listview.fields, filters: this.filter_list.get_filters(), - docstatus: docstatus_list, order_by: this.listview.order_by || undefined, group_by: this.listview.group_by || undefined, } @@ -269,13 +301,21 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ init_minbar: function() { var me = this; this.appframe.add_icon_btn("2", 'icon-tag', __('Show Tags'), function() { me.toggle_tags(); }); - this.wrapper.on("click", ".list-tag-preview", function() { me.toggle_tags(); }); + this.$page.on("click", ".list-tag-preview", function() { me.toggle_tags(); }); + + this.appframe.add_icon_btn("2", 'icon-user', __('Assigned To Me'), + function() { + me.filter_list.add_filter(me.doctype, "_assign", 'like', '%' + user + '%'); + me.run(); + }); + if(this.can_delete || this.listview.settings.selectable) { - this.appframe.add_icon_btn("2", 'icon-remove', __('Delete'), function() { me.delete_items(); }); this.appframe.add_icon_btn("2", 'icon-ok', __('Select All'), function() { me.$page.find('.list-delete').prop("checked", me.$page.find('.list-delete:checked').length ? false : true); }); + this.appframe.add_icon_btn("2", 'icon-trash', __('Delete'), + function() { me.delete_items(); }); } if(frappe.model.can_import(this.doctype)) { this.appframe.add_icon_btn("2", "icon-upload", __("Import"), function() { @@ -284,16 +324,23 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ }) }); } - if(frappe.model.can_restrict(this.doctype)) { + if(frappe.model.can_set_user_permissions(this.doctype)) { this.appframe.add_icon_btn("2", "icon-shield", - __("User Permission Restrictions"), function() { + __("User Permissions Manager"), function() { frappe.route_options = { - property: me.doctype + doctype: me.doctype }; - frappe.set_route("user-properties"); + frappe.set_route("user-permissions"); }); } if(in_list(user_roles, "System Manager")) { + this.appframe.add_icon_btn("2", "icon-lock", + __("Role Permissions Manager"), function() { + frappe.route_options = { + doctype: me.doctype + }; + frappe.set_route("permission-manager"); + }); this.appframe.add_icon_btn("2", "icon-glass", __("Customize"), function() { frappe.set_route("Form", "Customize Form", { doctype: me.doctype @@ -314,9 +361,10 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ get_checked_items: function() { return $.map(this.$page.find('.list-delete:checked'), function(e) { - return $(e).data('data'); + return $(e).parents(".list-row:first").data('data'); }); }, + delete_items: function() { var me = this; var dl = this.get_checked_items(); diff --git a/frappe/public/js/frappe/views/ganttview.js b/frappe/public/js/frappe/views/ganttview.js index 6c9d1bd80d..f6c102db3f 100644 --- a/frappe/public/js/frappe/views/ganttview.js +++ b/frappe/public/js/frappe/views/ganttview.js @@ -11,7 +11,7 @@ frappe.views.GanttFactory = frappe.views.Factory.extend({ $(page).on("show", function() { me.set_filters_from_route_options(); }); - + var options = { doctype: route[1], page: page @@ -29,7 +29,7 @@ frappe.views.Gantt = Class.extend({ frappe.require('assets/frappe/js/lib/jQuery.Gantt/css/style.css'); frappe.require('assets/frappe/js/lib/jQuery.Gantt/js/jquery.fn.gantt.js'); - + this.make_page(); frappe.route_options ? this.set_filters_from_route_options() : @@ -38,21 +38,21 @@ frappe.views.Gantt = Class.extend({ make_page: function() { var module = locals.DocType[this.doctype].module, me = this; - + this.appframe = this.page.appframe; this.appframe.set_title(__("Gantt Chart") + " - " + __(this.doctype)); this.appframe.add_module_icon(module) this.appframe.set_views_for(this.doctype, "gantt"); - this.appframe.set_title_right("Refresh", + this.appframe.set_title_right("Refresh", function() { me.refresh(); }, "icon-refresh") - this.appframe.add_field({fieldtype:"Date", label:"From", + this.appframe.add_field({fieldtype:"Date", label:"From", fieldname:"start", "default": frappe.datetime.month_start(), input_css: {"z-index": 3}}); - this.appframe.add_field({fieldtype:"Date", label:"To", + this.appframe.add_field({fieldtype:"Date", label:"To", fieldname:"end", "default": frappe.datetime.month_end(), input_css: {"z-index": 3}}); - + if(this.filters) { $.each(this.filters, function(i, df) { me.appframe.add_field(df); @@ -66,7 +66,7 @@ frappe.views.Gantt = Class.extend({ .empty() .css('min-height', '300px') .html('
Loading...
'); - + var me = this; return frappe.call({ method: this.get_events_method, @@ -95,11 +95,11 @@ frappe.views.Gantt = Class.extend({ onAddClick: function(dt, rowId) { newdoc(me.doctype); } - }); + }); } } }) - + }, set_filter: function(doctype, value) { var me = this; @@ -116,7 +116,7 @@ frappe.views.Gantt = Class.extend({ me = this; if(this.filters) { $.each(this.filters, function(i, df) { - filter_vals[df.fieldname || df.label] = + filter_vals[df.fieldname || df.label] = me.appframe.fields_dict[df.fieldname || df.label].get_parsed_value(); }); } @@ -132,7 +132,7 @@ frappe.views.Gantt = Class.extend({ $.each(me.field_map, function(target, source) { v[target] = v[source]; }); - + if(v.start && !v.end) { v.end = new Date(v.start) v.end.setHours(v.end.getHours() + 1); @@ -140,28 +140,28 @@ frappe.views.Gantt = Class.extend({ if(v.start && v.end) { source.push({ - name: v.title, + name: v.title, desc: v.status, values: [{ name: v.title, - desc: v.title + "
" + v.status, - from: '/Date("'+v.start+'")/', - to: '/Date("'+v.end+'")/', + desc: v.title + "
" + (v.status || ""), + from: '/Date('+moment(v.start).format("X")+'000)/', + to: '/Date('+moment(v.end).format("X")+'000)/', customClass: { 'danger':'ganttRed', 'warning':'ganttOrange', 'info':'ganttBlue', 'success':'ganttGreen', '':'ganttGray' - }[me.style_map ? + }[me.style_map ? me.style_map[v.status] : frappe.utils.guess_style(v.status, "standard")], dataObj: v }] - }) + }) } }); - return source + return source }, set_filters_from_route_options: function() { var me = this; diff --git a/frappe/public/js/frappe/views/grid_report.js b/frappe/public/js/frappe/views/grid_report.js index 3dc7314cd0..c97e2e55d1 100644 --- a/frappe/public/js/frappe/views/grid_report.js +++ b/frappe/public/js/frappe/views/grid_report.js @@ -104,6 +104,7 @@ frappe.views.GridReport = Class.extend({ $.extend(this, opts); this.wrapper = $('
').appendTo(this.parent); + this.appframe.parent.find(".appframe").css({"padding-top": "0px"}); if(this.filters) { this.make_filters(); @@ -255,7 +256,7 @@ frappe.views.GridReport = Class.extend({ } else if(v.fieldtype==='Button' && v.label==="Refresh") { input = me.appframe.set_title_right(v.label, null, v.icon); } else if(v.fieldtype==='Button') { - input = me.appframe.add_primary_action(v.label, null, v.icon); + input = me.appframe.add_button(v.label, null, v.icon); } else if(v.fieldtype==='Date') { input = me.appframe.add_date(v.label); } else if(v.fieldtype==='Label') { @@ -353,8 +354,7 @@ frappe.views.GridReport = Class.extend({ this.round_off_data(); this.prepare_data_view(); // plot might need prepared data - this.wrapper.find(".processing").toggle(true); - this.wrapper.find(".processing").delay(2000).fadeOut(300); + show_alert("Updated", 2); this.render(); this.render_plot && this.render_plot(); }, @@ -367,18 +367,10 @@ frappe.views.GridReport = Class.extend({ var me = this; // plot wrapper - this.plot_area = $('').appendTo(this.wrapper); - // print / export - $('').appendTo(this.wrapper); - - this.wrapper.find(".grid-report-export").click(function() { return me.export(); }); + this.appframe.add_button(__("Export"), function() { return me.export(); }, "icon-download"); // grid wrapper this.grid_wrapper = $("
\ -
\ -
\ -
').appendTo($(row).css({"position":"relative"})), + + if(this.template) { + this.render_template(row, data); + } else { + this.render_standard_columns(row, data); + } + + this.render_timestamp_and_comments(row, data); + this.render_tags(row, data); + + }, + + render_template: function (row, data) { + $(frappe.render(this.template, { + doc: frappe.get_format_helper(data), + list: this + })).appendTo($('
').appendTo(row)); + }, + + render_standard_columns: function(row, data) { + var left_cols = 4 + this.shift_right, + right_cols = 8 - this.shift_right, + body = $('
\ +
\ +
\ +
').appendTo(row), colspans = 0, me = this; - - me.render_avatar_and_id(data, body.find(".list-row-id-area")) - + + $(me.get_avatar_and_id(data, true)).appendTo(body.find(".list-row-id-area")); + // make table $.each(this.columns, function(i, v) { var colspan = v.colspan || 3; colspans = colspans + flt(colspan) - + if(colspans <= 12) { - var col = me.make_column(body.find(".list-row-content-area"), colspan); + var col = me.make_column(body.find(".list-row-content-area"), + colspan); me.render_column(data, col, v); } }); - - var comments = data._comments ? JSON.parse(data._comments) : []; - var tags = $.map((data._user_tags || "").split(","), function(v) { return v ? v : null; }); - - var timestamp_and_comment = + + }, + + render_timestamp_and_comments: function(row, data) { + var comments = data._comments ? JSON.parse(data._comments) : [], + tags = $.map((data._user_tags || "").split(","), + function(v) { return v ? v : null; }), + assign = data._assign ? JSON.parse(data._assign) : [], + me = this; + + if(me.title_field && data[me.title_field]!==data.name) { + $('
') + .appendTo(row) + .css({"left": me.title_offset_left}) + .html('#' + data.name + ""); + } + + $(row).find(".list-timestamp").remove(); + + var timestamp_and_comment = $('
') .appendTo(row) - .html("" - + (tags.length ? ( - '' + tags.join(", ") + '' - ): "") - + (comments.length ? - (' ' - + comments.length + " " + ( - comments.length===1 ? __("comment") : __("comments")) + '') - : "") - + comment_when(data.modified)); - - // row #2 + .html(frappe.render(frappe.templates.list_info_template, { + "tags": tags, + "comments": comments, + "assign": assign, + "data": data, + "doctype": this.doctype + })); + }, + + render_tags: function(row, data) { + var me = this; var row2 = $('
\
\
\
\
\ -
\
\
\
').appendTo(row); - - // modified - body.find(".list-last-modified").html(__("Last updated by") + ": " + frappe.user_info(data.modified_by).fullname); - + if(!me.doclistview.tags_shown) { row2.addClass("hide"); } - + // add tags var tag_editor = new frappe.ui.TagEditor({ parent: row2.find(".list-tag"), @@ -228,17 +267,23 @@ frappe.views.ListView = Class.extend({ doctype: this.doctype, docname: data.name }, - user_tags: data._user_tags + user_tags: data._user_tags, + on_change: function(user_tags) { + data._user_tags = user_tags; + me.render_timestamp_and_comments(row, data); + } }); tag_editor.$w.on("click", ".tagit-label", function() { - me.doclistview.set_filter("_user_tags", + me.doclistview.set_filter("_user_tags", $(this).text()); }); }, + make_column: function(body, colspan) { var col = $("
") .appendTo(body) .addClass("col-sm-" + cint(colspan)) + .addClass("col-xs-" + (cint(colspan) + 2)) .css({ "white-space": "nowrap", "text-overflow": "ellipsis", @@ -247,45 +292,72 @@ frappe.views.ListView = Class.extend({ }) return col; }, - render_avatar_and_id: function(data, parent) { + get_avatar_and_id: function(data, without_workflow) { + this.title_offset_left = 15; + + var html = ""; + + // checkbox if((frappe.model.can_delete(this.doctype) || this.settings.selectable) && !this.no_delete) { - $('') - .data('name', data.name) - .data('data', data) - .css({"margin-right": "5px"}) - .appendTo(parent) + html += ''; + + this.title_offset_left += 13 + 5; } - - var $avatar = $(frappe.avatar(data.modified_by, false, __("Modified by")+": " - + frappe.user_info(data.modified_by).fullname)) - .appendTo(parent) - .css({"max-width": "100%"}) + // avatar + var user_for_avatar = data.user_for_avatar || data.modified_by; + html += frappe.avatar(user_for_avatar, false, __("Modified by")+": " + + frappe.user_info(user_for_avatar).fullname) + this.title_offset_left += 30 + 5; + + // docstatus lock if(frappe.model.is_submittable(this.doctype)) { - $(parent).append(repl(' \ - ', data)); - } - - var title = data[this.title_field || "name"]; - $("") - .attr("href", "#Form/" + data.doctype + "/" + encodeURIComponent(data.name)) - .html(title) - .appendTo(parent.css({"overflow":"hidden"})); - - parent.attr("title", title).tooltip(); - + html += repl(' \ + ', data); + + this.title_offset_left += 15 + 4; + } + + // title + var full_title = data[this.title_field || "name"], title = full_title; + if(full_title.length > 40) { + title = full_title.slice(0, 40) + "..."; + } + html += repl('%(title)s', { + doctype: data.doctype, + name: encodeURIComponent(data.name), + title: title, + full_title: full_title, + }); + + this.title_offset_left += 5; + + if(!without_workflow && this.workflow_state_fieldname) { + html+= repl('\ + %(value)s', { + fieldname: this.workflow_state_fieldname, + value: data[this.workflow_state_fieldname], + style: frappe.utils.guess_style(data[this.workflow_state_fieldname]) + }); + } + + return html; }, + render_column: function(data, parent, opts) { var me = this; if(opts.type) opts.type= opts.type.toLowerCase(); - + // style if(opts.css) { $.each(opts.css, function(k, v) { $(parent).css(k, v)}); } - + // multiple content if(opts.content.indexOf && opts.content.indexOf('+')!=-1) { $.map(opts.content.split('+'), function(v) { @@ -293,7 +365,7 @@ frappe.views.ListView = Class.extend({ }); return; } - + // content if(typeof opts.content=='function') { opts.content(parent, data, me); @@ -305,7 +377,7 @@ frappe.views.ListView = Class.extend({ } else if(opts.template) { $(parent).append(repl(opts.template, data)); - } + } else if(opts.type=="date" && data[opts.content]) { $("") .html(frappe.datetime.str_to_user(data[opts.content])) @@ -324,22 +396,19 @@ frappe.views.ListView = Class.extend({ .appendTo(parent); } else if(opts.type=="select" && data[opts.content]) { - + var label_class = "label-default"; var style = frappe.utils.guess_style(data[opts.content]); if(style) label_class = "label-" + style; - - $("" + + $("" + data[opts.content] + "") .css({"cursor":"pointer"}) .addClass("label") + .addClass("filterable") .addClass(label_class) - .attr("data-fieldname", opts.content) - .click(function() { - me.doclistview.set_filter($(this).attr("data-fieldname"), - $(this).text()); - }) + .attr("data-filter", opts.df.fieldname + ",=," + data[opts.content]) .appendTo(parent.css({"overflow":"hidden"})); } else if(opts.type=="link" && data[opts.content]) { @@ -347,35 +416,35 @@ frappe.views.ListView = Class.extend({ .html(frappe.format(data[opts.content], opts.df, null, data)) .appendTo(parent.css({"overflow":"hidden"})) .click(function() { - me.doclistview.set_filter($(this).attr("data-fieldname"), + me.doclistview.set_filter($(this).attr("data-fieldname"), $(this).attr("data-value")); return false; }) .attr("data-fieldname", opts.content) .attr("data-value", data[opts.content]) - .find("a").attr("href", "#"); - + .find("a").removeAttr("href"); + } else if(data[opts.content]) { $("") .html(frappe.format(data[opts.content], opts.df, null, data)) .appendTo(parent.css({"overflow":"hidden"})) } - + // finally if(!$(parent).html()) { $("-").css({color:"#ccc"}).appendTo(parent); } - + // title if(!in_list(["avatar", "_user_tags", "check"], opts.content)) { if($(parent).attr("title")==undefined) { - $(parent).attr("title", (opts.title || opts.content) + ": " - + (data[opts.content] || "Not Set")) + $(parent).attr("title", (opts.title || opts.content) + ": " + + (data[opts.content] || __("Not Set"))) } $(parent).tooltip(); } - + }, show_hide_check_column: function() { if(!this.doclistview.can_delete) { @@ -383,22 +452,22 @@ frappe.views.ListView = Class.extend({ } }, prepare_data: function(data) { - + if(data.modified) this.prepare_when(data, data.modified); - + // docstatus if(data.docstatus==0 || data.docstatus==null) { - data.docstatus_icon = 'icon-check-empty'; + data.docstatus_icon = 'icon-edit text-danger'; data.docstatus_title = __('Editable'); } else if(data.docstatus==1) { - data.docstatus_icon = 'icon-lock'; + data.docstatus_icon = 'icon-lock'; data.docstatus_title = __('Submitted'); } else if(data.docstatus==2) { - data.docstatus_icon = 'icon-remove'; + data.docstatus_icon = 'icon-ban-circle'; data.docstatus_title = __('Cancelled'); } - + // nulls as strings for(key in data) { if(data[key]==null) { @@ -410,7 +479,7 @@ frappe.views.ListView = Class.extend({ if(this.settings.prepare_data) this.settings.prepare_data(data); }, - + prepare_when: function(data, date_str) { if (!date_str) date_str = data.modified; // when @@ -426,11 +495,11 @@ frappe.views.ListView = Class.extend({ data.when = __('2 days ago') } }, - + render_bar_graph: function(parent, data, field, label) { var args = { percent: data[field], - label: label + label: __(label) } $(parent).append(repl(' \ \ \
%(description)s
\
\ ', item)).appendTo($list); @@ -219,6 +223,7 @@ frappe.views.moduleview.ModuleView = Class.extend({ if(!route) { if(item.type==="doctype") { route = "List/" + encodeURIComponent(item.name); + frappe.listview_parent_route[item.name] = ["Module", me.module]; } else if(item.type==="page") { route = item.route || item.link || item.name; } else if(item.type==="report") { diff --git a/frappe/public/js/frappe/views/query_report.js b/frappe/public/js/frappe/views/query_report.js index 56141d8ee4..b994ef829b 100644 --- a/frappe/public/js/frappe/views/query_report.js +++ b/frappe/public/js/frappe/views/query_report.js @@ -60,7 +60,7 @@ frappe.views.QueryReport = Class.extend({ this.appframe.set_title_right(__('Refresh'), function() { me.refresh(); }); // Edit - var edit_btn = this.appframe.add_primary_action(__('Edit'), function() { + var edit_btn = this.appframe.add_button(__('Edit'), function() { if(!frappe.user.is_report_manager()) { msgprint(__("You are not allowed to create / edit reports")); return false; @@ -68,16 +68,16 @@ frappe.views.QueryReport = Class.extend({ frappe.set_route("Form", "Report", me.report_name); }, "icon-edit"); - this.appframe.add_primary_action(__('Export'), function() { me.export_report(); }, + this.appframe.add_button(__('Export'), function() { me.export_report(); }, "icon-download"); - if(frappe.model.can_restrict("Report")) { - this.appframe.add_primary_action(__("User Restrictions"), function() { + if(frappe.model.can_set_user_permissions("Report")) { + this.appframe.add_button(__("User Permissions"), function() { frappe.route_options = { - property: "Report", - restriction: me.report_name + doctype: "Report", + name: me.report_name }; - frappe.set_route("user-properties"); + frappe.set_route("user-permissions"); }, "icon-shield"); } }, @@ -91,13 +91,13 @@ frappe.views.QueryReport = Class.extend({ this.wrapper.find(".no-report-area").toggle(false); me.appframe.set_title(__("Query Report")+": " + __(me.report_name)); - me.appframe.set_title_left(function() { - frappe.set_route(frappe.get_module(me.report_doc.module).link); }); - - frappe.model.with_doc("Report", me.report_name, function() { + me.report_doc = frappe.get_doc("Report", me.report_name); + me.appframe.set_title_left(function() { + frappe.set_route(frappe.get_module(me.report_doc.module).link); }); + frappe.model.with_doctype(me.report_doc.ref_doctype, function() { if(!frappe.query_reports[me.report_name]) { return frappe.call({ @@ -107,13 +107,19 @@ frappe.views.QueryReport = Class.extend({ }, callback: function(r) { me.appframe.set_title(__("Query Report")+": " + __(me.report_name)); - frappe.dom.eval(r.message || ""); + frappe.dom.eval(r.message.script || ""); me.setup_filters(); + me.setup_html_format(r.message.html_format); + frappe.query_reports[me.report_name]["html_format"] = r.message.html_format; me.refresh(); } }); } else { me.setup_filters(); + + // setup a fresh print action + me.setup_html_format(frappe.query_reports[me.report_name]["html_format"]); + me.refresh(); } }); @@ -124,6 +130,45 @@ frappe.views.QueryReport = Class.extend({ this.wrapper.find(".no-report-area").html(msg).toggle(true); } }, + setup_html_format: function(html_format) { + var me = this; + + // don't add multiple Print buttons + if (this.$print_action) { + this.$print_action.remove(); + } + + if(html_format) { + this.$print_action = this.appframe.add_button(__('Print'), function() { + if(!me.data) { + msgprint(__("Run the report first")); + return; + } + + var data = []; + // get filtered data + for (var i=0, l=me.dataView.getLength(); i") + .css("padding-left", (cint(dataContext.indent) * 21) + "px") + .html(value); + + var idx = me.dataView.getIdxById(dataContext.id); + var show_toggle = me.data[idx + 1] && (me.data[idx + 1].indent > me.data[idx].indent) + + if (dataContext[me.name_field] && show_toggle) { + $('') + .addClass(dataContext._collapsed ? "expand" : "collapse") + .css("margin-right", "7px") + .prependTo($span); + } + + return $span.wrap("

").parent().html(); + }, compare_values: function(value, filter, columnDef) { var invert = false; @@ -431,9 +615,35 @@ frappe.views.QueryReport = Class.extend({ me.dataView.refresh(); }); }, + setup_tree: function() { + // set these in frappe.query_reports[report_name] + // "tree": true, + // "name_field": "account", + // "parent_field": "parent_account", + // "initial_depth": 3 + + // also set "is_tree" true for ColumnDef + + var me = this; + this.grid.onClick.subscribe(function (e, args) { + if ($(e.target).hasClass("toggle")) { + var item = me.dataView.getItem(args.row); + if (item) { + if (!item._collapsed) { + item._collapsed = true; + } else { + item._collapsed = false; + } + + me.dataView.updateItem(item.id, item); + } + e.stopImmediatePropagation(); + } + }); + }, export_report: function() { if(!frappe.model.can_export(this.report_doc.ref_doctype)) { - msgprint(_("You are not allowed to export this report")); + msgprint(__("You are not allowed to export this report")); return false; } diff --git a/frappe/public/js/frappe/views/reportview.js b/frappe/public/js/frappe/views/reportview.js index 07960f6bc0..e261f9db22 100644 --- a/frappe/public/js/frappe/views/reportview.js +++ b/frappe/public/js/frappe/views/reportview.js @@ -53,7 +53,7 @@ frappe.views.ReportViewPage = Class.extend({ var module = locals.DocType[this.doctype].module; this.page.appframe.set_title(__(this.doctype)); this.page.appframe.add_module_icon(module, this.doctype) - this.page.appframe.set_title_left(function() { frappe.set_route(frappe.get_module(module).link); }); + this.page.appframe.set_title_left(function() { frappe.set_route((frappe.get_module(module) || {}).link); }); this.page.appframe.set_views_for(this.doctype, "report"); this.page.reportview = new frappe.views.ReportView({ @@ -107,7 +107,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ this.make_export(); this.set_init_columns(); this.make_save(); - this.make_user_restrictions(); + this.make_user_permissions(); this.set_tag_and_status_filter(); }, @@ -118,7 +118,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ var columns = [['name', this.doctype],]; $.each(frappe.meta.docfield_list[this.doctype], function(i, df) { if((df.in_filter || df.in_list_view) && df.fieldname!='naming_series' - && !in_list(frappe.model.no_value_type, df.fieldname)) { + && !in_list(frappe.model.no_value_type, df.fieldtype)) { columns.push([df.fieldname, df.parent]); } }); @@ -171,7 +171,6 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ fields: $.map(this.columns, function(v) { return me.get_full_column_name(v) }), order_by: this.get_order_by(), filters: this.filter_list.get_filters(), - docstatus: ['0','1','2'], with_childnames: 1 } }, @@ -221,8 +220,12 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ width: (docfield ? cint(docfield.width) : 120) || 120, formatter: function(row, cell, value, columnDef, dataContext) { var docfield = columnDef.docfield; - if(docfield.fieldname==="_user_tags") docfield.fieldtype = "Tag"; - if(docfield.fieldname==="_comments") docfield.fieldtype = "Comment"; + docfield.fieldtype = { + "_user_tags": "Tag", + "_comments": "Comment", + "_assign": "Assign" + }[docfield.fieldname] || docfield.fieldtype; + if(docfield.fieldtype==="Link" && docfield.fieldname!=="name") { docfield.link_onclick = repl('frappe.container.page.reportview.set_filter("%(fieldname)s", "%(value)s").page.reportview.run()', @@ -327,14 +330,20 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ }); d.get_input(docfield.fieldname).val(row[docfield.fieldname]); d.get_input("update").on("click", function() { + var args = { + doctype: docfield.parent, + name: row[docfield.parent===me.doctype ? "name" : docfield.parent+":name"], + fieldname: docfield.fieldname, + value: d.get_value(docfield.fieldname) + }; + + if (!args.name) { + frappe.throw(__("ID field is required to edit values using Report. Please select the ID field using the Column Picker")); + } + frappe.call({ method: "frappe.client.set_value", - args: { - doctype: docfield.parent, - name: row[docfield.parent===me.doctype ? "name" : docfield.parent+":name"], - fieldname: docfield.fieldname, - value: d.get_value(docfield.fieldname) - }, + args: args, callback: function(r) { if(!r.exc) { d.hide(); @@ -398,12 +407,14 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ this.$w.find('.result-list').on("click", ".label-info", function() { if($(this).attr("data-label")) { me.set_filter("_user_tags", $(this).attr("data-label")); + me.refresh(); } }); this.$w.find('.result-list').on("click", "[data-workflow-state]", function() { if($(this).attr("data-workflow-state")) { me.set_filter(me.state_fieldname, $(this).attr("data-workflow-state")); + me.refresh(); } }); }, @@ -550,19 +561,19 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ }); })); - }, 'icon-remove'); + }, 'icon-trash'); } }, - make_user_restrictions: function() { + make_user_permissions: function() { var me = this; - if(this.docname && frappe.model.can_restrict("Report")) { - this.page.appframe.add_button(__("User Permission Restrictions"), function() { + if(this.docname && frappe.model.can_set_user_permissions("Report")) { + this.page.appframe.add_button(__("User Permissions Manager"), function() { frappe.route_options = { - property: "Report", - restriction: me.docname + doctype: "Report", + name: me.docname }; - frappe.set_route("user-properties"); + frappe.set_route("user-permissions"); }, "icon-shield"); } }, @@ -599,7 +610,15 @@ frappe.ui.ColumnPicker = Class.extend({ me.add_column(c); }); - $(this.dialog.body).find('.column-list').sortable(); + $(this.dialog.body).find('.column-list').sortable({ + update: function(event, ui) { + me.columns = []; + $.each($(me.dialog.body).find('.column-list .column-list-item'), + function(i, ele) { + me.columns.push($(ele).data("fieldselect")) + }); + } + }); // add column $(this.dialog.body).find('.btn-add').click(function() { @@ -624,7 +643,7 @@ frappe.ui.ColumnPicker = Class.extend({ add_column: function(c) { if(!c) return; var w = $('
\ + width: 90%; margin-bottom: 10px; border-radius: 3px; cursor: move;" class="column-list-item">\ \ ×\
') @@ -638,6 +657,8 @@ frappe.ui.ColumnPicker = Class.extend({ fieldselect.$select.css({width: '70%', 'margin-top':'5px'}) fieldselect.val((c[1] || this.doctype) + "." + c[0]); + w.data("fieldselect", fieldselect); + w.find('.close').data("fieldselect", fieldselect).click(function() { console.log(me.columns.indexOf($(this).data('fieldselect'))); delete me.columns[me.columns.indexOf($(this).data('fieldselect'))]; diff --git a/frappe/public/js/frappe/views/sidebar_stats.js b/frappe/public/js/frappe/views/sidebar_stats.js index eaf32ca54a..dd30a373d1 100644 --- a/frappe/public/js/frappe/views/sidebar_stats.js +++ b/frappe/public/js/frappe/views/sidebar_stats.js @@ -3,7 +3,7 @@ frappe.provide('frappe.views'); -// opts: +// opts: // stats = list of fields // doctype // parent @@ -29,10 +29,10 @@ frappe.views.SidebarStats = Class.extend({ $.each(me.stats, function(i, v) { me.render_stat(v, (r.message || {})[v]); }); - + // reload button at the end if(me.stats.length) { - $(' '+__('Refresh')+'') + $(' '+__('Refresh')+'') .css({"margin-top":"15px", "display":"inline-block"}) .click(function() { me.reload_stats(); @@ -46,11 +46,14 @@ frappe.views.SidebarStats = Class.extend({ }, render_stat: function(field, stat) { var me = this; + var show_tags = '' + +__("Show tags") +''; if(!stat || !stat.length) { if(field==='_user_tags') { $('
\ -
'+__('Tags')+'
\ +
\ + '+__('Tags')+show_tags+'
\
\
'+__('No records tagged.')+'
' +'
\ @@ -59,9 +62,9 @@ frappe.views.SidebarStats = Class.extend({ return; } - var label = frappe.meta.docfield_map[this.doctype][field] ? + var label = frappe.meta.docfield_map[this.doctype][field] ? frappe.meta.docfield_map[this.doctype][field].label : field; - if(label==='_user_tags') label = 'Tags'; + if(label==='_user_tags') label = 'Tags' + show_tags; // grid var $w = $('
\ @@ -76,12 +79,12 @@ frappe.views.SidebarStats = Class.extend({ $.each(stat, function(i,v) { sum = sum + v[1]; }) // render items - $.each(stat, function(i, v) { + $.each(stat, function(i, v) { me.render_stat_item(i, v, sum, field).appendTo($w.find('.side-panel-body')); }); $w.appendTo(this.wrapper); - }, + }, render_stat_item: function(i, v, max, field) { var me = this; var args = {} @@ -91,8 +94,8 @@ frappe.views.SidebarStats = Class.extend({ args.count = v[1]; args.field = field; args.bar_style = ""; - - $item = $(repl('
\ + + $item = $(repl('
\
\
\
\ %(_label)s (%(count)s)\
', args)); - + this.setup_stat_item_click($item); return $item; }, @@ -116,5 +119,5 @@ frappe.views.SidebarStats = Class.extend({ me.set_filter(fieldname, label); return false; }); - }, -}); \ No newline at end of file + }, +}); diff --git a/frappe/public/js/legacy/clientscriptAPI.js b/frappe/public/js/legacy/clientscriptAPI.js index 0c741993d0..91226ac0fb 100644 --- a/frappe/public/js/legacy/clientscriptAPI.js +++ b/frappe/public/js/legacy/clientscriptAPI.js @@ -169,7 +169,7 @@ _f.Frm.prototype.call_server = function(method, args, callback) { _f.Frm.prototype.get_files = function() { return cur_frm.attachments - ? keys(cur_frm.attachments.get_attachments()).sort() + ? frappe.utils.sort(cur_frm.attachments.get_attachments(), "file_name", "string") : [] ; } @@ -263,8 +263,14 @@ _f.Frm.prototype.new_doc = function(doctype, field) { _f.Frm.prototype.set_read_only = function() { var perm = []; - $.each(frappe.perm.get_perm(cur_frm.doc.doctype, cur_frm.doc.name), function(i, permlevel) { - if(permlevel!=null) perm[permlevel] = {read:1}; + $.each(frappe.perm.get_perm(cur_frm.doc.doctype), function(i, p) { + perm[p.permlevel || 0] = {read:1}; }); cur_frm.perm = perm; } + +_f.Frm.prototype.get_formatted = function(fieldname) { + return frappe.format(this.doc[fieldname], + frappe.meta.get_docfield(this.doctype, fieldname, this.docname), + {no_icon:true}, this.doc); +} diff --git a/frappe/public/js/legacy/datatype.js b/frappe/public/js/legacy/datatype.js index 04aa68092a..ad3383a696 100644 --- a/frappe/public/js/legacy/datatype.js +++ b/frappe/public/js/legacy/datatype.js @@ -1,11 +1,13 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -// MIT License. See license.txt +// MIT License. See license.txt -frappe.utils.full_name = function(fn, ln) { - return fn + (ln ? ' ' : '') + (ln ? ln : '') +frappe.utils.full_name = function(fn, ln) { + return fn + (ln ? ' ' : '') + (ln ? ln : '') } function fmt_money(v, format){ + // deprecated! + // for backward compatibility return format_number(v, format); } @@ -14,19 +16,19 @@ function fmt_money(v, format){ function toTitle(str){ var word_in = str.split(" "); var word_out = []; - + for(w in word_in){ word_out[w] = word_in[w].charAt(0).toUpperCase() + word_in[w].slice(1); } - + return word_out.join(" "); } function is_null(v) { - if(v===null || v===undefined || v==="") return true; + if(v===null || v===undefined || cstr(v).trim()==="") return true; } -function set_value_in(ele, v, ftype, fopt, doc) { +function set_value_in(ele, v, ftype, fopt, doc) { $(ele).html(frappe.format(v, {fieldtype:ftype, options:fopt}, null, doc)); return; } @@ -42,12 +44,12 @@ function replace_newlines(t) { return t?t.replace(/\n/g, '
'):''; } -function validate_email(txt) { +function validate_email(txt) { return frappe.utils.validate_type(txt, "email"); } -function validate_spl_chars(txt) { +function validate_spl_chars(txt) { return frappe.utils.validate_type(txt, "alphanum") -} +} function cstr(s) { if(s==null)return ''; return s+''; @@ -61,15 +63,15 @@ function nth(number) { return number+s; } -function esc_quotes(s) { - if(s==null)s=''; +function esc_quotes(s) { + if(s==null)s=''; return s.replace(/'/, "\'"); } var crop = function(s, len) { if(s.length>len) return s.substr(0, len-3) + '...'; - else + else return s; } @@ -105,12 +107,12 @@ function replace_all(s, t1, t2) { return s.split(t1).join(t2); } -function keys(obj) { +function keys(obj) { var mykeys=[]; for (var key in obj) mykeys[mykeys.length]=key; return mykeys; } -function values(obj) { +function values(obj) { var myvalues=[]; for (var key in obj) myvalues[myvalues.length]=obj[key]; return myvalues; diff --git a/frappe/public/js/legacy/datetime.js b/frappe/public/js/legacy/datetime.js deleted file mode 100644 index ff56951989..0000000000 --- a/frappe/public/js/legacy/datetime.js +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -// MIT License. See license.txt - -// Date - -function same_day(d1, d2) { - if(d1.getFullYear()==d2.getFullYear() && d1.getMonth()==d2.getMonth() && d1.getDate()==d2.getDate())return true; else return false; -} -var month_list = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; -var month_last = {1:31,2:28,3:31,4:30,5:31,6:30,7:31,8:31,9:30,10:31,11:30,12:31} -var month_list_full = ['January','February','March','April','May','June','July','August','September','October','November','December']; - -var week_list = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; -var week_list_full = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; - -function int_to_str(i, len) { - i = ''+i; - if(i.length').find('input:last'); for(key in attributes) $input.attr(key, attributes[key]); - + var input = $input.get(0); if(cs) $y(input,cs); return input; } -function $dh(d) { - if(d && d.substr)d=$i(d); - if(d && d.style.display.toLowerCase() != 'none') d.style.display = 'none'; +function $dh(d) { + if(d && d.substr)d=$i(d); + if(d && d.style.display.toLowerCase() != 'none') d.style.display = 'none'; } -function $ds(d) { - if(d && d.substr)d=$i(d); +function $ds(d) { + if(d && d.substr)d=$i(d); var t = 'block'; - if(d && in_list(['span','img','button'], d.tagName.toLowerCase())) + if(d && in_list(['span','img','button'], d.tagName.toLowerCase())) t = 'inline' - if(d && d.style.display.toLowerCase() != t) - d.style.display = t; + if(d && d.style.display.toLowerCase() != t) + d.style.display = t; } function $di(d) { if(d && d.substr)d=$i(d); if(d)d.style.display = 'inline'; } -function $i(id) { - if(!id) return null; +function $i(id) { + if(!id) return null; if(id && id.appendChild)return id; // already an element - return document.getElementById(id); + return document.getElementById(id); } function $w(e,w) { if(e && e.style && w)e.style.width = w; } function $h(e,h) { if(e && e.style && h)e.style.height = h; } function $bg(e,w) { if(e && e.style && w)e.style.backgroundColor = w; } -function $y(ele, s) { - if(ele && s) { - for(var i in s) ele.style[i]=s[i]; - }; +function $y(ele, s) { + if(ele && s) { + for(var i in s) ele.style[i]=s[i]; + }; return ele; } -function $yt(tab, r, c, s) { /// set style on tables with wildcards - var rmin = r; var rmax = r; - if(r=='*') { rmin = 0; rmax = tab.rows.length-1; } - if(r.search && r.search('-')!= -1) { - r = r.split('-'); - rmin = cint(r[0]); rmax = cint(r[1]); - } - - var cmin = c; var cmax = c; - if(c=='*') { cmin = 0; cmax = tab.rows[0].cells.length-1; } - if(c.search && c.search('-')!= -1) { - c = c.split('-'); - rmin = cint(c[0]); rmax = cint(c[1]); - } - - for(var ri = rmin; ri<=rmax; ri++) { - for(var ci = cmin; ci<=cmax; ci++) - $y($td(tab,ri,ci),s); - } -} - // Make table function make_table(parent, nr, nc, table_width, widths, cell_style, table_style) { @@ -277,16 +142,16 @@ function append_row(t, at, style) { return r } -function $td(t,r,c) { +function $td(t,r,c) { if(r<0)r=t.rows.length+r; if(c<0)c=t.rows[0].cells.length+c; - return t.rows[r].cells[c]; + return t.rows[r].cells[c]; } // URL utilities frappe.urllib = { - + // get argument from url get_arg: function(name) { name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); @@ -296,9 +161,9 @@ frappe.urllib = { if( results == null ) return ""; else - return decodeURIComponent(results[1]); + return decodeURIComponent(results[1]); }, - + // returns url dictionary get_dict: function() { var d = {} @@ -313,15 +178,15 @@ frappe.urllib = { var a = t[i].split('='); d[decodeURIComponent(a[0])] = decodeURIComponent(a[1]); } - return d; + return d; }, - + // returns the base url with http + domain + path (-index.cgi or # or ?) get_base_url: function() { var url= window.location.href.split('#')[0].split('?')[0].split('desk')[0]; if(url.substr(url.length-1, 1)=='/') url = url.substr(0, url.length-1) return url - } + } } get_url_arg = frappe.urllib.get_arg; diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index d11da18448..25a93ef924 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -115,58 +115,46 @@ _f.Frm.prototype.setup = function() { parent: $(this.wrapper).find(".appframe-footer") }) + this.setup_drag_drop(); this.setup_done = true; } -_f.Frm.prototype.setup_print_layout = function() { - this.print_wrapper = $('
\ - \ - \ -
') - .appendTo(this.layout_main) - .toggle(false); - +_f.Frm.prototype.setup_drag_drop = function() { var me = this; - this.print_wrapper.find(".close").click(function() { - me.hide_print(); - }); + $(this.wrapper).on('dragenter dragover', false) + .on('drop', function (e) { + e.stopPropagation(); + e.preventDefault(); + if(me.doc.__islocal) { + msgprint(__("Please save before attaching.")); + return false; + throw "attach error"; + } + if(me.attachments.max_reached()) { + msgprint(__("Maximum Attachment Limit for this record reached.")); + throw "attach error"; + } - this.print_formats = frappe.meta.get_print_formats(this.meta.name); - this.print_letterhead = this.print_wrapper - .find(".print-letterhead") - .on("change", function() { me.print_sel.trigger("change"); }); - this.print_sel = this.print_wrapper - .find(".print-preview-select") - .on("change", function() { - _p.build(me.print_sel.val(), function(html) { - me.print_wrapper.find(".print-preview").html(html); - }, !me.print_letterhead.is(":checked"), true, true); - }) + var dataTransfer = e.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) { + frappe.upload.upload_file(dataTransfer.files[0], me.attachments.get_args(), { + callback: function(attachment, r) { + me.attachments.attachment_uploaded(attachment, r); + } + }); + } + }); +} - this.print_wrapper.find(".print-print").click(function() { - _p.build( - me.print_sel.val(), // fmtname - _p.go, // onload - !me.print_letterhead.is(":checked") // no_letterhead - ); +_f.Frm.prototype.setup_print_layout = function() { + this.print_preview = new frappe.ui.form.PrintPreview({ + frm: this }) } _f.Frm.prototype.print_doc = function() { - if(this.print_wrapper.is(":visible")) { + if(this.print_preview.wrapper.is(":visible")) { this.hide_print(); return; } @@ -179,17 +167,17 @@ _f.Frm.prototype.print_doc = function() { msgprint(__("Cannot print cancelled documents")); return; } - this.print_wrapper.toggle(true); - this.print_sel - .empty().add_options(this.print_formats) + this.print_preview.print_sel + .empty().add_options(this.print_preview.print_formats) .trigger("change"); + this.print_preview.wrapper.toggle(true); this.form_wrapper.toggle(false); } _f.Frm.prototype.hide_print = function() { if(this.setup_done) { - this.print_wrapper.toggle(false); + this.print_preview.wrapper.toggle(false); this.form_wrapper.toggle(true); } } @@ -206,7 +194,7 @@ _f.Frm.prototype.watch_model_updates = function() { me.fields_dict[fieldname] && me.fields_dict[fieldname].refresh(fieldname); - me.refresh_dependency(); + me.layout.refresh_dependency(); me.script_manager.trigger(fieldname, doc.doctype, doc.name); } }) @@ -335,8 +323,7 @@ _f.Frm.prototype.refresh_header = function() { _f.Frm.prototype.check_doc_perm = function() { // get perm var dt = this.parent_doctype?this.parent_doctype : this.doctype; - var dn = this.parent_docname?this.parent_docname : this.docname; - this.perm = frappe.perm.get_perm(dt, dn); + this.perm = frappe.perm.get_perm(dt, this.doc); if(!this.perm[0].read) { return 0; @@ -359,6 +346,9 @@ _f.Frm.prototype.refresh = function(docname) { if(this.docname) { // document to show + // set the doc + this.doc = frappe.get_doc(this.doctype, this.docname); + // check permissions if(!this.check_doc_perm()) { frappe.show_not_permitted(__(this.doctype) + " " + __(this.docname)); @@ -368,9 +358,6 @@ _f.Frm.prototype.refresh = function(docname) { // read only (workflow) this.read_only = frappe.workflow.is_read_only(this.doctype, this.docname); - // set the doc - this.doc = frappe.get_doc(this.doctype, this.docname); - // check if doctype is already open if (!this.opendocs[this.docname]) { this.check_doctype_conflict(this.docname); @@ -445,9 +432,6 @@ _f.Frm.prototype.refresh_fields = function() { // cleanup activities after refresh this.cleanup_refresh(this); - - // dependent fields - this.refresh_dependency(); } @@ -481,60 +465,6 @@ _f.Frm.prototype.cleanup_refresh = function() { } } -// Resolve "depends_on" and show / hide accordingly - -_f.Frm.prototype.refresh_dependency = function() { - var me = this; - var doc = locals[this.doctype][this.docname]; - - // build dependants' dictionary - var has_dep = false; - - for(fkey in me.fields) { - var f = me.fields[fkey]; - f.dependencies_clear = true; - if(f.df.depends_on) { - has_dep = true; - } - } - - if(!has_dep)return; - - // show / hide based on values - for(var i=me.fields.length-1;i>=0;i--) { - var f = me.fields[i]; - f.guardian_has_value = true; - if(f.df.depends_on) { - // evaluate guardian - var v = doc[f.df.depends_on]; - if(f.df.depends_on.substr(0,5)=='eval:') { - f.guardian_has_value = eval(f.df.depends_on.substr(5)); - } else if(f.df.depends_on.substr(0,3)=='fn:') { - f.guardian_has_value = me.script_manager.trigger(f.df.depends_on.substr(3), me.doctype, me.docname); - } else { - if(!v) { - f.guardian_has_value = false; - } - } - - // show / hide - if(f.guardian_has_value) { - if(f.df.hidden_due_to_dependency) { - f.df.hidden_due_to_dependency = false; - f.refresh(); - } - } else { - if(!f.df.hidden_due_to_dependency) { - f.df.hidden_due_to_dependency = true; - f.refresh(); - } - } - } - } - - this.layout.refresh_section_count(); -} - _f.Frm.prototype.setnewdoc = function() { // moved this call to refresh function // this.check_doctype_conflict(docname); @@ -564,6 +494,12 @@ _f.Frm.prototype.runscript = function(scriptname, callingfield, onrefresh) { // fields me.refresh_fields(); + // enable button + if(callingfield) + $(callingfield.input).done_working(); + }, + // error + function() { // enable button if(callingfield) $(callingfield.input).done_working(); @@ -599,6 +535,7 @@ _f.Frm.prototype.reload_doc = function() { var validated; _f.Frm.prototype.save = function(save_action, callback, btn, on_error) { + btn && $(btn).prop("disabled", true); $(document.activeElement).blur(); // let any pending js process finish @@ -614,13 +551,16 @@ _f.Frm.prototype._save = function(save_action, callback, btn, on_error) { if((!this.meta.in_dialog || this.in_form) && !this.meta.istable) scroll(0, 0); - // validate - validated = true; - this.script_manager.trigger("validate"); - if(!validated) { - if(on_error) - on_error(); - return; + if(save_action != "Update") { + // validate + validated = true; + this.script_manager.trigger("validate"); + + if(!validated) { + if(on_error) + on_error(); + return; + } } var after_save = function(r) { @@ -646,7 +586,7 @@ _f.Frm.prototype._save = function(save_action, callback, btn, on_error) { } -_f.Frm.prototype.savesubmit = function(btn, on_error) { +_f.Frm.prototype.savesubmit = function(btn, callback, on_error) { var me = this; this.validate_form_action("Submit"); frappe.confirm(__("Permanently Submit {0}?", [this.docname]), function() { @@ -660,13 +600,14 @@ _f.Frm.prototype.savesubmit = function(btn, on_error) { me.save('Submit', function(r) { if(!r.exc) { + callback && callback(); me.script_manager.trigger("on_submit"); } }, btn, on_error); }); }; -_f.Frm.prototype.savecancel = function(btn, on_error) { +_f.Frm.prototype.savecancel = function(btn, callback, on_error) { var me = this; this.validate_form_action('Cancel'); frappe.confirm(__("Permanently Cancel {0}?", [this.docname]), function() { @@ -681,6 +622,7 @@ _f.Frm.prototype.savecancel = function(btn, on_error) { var after_cancel = function(r) { if(!r.exc) { me.refresh(); + callback && callback(); me.script_manager.trigger("after_cancel"); } else { on_error(); @@ -762,9 +704,10 @@ _f.Frm.prototype.set_footnote = function(txt) { } -_f.Frm.prototype.add_custom_button = function(label, fn, icon) { - return this.appframe.add_primary_action(label, fn, icon || "icon-arrow-right"); +_f.Frm.prototype.add_custom_button = function(label, fn, icon, toolbar_or_class) { + return this.appframe.add_primary_action(label, fn, icon || "icon-arrow-play", toolbar_or_class); } + _f.Frm.prototype.clear_custom_buttons = function() { this.appframe.clear_primary_action() } diff --git a/frappe/public/js/legacy/printElement.js b/frappe/public/js/legacy/printElement.js deleted file mode 100644 index f38b7547a2..0000000000 --- a/frappe/public/js/legacy/printElement.js +++ /dev/null @@ -1,133 +0,0 @@ -/// -/* -* Print Element Plugin 1.2 -* -* Copyright (c) 2010 Erik Zaadi -* -* Inspired by PrintArea (http://plugins.jquery.com/project/PrintArea) and -* http://stackoverflow.com/questions/472951/how-do-i-print-an-iframe-from-javascript-in-safari-chrome -* -* Home Page : http://projects.erikzaadi/jQueryPlugins/jQuery.printElement -* Issues (bug reporting) : http://github.com/erikzaadi/jQueryPlugins/issues/labels/printElement -* jQuery plugin page : http://plugins.jquery.com/project/printElement -* -* Thanks to David B (http://github.com/ungenio) and icgJohn (http://www.blogger.com/profile/11881116857076484100) -* For their great contributions! -* -* Dual licensed under the MIT and GPL licenses: -* http://www.opensource.org/licenses/mit-license.php -* http://www.gnu.org/licenses/gpl.html -* -* Note, Iframe Printing is not supported in Opera and Chrome 3.0, a popup window will be shown instead -*/ -; (function (window, undefined) { - var document = window["document"]; - var $ = window["jQuery"]; - $.fn["printElement"] = function (options) { - var mainOptions = $.extend({}, $.fn["printElement"]["defaults"], options); - //Remove previously printed iframe if exists - $("[id^='printElement_']").remove(); - - return this.each(function () { - //Support Metadata Plug-in if available - var opts = $.meta ? $.extend({}, mainOptions, $(this).data()) : mainOptions; - _printElement($(this), opts); - }); - }; - $.fn["printElement"]["defaults"] = { - "printMode": 'iframe', //Usage : iframe / popup - "pageTitle": '', //Print Page Title - "overrideElementCSS": null, - /* Can be one of the following 3 options: -* 1 : boolean (pass true for stripping all css linked) -* 2 : array of $.fn.printElement.cssElement (s) -* 3 : array of strings with paths to alternate css files (optimized for print) -*/ - "printBodyOptions": { - "styleToAdd": 'padding:10px;margin:10px;', //style attributes to add to the body of print document - "classNameToAdd": '' //css class to add to the body of print document - }, - "leaveOpen": false, // in case of popup, leave the print page open or not - "iframeElementOptions": { - "styleToAdd": 'border:none;position:absolute;width:0px;height:0px;bottom:0px;left:0px;', //style attributes to add to the iframe element - "classNameToAdd": '' //css class to add to the iframe element - } - }; - $.fn["printElement"]["cssElement"] = { - "href": '', - "media": '' - }; - function _printElement(element, opts) { - //Create markup to be printed - var html = _getMarkup(element, opts); - - var popupOrIframe = null; - var documentToWriteTo = null; - if (opts["printMode"].toLowerCase() == 'popup') { - popupOrIframe = window.open('about:blank', 'printElementWindow', 'width=650,height=440,scrollbars=yes'); - documentToWriteTo = popupOrIframe.document; - } - else { - //The random ID is to overcome a safari bug http://www.cjboco.com.sharedcopy.com/post.cfm/442dc92cd1c0ca10a5c35210b8166882.html - var printElementID = "printElement_" + (Math.round(Math.random() * 99999)).toString(); - //Native creation of the element is faster.. - var iframe = document.createElement('IFRAME'); - $(iframe).attr({ - style: opts["iframeElementOptions"]["styleToAdd"], - id: printElementID, - className: opts["iframeElementOptions"]["classNameToAdd"], - frameBorder: 0, - scrolling: 'no', - src: 'about:blank' - }); - document.body.appendChild(iframe); - documentToWriteTo = (iframe.contentWindow || iframe.contentDocument); - if (documentToWriteTo.document) - documentToWriteTo = documentToWriteTo.document; - iframe = document.frames ? document.frames[printElementID] : document.getElementById(printElementID); - popupOrIframe = iframe.contentWindow || iframe; - } - focus(); - documentToWriteTo.open(); - documentToWriteTo.write(html); - documentToWriteTo.close(); - _callPrint(popupOrIframe); - }; - - function _callPrint(element) { - if (element && element["printPage"]) - element["printPage"](); - else - setTimeout(function () { - _callPrint(element); - }, 50); - } - - function _getElementHTMLIncludingFormElements(element) { - var $element = $(element); - var elementHtml = $('
').append($element.clone()).html(); - return elementHtml; - } - - function _getBaseHref() { - var port = (window.location.port) ? ':' + window.location.port : ''; - return window.location.protocol + '//' + window.location.hostname + port + window.location.pathname; - } - - function _getMarkup(element, opts) { - var $element = $(element); - var elementHtml = _getElementHTMLIncludingFormElements(element); - - var html = new Array(); - html.push('' + opts["pageTitle"] + ''); - //Ensure that relative links work - html.push(''); - html.push(''); - html.push('
' + elementHtml + '
'); - html.push(''); - html.push(''); - - return html.join(''); - }; -})(window); \ No newline at end of file diff --git a/frappe/public/js/legacy/print_format.js b/frappe/public/js/legacy/print_format.js index deb074aacc..59066536b6 100644 --- a/frappe/public/js/legacy/print_format.js +++ b/frappe/public/js/legacy/print_format.js @@ -7,7 +7,7 @@ _p.def_print_style_body = "html, body, div, span, td, p { \ font-size: inherit; \ }\ .page-settings {\ - font-family: Arial, Helvetica Neue, Sans;\ + font-family: Helvetica, 'Open Sans', sans-serif;\ font-size: 9pt;\ }\ pre { margin:0; padding:0;}"; @@ -38,6 +38,7 @@ _p.preview = function(html) { return; } w.document.write(html); + w.document.close(); return w } @@ -85,7 +86,7 @@ $.extend(_p, { dialog.onshow = function() { var $print = dialog.fields_dict.print_format.$input; - $print.empty().add_options(cur_frm.print_formats); + $print.empty().add_options(cur_frm.print_preview.print_formats); if(cur_frm.$print_view_select && cur_frm.$print_view_select.val()) $print.val(cur_frm.$print_view_select.val()); @@ -173,6 +174,7 @@ $.extend(_p, { _p.show_letterhead(container, args); _p.run_embedded_js(container, args.doc); + var style = _p.consolidate_css(container, args); _p.render_header_on_break(container, args); @@ -285,23 +287,34 @@ $.extend(_p, { // This is used to calculate and substitude values in the HTML run_embedded_js: function(container, doc) { - script_list = $(container).find("script"); - for(var i=0; i]>", "]>", "]>", "]>", "]>"]; + var tags = ["") @@ -102,12 +94,12 @@ frappe.print.Table = Class.extend({ .css(me.head_cell_style) .css({"width": me.widths[ci]}) .appendTo(headrow) - + if(df && in_list(['Float', 'Currency'], df.fieldtype)) { td.css({"text-align": "right"}); } }); - + $.each(data, function(ri, row) { var allow = true; if(me.condition) { @@ -115,13 +107,13 @@ frappe.print.Table = Class.extend({ } if(allow) { var tr = $("
").appendTo(table); - + $.each(me.columns, function(ci, fieldname) { if(fieldname.toLowerCase()==="sr") var value = row.idx; else var value = row[fieldname]; - + var df = frappe.meta.docfield_map[me.tabletype][fieldname]; value = frappe.format(value, df, {for_print:true}); @@ -142,7 +134,7 @@ frappe.print.Table = Class.extend({ }); this.tables.push(wrapper) }, - + set_widths: function() { var me = this; // if widths not passed (like in standard), @@ -152,7 +144,7 @@ frappe.print.Table = Class.extend({ df = frappe.meta.docfield_map[me.tabletype][fieldname]; return df && df.print_width || (fieldname=="Sr" ? 30 : 80); }); - + var sum = 0; $.each(this.widths, function(i, w) { sum += cint(w); @@ -164,7 +156,7 @@ frappe.print.Table = Class.extend({ }); } }, - + get_tables: function() { if(this.tables.length > 1) { return $.map(this.tables, function(t) { @@ -174,14 +166,14 @@ frappe.print.Table = Class.extend({ return this.tables[0].get(0); } }, - + cell_style: { border: '1px solid #999', padding: '3px', 'vertical-align': 'top', 'word-wrap': 'break-word', }, - + head_cell_style: { border: '1px solid #999', padding: '3px', @@ -190,7 +182,7 @@ frappe.print.Table = Class.extend({ 'font-weight': 'bold', 'word-wrap': 'break-word', }, - + table_style: { width: '100%', 'border-collapse': 'collapse', @@ -201,7 +193,7 @@ frappe.print.Table = Class.extend({ }) function print_table(dt, dn, fieldname, tabletype, cols, head_labels, widths, condition, cssClass, modifier) { - return new frappe.print.Table({ + return new frappe.printTable({ doctype: dt, docname: dn, fieldname: fieldname, diff --git a/frappe/public/js/lib/bootstrap.min.js b/frappe/public/js/lib/bootstrap.min.js index 1d4a4ed370..6c8416253c 100755 --- a/frappe/public/js/lib/bootstrap.min.js +++ b/frappe/public/js/lib/bootstrap.min.js @@ -1,6 +1,6 @@ /*! - * Bootstrap v3.1.0 (http://getbootstrap.com) + * Bootstrap v3.1.1 (http://getbootstrap.com) * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); \ No newline at end of file +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.1.1",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.1.1",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};c.VERSION="3.1.1",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(".item"),this.$items.index(this.$active)},c.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")}));var l=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(l)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(l)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),(h=e.attr("data-slide-to"))&&f.data("bs.carousel").to(h),c.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.1.1",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(b){return b&&b.target!=this.$element[0]?void this.$element.one(a.support.transition.end,a.proxy(g,this)):(this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,void this.$element.off(a.support.transition.end+".bs.collapse").trigger("shown.bs.collapse"))};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.on(a.support.transition.end+".bs.collapse",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(b){return b&&b.target!=this.$element[0]?void this.$element.one(a.support.transition.end,a.proxy(d,this)):(this.transitioning=0,void this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse"))};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.1.1",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('
', '

', __('Quick Help for Setting Permissions'), ':

', '
    ', '
  1. ', - __('Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email, Only Restricted Documents and Can Restrict Others.'), + __('Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.'), '
  2. ', '
  3. ', __('Permissions get applied on Users based on what Roles they are assigned.'), @@ -442,7 +565,7 @@ var permissions_help = ['', '
  4. ', __('You can use Customize Form to set levels on fields.') @@ -452,27 +575,21 @@ var permissions_help = ['
  5. ', - __('Restricting Users'), + __('User Permissions'), ':', '
      ', '
    1. ', - __("To give acess to a role for only specific records, check the 'Restricted' perimssion. User Restriction Records are used to restrict users with such role to specific records.") - + ' (' + __('Setup > User Restriction') + ')', - '
    2. ', - '
    3. ', - __("If 'Restricted' is not checked, you can still restrict permissions based on certain values, like Company or Territory in a document by setting User Restrictions. But unless any restriction is set, a user will have permissions based on the Role."), - '
    4. ', - '
    5. ', - __("Permissions at higher levels are 'Field Level' permissions. All Fields have a 'Permission Level' set against them and the rules defined at that permissions apply to the field. This is useful in case you want to hide or make certain field read-only."), + __("To give acess to a role for only specific records, check the 'Apply User Permissions'. User Permissions are used to limit users with such role to specific records.") + + ' (' + __('Setup > User Permissions Manager') + ')', '
    6. ', '
    7. ', - __("If 'Restricted' is checked, the owner is always allowed based on Role."), + __("Select Document Types to set which User Permissions are used to limit access."), '
    8. ', '
    9. ', - __("Once you have set this, the users will only be able access documents where the link (e.g Company) exists."), + __("Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger)."), '
    10. ', '
    11. ', - __("Apart from System Manager, roles with 'Can Restrict Others' permission can restrict other users for that Document Type."), + __("Apart from System Manager, roles with 'Set User Permissions' right can set permissions for other users for that Document Type."), '
    12. ', '
    ', '', diff --git a/frappe/core/page/permission_manager/permission_manager.json b/frappe/core/page/permission_manager/permission_manager.json index db0a3129e3..9eb5b3a8d6 100644 --- a/frappe/core/page/permission_manager/permission_manager.json +++ b/frappe/core/page/permission_manager/permission_manager.json @@ -16,5 +16,5 @@ } ], "standard": "Yes", - "title": "Permission Manager" + "title": "Role Permissions Manager" } \ No newline at end of file diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 5e2bd5dd1e..43c8531791 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -5,10 +5,13 @@ from __future__ import unicode_literals import frappe import frappe.defaults from frappe.modules.import_file import get_file_path, read_doc_from_file +from frappe.translate import send_translations +from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for @frappe.whitelist() def get_roles_and_doctypes(): frappe.only_for("System Manager") + send_translations(frappe.get_lang_dict("doctype", "DocPerm")) return { "doctypes": [d[0] for d in frappe.db.sql("""select name from `tabDocType` dt where ifnull(istable,0)=0 and @@ -21,12 +24,27 @@ def get_roles_and_doctypes(): @frappe.whitelist() def get_permissions(doctype=None, role=None): frappe.only_for("System Manager") - return frappe.db.sql("""select * from tabDocPerm + out = frappe.db.sql("""select * from tabDocPerm where %s%s order by parent, permlevel, role""" % (doctype and (" parent='%s'" % doctype.replace("'", "\'")) or "", role and ((doctype and " and " or "") + " role='%s'" % role.replace("'", "\'")) or ""), as_dict=True) + def get_linked_doctypes(dt): + return list(set([dt] + [d.options for d in + frappe.get_meta(dt).get("fields", { + "fieldtype":"Link", + "ignore_user_permissions":("!=", 1), + "options": ("!=", "[Select]") + }) + ])) + + linked_doctypes = {} + for d in out: + d.linked_doctypes = linked_doctypes.setdefault(d.parent, get_linked_doctypes(d.parent)) + + return out + @frappe.whitelist() def remove(doctype, name): frappe.only_for("System Manager") @@ -50,7 +68,7 @@ def add(parent, role, permlevel): validate_and_reset(parent) @frappe.whitelist() -def update(name, doctype, ptype, value=0): +def update(name, doctype, ptype, value=None): frappe.only_for("System Manager") frappe.db.sql("""update tabDocPerm set `%s`=%s where name=%s"""\ % (ptype, '%s', '%s'), (value, name)) @@ -69,6 +87,7 @@ def reset(doctype): def clear_doctype_cache(doctype): frappe.clear_cache(doctype=doctype) + delete_notification_count_for(doctype) for user in frappe.db.sql_list("""select distinct tabUserRole.parent from tabUserRole, tabDocPerm where tabDocPerm.parent = %s and tabDocPerm.role = tabUserRole.role""", doctype): @@ -86,6 +105,7 @@ def get_users_with_role(role): @frappe.whitelist() def get_standard_permissions(doctype): + frappe.only_for("System Manager") module = frappe.db.get_value("DocType", doctype, "module") path = get_file_path(module, "DocType", doctype) return read_doc_from_file(path).get("permissions") diff --git a/frappe/core/page/user_properties/README.md b/frappe/core/page/user_permissions/README.md similarity index 100% rename from frappe/core/page/user_properties/README.md rename to frappe/core/page/user_permissions/README.md diff --git a/frappe/core/page/user_properties/__init__.py b/frappe/core/page/user_permissions/__init__.py similarity index 100% rename from frappe/core/page/user_properties/__init__.py rename to frappe/core/page/user_permissions/__init__.py diff --git a/frappe/core/page/user_permissions/user_permissions.js b/frappe/core/page/user_permissions/user_permissions.js new file mode 100644 index 0000000000..e9c18ed4c4 --- /dev/null +++ b/frappe/core/page/user_permissions/user_permissions.js @@ -0,0 +1,345 @@ +frappe.pages['user-permissions'].onload = function(wrapper) { + frappe.ui.make_app_page({ + parent: wrapper, + title: "User Permissions Manager", + icon: "icon-shield", + single_column: true + }); + + $(wrapper).find(".layout-main").html("\ +

    \ + Edit Role Permissions\ +

    \ +
    \ + \ +
    \ +

    "+__("Quick Help for User Permissions")+":

    \ +
      \ +
    1. " + + __("Apart from Role based Permission Rules, you can apply User Permissions based on DocTypes.") + + "
    2. " + + + "
    3. " + + __("These permissions will apply for all transactions where the permitted record is linked. For example, if Company C is added to User Permissions of user X, user X will only be able to see transactions that has company C as a linked value.") + + "
    4. " + + + "
    5. " + + __("These will also be set as default values for those links, if only one such permission record is defined.") + + "
    6. " + + + "
    7. " + + __("A user can be permitted to multiple records of the same DocType.") + + "
    8. \ +
    \ +
    "); + wrapper.user_permissions = new frappe.UserPermissions(wrapper); +} + +frappe.pages['user-permissions'].refresh = function(wrapper) { + wrapper.user_permissions.set_from_route(); +} + +frappe.UserPermissions = Class.extend({ + init: function(wrapper) { + this.wrapper = wrapper; + this.body = $(this.wrapper).find(".user-settings"); + this.filters = {}; + this.make(); + this.refresh(); + }, + make: function() { + var me = this; + + $(this.wrapper).find(".view-role-permissions").on("click", function() { + frappe.route_options = { doctype: me.get_doctype() || "" }; + frappe.set_route("permission-manager"); + }) + + return frappe.call({ + module:"frappe.core", + page:"user_permissions", + method: "get_users_and_links", + callback: function(r) { + me.options = r.message; + + me.filters.user = me.wrapper.appframe.add_field({ + fieldname: "user", + label: __("User"), + fieldtype: "Select", + options: (["Select User..."].concat(r.message.users)).join("\n") + }); + + me.filters.doctype = me.wrapper.appframe.add_field({ + fieldname: "doctype", + label: __("DocType"), + fieldtype: "Select", + options: (["Select DocType..."].concat(me.get_link_names())).join("\n") + }); + + me.filters.user_permission = me.wrapper.appframe.add_field({ + fieldname: "user_permission", + label: __("Name"), + fieldtype: "Link", + options: "[Select]" + }); + + if(user_roles.indexOf("System Manager")!==-1) { + me.download = me.wrapper.appframe.add_field({ + fieldname: "download", + label: __("Download"), + fieldtype: "Button", + icon: "icon-download" + }); + + me.upload = me.wrapper.appframe.add_field({ + fieldname: "upload", + label: __("Upload"), + fieldtype: "Button", + icon: "icon-upload" + }); + } + + // bind change event + $.each(me.filters, function(k, f) { + f.$input.on("change", function() { + me.refresh(); + }); + }); + + // change options in user_permission link + me.filters.doctype.$input.on("change", function() { + me.filters.user_permission.df.options = me.get_doctype(); + }); + + me.set_from_route(); + me.setup_download_upload(); + } + }); + }, + setup_download_upload: function() { + var me = this; + me.download.$input.on("click", function() { + window.location.href = frappe.urllib.get_base_url() + + "/api/method/frappe.core.page.user_permissions.user_permissions.get_user_permissions_csv"; + }); + + me.upload.$input.on("click", function() { + var d = new frappe.ui.Dialog({ + title: "Upload User Permissions", + fields: [ + { + fieldtype:"HTML", + options: '
      '+ + "
    1. "+__("Upload CSV file containing all user permissions in the same format as Download.")+"
    2. "+ + "
    3. "+__("Any existing permission will be deleted / overwritten.")+"
    4. "+ + '
    ' + }, + { + fieldtype:"Attach", fieldname:"attach", + } + ], + primary_action_label: __("Upload and Sync"), + primary_action: function() { + frappe.call({ + method:"frappe.core.page.user_permissions.user_permissions.import_user_permissions", + args: { + filedata: d.fields_dict.attach.get_value() + }, + callback: function(r) { + if(!r.exc) { + msgprint("Permissions Updated"); + d.hide(); + } + } + }); + } + }); + d.show(); + }) + }, + get_link_names: function() { + return this.options.link_fields; + }, + set_from_route: function() { + var me = this; + if(frappe.route_options && this.filters && !$.isEmptyObject(this.filters)) { + $.each(frappe.route_options, function(key, value) { + if(me.filters[key] && frappe.route_options[key]!=null) + me.set_filter(key, value); + }); + frappe.route_options = null; + } + this.refresh(); + }, + set_filter: function(key, value) { + this.filters[key].$input.val(value); + }, + get_user: function() { + var user = this.filters.user.$input.val(); + return user=="Select User..." ? null : user; + }, + get_doctype: function() { + var doctype = this.filters.doctype.$input.val(); + return doctype=="Select DocType..." ? null : doctype; + }, + get_user_permission: function() { + // autosuggest hack! + var user_permission = this.filters.user_permission.$input.val(); + return (user_permission === "%") ? null : user_permission; + }, + render: function(prop_list) { + this.body.empty(); + this.prop_list = prop_list; + if(!prop_list || !prop_list.length) { + this.add_message(__("No User Permissions found.")); + } else { + this.show_user_permissions_table(); + } + this.show_add_user_permission(); + }, + add_message: function(txt) { + $('
    ' + txt + '
    ').appendTo(this.body); + }, + refresh: function() { + var me = this; + if(!me.filters.user) { + this.body.html("
    "+__("Loading")+"...
    "); + return; + } + if(!me.get_user() && !me.get_doctype()) { + this.body.html("
    "+__("Select User or DocType to start.")+"
    "); + return; + } + // get permissions + return frappe.call({ + module: "frappe.core", + page: "user_permissions", + method: "get_permissions", + args: { + parent: me.get_user(), + defkey: me.get_doctype(), + defvalue: me.get_user_permission() + }, + callback: function(r) { + me.render(r.message); + } + }); + }, + show_user_permissions_table: function() { + var me = this; + this.table = $("\ + \ + \ +
    ").appendTo(this.body); + + $.each([[__("User"), 150], [__("DocType"), 150], [__("User Permission"),150], ["", 50]], + function(i, col) { + $("
").html(col[0]).css("width", col[1]+"px") + .appendTo(me.table.find("thead tr")); + }); + + + $.each(this.prop_list, function(i, d) { + var row = $("
").html('' + +d.parent+'').appendTo(row); + $("").html(d.defkey).appendTo(row); + $("").html(d.defvalue).appendTo(row); + + me.add_delete_button(row, d); + }); + + }, + add_delete_button: function(row, d) { + var me = this; + $("") + .appendTo($("").appendTo(row)) + .attr("data-name", d.name) + .attr("data-user", d.parent) + .attr("data-defkey", d.defkey) + .attr("data-defvalue", d.defvalue) + .click(function() { + return frappe.call({ + module: "frappe.core", + page: "user_permissions", + method: "remove", + args: { + name: $(this).attr("data-name"), + user: $(this).attr("data-user"), + defkey: $(this).attr("data-defkey"), + defvalue: $(this).attr("data-defvalue") + }, + callback: function(r) { + if(r.exc) { + msgprint(__("Did not remove")); + } else { + me.refresh(); + } + } + }) + }); + }, + + show_add_user_permission: function() { + var me = this; + $("") + .appendTo($("

").appendTo(this.body)) + .click(function() { + var d = new frappe.ui.Dialog({ + title: "Add New User Permission", + fields: [ + {fieldtype:"Select", label:__("User"), + options:me.options.users, reqd:1, fieldname:"user"}, + {fieldtype:"Select", label: __("DocType"), fieldname:"defkey", + options:me.get_link_names(), reqd:1}, + {fieldtype:"Link", label:__("Value"), fieldname:"defvalue", + options:'[Select]', reqd:1}, + {fieldtype:"Button", label: __("Add"), fieldname:"add"}, + ] + }); + if(me.get_user()) { + d.set_value("user", me.get_user()); + d.get_input("user").prop("disabled", true); + } + if(me.get_doctype()) { + d.set_value("defkey", me.get_doctype()); + d.get_input("defkey").prop("disabled", true); + } + if(me.get_user_permission()) { + d.set_value("defvalue", me.get_user_permission()); + d.get_input("defvalue").prop("disabled", true); + } + + d.fields_dict["defvalue"].get_query = function(txt) { + return { + doctype: d.get_value("defkey") + } + }; + + d.get_input("add").click(function() { + var args = d.get_values(); + if(!args) { + return; + } + frappe.call({ + module: "frappe.core", + page: "user_permissions", + method: "add", + args: args, + callback: function(r) { + if(r.exc) { + msgprint("Did not add"); + } else { + me.refresh(); + } + } + }) + d.hide(); + }); + d.show(); + }); + } +}) diff --git a/frappe/core/page/user_permissions/user_permissions.json b/frappe/core/page/user_permissions/user_permissions.json new file mode 100644 index 0000000000..f4e5d95ae5 --- /dev/null +++ b/frappe/core/page/user_permissions/user_permissions.json @@ -0,0 +1,19 @@ +{ + "content": null, + "creation": "2013-01-01 18:50:55", + "docstatus": 0, + "doctype": "Page", + "icon": "icon-user", + "idx": 1, + "modified": "2014-05-28 16:53:43.103533", + "modified_by": "Administrator", + "module": "Core", + "name": "user-permissions", + "owner": "Administrator", + "page_name": "user-permissions", + "roles": [], + "script": null, + "standard": "Yes", + "style": null, + "title": "User Permissions Manager" +} \ No newline at end of file diff --git a/frappe/core/page/user_permissions/user_permissions.py b/frappe/core/page/user_permissions/user_permissions.py new file mode 100644 index 0000000000..ef5a86c2b7 --- /dev/null +++ b/frappe/core/page/user_permissions/user_permissions.py @@ -0,0 +1,108 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +import frappe.defaults +import frappe.permissions +from frappe.core.doctype.user.user import get_system_users +from frappe.utils.csvutils import UnicodeWriter, read_csv_content_from_uploaded_file +from frappe.defaults import clear_default + +@frappe.whitelist() +def get_users_and_links(): + return { + "users": get_system_users(), + "link_fields": get_doctypes_for_user_permissions() + } + +@frappe.whitelist() +def get_permissions(parent=None, defkey=None, defvalue=None): + if defkey and not frappe.permissions.can_set_user_permissions(defkey, defvalue): + raise frappe.PermissionError + + conditions, values = _build_conditions(locals()) + + permissions = frappe.db.sql("""select name, parent, defkey, defvalue + from tabDefaultValue + where parent not in ('__default', '__global') + and substr(defkey,1,1)!='_' + and parenttype='User Permission' + {conditions} + order by parent, defkey""".format(conditions=conditions), values, as_dict=True) + + if not defkey: + out = [] + doctypes = get_doctypes_for_user_permissions() + for p in permissions: + if p.defkey in doctypes: + out.append(p) + permissions = out + + return permissions + +def _build_conditions(filters): + conditions = [] + values = {} + for key, value in filters.items(): + if filters.get(key): + conditions.append("and `{key}`=%({key})s".format(key=key)) + values[key] = value + + return "\n".join(conditions), values + +@frappe.whitelist() +def remove(user, name, defkey, defvalue): + if not frappe.permissions.can_set_user_permissions(defkey, defvalue): + frappe.throw(_("Cannot remove permission for DocType: {0} and Name: {1}").format( + defkey, defvalue), frappe.PermissionError) + + frappe.permissions.remove_user_permission(defkey, defvalue, user, name) + +@frappe.whitelist() +def add(user, defkey, defvalue): + if not frappe.permissions.can_set_user_permissions(defkey, defvalue): + frappe.throw(_("Cannot set permission for DocType: {0} and Name: {1}").format( + defkey, defvalue), frappe.PermissionError) + + frappe.permissions.add_user_permission(defkey, defvalue, user, with_message=True) + +def get_doctypes_for_user_permissions(): + user_roles = frappe.get_roles() + condition = "" + values = [] + if "System Manager" not in user_roles: + condition = """and exists(select `tabDocPerm`.name from `tabDocPerm` + where `tabDocPerm`.parent=`tabDocType`.name and `tabDocPerm`.`set_user_permissions`=1 + and `tabDocPerm`.role in ({roles}))""".format(roles=", ".join(["%s"]*len(user_roles))) + values = user_roles + + return frappe.db.sql_list("""select name from tabDocType + where ifnull(issingle,0)=0 and ifnull(istable,0)=0 {condition}""".format(condition=condition), + tuple(values)) + +@frappe.whitelist() +def get_user_permissions_csv(): + out = [["User Permissions"], ["User", "Document Type", "Value"]] + out += [[a.parent, a.defkey, a.defvalue] for a in get_permissions()] + + csv = UnicodeWriter() + for row in out: + csv.writerow(row) + + frappe.response['result'] = str(csv.getvalue()) + frappe.response['type'] = 'csv' + frappe.response['doctype'] = "User Permissions" + +@frappe.whitelist() +def import_user_permissions(): + frappe.only_for("System Manager") + rows = read_csv_content_from_uploaded_file(ignore_encoding=True) + clear_default(parenttype="User Permission") + + if rows[0][0]!="User Permissions" and rows[1][0] != "User": + frappe.throw(frappe._("Please upload using the same template as download.")) + + for row in rows[2:]: + frappe.permissions.add_user_permission(row[1], row[2], row[0]) diff --git a/frappe/core/page/user_properties/user_properties.js b/frappe/core/page/user_properties/user_properties.js deleted file mode 100644 index 88f9e5088e..0000000000 --- a/frappe/core/page/user_properties/user_properties.js +++ /dev/null @@ -1,261 +0,0 @@ -frappe.pages['user-properties'].onload = function(wrapper) { - frappe.ui.make_app_page({ - parent: wrapper, - title: 'User Permission Restrictions', - single_column: true - }); - $(wrapper).find(".layout-main").html("

\ - \ - \ -
\ -

"+__("Quick Help for Permission Restrictions")+":

\ -
    \ -
  1. "+__("Apart from the existing Permission Rules, you can apply addition restriction based on Type.")+"
  2. \ -
  3. "+__("These restrictions will apply for all transactions linked to the restricted record.") - +__("For example, if user X is restricted to company C, user X will not be able to see any transaction that has company C as a linked value.")+"
  4. \ -
  5. "+__("These will also be set as default values for those links.")+"
  6. \ -
  7. "+__("A user can be restricted to multiple records of the same type.")+"
  8. \ -
\ -
"); - wrapper.user_properties = new frappe.UserProperties(wrapper); -} - -frappe.pages['user-properties'].refresh = function(wrapper) { - wrapper.user_properties.set_from_route(); -} - -frappe.UserProperties = Class.extend({ - init: function(wrapper) { - this.wrapper = wrapper; - this.body = $(this.wrapper).find(".user-settings"); - this.filters = {}; - this.make(); - this.refresh(); - }, - make: function() { - var me = this; - return frappe.call({ - module:"frappe.core", - page:"user_properties", - method: "get_users_and_links", - callback: function(r) { - me.options = r.message; - - me.filters.user = me.wrapper.appframe.add_field({ - fieldname: "user", - label: __("User"), - fieldtype: "Select", - options: (["Select User..."].concat(r.message.users)).join("\n") - }); - - me.filters.property = me.wrapper.appframe.add_field({ - fieldname: "property", - label: __("Property"), - fieldtype: "Select", - options: (["Select Property..."].concat(me.get_link_names())).join("\n") - }); - - me.filters.restriction = me.wrapper.appframe.add_field({ - fieldname: "restriction", - label: __("Restriction"), - fieldtype: "Link", - options: "[Select]" - }); - - // bind change event - $.each(me.filters, function(k, f) { - f.$input.on("change", function() { - me.refresh(); - }); - }); - - // change options in restriction link - me.filters.property.$input.on("change", function() { - me.filters.restriction.df.options = $(this).val(); - }); - - me.set_from_route(); - } - }); - }, - get_link_names: function() { - return this.options.link_fields; - }, - set_from_route: function() { - var me = this; - if(frappe.route_options && this.filters && !$.isEmptyObject(this.filters)) { - $.each(frappe.route_options, function(key, value) { - if(me.filters[key] && frappe.route_options[key]!=null) - me.set_filter(key, value); - }); - frappe.route_options = null; - } - this.refresh(); - }, - set_filter: function(key, value) { - this.filters[key].$input.val(value); - }, - get_user: function() { - var user = this.filters.user.$input.val(); - return user=="Select User..." ? null : user; - }, - get_property: function() { - var property = this.filters.property.$input.val(); - return property=="Select Property..." ? null : property; - }, - get_restriction: function() { - // autosuggest hack! - var restriction = this.filters.restriction.$input.val(); - return (restriction === "%") ? null : restriction; - }, - render: function(prop_list) { - this.body.empty(); - this.prop_list = prop_list; - if(!prop_list || !prop_list.length) { - this.body.html("
"+__("No User Restrictions found.")+"
"); - } else { - this.show_property_table(); - } - this.show_add_property(); - }, - refresh: function() { - var me = this; - if(!me.filters.user) { - this.body.html("
"+__("Loading")+"...
"); - return; - } - if(!me.get_user() && !me.get_property()) { - this.body.html("
"+__("Select User or Property to start.")+"
"); - return; - } - // get permissions - return frappe.call({ - module: "frappe.core", - page: "user_properties", - method: "get_properties", - args: { - parent: me.get_user(), - defkey: me.get_property(), - defvalue: me.get_restriction() - }, - callback: function(r) { - me.render(r.message); - } - }); - }, - show_property_table: function() { - var me = this; - this.table = $("\ - \ - \ -
").appendTo(this.body); - - $.each([[__("User"), 150], [__("Type"), 150], [__("Restricted To"),150], ["", 50]], - function(i, col) { - $("
").html(col[0]).css("width", col[1]+"px") - .appendTo(me.table.find("thead tr")); - }); - - - $.each(this.prop_list, function(i, d) { - var row = $("
").html('' - +d.parent+'').appendTo(row); - $("").html(d.defkey).appendTo(row); - $("").html(d.defvalue).appendTo(row); - - me.add_delete_button(row, d); - }); - - }, - add_delete_button: function(row, d) { - var me = this; - $("") - .appendTo($("").appendTo(row)) - .attr("data-name", d.name) - .attr("data-user", d.parent) - .attr("data-defkey", d.defkey) - .attr("data-defvalue", d.defvalue) - .click(function() { - return frappe.call({ - module: "frappe.core", - page: "user_properties", - method: "remove", - args: { - name: $(this).attr("data-name"), - user: $(this).attr("data-user"), - defkey: $(this).attr("data-defkey"), - defvalue: $(this).attr("data-defvalue") - }, - callback: function(r) { - if(r.exc) { - msgprint(__("Did not remove")); - } else { - me.refresh(); - } - } - }) - }); - }, - - show_add_property: function() { - var me = this; - $("") - .appendTo($("

").appendTo(this.body)) - .click(function() { - var d = new frappe.ui.Dialog({ - title: "Add New Property", - fields: [ - {fieldtype:"Select", label:__("User"), - options:me.options.users, reqd:1, fieldname:"user"}, - {fieldtype:"Select", label: __("Property"), fieldname:"defkey", - options:me.get_link_names(), reqd:1}, - {fieldtype:"Link", label:__("Value"), fieldname:"defvalue", - options:'[Select]', reqd:1}, - {fieldtype:"Button", label: __("Add"), fieldname:"add"}, - ] - }); - if(me.get_user()) { - d.set_value("user", me.get_user()); - d.get_input("user").prop("disabled", true); - } - if(me.get_property()) { - d.set_value("defkey", me.get_property()); - d.get_input("defkey").prop("disabled", true); - } - if(me.get_restriction()) { - d.set_value("defvalue", me.get_restriction()); - d.get_input("defvalue").prop("disabled", true); - } - - d.fields_dict["defvalue"].get_query = function(txt) { - return { - doctype: d.get_value("defkey") - } - }; - - d.get_input("add").click(function() { - var args = d.get_values(); - if(!args) { - return; - } - frappe.call({ - module: "frappe.core", - page: "user_properties", - method: "add", - args: args, - callback: function(r) { - if(r.exc) { - msgprint("Did not add"); - } else { - me.refresh(); - } - } - }) - d.hide(); - }); - d.show(); - }); - } -}) diff --git a/frappe/core/page/user_properties/user_properties.json b/frappe/core/page/user_properties/user_properties.json deleted file mode 100644 index 414b7a5b8d..0000000000 --- a/frappe/core/page/user_properties/user_properties.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "creation": "2013-01-01 18:50:55.000000", - "docstatus": 0, - "doctype": "Page", - "icon": "icon-user", - "idx": 1, - "modified": "2014-03-13 11:11:56.000000", - "modified_by": "Administrator", - "module": "Core", - "name": "user-properties", - "owner": "Administrator", - "page_name": "user-properties", - "roles": [ - { - "role": "System Manager" - } - ], - "standard": "Yes", - "title": "User Properties" -} \ No newline at end of file diff --git a/frappe/core/page/user_properties/user_properties.py b/frappe/core/page/user_properties/user_properties.py deleted file mode 100644 index 303329dc62..0000000000 --- a/frappe/core/page/user_properties/user_properties.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -import frappe.defaults -import frappe.permissions -from frappe.core.doctype.user.user import get_system_users - -@frappe.whitelist() -def get_users_and_links(): - return { - "users": get_system_users(), - "link_fields": get_restrictable_doctypes() - } - -@frappe.whitelist() -def get_properties(parent=None, defkey=None, defvalue=None): - if defkey and not frappe.permissions.can_restrict(defkey, defvalue): - raise frappe.PermissionError - - conditions, values = _build_conditions(locals()) - - properties = frappe.db.sql("""select name, parent, defkey, defvalue - from tabDefaultValue - where parent not in ('__default', '__global') - and substr(defkey,1,1)!='_' - and parenttype='Restriction' - {conditions} - order by parent, defkey""".format(conditions=conditions), values, as_dict=True) - - if not defkey: - out = [] - doctypes = get_restrictable_doctypes() - for p in properties: - if p.defkey in doctypes: - out.append(p) - properties = out - - return properties - -def _build_conditions(filters): - conditions = [] - values = {} - for key, value in filters.items(): - if filters.get(key): - conditions.append("and `{key}`=%({key})s".format(key=key)) - values[key] = value - - return "\n".join(conditions), values - -@frappe.whitelist() -def remove(user, name, defkey, defvalue): - if not frappe.permissions.can_restrict_user(user, defkey, defvalue): - raise frappe.PermissionError("Cannot Remove Restriction for User: {user} on DocType: {doctype} and Name: {name}".format( - user=user, doctype=defkey, name=defvalue)) - - frappe.defaults.clear_default(key=defkey, value=defvalue, parent=user, name=name) - -def clear_restrictions(doctype): - frappe.defaults.clear_default(parenttype="Restriction", key=doctype) - -@frappe.whitelist() -def add(user, defkey, defvalue): - if not frappe.permissions.can_restrict_user(user, defkey, defvalue): - raise frappe.PermissionError("Cannot Restrict User: {user} for DocType: {doctype} and Name: {name}".format( - user=user, doctype=defkey, name=defvalue)) - - # check if already exists - d = frappe.db.sql("""select name from tabDefaultValue - where parent=%s and parenttype='Restriction' and defkey=%s and defvalue=%s""", (user, defkey, defvalue)) - - if not d: - frappe.defaults.add_default(defkey, defvalue, user, "Restriction") - -def get_restrictable_doctypes(): - user_roles = frappe.get_roles() - condition = "" - values = [] - if "System Manager" not in user_roles: - condition = """and exists(select `tabDocPerm`.name from `tabDocPerm` - where `tabDocPerm`.parent=`tabDocType`.name and `tabDocPerm`.`restrict`=1 - and `tabDocPerm`.role in ({roles}))""".format(roles=", ".join(["%s"]*len(user_roles))) - values = user_roles - - return frappe.db.sql_list("""select name from tabDocType - where ifnull(issingle,0)=0 and ifnull(istable,0)=0 {condition}""".format(condition=condition), - tuple(values)) diff --git a/frappe/core/report/permitted_documents_for_user/__init__.py b/frappe/core/report/permitted_documents_for_user/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js new file mode 100644 index 0000000000..f379963284 --- /dev/null +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js @@ -0,0 +1,29 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.query_reports["Permitted Documents For User"] = { + "filters": [ + { + "fieldname": "user", + "label": __("User"), + "fieldtype": "Link", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "doctype", + "label": __("DocType"), + "fieldtype": "Link", + "options": "DocType", + "reqd": 1, + "get_query": function() { + return { + "query": "frappe.core.report.permitted_documents_for_user.permitted_documents_for_user.query_doctypes", + "filters": { + "user": frappe.query_report.filters_by_name.user.get_value() + } + } + } + } + ] +} diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.json b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.json new file mode 100644 index 0000000000..458baa936c --- /dev/null +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.json @@ -0,0 +1,15 @@ +{ + "apply_user_permissions": 1, + "creation": "2014-06-03 05:20:35.218263", + "docstatus": 0, + "doctype": "Report", + "is_standard": "Yes", + "modified": "2014-06-03 07:18:17.218526", + "modified_by": "Administrator", + "module": "Core", + "name": "Permitted Documents For User", + "owner": "Administrator", + "ref_doctype": "User", + "report_name": "Permitted Documents For User", + "report_type": "Script Report" +} \ No newline at end of file diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py new file mode 100644 index 0000000000..22777957b8 --- /dev/null +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py @@ -0,0 +1,54 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _, throw +import frappe.utils.user +from frappe.permissions import check_admin_or_system_manager +from frappe.model.db_schema import type_map + +def execute(filters=None): + user, doctype = filters.get("user"), filters.get("doctype") + validate(user, doctype) + + columns, fields = get_columns_and_fields(doctype) + data = frappe.get_list(doctype, fields=fields, as_list=True, user=user) + + return columns, data + +def validate(user, doctype): + # check if current user is System Manager + check_admin_or_system_manager() + + if not user: + throw(_("Please specify user")) + + if not doctype: + throw(_("Please specify doctype")) + +def get_columns_and_fields(doctype): + columns = ["Name:Link/{}:200".format(doctype)] + fields = ["name"] + for df in frappe.get_meta(doctype).fields: + if df.in_list_view and df.fieldtype in type_map: + fields.append(df.fieldname) + fieldtype = "Link/{}".format(df.options) if df.fieldtype=="Link" else df.fieldtype + columns.append("{label}:{fieldtype}:{width}".format(label=df.label, fieldtype=fieldtype, width=df.width or 100)) + + return columns, fields + +def query_doctypes(doctype, txt, searchfield, start, page_len, filters): + user = filters.get("user") + user_obj = frappe.utils.user.User(user) + user_obj.build_permissions() + can_read = user_obj.can_read + + single_doctypes = [d[0] for d in frappe.db.get_values("DocType", {"issingle": 1})] + + out = [] + for dt in can_read: + if txt.lower().replace("%", "") in dt.lower() and dt not in single_doctypes: + out.append([dt]) + + return out diff --git a/frappe/core/report/todo/todo.js b/frappe/core/report/todo/todo.js index ff6cb20556..e69de29bb2 100644 --- a/frappe/core/report/todo/todo.js +++ b/frappe/core/report/todo/todo.js @@ -1 +0,0 @@ -__("Test") // for test case \ No newline at end of file diff --git a/frappe/core/report/todo/todo.json b/frappe/core/report/todo/todo.json index 768162f126..f808571789 100644 --- a/frappe/core/report/todo/todo.json +++ b/frappe/core/report/todo/todo.json @@ -1,10 +1,11 @@ { - "creation": "2013-02-25 14:26:30.000000", + "apply_user_permissions": 1, + "creation": "2013-02-25 14:26:30", "docstatus": 0, "doctype": "Report", "idx": 1, "is_standard": "Yes", - "modified": "2014-03-07 15:30:27.000000", + "modified": "2014-06-03 07:18:17.374222", "modified_by": "Administrator", "module": "Core", "name": "ToDo", diff --git a/frappe/core/report/todo/todo.py b/frappe/core/report/todo/todo.py index 0c5012962b..87f9496f6d 100644 --- a/frappe/core/report/todo/todo.py +++ b/frappe/core/report/todo/todo.py @@ -9,28 +9,28 @@ from frappe.utils import getdate def execute(filters=None): priority_map = {"High": 3, "Medium": 2, "Low": 1} - + todo_list = runreport(doctype="ToDo", fields=["name", "date", "description", - "priority", "reference_type", "reference_name", "assigned_by", "owner"], - filters=[["ToDo", "checked", "!=", 1]]) - - todo_list.sort(key=lambda todo: (priority_map.get(todo.priority, 0), + "priority", "reference_type", "reference_name", "assigned_by", "owner"], + filters=[["ToDo", "status", "=", "Open"]]) + + todo_list.sort(key=lambda todo: (priority_map.get(todo.priority, 0), todo.date and getdate(todo.date) or getdate("1900-01-01")), reverse=True) - - columns = [_("ID")+":Link/ToDo:90", _("Priority")+"::60", _("Date")+ ":Date", - _("Description")+"::150", _("Assigned To/Owner") + ":Data:120", + + columns = [_("ID")+":Link/ToDo:90", _("Priority")+"::60", _("Date")+ ":Date", + _("Description")+"::150", _("Assigned To/Owner") + ":Data:120", _("Assigned By")+":Data:120", _("Reference")+"::200"] result = [] for todo in todo_list: if todo.owner==frappe.session.user or todo.assigned_by==frappe.session.user: if todo.reference_type: - todo.reference = """%s: %s""" % (todo.reference_type, + todo.reference = """%s: %s""" % (todo.reference_type, todo.reference_name, todo.reference_type, todo.reference_name) else: todo.reference = None result.append([todo.name, todo.priority, todo.date, todo.description, todo.owner, todo.assigned_by, todo.reference]) - + return columns, result - + diff --git a/frappe/country_info.json b/frappe/country_info.json index 318ecc2ec1..1624f330f4 100644 --- a/frappe/country_info.json +++ b/frappe/country_info.json @@ -1,2629 +1,2627 @@ { "Afghanistan": { - "code": "af", - "currency_fraction": "Pul", - "currency_fraction_units": 100, - "currency_symbol": "\u060b", - "number_format": "#,###.##", + "code": "af", + "currency_fraction": "Pul", + "currency_fraction_units": 100, + "currency_symbol": "\u060b", + "number_format": "#,###.##", "timezones": [ "Asia/Kabul" ] - }, + }, "Albania": { - "code": "al", - "currency": "ALL", - "currency_fraction": "Qindark\u00eb", - "currency_fraction_units": 100, - "currency_name": "Lek", - "currency_symbol": "L", - "number_format": "#,###.##", + "code": "al", + "currency": "ALL", + "currency_fraction": "Qindark\u00eb", + "currency_fraction_units": 100, + "currency_name": "Lek", + "currency_symbol": "L", + "number_format": "#,###.##", "timezones": [ "Europe/Tirane" ] - }, + }, "Algeria": { - "code": "dz", - "currency": "DZD", - "currency_fraction": "Santeem", - "currency_fraction_units": 100, - "currency_name": "Algerian Dinar", - "currency_symbol": "\u062f.\u062c", - "number_format": "#,###.##", + "code": "dz", + "currency": "DZD", + "currency_fraction": "Santeem", + "currency_fraction_units": 100, + "currency_name": "Algerian Dinar", + "currency_symbol": "\u062f.\u062c", + "number_format": "#,###.##", "timezones": [ "Africa/Algiers" ] - }, + }, "American Samoa": { - "code": "as", + "code": "as", "number_format": "#,###.##" - }, + }, "Andorra": { - "code": "ad", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "ad", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Andorra" ] - }, + }, "Angola": { - "code": "ao", - "currency_fraction": "C\u00eantimo", - "currency_fraction_units": 100, - "currency_symbol": "Kz", - "number_format": "#,###.##", + "code": "ao", + "currency_fraction": "C\u00eantimo", + "currency_fraction_units": 100, + "currency_symbol": "Kz", + "number_format": "#,###.##", "timezones": [ "Africa/Luanda" ] - }, + }, "Anguilla": { - "code": "ai", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "ai", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Anguilla" ] - }, + }, "Antarctica": { - "code": "aq", - "number_format": "#,###.##", - "timezones": [ - "Antarctica/Casey", - "Antarctica/Davis", - "Antarctica/DumontDUrville", - "Antarctica/Macquarie", - "Antarctica/Mawson", - "Antarctica/McMurdo", - "Antarctica/Palmer", - "Antarctica/Rothera", - "Antarctica/South_Pole", - "Antarctica/Syowa", + "code": "aq", + "number_format": "#,###.##", + "timezones": [ + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/Syowa", "Antarctica/Vostok" ] - }, + }, "Antigua and Barbuda": { - "code": "ag", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "ag", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Antigua" ] - }, + }, "Argentina": { - "code": "ar", - "currency": "ARS", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Argentine Peso", - "currency_symbol": "$", - "number_format": "#.###,##", - "timezones": [ - "America/Argentina/Buenos_Aires", - "America/Argentina/Catamarca", - "America/Argentina/Cordoba", - "America/Argentina/Jujuy", - "America/Argentina/La_Rioja", - "America/Argentina/Mendoza", - "America/Argentina/Rio_Gallegos", - "America/Argentina/Salta", - "America/Argentina/San_Juan", - "America/Argentina/San_Luis", - "America/Argentina/Tucuman", + "code": "ar", + "currency": "ARS", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Argentine Peso", + "currency_symbol": "$", + "number_format": "#.###,##", + "timezones": [ + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", "America/Argentina/Ushuaia" ] - }, + }, "Armenia": { - "code": "am", - "currency": "AMD", - "currency_fraction": "Luma", - "currency_fraction_units": 100, - "currency_name": "Armenian Dram", - "currency_symbol": "\u058f", - "number_format": "#,###.##", + "code": "am", + "currency": "AMD", + "currency_fraction": "Luma", + "currency_fraction_units": 100, + "currency_name": "Armenian Dram", + "currency_symbol": "\u058f", + "number_format": "#,###.##", "timezones": [ "Asia/Yerevan" ] - }, + }, "Aruba": { - "code": "aw", - "currency": "AWG", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Aruban Florin", - "currency_symbol": "Afl", - "number_format": "#,###.##", + "code": "aw", + "currency": "AWG", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Aruban Florin", + "currency_symbol": "Afl", + "number_format": "#,###.##", "timezones": [ "America/Aruba" ] - }, + }, "Australia": { - "code": "au", - "currency": "AUD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Australian Dollar", - "currency_symbol": "$", - "number_format": "# ###.##", - "timezones": [ - "Australia/Adelaide", - "Australia/Brisbane", - "Australia/Broken_Hill", - "Australia/Currie", - "Australia/Darwin", - "Australia/Eucla", - "Australia/Hobart", - "Australia/Lindeman", - "Australia/Lord_Howe", - "Australia/Melbourne", - "Australia/Perth", + "code": "au", + "currency": "AUD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Australian Dollar", + "currency_symbol": "$", + "number_format": "# ###.##", + "timezones": [ + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/Perth", "Australia/Sydney" ] - }, + }, "Austria": { - "code": "at", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "at", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Vienna" ] - }, + }, "Azerbaijan": { - "code": "az", - "currency_fraction": "Q\u0259pik", - "currency_fraction_units": 100, - "currency_symbol": "", - "number_format": "#,###.##", + "code": "az", + "currency_fraction": "Q\u0259pik", + "currency_fraction_units": 100, + "currency_symbol": "", + "number_format": "#,###.##", "timezones": [ "Asia/Baku" ] - }, + }, "Bahamas": { - "code": "bs", - "currency": "BSD", - "currency_name": "Bahamian Dollar", - "number_format": "#,###.##", + "code": "bs", + "currency": "BSD", + "currency_name": "Bahamian Dollar", + "number_format": "#,###.##", "timezones": [ "America/Nassau" ] - }, + }, "Bahrain": { - "code": "bh", - "currency": "BHD", - "currency_fraction": "Fils", - "currency_fraction_units": 1000, - "currency_name": "Bahraini Dinar", - "currency_symbol": ".\u062f.\u0628", - "number_format": "#,###.###", + "code": "bh", + "currency": "BHD", + "currency_fraction": "Fils", + "currency_fraction_units": 1000, + "currency_name": "Bahraini Dinar", + "currency_symbol": ".\u062f.\u0628", + "number_format": "#,###.###", "timezones": [ "Asia/Bahrain" ] - }, + }, "Bangladesh": { - "code": "bd", - "currency": "BDT", - "currency_fraction": "Paisa", - "currency_fraction_units": 100, - "currency_name": "Taka", - "currency_symbol": "\u09f3", - "number_format": "#,###.##", + "code": "bd", + "currency": "BDT", + "currency_fraction": "Paisa", + "currency_fraction_units": 100, + "currency_name": "Taka", + "currency_symbol": "\u09f3", + "number_format": "#,###.##", "timezones": [ "Asia/Dhaka" ] - }, + }, "Barbados": { - "code": "bb", - "currency": "BBD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Barbados Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "bb", + "currency": "BBD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Barbados Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Barbados" ] - }, + }, "Belarus": { - "code": "by", - "currency_fraction": "Kapyeyka", - "currency_fraction_units": 100, - "currency_symbol": "Br", - "number_format": "#,###.##", + "code": "by", + "currency_fraction": "Kapyeyka", + "currency_fraction_units": 100, + "currency_symbol": "Br", + "number_format": "#,###.##", "timezones": [ "Europe/Minsk" ] - }, + }, "Belgium": { - "code": "be", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "be", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Brussels" ] - }, + }, "Belize": { - "code": "bz", - "currency": "BZD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Belize Dollar", - "currency_symbol": "$", - "date_format": "mm-dd-yyyy", - "number_format": "#,###.##", + "code": "bz", + "currency": "BZD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Belize Dollar", + "currency_symbol": "$", + "date_format": "mm-dd-yyyy", + "number_format": "#,###.##", "timezones": [ "America/Belize" ] - }, + }, "Benin": { - "code": "bj", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "bj", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Porto-Novo" ] - }, + }, "Bermuda": { - "code": "bm", - "currency": "BMD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Bermudian Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "bm", + "currency": "BMD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Bermudian Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Atlantic/Bermuda" ] - }, + }, "Bhutan": { - "code": "bt", - "currency": "BTN", - "currency_fraction": "Chetrum", - "currency_fraction_units": 100, - "currency_name": "Ngultrum", - "currency_symbol": "Nu.", - "number_format": "#,###.##", + "code": "bt", + "currency": "BTN", + "currency_fraction": "Chetrum", + "currency_fraction_units": 100, + "currency_name": "Ngultrum", + "currency_symbol": "Nu.", + "number_format": "#,###.##", "timezones": [ "Asia/Thimphu" ] - }, + }, "Bolivia, Plurinational State of": { - "code": "bo", - "currency": "BOB", - "currency_name": "Boliviano", + "code": "bo", + "currency": "BOB", + "currency_name": "Boliviano", "number_format": "#,###.##" - }, + }, "Bonaire, Sint Eustatius and Saba": { - "code": "bq", + "code": "bq", "number_format": "#,###.##" - }, + }, "Bosnia and Herzegovina": { - "code": "ba", - "currency_fraction": "Fening", - "currency_fraction_units": 100, - "currency_symbol": "KM or \u041a\u041c", - "number_format": "#,###.##", + "code": "ba", + "currency_fraction": "Fening", + "currency_fraction_units": 100, + "currency_symbol": "KM or \u041a\u041c", + "number_format": "#,###.##", "timezones": [ - "Europe/Sarajevo" + "Europe/Belgrade" ] - }, + }, "Botswana": { - "code": "bw", - "currency": "BWP", - "currency_fraction": "Thebe", - "currency_fraction_units": 100, - "currency_name": "Pula", - "currency_symbol": "P", - "number_format": "#,###.##", + "code": "bw", + "currency": "BWP", + "currency_fraction": "Thebe", + "currency_fraction_units": 100, + "currency_name": "Pula", + "currency_symbol": "P", + "number_format": "#,###.##", "timezones": [ "Africa/Gaborone" ] - }, + }, "Bouvet Island": { - "code": "bv", + "code": "bv", "number_format": "#,###.##" - }, + }, "Brazil": { - "code": "br", - "currency": "BRL", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_symbol": "R$", - "date_format": "dd/mm/yyyy", - "number_format": "#.###,##", - "timezones": [ - "America/Araguaina", - "America/Bahia", - "America/Belem", - "America/Boa_Vista", - "America/Campo_Grande", - "America/Cuiaba", - "America/Eirunepe", - "America/Fortaleza", - "America/Maceio", - "America/Manaus", - "America/Noronha", - "America/Porto_Velho", - "America/Recife", - "America/Rio_Branco", - "America/Santarem", + "code": "br", + "currency": "BRL", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_symbol": "R$", + "date_format": "dd/mm/yyyy", + "number_format": "#.###,##", + "timezones": [ + "America/Araguaina", + "America/Bahia", + "America/Belem", + "America/Boa_Vista", + "America/Campo_Grande", + "America/Cuiaba", + "America/Eirunepe", + "America/Fortaleza", + "America/Maceio", + "America/Manaus", + "America/Noronha", + "America/Porto_Velho", + "America/Recife", + "America/Rio_Branco", + "America/Santarem", "America/Sao_Paulo" ] - }, + }, "British Indian Ocean Territory": { - "code": "io", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "io", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Indian/Chagos" ] - }, + }, "Brunei Darussalam": { - "code": "bn", - "currency": "BND", - "currency_name": "Brunei Dollar", - "number_format": "#,###.##", + "code": "bn", + "currency": "BND", + "currency_name": "Brunei Dollar", + "number_format": "#,###.##", "timezones": [ "Asia/Brunei" ] - }, + }, "Bulgaria": { - "code": "bg", - "currency_fraction": "Stotinka", - "currency_fraction_units": 100, - "currency_symbol": "\u043b\u0432", - "number_format": "#,###.##", + "code": "bg", + "currency_fraction": "Stotinka", + "currency_fraction_units": 100, + "currency_symbol": "\u043b\u0432", + "number_format": "#,###.##", "timezones": [ "Europe/Sofia" ] - }, + }, "Burkina Faso": { - "code": "bf", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "bf", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Ouagadougou" ] - }, + }, "Burundi": { - "code": "bi", - "currency": "BIF", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_name": "Burundi Franc", - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "bi", + "currency": "BIF", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_name": "Burundi Franc", + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Bujumbura" ] - }, + }, "Cambodia": { - "code": "kh", - "currency": "KHR", - "currency_fraction": "Sen", - "currency_fraction_units": 100, - "currency_name": "Riel", - "currency_symbol": "\u17db", - "number_format": "#,###.##", + "code": "kh", + "currency": "KHR", + "currency_fraction": "Sen", + "currency_fraction_units": 100, + "currency_name": "Riel", + "currency_symbol": "\u17db", + "number_format": "#,###.##", "timezones": [ "Asia/Phnom_Penh" ] - }, + }, "Cameroon": { - "code": "cm", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "cm", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Douala" ] - }, + }, "Canada": { - "code": "ca", - "currency": "CAD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Canadian Dollar", - "currency_symbol": "$", - "date_format": "mm-dd-yyyy", - "number_format": "#,###.##", - "timezones": [ - "America/Atikokan", - "America/Blanc-Sablon", - "America/Cambridge_Bay", - "America/Creston", - "America/Dawson", - "America/Dawson_Creek", - "America/Edmonton", - "America/Glace_Bay", - "America/Goose_Bay", - "America/Halifax", - "America/Inuvik", - "America/Iqaluit", - "America/Moncton", - "America/Montreal", - "America/Nipigon", - "America/Pangnirtung", - "America/Rainy_River", - "America/Rankin_Inlet", - "America/Regina", - "America/Resolute", - "America/St_Johns", - "America/Swift_Current", - "America/Thunder_Bay", - "America/Toronto", - "America/Vancouver", - "America/Whitehorse", - "America/Winnipeg", + "code": "ca", + "currency": "CAD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Canadian Dollar", + "currency_symbol": "$", + "date_format": "mm-dd-yyyy", + "number_format": "#,###.##", + "timezones": [ + "America/Atikokan", + "America/Blanc-Sablon", + "America/Cambridge_Bay", + "America/Creston", + "America/Dawson", + "America/Dawson_Creek", + "America/Edmonton", + "America/Glace_Bay", + "America/Goose_Bay", + "America/Halifax", + "America/Inuvik", + "America/Iqaluit", + "America/Moncton", + "America/Montreal", + "America/Nipigon", + "America/Pangnirtung", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Regina", + "America/Resolute", + "America/St_Johns", + "America/Swift_Current", + "America/Thunder_Bay", + "America/Toronto", + "America/Vancouver", + "America/Whitehorse", + "America/Winnipeg", "America/Yellowknife" ] - }, + }, "Cape Verde": { - "code": "cv", - "currency": "CVE", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Cape Verde Escudo", - "currency_symbol": "Esc or $", - "number_format": "#,###.##", + "code": "cv", + "currency": "CVE", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Cape Verde Escudo", + "currency_symbol": "Esc or $", + "number_format": "#,###.##", "timezones": [ "Atlantic/Cape_Verde" ] - }, + }, "Cayman Islands": { - "code": "ky", - "currency": "KYD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Cayman Islands Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "ky", + "currency": "KYD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Cayman Islands Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Cayman" ] - }, + }, "Central African Republic": { - "code": "cf", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "cf", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Bangui" ] - }, + }, "Chad": { - "code": "td", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "td", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Ndjamena" ] - }, + }, "Chile": { - "code": "cl", - "currency": "CLP", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Chilean Peso", - "currency_symbol": "$", - "number_format": "#.###", - "timezones": [ - "America/Santiago", + "code": "cl", + "currency": "CLP", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Chilean Peso", + "currency_symbol": "$", + "number_format": "#.###", + "timezones": [ + "America/Santiago", "Pacific/Easter" ] - }, + }, "China": { - "code": "cn", - "currency": "CNY", - "currency_name": "Yuan Renminbi", - "date_format": "yyyy-mm-dd", - "number_format": "#,###.##", - "timezones": [ - "Asia/Chongqing", - "Asia/Harbin", - "Asia/Kashgar", - "Asia/Shanghai", + "code": "cn", + "currency": "CNY", + "currency_name": "Yuan Renminbi", + "date_format": "yyyy-mm-dd", + "number_format": "#,###.##", + "timezones": [ + "Asia/Chongqing", + "Asia/Harbin", + "Asia/Kashgar", + "Asia/Shanghai", "Asia/Urumqi" ] - }, + }, "Christmas Island": { - "code": "cx", - "number_format": "#,###.##", + "code": "cx", + "number_format": "#,###.##", "timezones": [ "Indian/Christmas" ] - }, + }, "Cocos (Keeling) Islands": { - "code": "cc", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "cc", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Indian/Cocos" ] - }, + }, "Colombia": { - "code": "co", - "currency": "COP", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Colombian Peso", - "currency_symbol": "$", - "number_format": "#.###,##", + "code": "co", + "currency": "COP", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Colombian Peso", + "currency_symbol": "$", + "number_format": "#.###,##", "timezones": [ "America/Bogota" ] - }, + }, "Comoros": { - "code": "km", - "currency": "KMF", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_name": "Comoro Franc", - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "km", + "currency": "KMF", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_name": "Comoro Franc", + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Indian/Comoro" ] - }, + }, "Congo": { - "code": "cg", + "code": "cg", "number_format": "#,###.##" - }, + }, "Congo, The Democratic Republic of the": { "number_format": "#,###.##" - }, + }, "Cook Islands": { - "code": "ck", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "ck", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Pacific/Rarotonga" ] - }, + }, "Costa Rica": { - "code": "cr", - "currency": "CRC", - "currency_fraction": "C\u00e9ntimo", - "currency_fraction_units": 100, - "currency_name": "Costa Rican Colon", - "currency_symbol": "\u20a1", - "number_format": "#.###,##", + "code": "cr", + "currency": "CRC", + "currency_fraction": "C\u00e9ntimo", + "currency_fraction_units": 100, + "currency_name": "Costa Rican Colon", + "currency_symbol": "\u20a1", + "number_format": "#.###,##", "timezones": [ "America/Costa_Rica" ] - }, + }, "Croatia": { - "code": "hr", - "currency": "HRK", - "currency_fraction": "Lipa", - "currency_fraction_units": 100, - "currency_name": "Croatian Kuna", - "currency_symbol": "kn", - "number_format": "#.###,##", + "code": "hr", + "currency": "HRK", + "currency_fraction": "Lipa", + "currency_fraction_units": 100, + "currency_name": "Croatian Kuna", + "currency_symbol": "kn", + "number_format": "#.###,##", "timezones": [ - "Europe/Zagreb" + "Europe/Belgrade" ] - }, + }, "Cuba": { - "code": "cu", - "currency": "CUP", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Cuban Peso", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "cu", + "currency": "CUP", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Cuban Peso", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Havana" ] - }, + }, "Cura\u00e7ao": { - "code": "cw", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u0192", + "code": "cw", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u0192", "number_format": "#,###.##" - }, + }, "Cyprus": { - "code": "cy", - "currency": "CYP", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Cyprus Pound", - "currency_symbol": "\u20ac", - "number_format": "#.###,##", + "code": "cy", + "currency": "CYP", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Cyprus Pound", + "currency_symbol": "\u20ac", + "number_format": "#.###,##", "timezones": [ "Asia/Nicosia" ] - }, + }, "Czech Republic": { - "code": "cz", - "currency": "CZK", - "currency_fraction": "Hal\u00e9\u0159", - "currency_fraction_units": 100, - "currency_name": "Czech Koruna", - "currency_symbol": "K\u010d", - "number_format": "#.###,##", + "code": "cz", + "currency": "CZK", + "currency_fraction": "Hal\u00e9\u0159", + "currency_fraction_units": 100, + "currency_name": "Czech Koruna", + "currency_symbol": "K\u010d", + "number_format": "#.###,##", "timezones": [ "Europe/Prague" ] - }, + }, "Denmark": { - "code": "dk", - "currency": "DKK", - "currency_fraction": "\u00d8re", - "currency_fraction_units": 100, - "currency_name": "Danish Krone", - "currency_symbol": "kr", - "number_format": "#.###,##", + "code": "dk", + "currency": "DKK", + "currency_fraction": "\u00d8re", + "currency_fraction_units": 100, + "currency_name": "Danish Krone", + "currency_symbol": "kr", + "number_format": "#.###,##", "timezones": [ "Europe/Copenhagen" ] - }, + }, "Djibouti": { - "code": "dj", - "currency": "DJF", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_name": "Djibouti Franc", - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "dj", + "currency": "DJF", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_name": "Djibouti Franc", + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Djibouti" ] - }, + }, "Dominica": { - "code": "dm", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "dm", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Dominica" ] - }, + }, "Dominican Republic": { - "code": "do", - "currency": "DOP", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Dominican Peso", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "do", + "currency": "DOP", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Dominican Peso", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Santo_Domingo" ] - }, + }, "Ecuador": { - "code": "ec", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "ec", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ - "America/Guayaquil", + "America/Guayaquil", "Pacific/Galapagos" ] - }, + }, "Egypt": { - "code": "eg", - "currency": "EGP", - "currency_fraction": "Piastre[F]", - "currency_fraction_units": 100, - "currency_name": "Egyptian Pound", - "currency_symbol": "\u00a3 or \u062c.\u0645", - "number_format": "#,###.##", + "code": "eg", + "currency": "EGP", + "currency_fraction": "Piastre[F]", + "currency_fraction_units": 100, + "currency_name": "Egyptian Pound", + "currency_symbol": "\u00a3 or \u062c.\u0645", + "number_format": "#,###.##", "timezones": [ "Africa/Cairo" ] - }, + }, "El Salvador": { - "code": "sv", - "currency": "SVC", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "El Salvador Colon", - "currency_symbol": "\u20a1", - "number_format": "#,###.##", + "code": "sv", + "currency": "SVC", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "El Salvador Colon", + "currency_symbol": "\u20a1", + "number_format": "#,###.##", "timezones": [ "America/El_Salvador" ] - }, + }, "Equatorial Guinea": { - "code": "gq", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "gq", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Malabo" ] - }, + }, "Eritrea": { - "code": "er", - "currency": "ERN", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Nakfa", - "currency_symbol": "Nfk", - "number_format": "#,###.##", - "timezones": [ - "Africa/Asmara", - "Africa/Asmera" - ] - }, + "code": "er", + "currency": "ERN", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Nakfa", + "currency_symbol": "Nfk", + "number_format": "#,###.##", + "timezones": [ + "Africa/Asmara" + ] + }, "Estonia": { - "code": "ee", - "currency": "EEK", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Kroon", - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "ee", + "currency": "EEK", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Kroon", + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Tallinn" ] - }, + }, "Ethiopia": { - "code": "et", - "currency_fraction": "Santim", - "currency_fraction_units": 100, - "currency_symbol": "Br", - "number_format": "#,###.##", + "code": "et", + "currency_fraction": "Santim", + "currency_fraction_units": 100, + "currency_symbol": "Br", + "number_format": "#,###.##", "timezones": [ "Africa/Addis_Ababa" ] - }, + }, "Falkland Islands (Malvinas)": { - "code": "fk", - "currency": "FKP", - "currency_name": "Falkland Islands Pound", + "code": "fk", + "currency": "FKP", + "currency_name": "Falkland Islands Pound", "number_format": "#,###.##" - }, + }, "Faroe Islands": { - "code": "fo", - "currency_fraction": "\u00d8re", - "currency_fraction_units": 100, - "currency_symbol": "kr", - "number_format": "#,###.##", + "code": "fo", + "currency_fraction": "\u00d8re", + "currency_fraction_units": 100, + "currency_symbol": "kr", + "number_format": "#,###.##", "timezones": [ "Atlantic/Faroe" ] - }, + }, "Fiji": { - "code": "fj", - "currency": "FJD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Fiji Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "fj", + "currency": "FJD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Fiji Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Pacific/Fiji" ] - }, + }, "Finland": { - "code": "fi", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "fi", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Helsinki" ] - }, + }, "France": { - "code": "fr", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "fr", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Paris" ] - }, + }, "French Guiana": { - "code": "gf", - "number_format": "#,###.##", + "code": "gf", + "number_format": "#,###.##", "timezones": [ "America/Cayenne" ] - }, + }, "French Polynesia": { - "code": "pf", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", - "timezones": [ - "Pacific/Gambier", - "Pacific/Marquesas", + "code": "pf", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", + "timezones": [ + "Pacific/Gambier", + "Pacific/Marquesas", "Pacific/Tahiti" ] - }, + }, "French Southern Territories": { - "code": "tf", + "code": "tf", "number_format": "#,###.##" - }, + }, "Gabon": { - "code": "ga", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "ga", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Libreville" ] - }, + }, "Gambia": { - "code": "gm", - "currency": "GMD", - "currency_name": "Dalasi", - "number_format": "#,###.##", + "code": "gm", + "currency": "GMD", + "currency_name": "Dalasi", + "number_format": "#,###.##", "timezones": [ "Africa/Banjul" ] - }, + }, "Georgia": { - "code": "ge", - "currency_fraction": "Tetri", - "currency_fraction_units": 100, - "currency_symbol": "\u10da", - "number_format": "#,###.##", + "code": "ge", + "currency_fraction": "Tetri", + "currency_fraction_units": 100, + "currency_symbol": "\u10da", + "number_format": "#,###.##", "timezones": [ "Asia/Tbilisi" ] - }, + }, "Germany": { - "code": "de", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "de", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Berlin" ] - }, + }, "Ghana": { - "code": "gh", - "currency_fraction": "Pesewa", - "currency_fraction_units": 100, - "currency_symbol": "\u20b5", - "number_format": "#,###.##", + "code": "gh", + "currency_fraction": "Pesewa", + "currency_fraction_units": 100, + "currency_symbol": "\u20b5", + "number_format": "#,###.##", "timezones": [ "Africa/Accra" ] - }, + }, "Gibraltar": { - "code": "gi", - "currency": "GIP", - "currency_fraction": "Penny", - "currency_fraction_units": 100, - "currency_name": "Gibraltar Pound", - "currency_symbol": "\u00a3", - "number_format": "#,###.##", + "code": "gi", + "currency": "GIP", + "currency_fraction": "Penny", + "currency_fraction_units": 100, + "currency_name": "Gibraltar Pound", + "currency_symbol": "\u00a3", + "number_format": "#,###.##", "timezones": [ "Europe/Gibraltar" ] - }, + }, "Greece": { - "code": "gr", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "gr", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Athens" ] - }, + }, "Greenland": { - "code": "gl", - "number_format": "#,###.##", + "code": "gl", + "number_format": "#,###.##", "timezones": [ - "America/Danmarkshavn", - "America/Godthab", - "America/Scoresbysund", + "America/Danmarkshavn", + "America/Godthab", + "America/Scoresbysund", "America/Thule" ] - }, + }, "Grenada": { - "code": "gd", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "gd", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Grenada" ] - }, + }, "Guadeloupe": { - "code": "gp", - "number_format": "#,###.##", + "code": "gp", + "number_format": "#,###.##", "timezones": [ "America/Guadeloupe" ] - }, + }, "Guam": { - "code": "gu", - "number_format": "#,###.##", + "code": "gu", + "number_format": "#,###.##", "timezones": [ "Pacific/Guam" ] - }, + }, "Guatemala": { - "code": "gt", - "currency": "GTQ", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Quetzal", - "currency_symbol": "Q", - "number_format": "#,###.##", + "code": "gt", + "currency": "GTQ", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Quetzal", + "currency_symbol": "Q", + "number_format": "#,###.##", "timezones": [ "America/Guatemala" ] - }, + }, "Guernsey": { - "code": "gg", - "currency_fraction": "Penny", - "currency_fraction_units": 100, - "currency_symbol": "\u00a3", - "number_format": "#,###.##", + "code": "gg", + "currency_fraction": "Penny", + "currency_fraction_units": 100, + "currency_symbol": "\u00a3", + "number_format": "#,###.##", "timezones": [ - "Europe/Guernsey" + "Europe/London" ] - }, + }, "Guinea": { - "code": "gn", - "currency": "GNF", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_name": "Guinea Franc", - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "gn", + "currency": "GNF", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_name": "Guinea Franc", + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Conakry" ] - }, + }, "Guinea-Bissau": { - "code": "gw", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "gw", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Bissau" ] - }, + }, "Guyana": { - "code": "gy", - "currency": "GYD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Guyana Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "gy", + "currency": "GYD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Guyana Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Guyana" ] - }, + }, "Haiti": { - "code": "ht", - "currency": "HTG", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_name": "Gourde", - "currency_symbol": "G", - "number_format": "#,###.##", - "timezones": [ - "America/Guatemala", + "code": "ht", + "currency": "HTG", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_name": "Gourde", + "currency_symbol": "G", + "number_format": "#,###.##", + "timezones": [ + "America/Guatemala", "America/Port-au-Prince" ] - }, + }, "Heard Island and McDonald Islands": { - "code": "hm", + "code": "hm", "number_format": "#,###.##" - }, + }, "Holy See (Vatican City State)": { - "code": "va", + "code": "va", "number_format": "#,###.##" - }, + }, "Honduras": { - "code": "hn", - "currency": "HNL", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Lempira", - "currency_symbol": "L", - "number_format": "#,###.##", + "code": "hn", + "currency": "HNL", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Lempira", + "currency_symbol": "L", + "number_format": "#,###.##", "timezones": [ "America/Tegucigalpa" ] - }, + }, "Hong Kong": { - "code": "hk", - "currency": "HKD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Hong Kong Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "hk", + "currency": "HKD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Hong Kong Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Asia/Hong_Kong" ] - }, + }, "Hungary": { - "code": "hu", - "currency": "HUF", - "currency_fraction": "Fill\u00e9r", - "currency_fraction_units": 100, - "currency_name": "Forint", - "currency_symbol": "Ft", - "date_format": "yyyy-mm-dd", - "number_format": "#.###", + "code": "hu", + "currency": "HUF", + "currency_fraction": "Fill\u00e9r", + "currency_fraction_units": 100, + "currency_name": "Forint", + "currency_symbol": "Ft", + "date_format": "yyyy-mm-dd", + "number_format": "#.###", "timezones": [ "Europe/Budapest" ] - }, + }, "Iceland": { - "code": "is", - "currency": "ISK", - "currency_fraction": "Eyrir", - "currency_fraction_units": 100, - "currency_name": "Iceland Krona", - "currency_symbol": "kr", - "number_format": "#.###", + "code": "is", + "currency": "ISK", + "currency_fraction": "Eyrir", + "currency_fraction_units": 100, + "currency_name": "Iceland Krona", + "currency_symbol": "kr", + "number_format": "#.###", "timezones": [ "Atlantic/Reykjavik" ] - }, + }, "India": { - "code": "in", - "currency": "INR", - "currency_fraction": "Paisa", - "currency_fraction_units": 100, - "currency_name": "Indian Rupee", - "currency_symbol": "\u20b9", - "number_format": "#,##,###.##", - "timezones": [ - "Asia/Calcutta", + "code": "in", + "currency": "INR", + "currency_fraction": "Paisa", + "currency_fraction_units": 100, + "currency_name": "Indian Rupee", + "currency_symbol": "\u20b9", + "number_format": "#,##,###.##", + "timezones": [ "Asia/Kolkata" ] - }, + }, "Indonesia": { - "code": "id", - "currency": "IDR", - "currency_fraction": "Sen", - "currency_fraction_units": 100, - "currency_name": "Rupiah", - "currency_symbol": "Rp", - "number_format": "#.###,##", - "timezones": [ - "Asia/Jakarta", - "Asia/Jayapura", - "Asia/Makassar", + "code": "id", + "currency": "IDR", + "currency_fraction": "Sen", + "currency_fraction_units": 100, + "currency_name": "Rupiah", + "currency_symbol": "Rp", + "number_format": "#.###,##", + "timezones": [ + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Makassar", "Asia/Pontianak" ] - }, + }, "Iran, Islamic Republic of": { - "code": "ir", - "currency": "IRR", - "currency_name": "Iranian Rial", + "code": "ir", + "currency": "IRR", + "currency_name": "Iranian Rial", "number_format": "#,###.##" - }, + }, "Iraq": { - "code": "iq", - "currency": "IQD", - "currency_fraction": "Fils", - "currency_fraction_units": 1000, - "currency_name": "Iraqi Dinar", - "currency_symbol": "\u0639.\u062f", - "number_format": "#,###.###", + "code": "iq", + "currency": "IQD", + "currency_fraction": "Fils", + "currency_fraction_units": 1000, + "currency_name": "Iraqi Dinar", + "currency_symbol": "\u0639.\u062f", + "number_format": "#,###.###", "timezones": [ "Asia/Baghdad" ] - }, + }, "Ireland": { - "code": "ie", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "ie", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Dublin" ] - }, + }, "Isle of Man": { - "code": "im", - "currency_fraction": "Penny", - "currency_fraction_units": 100, - "currency_symbol": "\u00a3", - "number_format": "#,###.##", + "code": "im", + "currency_fraction": "Penny", + "currency_fraction_units": 100, + "currency_symbol": "\u00a3", + "number_format": "#,###.##", "timezones": [ - "Europe/Isle_of_Man" + "Europe/London" ] - }, + }, "Israel": { - "code": "il", - "currency": "ILS", - "currency_fraction": "Agora", - "currency_fraction_units": 100, - "currency_name": "New Israeli Sheqel", - "currency_symbol": "\u20aa", - "number_format": "#,###.##", + "code": "il", + "currency": "ILS", + "currency_fraction": "Agora", + "currency_fraction_units": 100, + "currency_name": "New Israeli Sheqel", + "currency_symbol": "\u20aa", + "number_format": "#,###.##", "timezones": [ "Asia/Jerusalem" ] - }, + }, "Italy": { - "code": "it", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "it", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Rome" ] - }, + }, "Ivory Coast": { - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", "number_format": "#,###.##" - }, + }, "Jamaica": { - "code": "jm", - "currency": "JMD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Jamaican Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "jm", + "currency": "JMD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Jamaican Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Jamaica" ] - }, + }, "Japan": { - "code": "jp", - "currency": "JPY", - "currency_fraction": "Sen[G]", - "currency_fraction_units": 100, - "currency_name": "Yen", - "currency_symbol": "\u00a5", - "number_format": "#,###", + "code": "jp", + "currency": "JPY", + "currency_fraction": "Sen[G]", + "currency_fraction_units": 100, + "currency_name": "Yen", + "currency_symbol": "\u00a5", + "number_format": "#,###", "timezones": [ "Asia/Tokyo" ] - }, + }, "Jersey": { - "code": "je", - "currency_fraction": "Penny", - "currency_fraction_units": 100, - "currency_symbol": "\u00a3", - "number_format": "#,###.##", + "code": "je", + "currency_fraction": "Penny", + "currency_fraction_units": 100, + "currency_symbol": "\u00a3", + "number_format": "#,###.##", "timezones": [ - "Europe/Jersey" + "Europe/London" ] - }, + }, "Jordan": { - "code": "jo", - "currency": "JOD", - "currency_fraction": "Piastre[H]", - "currency_fraction_units": 100, - "currency_name": "Jordanian Dinar", - "currency_symbol": "\u062f.\u0627", - "number_format": "#,###.###", + "code": "jo", + "currency": "JOD", + "currency_fraction": "Piastre[H]", + "currency_fraction_units": 100, + "currency_name": "Jordanian Dinar", + "currency_symbol": "\u062f.\u0627", + "number_format": "#,###.###", "timezones": [ "Asia/Amman" ] - }, + }, "Kazakhstan": { - "code": "kz", - "currency": "KZT", - "currency_fraction": "T\u00ef\u0131n", - "currency_fraction_units": 100, - "currency_name": "Tenge", - "currency_symbol": "\u20b8", - "number_format": "#,###.##", - "timezones": [ - "Asia/Almaty", - "Asia/Aqtau", - "Asia/Aqtobe", - "Asia/Oral", + "code": "kz", + "currency": "KZT", + "currency_fraction": "T\u00ef\u0131n", + "currency_fraction_units": 100, + "currency_name": "Tenge", + "currency_symbol": "\u20b8", + "number_format": "#,###.##", + "timezones": [ + "Asia/Almaty", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Oral", "Asia/Qyzylorda" ] - }, + }, "Kenya": { - "code": "ke", - "currency": "KES", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Kenyan Shilling", - "currency_symbol": "Sh", - "number_format": "#,###.##", + "code": "ke", + "currency": "KES", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Kenyan Shilling", + "currency_symbol": "Sh", + "number_format": "#,###.##", "timezones": [ "Africa/Nairobi" ] - }, + }, "Kiribati": { - "code": "ki", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", - "timezones": [ - "Pacific/Enderbury", - "Pacific/Kiritimati", + "code": "ki", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", + "timezones": [ + "Pacific/Enderbury", + "Pacific/Kiritimati", "Pacific/Tarawa" ] - }, + }, "Korea, Democratic Peoples Republic of": { - "currency": "KPW", - "currency_name": "North Korean Won", + "currency": "KPW", + "currency_name": "North Korean Won", "number_format": "#,###.##" - }, + }, "Korea, Republic of": { - "code": "kr", - "currency": "KRW", - "currency_name": "Won", + "code": "kr", + "currency": "KRW", + "currency_name": "Won", "number_format": "#,###" - }, + }, "Kuwait": { - "code": "kw", - "currency": "KWD", - "currency_fraction": "Fils", - "currency_fraction_units": 1000, - "currency_name": "Kuwaiti Dinar", - "currency_symbol": "\u062f.\u0643", - "number_format": "#,###.###", + "code": "kw", + "currency": "KWD", + "currency_fraction": "Fils", + "currency_fraction_units": 1000, + "currency_name": "Kuwaiti Dinar", + "currency_symbol": "\u062f.\u0643", + "number_format": "#,###.###", "timezones": [ "Asia/Kuwait" ] - }, + }, "Kyrgyzstan": { - "code": "kg", - "currency": "KGS", - "currency_fraction": "Tyiyn", - "currency_fraction_units": 100, - "currency_name": "Som", - "currency_symbol": "\u043b\u0432", - "number_format": "#,###.##", + "code": "kg", + "currency": "KGS", + "currency_fraction": "Tyiyn", + "currency_fraction_units": 100, + "currency_name": "Som", + "currency_symbol": "\u043b\u0432", + "number_format": "#,###.##", "timezones": [ "Asia/Bishkek" ] - }, + }, "Lao Peoples Democratic Republic": { - "currency": "LAK", - "currency_name": "Kip", + "currency": "LAK", + "currency_name": "Kip", "number_format": "#,###.##" - }, + }, "Latvia": { - "code": "lv", - "currency": "LVL", - "currency_fraction": "Sant\u012bms", - "currency_fraction_units": 100, - "currency_name": "Latvian Lats", - "currency_symbol": "Ls", - "number_format": "#,###.##", + "code": "lv", + "currency": "LVL", + "currency_fraction": "Sant\u012bms", + "currency_fraction_units": 100, + "currency_name": "Latvian Lats", + "currency_symbol": "Ls", + "number_format": "#,###.##", "timezones": [ "Europe/Riga" ] - }, + }, "Lebanon": { - "code": "lb", - "currency": "LBP", - "currency_fraction": "Piastre", - "currency_fraction_units": 100, - "currency_name": "Lebanese Pound", - "currency_symbol": "\u0644.\u0644", - "number_format": "#,###.##", + "code": "lb", + "currency": "LBP", + "currency_fraction": "Piastre", + "currency_fraction_units": 100, + "currency_name": "Lebanese Pound", + "currency_symbol": "\u0644.\u0644", + "number_format": "#,###.##", "timezones": [ "Asia/Beirut" ] - }, + }, "Lesotho": { - "code": "ls", - "currency": "LSL", - "currency_fraction": "Sente", - "currency_fraction_units": 100, - "currency_name": "Loti", - "currency_symbol": "L", - "number_format": "#,###.##", + "code": "ls", + "currency": "LSL", + "currency_fraction": "Sente", + "currency_fraction_units": 100, + "currency_name": "Loti", + "currency_symbol": "L", + "number_format": "#,###.##", "timezones": [ "Africa/Maseru" ] - }, + }, "Liberia": { - "code": "lr", - "currency": "LRD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Liberian Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "lr", + "currency": "LRD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Liberian Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Africa/Monrovia" ] - }, + }, "Libya": { - "code": "ly", - "currency": "LYD", - "currency_fraction": "Dirham", - "currency_fraction_units": 1000, - "currency_name": "Libyan Dinar", - "currency_symbol": "\u0644.\u062f", - "number_format": "#,###.###", + "code": "ly", + "currency": "LYD", + "currency_fraction": "Dirham", + "currency_fraction_units": 1000, + "currency_name": "Libyan Dinar", + "currency_symbol": "\u0644.\u062f", + "number_format": "#,###.###", "timezones": [ "Africa/Tripoli" ] - }, + }, "Liechtenstein": { - "code": "li", - "currency_fraction": "Rappen", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "li", + "currency_fraction": "Rappen", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Europe/Vaduz" ] - }, + }, "Lithuania": { - "code": "lt", - "currency": "LTL", - "currency_fraction": "Centas", - "currency_fraction_units": 100, - "currency_name": "Lithuanian Litas", - "currency_symbol": "Lt", - "date_format": "yyyy-mm-dd", - "number_format": "# ###,##", + "code": "lt", + "currency": "LTL", + "currency_fraction": "Centas", + "currency_fraction_units": 100, + "currency_name": "Lithuanian Litas", + "currency_symbol": "Lt", + "date_format": "yyyy-mm-dd", + "number_format": "# ###,##", "timezones": [ "Europe/Vilnius" ] - }, + }, "Luxembourg": { - "code": "lu", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "lu", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Luxembourg" ] - }, + }, "Macao": { - "code": "mo", - "currency": "MOP", - "currency_name": "Pataca", + "code": "mo", + "currency": "MOP", + "currency_name": "Pataca", "number_format": "#,###.##" - }, + }, "Macedonia, Republic of": { - "currency": "MKD", - "currency_fraction": "Deni", - "currency_fraction_units": 100, - "currency_name": "Denar", - "currency_symbol": "\u0434\u0435\u043d", + "currency": "MKD", + "currency_fraction": "Deni", + "currency_fraction_units": 100, + "currency_name": "Denar", + "currency_symbol": "\u0434\u0435\u043d", "number_format": "#,###.##" - }, + }, "Madagascar": { - "code": "mg", - "currency_fraction": "Iraimbilanja", - "currency_fraction_units": 5, - "currency_symbol": "Ar", - "number_format": "#,###.##", + "code": "mg", + "currency_fraction": "Iraimbilanja", + "currency_fraction_units": 5, + "currency_symbol": "Ar", + "number_format": "#,###.##", "timezones": [ "Indian/Antananarivo" ] - }, + }, "Malawi": { - "code": "mw", - "currency": "MWK", - "currency_fraction": "Tambala", - "currency_fraction_units": 100, - "currency_name": "Kwacha", - "currency_symbol": "MK", - "number_format": "#,###.##", + "code": "mw", + "currency": "MWK", + "currency_fraction": "Tambala", + "currency_fraction_units": 100, + "currency_name": "Kwacha", + "currency_symbol": "MK", + "number_format": "#,###.##", "timezones": [ "Africa/Blantyre" ] - }, + }, "Malaysia": { - "code": "my", - "currency": "MYR", - "currency_fraction": "Sen", - "currency_fraction_units": 100, - "currency_name": "Malaysian Ringgit", - "currency_symbol": "RM", - "number_format": "#,###.##", - "timezones": [ - "Asia/Kuala_Lumpur", + "code": "my", + "currency": "MYR", + "currency_fraction": "Sen", + "currency_fraction_units": 100, + "currency_name": "Malaysian Ringgit", + "currency_symbol": "RM", + "number_format": "#,###.##", + "timezones": [ + "Asia/Kuala_Lumpur", "Asia/Kuching" ] - }, + }, "Maldives": { - "code": "mv", - "currency": "MVR", - "currency_fraction": "Laari", - "currency_fraction_units": 100, - "currency_name": "Rufiyaa", - "currency_symbol": ".\u0783", - "number_format": "#,###.##", + "code": "mv", + "currency": "MVR", + "currency_fraction": "Laari", + "currency_fraction_units": 100, + "currency_name": "Rufiyaa", + "currency_symbol": ".\u0783", + "number_format": "#,###.##", "timezones": [ "Indian/Maldives" ] - }, + }, "Mali": { - "code": "ml", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "ml", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Bamako" ] - }, + }, "Malta": { - "code": "mt", - "currency": "MTL", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Maltese Lira", - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "mt", + "currency": "MTL", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Maltese Lira", + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Malta" ] - }, + }, "Marshall Islands": { - "code": "mh", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "mh", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ - "Pacific/Kwajalein", + "Pacific/Kwajalein", "Pacific/Majuro" ] - }, + }, "Martinique": { - "code": "mq", - "number_format": "#,###.##", + "code": "mq", + "number_format": "#,###.##", "timezones": [ "America/Martinique" ] - }, + }, "Mauritania": { - "code": "mr", - "currency": "MRO", - "currency_fraction": "Khoums", - "currency_fraction_units": 5, - "currency_name": "Ouguiya", - "currency_symbol": "UM", - "number_format": "#,###.##", + "code": "mr", + "currency": "MRO", + "currency_fraction": "Khoums", + "currency_fraction_units": 5, + "currency_name": "Ouguiya", + "currency_symbol": "UM", + "number_format": "#,###.##", "timezones": [ "Africa/Nouakchott" ] - }, + }, "Mauritius": { - "code": "mu", - "currency": "MUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Mauritius Rupee", - "currency_symbol": "\u20a8", - "number_format": "#,###", + "code": "mu", + "currency": "MUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Mauritius Rupee", + "currency_symbol": "\u20a8", + "number_format": "#,###", "timezones": [ "Indian/Mauritius" ] - }, + }, "Mayotte": { - "code": "yt", - "number_format": "#,###.##", + "code": "yt", + "number_format": "#,###.##", "timezones": [ "Indian/Mayotte" ] - }, + }, "Mexico": { - "code": "mx", - "currency": "MXN", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Mexican Peso", - "currency_symbol": "$", - "number_format": "#,###.##", - "timezones": [ - "America/Bahia_Banderas", - "America/Cancun", - "America/Chihuahua", - "America/Hermosillo", - "America/Matamoros", - "America/Mazatlan", - "America/Merida", - "America/Mexico_City", - "America/Monterrey", - "America/Ojinaga", - "America/Santa_Isabel", + "code": "mx", + "currency": "MXN", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Mexican Peso", + "currency_symbol": "$", + "number_format": "#,###.##", + "timezones": [ + "America/Bahia_Banderas", + "America/Cancun", + "America/Chihuahua", + "America/Hermosillo", + "America/Matamoros", + "America/Mazatlan", + "America/Merida", + "America/Mexico_City", + "America/Monterrey", + "America/Ojinaga", + "America/Santa_Isabel", "America/Tijuana" ] - }, + }, "Micronesia, Federated States of": { - "code": "fm", + "code": "fm", "number_format": "#,###.##" - }, + }, "Moldova, Republic of": { - "code": "md", - "currency": "MDL", - "currency_name": "Moldovan Leu", + "code": "md", + "currency": "MDL", + "currency_name": "Moldovan Leu", "number_format": "#,###.##" - }, + }, "Monaco": { - "code": "mc", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "mc", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Monaco" ] - }, + }, "Mongolia": { - "code": "mn", - "currency": "MNT", - "currency_fraction": "M\u00f6ng\u00f6", - "currency_fraction_units": 100, - "currency_name": "Tugrik", - "currency_symbol": "\u20ae", - "date_format": "yyyy-mm-dd", - "number_format": "#,###.##", - "timezones": [ - "Asia/Choibalsan", - "Asia/Hovd", + "code": "mn", + "currency": "MNT", + "currency_fraction": "M\u00f6ng\u00f6", + "currency_fraction_units": 100, + "currency_name": "Tugrik", + "currency_symbol": "\u20ae", + "date_format": "yyyy-mm-dd", + "number_format": "#,###.##", + "timezones": [ + "Asia/Choibalsan", + "Asia/Hovd", "Asia/Ulaanbaatar" ] - }, + }, "Montenegro": { - "code": "me", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "me", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ - "Europe/Podgorica" + "Europe/Belgrade" ] - }, + }, "Montserrat": { - "code": "ms", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "ms", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Montserrat" ] - }, + }, "Morocco": { - "code": "ma", - "currency": "MAD", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_name": "Moroccan Dirham", - "currency_symbol": "\u062f.\u0645.", - "number_format": "#,###.##", + "code": "ma", + "currency": "MAD", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_name": "Moroccan Dirham", + "currency_symbol": "\u062f.\u0645.", + "number_format": "#,###.##", "timezones": [ "Africa/Casablanca" ] - }, + }, "Mozambique": { - "code": "mz", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_symbol": "MT", - "number_format": "#,###.##", + "code": "mz", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_symbol": "MT", + "number_format": "#,###.##", "timezones": [ "Africa/Maputo" ] - }, + }, "Myanmar": { - "code": "mm", - "currency": "MMK", - "currency_name": "Kyat", - "number_format": "#,###.##", + "code": "mm", + "currency": "MMK", + "currency_name": "Kyat", + "number_format": "#,###.##", "timezones": [ "Asia/Rangoon" ] - }, + }, "Namibia": { - "code": "na", - "currency": "NAD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Namibia Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "na", + "currency": "NAD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Namibia Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Africa/Windhoek" ] - }, + }, "Nauru": { - "code": "nr", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "nr", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Pacific/Nauru" ] - }, + }, "Nepal": { - "code": "np", - "currency": "NPR", - "currency_fraction": "Paisa", - "currency_fraction_units": 100, - "currency_name": "Nepalese Rupee", - "currency_symbol": "\u20a8", - "number_format": "#,###.##", - "timezones": [ - "Asia/Kathmandu", - "Asia/Katmandu" - ] - }, + "code": "np", + "currency": "NPR", + "currency_fraction": "Paisa", + "currency_fraction_units": 100, + "currency_name": "Nepalese Rupee", + "currency_symbol": "\u20a8", + "number_format": "#,###.##", + "timezones": [ + "Asia/Kathmandu" + ] + }, "Netherlands": { - "code": "nl", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "nl", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ "Europe/Amsterdam" ] - }, + }, "New Caledonia": { - "code": "nc", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "nc", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Pacific/Noumea" ] - }, + }, "New Zealand": { - "code": "nz", - "currency": "NZD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "New Zealand Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", - "timezones": [ - "Pacific/Auckland", + "code": "nz", + "currency": "NZD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "New Zealand Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", + "timezones": [ + "Pacific/Auckland", "Pacific/Chatham" ] - }, + }, "Nicaragua": { - "code": "ni", - "currency": "NIO", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Cordoba Oro", - "currency_symbol": "C$", - "number_format": "#,###.##", + "code": "ni", + "currency": "NIO", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Cordoba Oro", + "currency_symbol": "C$", + "number_format": "#,###.##", "timezones": [ "America/Managua" ] - }, + }, "Niger": { - "code": "ne", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "ne", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Niamey" ] - }, + }, "Nigeria": { - "code": "ng", - "currency": "NGN", - "currency_fraction": "Kobo", - "currency_fraction_units": 100, - "currency_name": "Naira", - "currency_symbol": "\u20a6", - "number_format": "#,###.##", + "code": "ng", + "currency": "NGN", + "currency_fraction": "Kobo", + "currency_fraction_units": 100, + "currency_name": "Naira", + "currency_symbol": "\u20a6", + "number_format": "#,###.##", "timezones": [ "Africa/Lagos" ] - }, + }, "Niue": { - "code": "nu", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "nu", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Pacific/Niue" ] - }, + }, "Norfolk Island": { - "code": "nf", - "number_format": "#,###.##", + "code": "nf", + "number_format": "#,###.##", "timezones": [ "Pacific/Norfolk" ] - }, + }, "Northern Mariana Islands": { - "code": "mp", - "number_format": "#,###.##", + "code": "mp", + "number_format": "#,###.##", "timezones": [ "Pacific/Saipan" ] - }, + }, "Norway": { - "code": "no", - "currency": "NOK", - "currency_fraction": "\u00d8re", - "currency_fraction_units": 100, - "currency_name": "Norwegian Krone", - "currency_symbol": "kr", - "number_format": "#.###,##", + "code": "no", + "currency": "NOK", + "currency_fraction": "\u00d8re", + "currency_fraction_units": 100, + "currency_name": "Norwegian Krone", + "currency_symbol": "kr", + "number_format": "#.###,##", "timezones": [ "Europe/Oslo" ] - }, + }, "Oman": { - "code": "om", - "currency": "OMR", - "currency_fraction": "Baisa", - "currency_fraction_units": 1000, - "currency_name": "Rial Omani", - "currency_symbol": "\u0631.\u0639.", - "number_format": "#,###.###", + "code": "om", + "currency": "OMR", + "currency_fraction": "Baisa", + "currency_fraction_units": 1000, + "currency_name": "Rial Omani", + "currency_symbol": "\u0631.\u0639.", + "number_format": "#,###.###", "timezones": [ "Asia/Muscat" ] - }, + }, "Pakistan": { - "code": "pk", - "currency": "PKR", - "currency_fraction": "Paisa", - "currency_fraction_units": 100, - "currency_name": "Pakistan Rupee", - "currency_symbol": "\u20a8", - "number_format": "#,###.##", + "code": "pk", + "currency": "PKR", + "currency_fraction": "Paisa", + "currency_fraction_units": 100, + "currency_name": "Pakistan Rupee", + "currency_symbol": "\u20a8", + "number_format": "#,###.##", "timezones": [ "Asia/Karachi" ] - }, + }, "Palau": { - "code": "pw", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "date_format": "mm-dd-yyyy", - "number_format": "#,###.##", + "code": "pw", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "date_format": "mm-dd-yyyy", + "number_format": "#,###.##", "timezones": [ "Pacific/Palau" ] - }, + }, "Palestinian Territory, Occupied": { "number_format": "#,###.##" - }, + }, "Panama": { - "code": "pa", - "currency_fraction": "Cent\u00e9simo", - "currency_fraction_units": 100, - "currency_symbol": "B/.", - "number_format": "#,###.##", + "code": "pa", + "currency_fraction": "Cent\u00e9simo", + "currency_fraction_units": 100, + "currency_symbol": "B/.", + "number_format": "#,###.##", "timezones": [ "America/Panama" ] - }, + }, "Papua New Guinea": { - "code": "pg", - "currency": "PGK", - "currency_fraction": "Toea", - "currency_fraction_units": 100, - "currency_name": "Kina", - "currency_symbol": "K", - "number_format": "#,###.##", + "code": "pg", + "currency": "PGK", + "currency_fraction": "Toea", + "currency_fraction_units": 100, + "currency_name": "Kina", + "currency_symbol": "K", + "number_format": "#,###.##", "timezones": [ "Pacific/Port_Moresby" ] - }, + }, "Paraguay": { - "code": "py", - "currency": "PYG", - "currency_fraction": "C\u00e9ntimo", - "currency_fraction_units": 100, - "currency_name": "Guarani", - "currency_symbol": "\u20b2", - "number_format": "#,###.##", + "code": "py", + "currency": "PYG", + "currency_fraction": "C\u00e9ntimo", + "currency_fraction_units": 100, + "currency_name": "Guarani", + "currency_symbol": "\u20b2", + "number_format": "#,###.##", "timezones": [ "America/Asuncion" ] - }, + }, "Peru": { - "code": "pe", - "currency": "PEN", - "currency_fraction": "C\u00e9ntimo", - "currency_fraction_units": 100, - "currency_name": "Nuevo Sol", - "currency_symbol": "S/.", - "number_format": "#,###.##", + "code": "pe", + "currency": "PEN", + "currency_fraction": "C\u00e9ntimo", + "currency_fraction_units": 100, + "currency_name": "Nuevo Sol", + "currency_symbol": "S/.", + "number_format": "#,###.##", "timezones": [ "America/Lima" ] - }, + }, "Philippines": { - "code": "ph", - "currency": "PHP", - "currency_fraction": "Centavo", - "currency_fraction_units": 100, - "currency_name": "Philippine Peso", - "currency_symbol": "\u20b1", - "date_format": "mm-dd-yyyy", - "number_format": "#,###.##", + "code": "ph", + "currency": "PHP", + "currency_fraction": "Centavo", + "currency_fraction_units": 100, + "currency_name": "Philippine Peso", + "currency_symbol": "\u20b1", + "date_format": "mm-dd-yyyy", + "number_format": "#,###.##", "timezones": [ "Asia/Manila" ] - }, + }, "Pitcairn": { - "code": "pn", - "number_format": "#,###.##", + "code": "pn", + "number_format": "#,###.##", "timezones": [ "Pacific/Pitcairn" ] - }, + }, "Poland": { - "code": "pl", - "currency_fraction": "Grosz", - "currency_fraction_units": 100, - "currency_symbol": "z\u0142", - "number_format": "#,###.##", + "code": "pl", + "currency": "PLN", + "currency_fraction": "Grosz", + "currency_fraction_units": 100, + "currency_symbol": "z\u0142", + "number_format": "#.###,##", "timezones": [ "Europe/Warsaw" ] - }, + }, "Portugal": { - "code": "pt", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", - "timezones": [ - "Atlantic/Azores", - "Atlantic/Madeira", + "code": "pt", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", + "timezones": [ + "Atlantic/Azores", + "Atlantic/Madeira", "Europe/Lisbon" ] - }, + }, "Puerto Rico": { - "code": "pr", - "number_format": "#,###.##", + "code": "pr", + "number_format": "#,###.##", "timezones": [ "America/Puerto_Rico" ] - }, + }, "Qatar": { - "code": "qa", - "currency": "QAR", - "currency_fraction": "Dirham", - "currency_fraction_units": 100, - "currency_name": "Qatari Rial", - "currency_symbol": "\u0631.\u0642", - "number_format": "#,###.##", + "code": "qa", + "currency": "QAR", + "currency_fraction": "Dirham", + "currency_fraction_units": 100, + "currency_name": "Qatari Rial", + "currency_symbol": "\u0631.\u0642", + "number_format": "#,###.##", "timezones": [ "Asia/Qatar" ] - }, + }, "Romania": { - "code": "ro", - "currency_fraction": "Ban", - "currency_fraction_units": 100, - "currency_symbol": "L", - "number_format": "#,###.##", + "code": "ro", + "currency_fraction": "Ban", + "currency_fraction_units": 100, + "currency_symbol": "L", + "number_format": "#,###.##", "timezones": [ "Europe/Bucharest" ] - }, + }, "Russian Federation": { - "code": "ru", - "currency": "RUB", - "currency_name": "Russian Ruble", + "code": "ru", + "currency": "RUB", + "currency_name": "Russian Ruble", "number_format": "#.###,##" - }, + }, "Rwanda": { - "code": "rw", - "currency": "RWF", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_name": "Rwanda Franc", - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "rw", + "currency": "RWF", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_name": "Rwanda Franc", + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Kigali" ] - }, + }, "R\u00e9union": { - "code": "re", + "code": "re", "number_format": "#,###.##" - }, + }, "Saint Barth\u00e9lemy": { - "code": "bl", + "code": "bl", "number_format": "#,###.##" - }, + }, "Saint Helena, Ascension and Tristan da Cunha": { - "code": "sh", - "currency": "SHP", - "currency_name": "Saint Helena Pound", + "code": "sh", + "currency": "SHP", + "currency_name": "Saint Helena Pound", "number_format": "#,###.##" - }, + }, "Saint Kitts and Nevis": { - "code": "kn", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "kn", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/St_Kitts" ] - }, + }, "Saint Lucia": { - "code": "lc", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "lc", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/St_Lucia" ] - }, + }, "Saint Martin (French part)": { - "code": "mf", + "code": "mf", "number_format": "#,###.##" - }, + }, "Saint Pierre and Miquelon": { - "code": "pm", + "code": "pm", "number_format": "#,###.##" - }, + }, "Saint Vincent and the Grenadines": { - "code": "vc", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "vc", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/St_Vincent" ] - }, + }, "Samoa": { - "code": "ws", - "currency": "WST", - "currency_fraction": "Sene", - "currency_fraction_units": 100, - "currency_name": "Tala", - "currency_symbol": "T", - "number_format": "#,###.##", + "code": "ws", + "currency": "WST", + "currency_fraction": "Sene", + "currency_fraction_units": 100, + "currency_name": "Tala", + "currency_symbol": "T", + "number_format": "#,###.##", "timezones": [ "Pacific/Apia" ] - }, + }, "San Marino": { - "code": "sm", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "sm", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ - "Europe/San_Marino" + "Europe/Rome" ] - }, + }, "Sao Tome and Principe": { - "code": "st", - "currency": "STD", - "currency_name": "Dobra", + "code": "st", + "currency": "STD", + "currency_name": "Dobra", "number_format": "#,###.##" - }, + }, "Saudi Arabia": { - "code": "sa", - "currency": "SAR", - "currency_fraction": "Halala", - "currency_fraction_units": 100, - "currency_name": "Saudi Riyal", - "currency_symbol": "\u0631.\u0633", - "number_format": "#,###.##", + "code": "sa", + "currency": "SAR", + "currency_fraction": "Halala", + "currency_fraction_units": 100, + "currency_name": "Saudi Riyal", + "currency_symbol": "\u0631.\u0633", + "number_format": "#,###.##", "timezones": [ "Asia/Riyadh" ] - }, + }, "Senegal": { - "code": "sn", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "sn", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Dakar" ] - }, + }, "Serbia": { - "code": "rs", - "currency_fraction": "Para", - "currency_fraction_units": 100, - "currency_symbol": "\u0434\u0438\u043d. or din.", - "number_format": "#,###.##", + "code": "rs", + "currency_fraction": "Para", + "currency_fraction_units": 100, + "currency_symbol": "\u0434\u0438\u043d. or din.", + "number_format": "#,###.##", "timezones": [ "Europe/Belgrade" ] - }, + }, "Seychelles": { - "code": "sc", - "currency": "SCR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Seychelles Rupee", - "currency_symbol": "\u20a8", - "number_format": "#,###.##", + "code": "sc", + "currency": "SCR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Seychelles Rupee", + "currency_symbol": "\u20a8", + "number_format": "#,###.##", "timezones": [ "Indian/Mahe" ] - }, + }, "Sierra Leone": { - "code": "sl", - "currency": "SLL", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Leone", - "currency_symbol": "Le", - "number_format": "#,###.##", + "code": "sl", + "currency": "SLL", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Leone", + "currency_symbol": "Le", + "number_format": "#,###.##", "timezones": [ "Africa/Freetown" ] - }, + }, "Singapore": { - "code": "sg", - "currency": "SGD", - "currency_fraction": "Sen", - "currency_fraction_units": 100, - "currency_name": "Singapore Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "sg", + "currency": "SGD", + "currency_fraction": "Sen", + "currency_fraction_units": 100, + "currency_name": "Singapore Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Asia/Singapore" ] - }, + }, "Sint Maarten (Dutch part)": { - "code": "sx", + "code": "sx", "number_format": "#,###.##" - }, + }, "Slovakia": { - "code": "sk", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "sk", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ - "Europe/Bratislava" + "Europe/Prague" ] - }, + }, "Slovenia": { - "code": "si", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", + "code": "si", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", "timezones": [ - "Europe/Ljubljana" + "Europe/Belgrade" ] - }, + }, "Solomon Islands": { - "code": "sb", - "currency": "SBD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Solomon Islands Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "sb", + "currency": "SBD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Solomon Islands Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Pacific/Guadalcanal" ] - }, + }, "Somalia": { - "code": "so", - "currency": "SOS", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Somali Shilling", - "currency_symbol": "Sh", - "number_format": "#,###.##", + "code": "so", + "currency": "SOS", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Somali Shilling", + "currency_symbol": "Sh", + "number_format": "#,###.##", "timezones": [ "Africa/Mogadishu" ] - }, + }, "South Africa": { - "code": "za", - "currency": "ZAR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Rand", - "currency_symbol": "R", - "date_format": "yyyy-mm-dd", - "number_format": "# ###.##", + "code": "za", + "currency": "ZAR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Rand", + "currency_symbol": "R", + "date_format": "yyyy-mm-dd", + "number_format": "# ###.##", "timezones": [ "Africa/Johannesburg" ] - }, + }, "South Georgia and the South Sandwich Islands": { - "code": "gs", - "currency_fraction": "Penny", - "currency_fraction_units": 100, - "currency_symbol": "\u00a3", + "code": "gs", + "currency_fraction": "Penny", + "currency_fraction_units": 100, + "currency_symbol": "\u00a3", "number_format": "#,###.##" - }, + }, "South Sudan": { - "code": "ss", - "currency_fraction": "Piastre", - "currency_fraction_units": 100, - "currency_symbol": "\u00a3", - "number_format": "#,###.##", + "code": "ss", + "currency_fraction": "Piastre", + "currency_fraction_units": 100, + "currency_symbol": "\u00a3", + "number_format": "#,###.##", "timezones": [ "Africa/Juba" ] - }, + }, "Spain": { - "code": "es", - "currency": "EUR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "\u20ac", - "number_format": "#,###.##", - "timezones": [ - "Africa/Ceuta", - "Atlantic/Canary", + "code": "es", + "currency": "EUR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "\u20ac", + "number_format": "#,###.##", + "timezones": [ + "Africa/Ceuta", + "Atlantic/Canary", "Europe/Madrid" ] - }, + }, "Sri Lanka": { - "code": "lk", - "currency": "LKR", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Sri Lanka Rupee", - "currency_symbol": "Rs", - "number_format": "#,###.##", + "code": "lk", + "currency": "LKR", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Sri Lanka Rupee", + "currency_symbol": "Rs", + "number_format": "#,###.##", "timezones": [ "Asia/Colombo" ] - }, + }, "Sudan": { - "code": "sd", - "currency_fraction": "Piastre", - "currency_fraction_units": 100, - "currency_symbol": "\u00a3", - "number_format": "#,###.##", + "code": "sd", + "currency_fraction": "Piastre", + "currency_fraction_units": 100, + "currency_symbol": "\u00a3", + "number_format": "#,###.##", "timezones": [ "Africa/Khartoum" ] - }, + }, "Suriname": { - "code": "sr", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "sr", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Paramaribo" ] - }, + }, "Svalbard and Jan Mayen": { - "code": "sj", + "code": "sj", "number_format": "#,###.##" - }, + }, "Swaziland": { - "code": "sz", - "currency": "SZL", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Lilangeni", - "currency_symbol": "L", - "number_format": "#, ###.##", + "code": "sz", + "currency": "SZL", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Lilangeni", + "currency_symbol": "L", + "number_format": "#, ###.##", "timezones": [ "Africa/Mbabane" ] - }, + }, "Sweden": { - "code": "se", - "currency": "SEK", - "currency_fraction": "\u00d6re", - "currency_fraction_units": 100, - "currency_name": "Swedish Krona", - "currency_symbol": "kr", - "number_format": "#.###,##", + "code": "se", + "currency": "SEK", + "currency_fraction": "\u00d6re", + "currency_fraction_units": 100, + "currency_name": "Swedish Krona", + "currency_symbol": "kr", + "number_format": "#.###,##", "timezones": [ "Europe/Stockholm" ] - }, + }, "Switzerland": { - "code": "ch", - "currency": "CHF", - "currency_fraction": "Rappen[K]", - "currency_fraction_units": 100, - "currency_name": "Swiss Franc", - "currency_symbol": "Fr", - "number_format": "#'###.##", + "code": "ch", + "currency": "CHF", + "currency_fraction": "Rappen[K]", + "currency_fraction_units": 100, + "currency_name": "Swiss Franc", + "currency_symbol": "Fr", + "number_format": "#'###.##", "timezones": [ "Europe/Zurich" ] - }, + }, "Syrian Arab Republic": { - "code": "sy", - "currency": "SYP", - "currency_name": "Syrian Pound", + "code": "sy", + "currency": "SYP", + "currency_name": "Syrian Pound", "number_format": "#,###.##" - }, + }, "Taiwan, Province of China": { - "code": "tw", - "date_format": "yyyy-mm-dd", + "code": "tw", + "date_format": "yyyy-mm-dd", "number_format": "#,###.##" - }, + }, "Tajikistan": { - "code": "tj", - "currency_fraction": "Diram", - "currency_fraction_units": 100, - "currency_symbol": "\u0405\u041c", - "number_format": "#,###.##", + "code": "tj", + "currency_fraction": "Diram", + "currency_fraction_units": 100, + "currency_symbol": "\u0405\u041c", + "number_format": "#,###.##", "timezones": [ "Asia/Dushanbe" ] - }, + }, "Tanzania, United Republic of": { - "code": "tz", - "currency": "TZS", - "currency_name": "Tanzanian Shilling", + "code": "tz", + "currency": "TZS", + "currency_name": "Tanzanian Shilling", "number_format": "#,###.##" - }, + }, "Thailand": { - "code": "th", - "currency": "THB", - "currency_fraction": "Satang", - "currency_fraction_units": 100, - "currency_name": "Baht", - "currency_symbol": "\u0e3f", - "number_format": "#,###.##", + "code": "th", + "currency": "THB", + "currency_fraction": "Satang", + "currency_fraction_units": 100, + "currency_name": "Baht", + "currency_symbol": "\u0e3f", + "number_format": "#,###.##", "timezones": [ "Asia/Bangkok" ] - }, + }, "Timor-Leste": { - "code": "tl", + "code": "tl", "number_format": "#,###.##" - }, + }, "Togo": { - "code": "tg", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", - "number_format": "#,###.##", + "code": "tg", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", + "number_format": "#,###.##", "timezones": [ "Africa/Lome" ] - }, + }, "Tokelau": { - "code": "tk", - "number_format": "#,###.##", + "code": "tk", + "number_format": "#,###.##", "timezones": [ "Pacific/Fakaofo" ] - }, + }, "Tonga": { - "code": "to", - "currency": "TOP", - "currency_fraction": "Seniti[L]", - "currency_fraction_units": 100, - "currency_name": "Pa'anga", - "currency_symbol": "T$", - "number_format": "#,###.##", + "code": "to", + "currency": "TOP", + "currency_fraction": "Seniti[L]", + "currency_fraction_units": 100, + "currency_name": "Pa'anga", + "currency_symbol": "T$", + "number_format": "#,###.##", "timezones": [ "Pacific/Tongatapu" ] - }, + }, "Trinidad and Tobago": { - "code": "tt", - "currency": "TTD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Trinidad and Tobago Dollar", - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "tt", + "currency": "TTD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Trinidad and Tobago Dollar", + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "America/Port_of_Spain" ] - }, + }, "Tunisia": { - "code": "tn", - "currency": "TND", - "currency_fraction": "Millime", - "currency_fraction_units": 1000, - "currency_name": "Tunisian Dinar", - "currency_symbol": "\u062f.\u062a", - "number_format": "#,###.###", + "code": "tn", + "currency": "TND", + "currency_fraction": "Millime", + "currency_fraction_units": 1000, + "currency_name": "Tunisian Dinar", + "currency_symbol": "\u062f.\u062a", + "number_format": "#,###.###", "timezones": [ "Africa/Tunis" ] - }, + }, "Turkey": { - "code": "tr", - "currency_fraction": "Kuru\u015f", - "currency_fraction_units": 100, - "currency_symbol": "", - "number_format": "#,###.##", + "code": "tr", + "currency": "TRY", + "currency_fraction": "Kuru\u015f", + "currency_fraction_units": 100, + "currency_symbol": "\u20ba", + "number_format": "#,###.##", "timezones": [ "Europe/Istanbul" ] - }, + }, "Turkmenistan": { - "code": "tm", - "currency": "TMM", - "currency_fraction": "Tennesi", - "currency_fraction_units": 100, - "currency_name": "Manat", - "currency_symbol": "m", - "number_format": "#,###.##", + "code": "tm", + "currency": "TMM", + "currency_fraction": "Tennesi", + "currency_fraction_units": 100, + "currency_name": "Manat", + "currency_symbol": "m", + "number_format": "#,###.##", "timezones": [ "Asia/Ashgabat" ] - }, + }, "Turks and Caicos Islands": { - "code": "tc", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", + "code": "tc", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", "number_format": "#,###.##" - }, + }, "Tuvalu": { - "code": "tv", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_symbol": "$", - "number_format": "#,###.##", + "code": "tv", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_symbol": "$", + "number_format": "#,###.##", "timezones": [ "Pacific/Funafuti" ] - }, + }, "Uganda": { - "code": "ug", - "currency": "UGX", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "Uganda Shilling", - "currency_symbol": "Sh", - "number_format": "#,###.##", + "code": "ug", + "currency": "UGX", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "Uganda Shilling", + "currency_symbol": "Sh", + "number_format": "#,###.##", "timezones": [ "Africa/Kampala" ] - }, + }, "Ukraine": { - "code": "ua", - "currency_fraction": "Kopiyka", - "currency_fraction_units": 100, - "currency_symbol": "\u20b4", - "number_format": "#,###.##", - "timezones": [ - "Europe/Kiev", - "Europe/Simferopol", - "Europe/Uzhgorod", + "code": "ua", + "currency_fraction": "Kopiyka", + "currency_fraction_units": 100, + "currency_symbol": "\u20b4", + "number_format": "#,###.##", + "timezones": [ + "Europe/Kiev", + "Europe/Simferopol", + "Europe/Uzhgorod", "Europe/Zaporozhye" ] - }, + }, "United Arab Emirates": { - "code": "ae", - "currency": "AED", - "currency_fraction": "Fils", - "currency_fraction_units": 100, - "currency_name": "UAE Dirham", - "currency_symbol": "\u062f.\u0625", - "number_format": "#,###.##", + "code": "ae", + "currency": "AED", + "currency_fraction": "Fils", + "currency_fraction_units": 100, + "currency_name": "UAE Dirham", + "currency_symbol": "\u062f.\u0625", + "number_format": "#,###.##", "timezones": [ "Asia/Dubai" ] - }, + }, "United Kingdom": { - "code": "gb", - "currency": "GBP", - "currency_fraction": "Penny", - "currency_fraction_units": 100, - "currency_name": "Pound Sterling", - "currency_symbol": "\u00a3", - "number_format": "#,###.##", + "code": "gb", + "currency": "GBP", + "currency_fraction": "Penny", + "currency_fraction_units": 100, + "currency_name": "Pound Sterling", + "currency_symbol": "\u00a3", + "number_format": "#,###.##", "timezones": [ "Europe/London" ] - }, + }, "United States": { - "code": "us", - "currency": "USD", - "currency_fraction": "Cent", - "currency_fraction_units": 100, - "currency_name": "US Dollar", - "currency_symbol": "$", - "date_format": "mm-dd-yyyy", - "number_format": "#,###.##", - "timezones": [ - "America/Adak", - "America/Anchorage", - "America/Boise", - "America/Chicago", - "America/Denver", - "America/Detroit", - "America/Indiana/Indianapolis", - "America/Indiana/Knox", - "America/Indiana/Marengo", - "America/Indiana/Petersburg", - "America/Indiana/Tell_City", - "America/Indiana/Vevay", - "America/Indiana/Vincennes", - "America/Indiana/Winamac", - "America/Juneau", - "America/Kentucky/Louisville", - "America/Kentucky/Monticello", - "America/Los_Angeles", - "America/Menominee", - "America/Metlakatla", - "America/New_York", - "America/Nome", - "America/North_Dakota/Beulah", - "America/North_Dakota/Center", - "America/North_Dakota/New_Salem", - "America/Phoenix", - "America/Shiprock", - "America/Sitka", - "America/Yakutat", + "code": "us", + "currency": "USD", + "currency_fraction": "Cent", + "currency_fraction_units": 100, + "currency_name": "US Dollar", + "currency_symbol": "$", + "date_format": "mm-dd-yyyy", + "number_format": "#,###.##", + "timezones": [ + "America/Adak", + "America/Anchorage", + "America/Boise", + "America/Chicago", + "America/Denver", + "America/Detroit", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Los_Angeles", + "America/Menominee", + "America/Metlakatla", + "America/New_York", + "America/Nome", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Phoenix", + "America/Denver", + "America/Sitka", + "America/Yakutat", "Pacific/Honolulu" ] - }, + }, "United States Minor Outlying Islands": { - "code": "um", + "code": "um", "number_format": "#,###.##" - }, + }, "Uruguay": { - "code": "uy", - "currency": "UYU", - "currency_fraction": "Cent\u00e9simo", - "currency_fraction_units": 100, - "currency_name": "Peso Uruguayo", - "currency_symbol": "$", - "number_format": "#.###,##", + "code": "uy", + "currency": "UYU", + "currency_fraction": "Cent\u00e9simo", + "currency_fraction_units": 100, + "currency_name": "Peso Uruguayo", + "currency_symbol": "$", + "number_format": "#.###,##", "timezones": [ "America/Montevideo" ] - }, + }, "Uzbekistan": { - "code": "uz", - "currency": "UZS", - "currency_fraction": "Tiyin", - "currency_fraction_units": 100, - "currency_name": "Uzbekistan Sum", - "currency_symbol": "\u043b\u0432", - "number_format": "#,###.##", - "timezones": [ - "Asia/Samarkand", + "code": "uz", + "currency": "UZS", + "currency_fraction": "Tiyin", + "currency_fraction_units": 100, + "currency_name": "Uzbekistan Sum", + "currency_symbol": "\u043b\u0432", + "number_format": "#,###.##", + "timezones": [ + "Asia/Samarkand", "Asia/Tashkent" ] - }, + }, "Vanuatu": { - "code": "vu", - "currency": "VUV", - "currency_fraction": "None", - "currency_fraction_units": 0, - "currency_name": "Vatu", - "currency_symbol": "Vt", - "number_format": "#,###", + "code": "vu", + "currency": "VUV", + "currency_fraction": "None", + "currency_fraction_units": 0, + "currency_name": "Vatu", + "currency_symbol": "Vt", + "number_format": "#,###", "timezones": [ "Pacific/Efate" ] - }, + }, "Venezuela, Bolivarian Republic of": { - "code": "ve", + "code": "ve", "number_format": "#,###.##" - }, - "Viet Nam": { - "code": "vn", - "currency": "VND", - "currency_name": "Dong", + }, + "Vietnam": { + "code": "vn", + "currency": "VND", + "currency_name": "Dong", "number_format": "#.###" - }, + }, "Virgin Islands, British": { - "code": "vg", + "code": "vg", "number_format": "#,###.##" - }, + }, "Virgin Islands, U.S.": { - "code": "vi", + "code": "vi", "number_format": "#,###.##" - }, + }, "Wallis and Futuna": { - "code": "wf", - "currency_fraction": "Centime", - "currency_fraction_units": 100, - "currency_symbol": "Fr", + "code": "wf", + "currency_fraction": "Centime", + "currency_fraction_units": 100, + "currency_symbol": "Fr", "number_format": "#,###.##" - }, + }, "Western Sahara": { - "code": "eh", - "number_format": "#,###.##", + "code": "eh", + "number_format": "#,###.##", "timezones": [ "Africa/El_Aaiun" ] - }, + }, "Yemen": { - "code": "ye", - "currency_fraction": "Fils", - "currency_fraction_units": 100, - "currency_symbol": "\ufdfc", - "number_format": "#,###.##", + "code": "ye", + "currency_fraction": "Fils", + "currency_fraction_units": 100, + "currency_symbol": "\ufdfc", + "number_format": "#,###.##", "timezones": [ "Asia/Aden" ] - }, + }, "Zambia": { - "code": "zm", - "currency": "ZMK", - "currency_fraction": "Ngwee", - "currency_fraction_units": 100, - "currency_name": "Zambian Kwacha", - "currency_symbol": "ZK", - "number_format": "#,###.##", + "code": "zm", + "currency": "ZMK", + "currency_fraction": "Ngwee", + "currency_fraction_units": 100, + "currency_name": "Zambian Kwacha", + "currency_symbol": "ZK", + "number_format": "#,###.##", "timezones": [ "Africa/Lusaka" ] - }, + }, "Zimbabwe": { - "code": "zw", - "currency": "ZWD", - "currency_fraction": "Thebe", - "currency_fraction_units": 100, - "currency_name": "Zimbabwe Dollar", - "currency_symbol": "P", - "number_format": "# ###.##", + "code": "zw", + "currency": "ZWD", + "currency_fraction": "Thebe", + "currency_fraction_units": 100, + "currency_name": "Zimbabwe Dollar", + "currency_symbol": "P", + "number_format": "# ###.##", "timezones": [ "Africa/Harare" ] - }, + }, "\u00c5land Islands": { - "code": "ax", + "code": "ax", "number_format": "#,###.##" } -} \ No newline at end of file +} diff --git a/frappe/country_info.py b/frappe/country_info.py index 81b045acd3..d3a7c94504 100644 --- a/frappe/country_info.py +++ b/frappe/country_info.py @@ -5,13 +5,14 @@ from __future__ import unicode_literals import os, json, frappe +from frappe.utils.momentjs import get_all_timezones def get_country_info(country=None): data = get_all() data = frappe._dict(data.get(country, {})) if not 'date_format' in data: data.date_format = "dd-mm-yyyy" - + return data def get_all(): @@ -19,26 +20,23 @@ def get_all(): all_data = json.loads(local_info.read()) return all_data -@frappe.whitelist() +@frappe.whitelist() def get_country_timezone_info(): - import pytz return { "country_info": get_all(), - "all_timezones": pytz.all_timezones + "all_timezones": get_all_timezones() } def update(): with open(os.path.join(os.path.dirname(__file__), "currency_info.json"), "r") as nformats: nformats = json.loads(nformats.read()) - + all_data = get_all() - + for country in all_data: data = all_data[country] - data["number_format"] = nformats.get(data.get("currency", "default"), + data["number_format"] = nformats.get(data.get("currency", "default"), nformats.get("default"))["display"] - - print all_data - + with open(os.path.join(os.path.dirname(__file__), "country_info.json"), "w") as local_info: local_info.write(json.dumps(all_data, indent=1)) diff --git a/frappe/data/Framework.sql b/frappe/data/Framework.sql index 79c0990491..5a2c1cd82d 100644 --- a/frappe/data/Framework.sql +++ b/frappe/data/Framework.sql @@ -35,7 +35,7 @@ CREATE TABLE `tabDocField` ( `trigger` varchar(255) DEFAULT NULL, `depends_on` varchar(255) DEFAULT NULL, `permlevel` int(11) DEFAULT '0', - `ignore_restrictions` int(1) DEFAULT NULL, + `ignore_user_permissions` int(1) DEFAULT NULL, `width` varchar(255) DEFAULT NULL, `print_width` varchar(255) DEFAULT NULL, `default` text, @@ -130,7 +130,6 @@ CREATE TABLE `tabDocType` ( `allow_import` int(1) DEFAULT NULL, `hide_toolbar` int(1) DEFAULT NULL, `hide_heading` int(1) DEFAULT NULL, - `allow_attach` int(1) DEFAULT NULL, `use_template` int(1) DEFAULT NULL, `max_attachments` int(11) DEFAULT NULL, `print_outline` varchar(255) DEFAULT NULL, diff --git a/frappe/data/languages.txt b/frappe/data/languages.txt index ed686d7802..6337ae8439 100644 --- a/frappe/data/languages.txt +++ b/frappe/data/languages.txt @@ -6,13 +6,21 @@ es español fr français hi हिंदी hr hrvatski +id Indonesia it italiano +ja 日本語 kn ಕನ್ನಡ +ko 한국의 nl nederlands -pt-BR português brasileiro +pl polski pt português +pt-BR português brasileiro +ro român +ru русский sr српски ta தமிழ் th ไทย +tr Türk +vi việt zh-cn 中国(简体) zh-tw 中國(繁體) diff --git a/frappe/data/sample_site_config.json b/frappe/data/sample_site_config.json index 3c7c6e2c43..21f7f5910c 100644 --- a/frappe/data/sample_site_config.json +++ b/frappe/data/sample_site_config.json @@ -1,20 +1,20 @@ { - "db_name": "testdb", + "db_name": "testdb", "db_password": "password", "mute_emails": true, - + "developer_mode": 1, "auto_cache_clear": true, "disable_website_cache": true, "max_file_size": 1000000, - + "mail_server": "localhost", "mail_login": null, "mail_password": null, "mail_port": 25, "use_ssl": 0, "auto_email_id": "hello@example.com", - + "google_login": { "client_id": "google_client_id", "client_secret": "google_client_secret" @@ -27,8 +27,9 @@ "client_id": "facebook_client_id", "client_secret": "facebook_client_secret" }, - + "celery_broker": "redis://localhost", "celery_result_backend": null, - "scheduler_interval": 300 -} \ No newline at end of file + "scheduler_interval": 300, + "celery_queue_per_site": true +} diff --git a/frappe/database.py b/frappe/database.py index a0fbe8c827..4f72cfdf6c 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -9,6 +9,7 @@ import MySQLdb import warnings import datetime import frappe +import re import frappe.model.meta from frappe.utils import now, get_datetime from frappe import _ @@ -169,7 +170,7 @@ class Database: if query[:6].lower() in ['update', 'insert']: self.transaction_writes += 1 - if self.transaction_writes > 10000: + if self.transaction_writes > 20000: if self.auto_commit_on_many_writes: frappe.db.commit() else: @@ -367,16 +368,20 @@ class Database: return frappe._dict(self.sql("""select field, value from tabSingles where doctype=%s""", doctype)) + def get_single_value(self, doctype, fieldname): + val = self.sql("""select value from + tabSingles where doctype=%s and field=%s""", (doctype, fieldname)) + return val[0][0] if val else None def get_values_from_table(self, fields, filters, doctype, as_dict, debug, order_by=None, update=None): fl = [] if isinstance(fields, (list, tuple)): for f in fields: - if "(" in f: # function + if "(" in f or " as " in f: # function fl.append(f) else: fl.append("`" + f + "`") - fl = ", ".join(fields) + fl = ", ".join(fl) else: fl = fields if fields=="*": @@ -485,6 +490,9 @@ class Database: def table_exists(self, tablename): return tablename in [d[0] for d in self.sql("show tables")] + def a_row_exists(self, doctype): + return self.sql("select name from `tab{doctype}` limit 1".format(doctype=doctype)) + def exists(self, dt, dn=None): if isinstance(dt, basestring): if dt!="DocType" and dt==dn: @@ -529,10 +537,14 @@ class Database: def add_index(self, doctype, fields, index_name=None): if not index_name: index_name = "_".join(fields) + "_index" + + # remove index length if present e.g. (10) from index name + index_name = re.sub(r"\s*\([^)]+\)\s*", r"", index_name) + if not frappe.db.sql("""show index from `tab%s` where Key_name="%s" """ % (doctype, index_name)): frappe.db.commit() frappe.db.sql("""alter table `tab%s` - add index %s(%s)""" % (doctype, index_name, ", ".join(fields))) + add index `%s`(%s)""" % (doctype, index_name, ", ".join(fields))) def close(self): if self._conn: @@ -540,3 +552,6 @@ class Database: self._conn.close() self._cursor = None self._conn = None + + def escape(self, s): + return unicode(MySQLdb.escape_string((s or "").encode("utf-8")), "utf-8") diff --git a/frappe/defaults.py b/frappe/defaults.py index de368525d2..3f96d524bc 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -3,6 +3,10 @@ from __future__ import unicode_literals import frappe +from frappe.core.doctype.notification_count.notification_count import clear_notifications + +# Note: DefaultValue records are identified by parenttype +# __default, __global or 'User Permission' common_keys = ["__default", "__global"] @@ -20,26 +24,25 @@ def get_user_default_as_list(key, user=None): d = get_defaults(user or frappe.session.user).get(key, None) return (not isinstance(d, list)) and [d] or d -def get_restrictions(user=None): +def get_user_permissions(user=None): if not user: user = frappe.session.user - if user == frappe.session.user: - if frappe.local.restrictions is None: - frappe.local.restrictions = build_restrictions(user) - return frappe.local.restrictions - else: - return build_restrictions(user) + return build_user_permissions(user) -def build_restrictions(user): - out = frappe.cache().get_value("restrictions:" + user) +def build_user_permissions(user): + out = frappe.cache().get_value("user_permissions:" + user) if out==None: out = {} - for key, value in frappe.db.sql("""select defkey, defvalue - from tabDefaultValue where parent=%s and parenttype='Restriction'""", (user,)): - out.setdefault(key, []) - out[key].append(value) - frappe.cache().set_value("restrictions:" + user, out) + for key, value in frappe.db.sql("""select defkey, ifnull(defvalue, '') as defvalue + from tabDefaultValue where parent=%s and parenttype='User Permission'""", (user,)): + out.setdefault(key, []).append(value) + + # add profile match + if user not in out.get("User", []): + out.setdefault("User", []).append(user) + + frappe.cache().set_value("user_permissions:" + user, out) return out def get_defaults(user=None): @@ -91,45 +94,45 @@ def add_default(key, value, parent, parenttype=None): "defvalue": value }) d.db_insert() - if parenttype=="Restriction": - frappe.local.restrictions = None _clear_cache(parent) def clear_default(key=None, value=None, parent=None, name=None, parenttype=None): conditions = [] values = [] - if key: - conditions.append("defkey=%s") - values.append(key) - - if value: - conditions.append("defvalue=%s") - values.append(value) - if name: conditions.append("name=%s") values.append(name) + else: + if key: + conditions.append("defkey=%s") + values.append(key) + + if value: + conditions.append("defvalue=%s") + values.append(value) + + if parent: + conditions.append("parent=%s") + values.append(parent) + + if parenttype: + conditions.append("parenttype=%s") + values.append(parenttype) + if parent: - conditions.append("parent=%s") clear_cache(parent) - values.append(parent) else: clear_cache("__default") clear_cache("__global") - if parenttype: - conditions.append("parenttype=%s") - values.append(parenttype) - if parenttype=="Restriction": - frappe.local.restrictions = None - if not conditions: raise Exception, "[clear_default] No key specified." frappe.db.sql("""delete from tabDefaultValue where {0}""".format(" and ".join(conditions)), tuple(values)) + _clear_cache(parent) def get_defaults_for(parent="__default"): @@ -159,6 +162,7 @@ def _clear_cache(parent): if parent in common_keys: frappe.clear_cache() else: + clear_notifications(user=parent) frappe.clear_cache(user=parent) def clear_cache(user=None): diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 805d68315b..76af10a8dd 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -5,6 +5,9 @@ from __future__ import unicode_literals # BEWARE don't put anything in this file except exceptions +from werkzeug.exceptions import NotFound +from MySQLdb import ProgrammingError as SQLError + class ValidationError(Exception): http_status_code = 417 @@ -40,7 +43,10 @@ class RateLimitExceededError(ValidationError): pass class CannotChangeConstantError(ValidationError): pass class UpdateAfterSubmitError(ValidationError): pass class LinkValidationError(ValidationError): pass +class CancelledLinkError(LinkValidationError): pass class DocstatusTransitionError(ValidationError): pass class TimestampMismatchError(ValidationError): pass class EmptyTableError(ValidationError): pass class LinkExistsError(ValidationError): pass +class InvalidEmailAddressError(ValidationError): pass +class TemplateNotFoundError(ValidationError): pass diff --git a/frappe/handler.py b/frappe/handler.py index 2762e51b9d..b9ed28f93c 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -69,6 +69,11 @@ def handle(): def execute_cmd(cmd): """execute a request as python module""" + for hook in frappe.get_hooks("override_whitelisted_methods", {}).get(cmd, []): + # override using the first hook + cmd = hook + break + method = get_attr(cmd) # check if whitelisted @@ -87,11 +92,6 @@ def execute_cmd(cmd): if ret: frappe.response['message'] = ret - # update session - if "session_obj" in frappe.local: - frappe.local.session_obj.update() - - def get_attr(cmd): """get method object from cmd""" if '.' in cmd: diff --git a/frappe/hooks.py b/frappe/hooks.py index 95f2299710..d0b51a5da0 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -1,10 +1,11 @@ app_name = "frappe" app_title = "Frappe Framework" -app_publisher = "Web Notes Technologies Pvt. Ltd. and Contributors" +app_publisher = "Web Notes Technologies Pvt. Ltd." app_description = "Full Stack Web Application Framwork in Python" app_icon = "assets/frappe/images/frappe.svg" -app_version = "4.0.0-wip" +app_version = "4.3.0" app_color = "#3498db" +app_email = "support@frappe.io" before_install = "frappe.utils.install.before_install" after_install = "frappe.utils.install.after_install" @@ -24,7 +25,7 @@ web_include_css = [ "style_settings.css" ] -website_clear_cache = "frappe.templates.generators.website_group.clear_cache" +website_clear_cache = "frappe.website.doctype.website_group.website_group.clear_cache" write_file_keys = ["file_url", "file_name"] @@ -32,43 +33,55 @@ notification_config = "frappe.core.notifications.get_notification_config" before_tests = "frappe.utils.install.before_tests" +website_generators = ["Web Page", "Blog Post", "Website Group", "Blog Category", "Web Form"] + # permissions permission_query_conditions = { - "Event": "frappe.core.doctype.event.event.get_permission_query_conditions", - "ToDo": "frappe.core.doctype.todo.todo.get_permission_query_conditions" - } + "Event": "frappe.core.doctype.event.event.get_permission_query_conditions", + "ToDo": "frappe.core.doctype.todo.todo.get_permission_query_conditions", + "User": "frappe.core.doctype.user.user.get_permission_query_conditions" +} has_permission = { - "Event": "frappe.core.doctype.event.event.has_permission", - "ToDo": "frappe.core.doctype.todo.todo.has_permission" - } - -# bean + "Event": "frappe.core.doctype.event.event.has_permission", + "ToDo": "frappe.core.doctype.todo.todo.has_permission", + "User": "frappe.core.doctype.user.user.has_permission" +} doc_events = { - "*": { - "on_update": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", - "on_cancel": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", - "on_trash": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications" - }, - "User Vote": { - "after_insert": "frappe.templates.generators.website_group.clear_cache_on_doc_event" - }, - "Website Route Permission": { - "on_update": "frappe.templates.generators.website_group.clear_cache_on_doc_event" - } + "*": { + "after_insert": "frappe.core.doctype.email_alert.email_alert.trigger_email_alerts", + "validate": "frappe.core.doctype.email_alert.email_alert.trigger_email_alerts", + "on_update": [ + "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", + "frappe.core.doctype.email_alert.email_alert.trigger_email_alerts" + ], + "after_rename": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", + "on_submit": "frappe.core.doctype.email_alert.email_alert.trigger_email_alerts", + "on_cancel": [ + "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", + "frappe.core.doctype.email_alert.email_alert.trigger_email_alerts" + ], + "on_trash": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications" + }, + "Website Route Permission": { + "on_update": "frappe.website.doctype.website_group.website_group.clear_cache_on_doc_event" } +} scheduler_events = { "all": ["frappe.utils.email_lib.bulk.flush"], "daily": [ "frappe.utils.email_lib.bulk.clear_outbox", - "frappe.core.doctype.notification_count.notification_count.delete_event_notification_count", + "frappe.core.doctype.notification_count.notification_count.clear_notifications", "frappe.core.doctype.event.event.send_event_digest", "frappe.sessions.clear_expired_sessions", + "frappe.core.doctype.email_alert.email_alert.trigger_daily_alerts", ], "hourly": [ - "frappe.templates.generators.website_group.clear_event_cache" + "frappe.website.doctype.website_group.website_group.clear_event_cache" ] } + +mail_footer = "frappe.core.doctype.outgoing_email_settings.outgoing_email_settings.get_mail_footer" diff --git a/frappe/installer.py b/frappe/installer.py index 2dc37116d1..96e04a7aff 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -10,10 +10,10 @@ import os, json import frappe import frappe.database import getpass -from frappe import _ from frappe.model.db_schema import DbManager from frappe.model.sync import sync_for from frappe.utils.fixtures import sync_fixtures +from frappe.website import render, statics def install_db(root_login="root", root_password=None, db_name=None, source_sql=None, admin_password = 'admin', verbose=True, force=0, site_config=None, reinstall=False): @@ -94,7 +94,7 @@ def install_app(name, verbose=False, set_as_patched=True): if name in installed_apps: print "App Already Installed" - frappe.msgprint(_("App Already Installed")) + frappe.msgprint("App {0} already installed".format(name)) return if name != "frappe": @@ -116,20 +116,23 @@ def install_app(name, verbose=False, set_as_patched=True): for after_install in app_hooks.after_install or []: frappe.get_attr(after_install)() - sync_fixtures() + print "Installing Fixtures..." + sync_fixtures(name) frappe.flags.in_install_app = False -def add_to_installed_apps(app_name, rebuild_sitemap=True): +def add_to_installed_apps(app_name, rebuild_website=True): installed_apps = frappe.get_installed_apps() if not app_name in installed_apps: installed_apps.append(app_name) frappe.db.set_global("installed_apps", json.dumps(installed_apps)) frappe.db.commit() - if rebuild_sitemap: - from frappe.website.doctype.website_template.website_template import rebuild_website_template - rebuild_website_template() + if rebuild_website: + render.clear_cache() + statics.sync().start() + + frappe.db.commit() frappe.clear_cache() @@ -190,5 +193,5 @@ def add_module_defs(app): for module in modules: d = frappe.new_doc("Module Def") d.app_name = app - d.module_name = frappe.unscrub(module) + d.module_name = module d.save() diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 067df6bc27..f6d3af095f 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -7,9 +7,9 @@ import frappe import json -no_value_fields = ['Section Break', 'Column Break', 'HTML', 'Table', 'Button', 'Image'] +no_value_fields = ['Section Break', 'Column Break', 'HTML', 'Table', 'Button', 'Image', 'Fold'] default_fields = ['doctype','name','owner','creation','modified','modified_by','parent','parentfield','parenttype','idx','docstatus'] -integer_docfield_properties = ["reqd", "search_index", "in_list_view", "permlevel", "hidden", "read_only", "ignore_restrictions", "allow_on_submit", "report_hide", "in_filter", "no_copy", "print_hide"] +integer_docfield_properties = ["reqd", "search_index", "in_list_view", "permlevel", "hidden", "read_only", "ignore_user_permissions", "allow_on_submit", "report_hide", "in_filter", "no_copy", "print_hide"] def insert(doclist): if not isinstance(doclist, list): @@ -113,7 +113,7 @@ def rename_field(doctype, old_fieldname, new_fieldname): where doc_type=%s and field_name=%s""", (new_fieldname, doctype, old_fieldname)) update_reports(doctype, old_fieldname, new_fieldname) - update_users_report_view_settings(doctype, old_fieldname) + update_users_report_view_settings(doctype, old_fieldname, new_fieldname) def update_reports(doctype, old_fieldname, new_fieldname): def _get_new_sort_by(report_dict, report, key): @@ -175,7 +175,7 @@ def update_reports(doctype, old_fieldname, new_fieldname): frappe.db.sql("""update `tabReport` set `json`=%s where name=%s""", (new_val, r.name)) -def update_users_report_view_settings(doctype, ref_fieldname): +def update_users_report_view_settings(doctype, ref_fieldname, new_fieldname): user_report_cols = frappe.db.sql("""select defkey, defvalue from `tabDefaultValue` where defkey like '_list_settings:%'""") for key, value in user_report_cols: @@ -183,8 +183,11 @@ def update_users_report_view_settings(doctype, ref_fieldname): columns_modified = False for field, field_doctype in json.loads(value): if field == ref_fieldname and field_doctype == doctype: - new_columns.append([field, field_doctype]) + new_columns.append([new_fieldname, field_doctype]) columns_modified=True + else: + new_columns.append([field, field_doctype]) + if columns_modified: frappe.db.sql("""update `tabDefaultValue` set defvalue=%s where defkey=%s""" % ('%s', '%s'), (json.dumps(new_columns), key)) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index e723579bce..c8c264354a 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, json, sys from frappe import _ -from frappe.utils import cint, flt, now +from frappe.utils import cint, flt, now, cstr, strip_html from frappe.model import default_fields from frappe.model.naming import set_new_name @@ -13,6 +13,7 @@ class BaseDocument(object): def __init__(self, d): self.update(d) + self.dont_update_if_missing = [] @property def meta(self): @@ -33,6 +34,8 @@ class BaseDocument(object): for key, value in d.iteritems(): self.set(key, value) + return self + def update_if_missing(self, d): if isinstance(d, BaseDocument): d = d.get_valid_dict() @@ -40,7 +43,8 @@ class BaseDocument(object): if "doctype" in d: self.set("doctype", d.get("doctype")) for key, value in d.iteritems(): - if self.get(key) is None: + # dont_update_if_missing is a list of fieldnames, for which, you don't want to set default value + if (self.get(key) is None) and (value is not None) and (key not in self.dont_update_if_missing): self.set(key, value) def get_db_value(self, key): @@ -79,6 +83,10 @@ class BaseDocument(object): else: self.__dict__[key] = value + def delete_key(self, key): + if key in self.__dict__: + del self.__dict__[key] + def append(self, key, value=None): if value==None: value={} @@ -160,6 +168,9 @@ class BaseDocument(object): if doc[k] is None: del doc[k] + if self.get("_user_tags"): + doc["_user_tags"] = self.get("_user_tags") + if self.get("__islocal"): doc["__islocal"] = 1 @@ -220,10 +231,15 @@ class BaseDocument(object): def _fix_numeric_types(self): for df in self.meta.get("fields"): - if df.fieldtype in ("Int", "Check"): + if df.fieldtype == "Check": self.set(df.fieldname, cint(self.get(df.fieldname))) - elif df.fieldtype in ("Float", "Currency"): - self.set(df.fieldname, flt(self.get(df.fieldname))) + + elif self.get(df.fieldname) is not None: + if df.fieldtype == "Int": + self.set(df.fieldname, cint(self.get(df.fieldname))) + + elif df.fieldtype in ("Float", "Currency", "Percent"): + self.set(df.fieldname, flt(self.get(df.fieldname))) if self.docstatus is not None: self.docstatus = cint(self.docstatus) @@ -244,12 +260,12 @@ class BaseDocument(object): missing = [] for df in self.meta.get("fields", {"reqd": 1}): - if self.get(df.fieldname) in (None, []): + if self.get(df.fieldname) in (None, []) or not strip_html(cstr(self.get(df.fieldname))).strip(): missing.append((df.fieldname, get_msg(df))) return missing - def get_invalid_links(self): + def get_invalid_links(self, is_submittable=False): def get_msg(df, docname): if self.parentfield: return "{} #{}: {}: {}".format(_("Row"), self.idx, _(df.label), docname) @@ -257,17 +273,63 @@ class BaseDocument(object): return "{}: {}".format(_(df.label), docname) invalid_links = [] - for df in self.meta.get_link_fields(): - doctype = df.options + cancelled_links = [] + for df in self.meta.get_link_fields() + self.meta.get("fields", + {"fieldtype":"Dynamic Link"}): - if not doctype: - frappe.throw(_("Options not set for link field {0}").format(df.fieldname)) docname = self.get(df.fieldname) - if docname and not frappe.db.get_value(doctype, docname): - invalid_links.append((df.fieldname, docname, get_msg(df, docname))) + if docname: + if df.fieldtype=="Link": + doctype = df.options + if not doctype: + frappe.throw(_("Options not set for link field {0}").format(df.fieldname)) + else: + doctype = self.get(df.options) + if not doctype: + frappe.throw(_("{0} must be set first").format(self.meta.get_label(df.options))) + + # MySQL is case insensitive. Preserve case of the original docname in the Link Field. + value = frappe.db.get_value(doctype, docname) + setattr(self, df.fieldname, value) + + if not value: + invalid_links.append((df.fieldname, docname, get_msg(df, docname))) + + elif (df.fieldname != "amended_from" + and (is_submittable or self.meta.is_submittable) and frappe.get_meta(doctype).is_submittable + and cint(frappe.db.get_value(doctype, docname, "docstatus"))==2): + + cancelled_links.append((df.fieldname, docname, get_msg(df, docname))) - return invalid_links + return invalid_links, cancelled_links + + def _validate_selects(self): + if frappe.flags.in_import: + return + + for df in self.meta.get_select_fields(): + if df.fieldname=="naming_series" or not (self.get(df.fieldname) and df.options): + continue + + options = (df.options or "").split("\n") + + # if only empty options + if not filter(None, options): + continue + + # strip and set + self.set(df.fieldname, cstr(self.get(df.fieldname)).strip()) + value = self.get(df.fieldname) + + if value not in options and not (frappe.flags.in_test and value.startswith("_T-")): + # show an elaborate message + prefix = _("Row #{0}:").format(self.idx) if self.get("parentfield") else "" + label = _(self.meta.get_label(df.fieldname)) + comma_options = '", "'.join(_(each) for each in options) + + frappe.throw(_('{0} {1} cannot be "{2}". It should be one of "{3}"').format(prefix, label, + value, comma_options)) def _validate_constants(self): if frappe.flags.in_import: @@ -290,6 +352,11 @@ class BaseDocument(object): frappe.throw(_("Not allowed to change {0} after submission").format(df.label), frappe.UpdateAfterSubmitError) + def get_formatted(self, fieldname, doc=None, currency=None): + from frappe.utils.formatters import format_value + return format_value(self.get(fieldname), self.meta.get_field(fieldname), + doc=doc or self, currency=currency) + def _filter(data, filters, limit=None): """pass filters as: {"key": "val", "key": ["!=", "val"], diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py index ae3d12dc82..aa4f4983ee 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -9,6 +9,7 @@ Create a new document with defaults set import frappe from frappe.utils import nowdate, nowtime, cint, flt import frappe.defaults +from frappe.model.db_schema import type_map def get_new_doc(doctype, parent_doc = None, parentfield = None): doc = frappe.get_doc({ @@ -18,7 +19,7 @@ def get_new_doc(doctype, parent_doc = None, parentfield = None): "docstatus": 0 }) - restrictions = frappe.defaults.get_restrictions() + user_permissions = frappe.defaults.get_user_permissions() if parent_doc: doc.parent = parent_doc.name @@ -29,41 +30,64 @@ def get_new_doc(doctype, parent_doc = None, parentfield = None): defaults = frappe.defaults.get_defaults() - for d in doc.meta.get("fields"): - default = defaults.get(d.fieldname) - - if (d.fieldtype=="Link") and d.ignore_restrictions != 1 and (d.options in restrictions)\ - and len(restrictions[d.options])==1: - doc.set(d.fieldname, restrictions[d.options][0]) - elif default: - doc.set(d.fieldname, default) - elif d.get("default"): - if d.default == "__user": - doc.set(d.fieldname, frappe.session.user) - elif d.default == "Today": - doc.set(d.fieldname, nowdate()) - - elif d.default.startswith(":"): - ref_doctype = d.default[1:] - ref_fieldname = ref_doctype.lower().replace(" ", "_") - if parent_doc: - ref_docname = parent_doc.get(ref_fieldname) - else: - ref_docname = frappe.db.get_default(ref_fieldname) - doc.set(d.fieldname, frappe.db.get_value(ref_doctype, ref_docname, d.fieldname)) - else: - doc.set(d.fieldname, d.default) - - # convert type of default - if d.fieldtype in ("Int", "Check"): - doc.set(d.fieldname, cint(doc.get(d.fieldname))) - elif d.fieldtype in ("Float", "Currency"): - doc.set(d.fieldname, flt(doc.get(d.fieldname))) - - elif d.fieldtype == "Time": - doc.set(d.fieldname, nowtime()) - - elif (d.fieldtype == "Select" and d.options and d.options != "[Select]"): - doc.set(d.fieldname, d.options.split("\n")[0]) + for df in doc.meta.get("fields"): + if df.fieldtype in type_map: + default_value = get_default_value(df, defaults, user_permissions, parent_doc) + doc.set(df.fieldname, default_value) + + doc._fix_numeric_types() return doc + +def get_default_value(df, defaults, user_permissions, parent_doc): + user_permissions_exist = (df.fieldtype=="Link" + and not getattr(df, "ignore_user_permissions", False) + and df.options in (user_permissions or [])) + + # don't set defaults for "User" link field using User Permissions! + if df.fieldtype == "Link" and df.options != "User": + # 1 - look in user permissions + if user_permissions_exist and len(user_permissions[df.options])==1: + return user_permissions[df.options][0] + + # 2 - Look in user defaults + user_default = defaults.get(df.fieldname) + is_allowed_user_default = user_default and (not user_permissions_exist + or (user_default in user_permissions.get(df.options, []))) + + # is this user default also allowed as per user permissions? + if is_allowed_user_default: + return user_default + + # 3 - look in default of docfield + if df.get("default"): + if df.default == "__user": + return frappe.session.user + + elif df.default == "Today": + return nowdate() + + elif df.default.startswith(":"): + # default value based on another document + ref_doctype = df.default[1:] + ref_fieldname = ref_doctype.lower().replace(" ", "_") + ref_docname = parent_doc.get(ref_fieldname) if parent_doc else frappe.db.get_default(ref_fieldname) + + default_value = frappe.db.get_value(ref_doctype, ref_docname, df.fieldname) + is_allowed_default_value = (not user_permissions_exist or + (default_value in user_permissions.get(df.options, []))) + + # is this allowed as per user permissions + if is_allowed_default_value: + return default_value + + # a static default value + is_allowed_default_value = (not user_permissions_exist or (df.default in user_permissions.get(df.options, []))) + if df.fieldtype!="Link" or df.options=="User" or is_allowed_default_value: + return df.default + + elif df.fieldtype == "Time": + return nowtime() + + elif (df.fieldtype == "Select" and df.options and df.options != "[Select]"): + return df.options.split("\n")[0] diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index da49f92fde..6658a792bf 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -15,15 +15,16 @@ class DatabaseQuery(object): self.doctype = doctype self.tables = [] self.conditions = [] + self.fields = ["`tab{0}`.`name`".format(doctype)] + self.user = None self.ignore_permissions = False - self.fields = ["name"] def execute(self, query=None, filters=None, fields=None, or_filters=None, docstatus=None, group_by=None, order_by=None, limit_start=0, limit_page_length=20, as_list=False, with_childnames=False, debug=False, - ignore_permissions=False): - if not frappe.has_permission(self.doctype, "read"): - raise frappe.PermissionError + ignore_permissions=False, user=None): + if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user): + raise frappe.PermissionError, self.doctype if fields: self.fields = fields @@ -38,7 +39,7 @@ class DatabaseQuery(object): self.debug = debug self.as_list = as_list self.ignore_permissions = ignore_permissions - + self.user = user or frappe.session.user if query: return self.run_custom_query(query) @@ -49,8 +50,11 @@ class DatabaseQuery(object): args = self.prepare_args() args.limit = self.add_limit() - query = """select %(fields)s from %(tables)s where %(conditions)s - %(group_by)s order by %(order_by)s %(limit)s""" % args + if args.conditions: + args.conditions = "where " + args.conditions + + query = """select %(fields)s from %(tables)s %(conditions)s + %(group_by)s %(order_by)s %(limit)s""" % args return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug) @@ -75,18 +79,18 @@ class DatabaseQuery(object): args.fields = ', '.join(self.fields) self.set_order_by(args) + self.check_sort_by_table(args.order_by) + args.order_by = args.order_by and (" order by " + args.order_by) or "" args.group_by = self.group_by and (" group by " + self.group_by) or "" - self.check_sort_by_table(args.order_by) - return args def parse_args(self): if isinstance(self.filters, basestring): self.filters = json.loads(self.filters) if isinstance(self.fields, basestring): - self.filters = json.loads(self.fields) + self.fields = json.loads(self.fields) if isinstance(self.filters, dict): fdict = self.filters self.filters = [] @@ -106,7 +110,8 @@ class DatabaseQuery(object): # add tables from fields if self.fields: for f in self.fields: - if "." not in f: continue + if ( not ("tab" in f and "." in f) ) or ("locate(" in f): continue + table_name = f.split('.')[0] if table_name.lower().startswith('group_concat('): @@ -127,19 +132,36 @@ class DatabaseQuery(object): def remove_user_tags(self): """remove column _user_tags if not in table""" columns = frappe.db.get_table_columns(self.doctype) + + # remove from fields to_remove = [] for fld in self.fields: - for f in ("_user_tags", "_comments"): + for f in ("_user_tags", "_comments", "_assign"): if f in fld and not f in columns: to_remove.append(fld) for fld in to_remove: del self.fields[self.fields.index(fld)] + # remove from filters + to_remove = [] + for each in self.filters: + if isinstance(each, basestring): + each = [each] + + for element in each: + if element in ("_user_tags", "_comments", "_assign") and element not in columns: + to_remove.append(each) + + for each in to_remove: + if isinstance(self.filters, dict): + del self.filters[each] + else: + self.filters.remove(each) + def build_conditions(self): self.conditions = [] self.or_conditions = [] - self.add_docstatus_conditions() self.build_filter_conditions(self.filters, self.conditions) self.build_filter_conditions(self.or_filters, self.or_conditions) @@ -151,13 +173,7 @@ class DatabaseQuery(object): if not self.ignore_permissions: match_conditions = self.build_match_conditions() if match_conditions: - self.conditions.append(match_conditions) - - def add_docstatus_conditions(self): - if self.docstatus: - self.conditions.append(self.tables[0] + '.docstatus in (' + ','.join(self.docstatus) + ')') - else: - self.conditions.append(self.tables[0] + '.docstatus < 2') + self.conditions.append("(" + match_conditions + ")") def build_filter_conditions(self, filters, conditions): """build conditions from user filters""" @@ -178,15 +194,20 @@ class DatabaseQuery(object): opts = f[3] if not isinstance(opts, (list, tuple)): opts = f[3].split(",") - opts = ["'" + t.strip().replace("'", "\\'") + "'" for t in opts] - f[3] = "(" + ', '.join(opts) + ")" - conditions.append('ifnull(' + tname + '.' + f[1] + ", '') " + f[2] + " " + f[3]) + opts = [frappe.db.escape(t.strip()) for t in opts] + f[3] = '("{0}")'.format('", "'.join(opts)) + conditions.append('ifnull({tname}.{fname}, "") {operator} {value}'.format( + tname=tname, fname=f[1], operator=f[2], value=f[3])) else: df = frappe.get_meta(f[0]).get("fields", {"fieldname": f[1]}) if f[2] == "like" or (isinstance(f[3], basestring) and (not df or df[0].fieldtype not in ["Float", "Int", "Currency", "Percent"])): - value, default_val = ("'" + f[3].replace("'", "\\'") + "'"), '""' + if f[2] == "like": + # because "like" uses backslash (\) for escaping + f[3] = f[3].replace("\\", "\\\\") + + value, default_val = '"{0}"'.format(frappe.db.escape(f[3])), '""' else: value, default_val = flt(f[3]), 0 @@ -209,67 +230,68 @@ class DatabaseQuery(object): def build_match_conditions(self, as_condition=True): """add match conditions if applicable""" - self.match_filters = {} + self.match_filters = [] self.match_conditions = [] - self.match_or_conditions = [] if not self.tables: self.extract_tables() - # explict permissions - restricted_by_user = frappe.permissions.get_user_perms(frappe.get_meta(self.doctype)).restricted + meta = frappe.get_meta(self.doctype) + role_permissions = frappe.permissions.get_role_permissions(meta, user=self.user) + if not meta.istable and not role_permissions.get("read") and not getattr(self, "ignore_permissions", False): + frappe.throw(_("No permission to read {0}").format(self.doctype)) - # get restrictions - restrictions = frappe.defaults.get_restrictions() + # apply user permissions? + if role_permissions.get("apply_user_permissions", {}).get("read"): + # get user permissions + user_permissions = frappe.defaults.get_user_permissions(self.user) + self.add_user_permissions(user_permissions, + user_permission_doctypes=role_permissions.get("user_permission_doctypes")) - if restricted_by_user: - self.match_or_conditions.append('`tab{doctype}`.`owner`="{user}"'.format(doctype=self.doctype, - user=frappe.local.session.user)) - self.match_filters["owner"] = frappe.session.user + if as_condition: + conditions = "" + if self.match_conditions: + # will turn out like ((blog_post in (..) and blogger in (...)) or (blog_category in (...))) + conditions = "((" + ") or (".join(self.match_conditions) + "))" - if restrictions: - self.add_restrictions(restrictions) + doctype_conditions = self.get_permission_query_conditions() + if doctype_conditions: + conditions += (' and ' + doctype_conditions) if conditions else doctype_conditions + + return conditions - if as_condition: - return self.build_match_condition_string() else: return self.match_filters - def add_restrictions(self, restrictions): - fields_to_check = frappe.get_meta(self.doctype).get_restricted_fields(restrictions.keys()) - if self.doctype in restrictions: - fields_to_check.append(frappe._dict({"fieldname":"name", "options":self.doctype})) + def add_user_permissions(self, user_permissions, user_permission_doctypes=None): + user_permission_doctypes = frappe.permissions.get_user_permission_doctypes(user_permission_doctypes, + user_permissions) + meta = frappe.get_meta(self.doctype) - # check in links - for df in fields_to_check: - self.match_conditions.append("""(ifnull(`tab{doctype}`.`{fieldname}`, "")="" or \ - `tab{doctype}`.`{fieldname}` in ({values}))""".format(doctype=self.doctype, + for doctypes in user_permission_doctypes: + match_filters = {} + match_conditions = [] + # check in links + for df in meta.get_fields_to_check_permissions(doctypes): + match_conditions.append("""(ifnull(`tab{doctype}`.`{fieldname}`, "")="" + or `tab{doctype}`.`{fieldname}` in ({values}))""".format( + doctype=self.doctype, fieldname=df.fieldname, - values=", ".join([('"'+v.replace('"', '\"')+'"') \ - for v in restrictions[df.options]]))) - self.match_filters.setdefault(df.fieldname, []) - self.match_filters[df.fieldname]= restrictions[df.options] - - def build_match_condition_string(self): - conditions = " and ".join(self.match_conditions) - doctype_conditions = self.get_permission_query_conditions() - if doctype_conditions: - conditions += ' and ' + doctype_conditions if conditions else doctype_conditions - - if self.match_or_conditions: - if conditions: - conditions = '({conditions}) or {or_conditions}'.format(conditions=conditions, - or_conditions = ' or '.join(self.match_or_conditions)) - else: - conditions = " or ".join(self.match_or_conditions) + values=", ".join([('"'+v.replace('"', '\"')+'"') for v in user_permissions[df.options]]) + )) + match_filters[df.options] = user_permissions[df.options] - return conditions + if match_conditions: + self.match_conditions.append(" and ".join(match_conditions)) + + if match_filters: + self.match_filters.append(match_filters) def get_permission_query_conditions(self): condition_methods = frappe.get_hooks("permission_query_conditions", {}).get(self.doctype, []) if condition_methods: conditions = [] for method in condition_methods: - c = frappe.get_attr(method)() + c = frappe.call(frappe.get_attr(method), self.user) if c: conditions.append(c) @@ -285,8 +307,23 @@ class DatabaseQuery(object): if self.order_by: args.order_by = self.order_by else: - args.order_by = "`tab{0}`.`{1}` {2}".format(self.doctype, - meta.sort_field or "modified", meta.sort_order or "desc") + args.order_by = "" + + # don't add order by from meta if a mysql group function is used without group by clause + group_function_without_group_by = (len(self.fields)==1 and + ( self.fields[0].lower().startswith("count(") + or self.fields[0].lower().startswith("min(") + or self.fields[0].lower().startswith("max(") + ) and not self.group_by) + + if not group_function_without_group_by: + + args.order_by = "`tab{0}`.`{1}` {2}".format(self.doctype, + meta.sort_field or "modified", meta.sort_order or "desc") + + # draft docs always on top + if meta.is_submittable: + args.order_by = "`tab{0}`.docstatus asc, ".format(self.doctype) + args.order_by def check_sort_by_table(self, order_by): if "." in order_by: diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index 127f2de333..7f2a013d8c 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -29,6 +29,7 @@ type_map = { ,'Text': ('text', '') ,'Data': ('varchar', '255') ,'Link': ('varchar', '255') + ,'Dynamic Link':('varchar', '255') ,'Password': ('varchar', '255') ,'Select': ('varchar', '255') ,'Read Only': ('varchar', '255') @@ -77,8 +78,8 @@ class DbTable: name varchar(255) not null primary key, creation datetime(6), modified datetime(6), - modified_by varchar(40), - owner varchar(60), + modified_by varchar(255), + owner varchar(255), docstatus int(1) default '0', parent varchar(255), parentfield varchar(255), @@ -197,6 +198,9 @@ class DbTable: query.append("drop index `{}`".format(col.fieldname)) for col in list(set(self.set_default).difference(set(self.change_type))): + if col.fieldname=="name": + continue + if not col.default: col_default = "null" else: @@ -390,7 +394,7 @@ def remove_all_foreign_keys(): frappe.db.sql("set foreign_key_checks = 0") frappe.db.commit() for t in frappe.db.sql("select name from tabDocType where ifnull(issingle,0)=0"): - dbtab = frappe.model.db_schema.DbTable(t[0]) + dbtab = DbTable(t[0]) try: fklist = dbtab.get_foreign_keys() except Exception, e: diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 5314698194..a0d56f31f0 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -8,6 +8,8 @@ import frappe.model.meta import frappe.defaults from frappe.utils.file_manager import remove_all from frappe import _ +from rename_doc import dynamic_link_queries +from frappe.model.naming import revert_series_if_last def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False): """ @@ -20,54 +22,97 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa doctype = frappe.form_dict.get('dt') name = frappe.form_dict.get('dn') - if not doctype: - frappe.msgprint(_('Nothing to delete'), raise_exception =1) + names = name + if isinstance(name, basestring): + names = [name] - # already deleted..? - if not frappe.db.exists(doctype, name): - return + for name in names or []: - doc = frappe.get_doc(doctype, name) + # already deleted..? + if not frappe.db.exists(doctype, name): + return - if not for_reload: - check_permission_and_not_submitted(doc, ignore_permissions) - doc.run_method("on_trash") - # check if links exist - if not force: - check_if_doc_is_linked(doc) + # delete attachments + remove_all(doctype, name) - try: - if doctype!="DocType" and doctype==name: - frappe.db.sql("delete from `tabSingles` where doctype=%s", name) - else: - frappe.db.sql("delete from `tab%s` where name=%s" % (doctype, "%s"), (name,)) + if doctype=="DocType": + if for_reload: - for t in doc.meta.get_table_fields(): - if t.options not in ignore_doctypes: - frappe.db.sql("delete from `tab%s` where parent = %s" % (t.options, '%s'), (name,)) + try: + doc = frappe.get_doc(doctype, name) + except frappe.DoesNotExistError: + pass + else: + doc.run_method("before_reload") - except Exception, e: - if e.args[0]==1451: - frappe.throw(_("Cannot delete {0} {1} is it is referenced in another record").format(doctype, name)) + else: + frappe.db.sql("delete from `tabCustom Field` where dt = %s", name) + frappe.db.sql("delete from `tabCustom Script` where dt = %s", name) + frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", name) + frappe.db.sql("delete from `tabReport` where ref_doctype=%s", name) - raise + delete_from_table(doctype, name, ignore_doctypes, None) + + else: + doc = frappe.get_doc(doctype, name) - # delete attachments - remove_all(doctype, name) + if not for_reload: + check_permission_and_not_submitted(doc, ignore_permissions) + doc.run_method("on_trash") - # delete restrictions - frappe.defaults.clear_default(parenttype="Restriction", key=doctype, value=name) + delete_linked_todos(doc) + # check if links exist + if not force: + check_if_doc_is_linked(doc) + check_if_doc_is_dynamically_linked(doc) + + update_naming_series(doc) + delete_from_table(doctype, name, ignore_doctypes, doc) + + # delete user_permissions + frappe.defaults.clear_default(parenttype="User Permission", key=doctype, value=name) return 'okay' +def update_naming_series(doc): + if doc.meta.autoname: + if doc.meta.autoname.startswith("naming_series:") \ + and getattr(doc, "naming_series", None): + revert_series_if_last(doc.naming_series, doc.name) + + elif doc.meta.autoname.split(":")[0] not in ("Prompt", "field", "hash"): + revert_series_if_last(doc.meta.autoname, doc.name) + +def delete_from_table(doctype, name, ignore_doctypes, doc): + if doctype!="DocType" and doctype==name: + frappe.db.sql("delete from `tabSingles` where doctype=%s", name) + else: + frappe.db.sql("delete from `tab%s` where name=%s" % (doctype, "%s"), (name,)) + + # get child tables + if doc: + tables = [d.options for d in doc.meta.get_table_fields()] + + else: + def get_table_fields(field_doctype): + return frappe.db.sql_list("""select options from `tab{}` where fieldtype='Table' + and parent=%s""".format(field_doctype), doctype) + + tables = get_table_fields("DocField") + get_table_fields("Custom Field") + + # delete from child tables + for t in list(set(tables)): + if t not in ignore_doctypes: + frappe.db.sql("delete from `tab%s` where parenttype=%s and parent = %s" % (t, '%s', '%s'), (doctype, name)) + def check_permission_and_not_submitted(doc, ignore_permissions=False): # permission if not ignore_permissions and frappe.session.user!="Administrator" and not doc.has_permission("delete"): - frappe.msgprint(_("User not allowed to delete."), raise_exception=True) + frappe.msgprint(_("User not allowed to delete {0}: {1}").format(doc.doctype, doc.name), raise_exception=True) # check if submitted if doc.docstatus == 1: - frappe.msgprint(_("Submitted Record cannot be deleted")+": "+doc.name+"("+doc.doctype+")", + frappe.msgprint(_("{0} {1}: Submitted Record cannot be deleted.").format(doc.doctype, doc.name), raise_exception=True) def check_if_doc_is_linked(doc, method="Delete"): @@ -88,3 +133,25 @@ def check_if_doc_is_linked(doc, method="Delete"): frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, doc.name, item.parent or item.name, item.parenttype if item.parent else link_dt), frappe.LinkExistsError) + +def check_if_doc_is_dynamically_linked(doc): + for query in dynamic_link_queries: + for df in frappe.db.sql(query, as_dict=True): + if frappe.get_meta(df.parent).issingle: + + # dynamic link in single doc + refdoc = frappe.get_singles_dict(df.parent) + if refdoc.get(df.options)==doc.doctype and refdoc.get(df.fieldname)==doc.name: + frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, + doc.name, df.parent, ""), frappe.LinkExistsError) + else: + + # dynamic link in table + for name in frappe.db.sql_list("""select name from `tab{parent}` where + {options}=%s and {fieldname}=%s""".format(**df), (doc.doctype, doc.name)): + frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, + doc.name, df.parent, name), frappe.LinkExistsError) + +def delete_linked_todos(doc): + delete_doc("ToDo", frappe.db.sql_list("""select name from `tabToDo` + where reference_type=%s and reference_name=%s""", (doc.doctype, doc.name))) diff --git a/frappe/model/document.py b/frappe/model/document.py index 8ff78e119d..0993c19008 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -32,7 +32,7 @@ _classes = {} def get_controller(doctype): if not doctype in _classes: module = load_doctype_module(doctype) - classname = doctype.replace(" ", "") + classname = doctype.replace(" ", "").replace("-", "") if hasattr(module, classname): _class = getattr(module, classname) if issubclass(_class, Document): @@ -48,6 +48,7 @@ def get_controller(doctype): class Document(BaseDocument): def __init__(self, arg1, arg2=None): self.doctype = self.name = None + if arg1 and isinstance(arg1, basestring): if not arg2: # single @@ -58,7 +59,7 @@ class Document(BaseDocument): # filter self.name = frappe.db.get_value(arg1, arg2, "name") if self.name is None: - raise frappe.DoesNotExistError, (arg1, arg2) + frappe.throw(_("{0} {1} not found").format(_(arg1), arg2), frappe.DoesNotExistError) else: self.name = arg2 @@ -72,6 +73,11 @@ class Document(BaseDocument): # incorrect arguments. let's not proceed. raise frappe.DataError("Document({0}, {1})".format(arg1, arg2)) + if hasattr(self, "__setup__"): + self.__setup__() + + self.dont_update_if_missing = [] + def load_from_db(self): if not getattr(self, "_metaclass", False) and self.meta.issingle: self.update(frappe.db.get_singles_dict(self.doctype)) @@ -81,7 +87,7 @@ class Document(BaseDocument): else: d = frappe.db.get_value(self.doctype, self.name, "*", as_dict=1) if not d: - frappe.throw(("{0} {1} not found").format(_(self.doctype), self.name), frappe.DoesNotExistError) + frappe.throw(_("{0} {1} not found").format(_(self.doctype), self.name), frappe.DoesNotExistError) self.update(d) if self.name=="DocType" and self.doctype=="DocType": @@ -99,6 +105,10 @@ class Document(BaseDocument): else: self.set(df.fieldname, []) + def check_permission(self, permtype, permlabel=None): + if not self.has_permission(permtype): + self.raise_no_permission_to(permlabel or permtype) + def has_permission(self, permtype): if getattr(self, "ignore_permissions", False): return True @@ -108,21 +118,26 @@ class Document(BaseDocument): raise frappe.PermissionError("No permission to {} {} {}".format(perm_type, self.doctype, self.name or "")) def insert(self, ignore_permissions=None): + if getattr(self, "in_print", False): + return + if ignore_permissions!=None: self.ignore_permissions = ignore_permissions self.set("__islocal", True) - if not self.has_permission("create"): - self.raise_no_permission_to("create") + self.check_permission("create") self._set_defaults() self._set_docstatus_user_and_timestamp() self.check_if_latest() - set_new_name(self) + self.set_new_name() self.run_method("before_insert") self.set_parent_in_children() + + self.set("__in_insert", True) self.run_before_save_methods() self._validate() + self.delete_key("__in_insert") # run validate, on update etc. @@ -137,11 +152,16 @@ class Document(BaseDocument): d.db_insert() self.run_method("after_insert") + self.set("__in_insert", True) self.run_post_save_methods() + self.delete_key("__in_insert") return self def save(self, ignore_permissions=None): + if getattr(self, "in_print", False): + return + if ignore_permissions!=None: self.ignore_permissions = ignore_permissions @@ -149,14 +169,16 @@ class Document(BaseDocument): self.insert() return - if not self.has_permission("write"): - self.raise_no_permission_to("save") + self.check_permission("write", "save") self._set_docstatus_user_and_timestamp() self.check_if_latest() self.set_parent_in_children() self.run_before_save_methods() - self._validate() + + if self._action != "cancel": + self._validate() + if self._action == "update_after_submit": self.validate_update_after_submit() @@ -189,6 +211,12 @@ class Document(BaseDocument): return self + def set_new_name(self): + set_new_name(self) + # set name for children + for d in self.get_all_children(): + set_new_name(d) + def update_single(self, d): frappe.db.sql("""delete from tabSingles where doctype=%s""", self.doctype) for field, value in d.iteritems(): @@ -219,8 +247,10 @@ class Document(BaseDocument): def _validate(self): self._validate_mandatory() self._validate_links() + self._validate_selects() self._validate_constants() for d in self.get_all_children(): + d._validate_selects() d._validate_constants() self._extract_images_from_text_editor() @@ -278,20 +308,17 @@ class Document(BaseDocument): self._action = "save" elif self.docstatus==1: self._action = "submit" - if not self.has_permission("submit"): - self.raise_no_permission_to("submit") + self.check_permission("submit") else: raise frappe.DocstatusTransitionError("Cannot change docstatus from 0 to 2") elif docstatus==1: if self.docstatus==1: self._action = "update_after_submit" - if not self.has_permission("submit"): - self.raise_no_permission_to("submit") + self.check_permission("submit") elif self.docstatus==2: self._action = "cancel" - if not self.has_permission("cancel"): - self.raise_no_permission_to("cancel") + self.check_permission("cancel") else: raise frappe.DocstatusTransitionError("Cannot change docstatus from 1 to 0") @@ -333,16 +360,22 @@ class Document(BaseDocument): if self.get("ignore_links"): return - invalid_links = self.get_invalid_links() + invalid_links, cancelled_links = self.get_invalid_links() + for d in self.get_all_children(): - invalid_links.extend(d.get_invalid_links()) + result = d.get_invalid_links(is_submittable=self.meta.is_submittable) + invalid_links.extend(result[0]) + cancelled_links.extend(result[1]) - if not invalid_links: - return + if invalid_links: + msg = ", ".join((each[2] for each in invalid_links)) + frappe.throw(_("Could not find {0}").format(msg), + frappe.LinkValidationError) - msg = ", ".join((each[2] for each in invalid_links)) - frappe.throw(_("Could not find {0}").format(msg), - frappe.LinkValidationError) + if cancelled_links: + msg = ", ".join((each[2] for each in cancelled_links)) + frappe.throw(_("Cannot link cancelled document: {0}").format(msg), + frappe.CancelledLinkError) def get_all_children(self, parenttype=None): ret = [] @@ -363,10 +396,14 @@ class Document(BaseDocument): def run_method(self, method, *args, **kwargs): """run standard triggers, plus those in frappe""" - if hasattr(self, method): + if hasattr(self, method) and hasattr(getattr(self, method), "__call__"): fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs) - fn.__name__ = method.encode("utf-8") - return Document.hook(fn)(self, *args, **kwargs) + else: + # hack! to run hooks even if method does not exist + fn = lambda self, *args, **kwargs: None + + fn.__name__ = method.encode("utf-8") + return Document.hook(fn)(self, *args, **kwargs) def submit(self): self.docstatus = 1 @@ -376,6 +413,9 @@ class Document(BaseDocument): self.docstatus = 2 self.save() + def delete(self): + frappe.delete_doc(self.doctype, self.name) + def run_before_save_methods(self): if getattr(self, "ignore_validate", False): return @@ -389,7 +429,6 @@ class Document(BaseDocument): elif self._action=="cancel": self.run_method("before_cancel") elif self._action=="update_after_submit": - self.run_method("validate") self.run_method("before_update_after_submit") def run_post_save_methods(self): @@ -398,11 +437,19 @@ class Document(BaseDocument): elif self._action=="submit": self.run_method("on_update") self.run_method("on_submit") + self.add_comment("Submitted") elif self._action=="cancel": self.run_method("on_cancel") + self.check_no_back_links_exist() + self.add_comment("Cancelled") elif self._action=="update_after_submit": self.run_method("on_update_after_submit") + def check_no_back_links_exist(self): + from frappe.model.delete_doc import check_if_doc_is_linked + if not self.get("ignore_links"): + check_if_doc_is_linked(self, method="Cancel") + @staticmethod def whitelist(f): f.whitelisted = True @@ -464,14 +511,14 @@ class Document(BaseDocument): val1 = doc.get(fieldname) - if df.fieldtype in ("Currency", "Float"): + if df.fieldtype in ("Currency", "Float", "Percent"): val1 = flt(val1, self.precision(df.fieldname, doc.parentfield or None)) val2 = flt(val2, self.precision(df.fieldname, doc.parentfield or None)) elif df.fieldtype in ("Int", "Check"): val1 = cint(val1) val2 = cint(val2) elif df.fieldtype in ("Data", "Text", "Small Text", "Long Text", - "Text Editor", "Select", "Link"): + "Text Editor", "Select", "Link", "Dynamic Link"): val1 = cstr(val1) val2 = cstr(val2) @@ -481,7 +528,7 @@ class Document(BaseDocument): if doc.parentfield: msg = _("Incorrect value in row {0}: {1} must be {2} {3}".format(doc.idx, label, condition_str, val2)) else: - msg = _("Incorrect value: {1} must be {2} {3}".format(label, condition_str, val2)) + msg = _("Incorrect value: {0} must be {1} {2}".format(label, condition_str, val2)) # raise passed exception or True msgprint(msg, raise_exception=raise_exception or True) @@ -494,7 +541,7 @@ class Document(BaseDocument): def round_floats_in(self, doc, fieldnames=None): if not fieldnames: fieldnames = (df.fieldname for df in - doc.meta.get("fields", {"fieldtype": ["in", ["Currency", "Float"]]})) + doc.meta.get("fields", {"fieldtype": ["in", ["Currency", "Float", "Percent"]]})) for fieldname in fieldnames: doc.set(fieldname, flt(doc.get(fieldname), self.precision(fieldname, doc.parentfield))) @@ -505,23 +552,35 @@ class Document(BaseDocument): if parentfield and not isinstance(parentfield, basestring): parentfield = parentfield.parentfield + cache_key = parentfield or "main" + if not hasattr(self, "_precision"): - self._precision = frappe._dict({ - "default": cint(frappe.db.get_default("float_precision")) or 3, - "options": {} - }) + self._precision = frappe._dict() + + if cache_key not in self._precision: + self._precision[cache_key] = frappe._dict() + + if fieldname not in self._precision[cache_key]: + self._precision[cache_key][fieldname] = None + + doctype = self.meta.get_field(parentfield).options if parentfield else self.doctype + df = frappe.get_meta(doctype).get_field(fieldname) - if self._precision.setdefault(parentfield or "main", {}).get(fieldname) is None: - meta = frappe.get_meta(self.meta.get_field(parentfield).options if parentfield else self.doctype) - df = meta.get_field(fieldname) + if df.fieldtype in ("Currency", "Float", "Percent"): + self._precision[cache_key][fieldname] = get_field_precision(df, self) - if df.fieldtype == "Currency" and df.options and not self._precision.options.get(df.options): - self._precision.options[df.options] = get_field_precision(df, self) + return self._precision[cache_key][fieldname] - if df.fieldtype == "Currency": - self._precision[parentfield or "main"][fieldname] = cint(self._precision.options.get(df.options)) or \ - self._precision.default - elif df.fieldtype == "Float": - self._precision[parentfield or "main"][fieldname] = self._precision.default + def get_url(self): + return "/desk#Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name) - return self._precision[parentfield or "main"][fieldname] + def add_comment(self, comment_type, text=None): + comment = frappe.get_doc({ + "doctype":"Comment", + "comment_by": frappe.session.user, + "comment_type": comment_type, + "comment_doctype": self.doctype, + "comment_docname": self.name, + "comment": text or comment_type + }).insert(ignore_permissions=True) + return comment diff --git a/frappe/model/mapper.py b/frappe/model/mapper.py index 32992f1de2..13b85b3b4d 100644 --- a/frappe/model/mapper.py +++ b/frappe/model/mapper.py @@ -22,7 +22,7 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, elif isinstance(target_doc, basestring): target_doc = frappe.get_doc(json.loads(target_doc)) - if not target_doc.has_permission("create"): + if not ignore_permissions and not target_doc.has_permission("create"): target_doc.raise_no_permission_to("create") map_doc(source_doc, target_doc, table_maps[source_doc.doctype]) @@ -43,20 +43,18 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, target_parentfield = target_doc.get_parentfield_of_doctype(target_child_doctype) # does row exist for a parentfield? - if df.fieldname not in row_exists_for_parentfield: + if target_parentfield not in row_exists_for_parentfield: row_exists_for_parentfield[target_parentfield] = (True if target_doc.get(target_parentfield) else False) - if table_map.get("add_if_empty") and row_exists_for_parentfield.get(target_parentfield): + if table_map.get("add_if_empty") and \ + row_exists_for_parentfield.get(target_parentfield): continue if table_map.get("filter") and table_map.get("filter")(source_d): continue - target_d = frappe.new_doc(target_child_doctype, target_doc, target_parentfield) - map_doc(source_d, target_d, table_map, source_doc) - target_d.idx = None - target_doc.append(target_parentfield, target_d) + map_child_doc(source_d, target_doc, table_map, source_doc) if postprocess: postprocess(source_doc, target_doc) @@ -64,11 +62,6 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, return target_doc def map_doc(source_doc, target_doc, table_map, source_parent=None): - no_copy_fields = set([d.fieldname for d in source_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype=="Table")] - + [d.fieldname for d in target_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype=="Table")] - + default_fields - + table_map.get("field_no_map", [])) - if table_map.get("validation"): for key, condition in table_map["validation"].items(): if condition[0]=="=": @@ -76,13 +69,33 @@ def map_doc(source_doc, target_doc, table_map, source_parent=None): frappe.throw(_("Cannot map because following condition fails: ") + key + "=" + cstr(condition[1])) - # map same fields + map_fields(source_doc, target_doc, table_map, source_parent) + + if "postprocess" in table_map: + table_map["postprocess"](source_doc, target_doc, source_parent) + +def map_fields(source_doc, target_doc, table_map, source_parent): + no_copy_fields = set([d.fieldname for d in source_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype=="Table")] + + [d.fieldname for d in target_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype=="Table")] + + default_fields + + table_map.get("field_no_map", [])) + for df in target_doc.meta.get("fields"): if df.fieldname not in no_copy_fields: + # map same fields val = source_doc.get(df.fieldname) if val not in (None, ""): target_doc.set(df.fieldname, val) + elif df.fieldtype == "Link": + if not target_doc.get(df.fieldname): + # map link fields having options == source doctype + if df.options == source_doc.doctype: + target_doc.set(df.fieldname, source_doc.name) + + elif source_parent and df.options == source_parent.doctype: + target_doc.set(df.fieldname, source_parent.name) + # map other fields field_map = table_map.get("field_map") @@ -102,5 +115,35 @@ def map_doc(source_doc, target_doc, table_map, source_parent=None): if source_doc.idx: target_doc.idx = source_doc.idx - if "postprocess" in table_map: - table_map["postprocess"](source_doc, target_doc, source_parent) + # add fetch + for df in target_doc.meta.get("fields", {"fieldtype": "Link"}): + if target_doc.get(df.fieldname): + map_fetch_fields(target_doc, df, no_copy_fields) + +def map_fetch_fields(target_doc, df, no_copy_fields): + try: + linked_doc = frappe.get_doc(df.options, target_doc.get(df.fieldname)) + except: + return + + # options should be like "link_fieldname.fieldname_in_liked_doc" + for fetch_df in target_doc.meta.get("fields", {"options": "^{0}.".format(df.fieldname)}): + if not (fetch_df.fieldtype == "Read Only" or fetch_df.read_only): + continue + + if not target_doc.get(fetch_df.fieldname) and fetch_df.fieldname not in no_copy_fields: + source_fieldname = fetch_df.options.split(".")[1] + val = linked_doc.get(source_fieldname) + + if val not in (None, ""): + target_doc.set(fetch_df.fieldname, val) + +def map_child_doc(source_d, target_parent, table_map, source_parent=None): + target_child_doctype = table_map["doctype"] + target_parentfield = target_parent.get_parentfield_of_doctype(target_child_doctype) + target_d = frappe.new_doc(target_child_doctype, target_parent, target_parentfield) + + map_doc(source_d, target_d, table_map, source_parent) + target_d.idx = None + target_parent.append(target_parentfield, target_d) + return target_d diff --git a/frappe/model/meta.py b/frappe/model/meta.py index c0e1ac0bca..0a65f5e4c5 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -61,6 +61,10 @@ class Meta(Document): def get_link_fields(self): return self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]}) + def get_select_fields(self): + return self.get("fields", {"fieldtype": "Select", "options":["not in", + ["[Select]", "Loading...", "attach_files:"]]}) + def get_table_fields(self): if not hasattr(self, "_table_fields"): if self.name!="DocType": @@ -190,19 +194,26 @@ class Meta(Document): self.set("fields", newlist) - def get_restricted_fields(self, restricted_types): - restricted_fields = self.get("fields", { + def get_fields_to_check_permissions(self, user_permission_doctypes): + fields = self.get("fields", { "fieldtype":"Link", "parent": self.name, - "ignore_restrictions":("!=", 1), - "options":("in", restricted_types) + "ignore_user_permissions":("!=", 1), + "options":("in", user_permission_doctypes) }) - if self.name in restricted_types: - restricted_fields.append(frappe._dict({ - "label":"Name", "fieldname":"name", "options": self.name + + if self.name in user_permission_doctypes: + fields.append(frappe._dict({ + "label":"Name", + "fieldname":"name", + "options": self.name })) - return restricted_fields + return fields + + def is_print_hide(self, fieldname): + df = self.get_field(fieldname) + return df.get("__print_hide") or df.print_hide doctype_table_fields = [ frappe._dict({"fieldname": "fields", "options": "DocField"}), @@ -230,13 +241,16 @@ def get_field_currency(df, doc): """get currency based on DocField options and fieldvalue in doc""" currency = None - if ":" in cstr(df.options): - split_opts = df.options.split(":") + if not df.get("options"): + return None + + if ":" in cstr(df.get("options")): + split_opts = df.get("options").split(":") if len(split_opts)==3: currency = frappe.db.get_value(split_opts[0], doc.get(split_opts[1]), split_opts[2]) else: - currency = doc.get(df.options) + currency = doc.get(df.get("options")) return currency @@ -244,19 +258,23 @@ def get_field_precision(df, doc): """get precision based on DocField options and fieldvalue in doc""" from frappe.utils import get_number_format_info - number_format = None + precision = cint(df.precision) or cint(frappe.db.get_default("float_precision")) or 3 + if df.fieldtype == "Currency": + number_format = None currency = get_field_currency(df, doc) + + if not currency: + # use default currency + currency = frappe.db.get_default("currency") + if currency: number_format = frappe.db.get_value("Currency", currency, "number_format") - if not number_format: - number_format = frappe.db.get_default("number_format") or "#,###.##" - - decimal_str, comma_str, precision = get_number_format_info(number_format) + if not number_format: + number_format = frappe.db.get_default("number_format") or "#,###.##" - if df.fieldtype == "Float": - precision = cint(frappe.db.get_default("float_precision")) or 3 + decimal_str, comma_str, precision = get_number_format_info(number_format) return precision diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 48b551f209..4f1f827758 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe - +from frappe import _ from frappe.utils import now_datetime, cint def set_new_name(doc): @@ -13,13 +13,11 @@ def set_new_name(doc): # amendments if getattr(doc, "amended_from", None): return _get_amended_name(doc) - else: - tmp = getattr(doc, "autoname", None) - if tmp and not isinstance(tmp, basestring): - # autoname in a function, not a property - doc.autoname() - if doc.name: - return + + elif hasattr(doc, "run_method"): + doc.run_method("autoname") + if doc.name: + return autoname = frappe.get_meta(doc.doctype).autoname @@ -50,11 +48,16 @@ def set_new_name(doc): # default name for table elif doc.meta.istable: - doc.name = make_autoname('#########', doc.doctype) + doc.name = make_autoname('hash', doc.doctype) + + elif doc.meta.issingle: + doc.name = doc.doctype # unable to determine a name, use global series if not doc.name: - doc.name = make_autoname('#########', doc.doctype) + doc.name = make_autoname('hash', doc.doctype) + + doc.name = doc.name.strip() validate_name(doc.doctype, doc.name) @@ -120,6 +123,20 @@ def getseries(key, digits, doctype=''): current = 1 return ('%0'+str(digits)+'d') % current +def revert_series_if_last(key, name): + if ".#" in key: + prefix, hashes = key.rsplit(".", 1) + if "#" not in hashes: + return + else: + prefix = key + + count = cint(name.replace(prefix, "")) + current = frappe.db.sql("select `current` from `tabSeries` where name=%s for update", (prefix,)) + + if current and current[0][0]==count: + frappe.db.sql("update tabSeries set current=current-1 where name=%s", prefix) + def get_default_naming_series(doctype): """get default value for `naming_series` property""" naming_series = frappe.get_meta(doctype).get_field("naming_series").options or "" @@ -132,10 +149,14 @@ def get_default_naming_series(doctype): def validate_name(doctype, name, case=None, merge=False): if not name: return 'No Name Specified for %s' % doctype if name.startswith('New '+doctype): - raise frappe.NameError, 'There were some errors setting the name, please contact the administrator' + frappe.throw(_('There were some errors setting the name, please contact the administrator'), frappe.NameError) if case=='Title Case': name = name.title() if case=='UPPER CASE': name = name.upper() name = name.strip() + + if not frappe.get_meta(doctype).get("issingle") and doctype == name: + frappe.throw(_("Name of {0} cannot be {1}").format(doctype, name), frappe.NameError) + return name def _get_amended_name(doc): diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 1f4b5fb180..f3d0a961f4 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -22,8 +22,9 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F meta = frappe.get_meta(doctype) # call before_rename - out = frappe.get_doc(doctype, old).run_method("before_rename", old, new, merge) or {} - new = (out.get("new") or new) if isinstance(out, dict) else new + old_doc = frappe.get_doc(doctype, old) + out = old_doc.run_method("before_rename", old, new, merge) or {} + new = (out.get("new") or new) if isinstance(out, dict) else (out or new) new = validate_rename(doctype, new, meta, merge, force, ignore_permissions) if not merge: @@ -33,21 +34,29 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F link_fields = get_link_fields(doctype) update_link_field_values(link_fields, old, new, doctype) + rename_dynamic_links(doctype, old, new) + if doctype=='DocType': rename_doctype(doctype, old, new, force) + update_comments(doctype, old, new, force) update_attachments(doctype, old, new) if merge: frappe.delete_doc(doctype, old) # call after_rename - frappe.get_doc(doctype, new).run_method("after_rename", old, new, merge) + new_doc = frappe.get_doc(doctype, new) + + # copy any flags if required + new_doc._local = getattr(old_doc, "_local", None) + + new_doc.run_method("after_rename", old, new, merge) rename_versions(doctype, old, new) - # update restrictions - frappe.db.sql("""update tabDefaultValue set defvalue=%s where parenttype='Restriction' + # update user_permissions + frappe.db.sql("""update tabDefaultValue set defvalue=%s where parenttype='User Permission' and defkey=%s and defvalue=%s""", (new, doctype, old)) frappe.clear_cache() @@ -73,12 +82,12 @@ def rename_parent_and_child(doctype, old, new, meta): update_child_docs(old, new, meta) def validate_rename(doctype, new, meta, merge, force, ignore_permissions): - exists = frappe.db.exists(doctype, new) + exists = frappe.db.get_value(doctype, new) if merge and not exists: frappe.msgprint(_("{0} {1} does not exist, select a new target to merge").format(doctype, new), raise_exception=1) - if (not merge) and exists: + if (not merge) and exists == new: frappe.msgprint(_("Another {0} with name {1} exists, select another name").format(doctype, new), raise_exception=1) if not (ignore_permissions or frappe.has_permission(doctype, "write")): @@ -94,7 +103,8 @@ def validate_rename(doctype, new, meta, merge, force, ignore_permissions): def rename_doctype(doctype, old, new, force=False): # change options for fieldtype Table - update_parent_of_fieldtype_table(old, new) + update_options_for_fieldtype("Table", old, new) + update_options_for_fieldtype("Link", old, new) # change options where select options are hardcoded i.e. listed select_fields = get_select_fields(old, new) @@ -108,6 +118,10 @@ def rename_doctype(doctype, old, new, force=False): frappe.db.sql("""update tabComment set comment_doctype=%s where comment_doctype=%s""", (new, old)) +def update_comments(doctype, old, new, force=False): + frappe.db.sql("""update `tabComment` set comment_docname=%s + where comment_doctype=%s and comment_docname=%s""", (new, doctype, old)) + def update_child_docs(old, new, meta): # update "parent" for df in meta.get_table_fields(): @@ -167,17 +181,14 @@ def get_link_fields(doctype): return link_fields -def update_parent_of_fieldtype_table(old, new): - frappe.db.sql("""\ - update `tabDocField` set options=%s - where fieldtype='Table' and options=%s""", (new, old)) +def update_options_for_fieldtype(fieldtype, old, new): + frappe.db.sql("""update `tabDocField` set options=%s + where fieldtype=%s and options=%s""", (new, fieldtype, old)) - frappe.db.sql("""\ - update `tabCustom Field` set options=%s - where fieldtype='Table' and options=%s""", (new, old)) + frappe.db.sql("""update `tabCustom Field` set options=%s + where fieldtype=%s and options=%s""", (new, fieldtype, old)) - frappe.db.sql("""\ - update `tabProperty Setter` set value=%s + frappe.db.sql("""update `tabProperty Setter` set value=%s where property='options' and value=%s""", (new, old)) def get_select_fields(old, new): @@ -276,3 +287,27 @@ def update_parenttype_values(old, new): update `tab%s` set parenttype=%s where parenttype=%s""" % (doctype, '%s', '%s'), (new, old)) + +dynamic_link_queries = [ + """select parent, fieldname, options from tabDocField where fieldtype='Dynamic Link'""", + """select dt as parent, fieldname, options from `tabCustom Field` where fieldtype='Dynamic Link'""", +] + +def rename_dynamic_links(doctype, old, new): + for query in dynamic_link_queries: + for df in frappe.db.sql(query, as_dict=True): + + # dynamic link in single, just one value to check + if frappe.get_meta(df.parent).issingle: + refdoc = frappe.get_singles_dict(df.parent) + if refdoc.get(df.options)==doctype and refdoc.get(df.fieldname)==old: + + frappe.db.sql("""update tabSingles set value=%s where + field=%s and value=%s and doctype=%s""", (new, df.fieldname, old, df.parent)) + else: + # replace for each value where renamed + for to_change in frappe.db.sql_list("""select name from `tab{parent}` where + {options}=%s and {fieldname}=%s""".format(**df), (doctype, old)): + + frappe.db.sql("""update `tab{parent}` set {fieldname}=%s + where name=%s""".format(**df), (new, to_change)) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 0c285f423a..a3d411797b 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -9,48 +9,55 @@ from __future__ import unicode_literals import frappe import os, sys from frappe.modules.import_file import import_file_by_path -from frappe.utils import get_path, cstr +from frappe.modules.patch_handler import block_user +from frappe.utils import update_progress_bar def sync_all(force=0, verbose=False): + block_user(True) + for app in frappe.get_installed_apps(): sync_for(app, force, verbose=verbose) + + block_user(False) + frappe.clear_cache() def sync_for(app_name, force=0, sync_everything = False, verbose=False): + files = [] for module_name in frappe.local.app_modules.get(app_name) or []: folder = os.path.dirname(frappe.get_module(app_name + "." + module_name).__file__) - walk_and_sync(folder, force, sync_everything, verbose=verbose) - -def walk_and_sync(start_path, force=0, sync_everything = False, verbose=False): - """walk and sync all doctypes and pages""" + files += get_doc_files(folder, force, sync_everything, verbose=verbose) - modules = [] + l = len(files) + if l: + for i, doc_path in enumerate(files): + import_file_by_path(doc_path, force=force) + #print module_name + ' | ' + doctype + ' | ' + name - document_type = ['doctype', 'page', 'report', 'print_format'] + frappe.db.commit() - for path, folders, files in os.walk(start_path): - # sort folders so that doctypes are synced before pages or reports + # show progress bar + update_progress_bar("Updating {0}".format(app_name), i, l) - for dontwalk in (".git", "locale", "public"): - if dontwalk in folders: - folders.remove(dontwalk) + print "" - folders.sort() - if sync_everything or (os.path.basename(os.path.dirname(path)) in document_type): - for f in files: - f = cstr(f) - if f.endswith(".json"): - doc_name = f.split(".json")[0] - if doc_name == os.path.basename(path): +def get_doc_files(start_path, force=0, sync_everything = False, verbose=False): + """walk and sync all doctypes and pages""" - module_name = path.split(os.sep)[-3] - doctype = path.split(os.sep)[-2] - name = path.split(os.sep)[-1] + out = [] + document_type = ['doctype', 'page', 'report', 'print_format'] + for doctype in document_type: + doctype_path = os.path.join(start_path, doctype) + if os.path.exists(doctype_path): - if import_file_by_path(os.path.join(path, f), force=force) and verbose: - print module_name + ' | ' + doctype + ' | ' + name + # Note: sorted is a hack because custom* and doc* need + # be synced first - frappe.db.commit() + for docname in sorted(os.listdir(doctype_path)): + if os.path.isdir(os.path.join(doctype_path, docname)): + doc_path = os.path.join(doctype_path, docname, docname) + ".json" + if os.path.exists(doc_path): + out.append(doc_path) - return modules + return out diff --git a/frappe/modules.txt b/frappe/modules.txt index 2287e7c2d4..8fe4b686dd 100644 --- a/frappe/modules.txt +++ b/frappe/modules.txt @@ -1,2 +1,2 @@ -core -website \ No newline at end of file +Core +Website diff --git a/frappe/modules/__init__.py b/frappe/modules/__init__.py index 9f8890c482..8ee6446001 100644 --- a/frappe/modules/__init__.py +++ b/frappe/modules/__init__.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals """ import frappe, os import frappe.utils +from frappe import _ lower_case_files_for = ['DocType', 'Page', 'Report', "Workflow", 'Module Def', 'Desktop Item', 'Workflow State', 'Workflow Action', 'Print Format'] @@ -44,13 +45,50 @@ def export_doc(doctype, name, module=None): def get_doctype_module(doctype): return frappe.db.get_value('DocType', doctype, 'module') or "core" +doctype_python_modules = {} def load_doctype_module(doctype, module=None, prefix=""): if not module: module = get_doctype_module(doctype) - return frappe.get_module(get_module_name(doctype, module, prefix)) -def get_module_name(doctype, module, prefix=""): - from frappe.modules import scrub + app = get_module_app(module) + + key = (app, doctype, prefix) + + if key not in doctype_python_modules: + doctype_python_modules[key] = frappe.get_module(get_module_name(doctype, module, prefix)) + + return doctype_python_modules[key] + +def get_module_name(doctype, module, prefix="", app=None): return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}'.format(\ - app = scrub(frappe.local.module_app[scrub(module)]), - module = scrub(module), doctype = scrub(doctype), prefix=prefix) + app = scrub(app or get_module_app(module)), + module = scrub(module), + doctype = scrub(doctype), + prefix=prefix) + +def get_module_app(module): + return frappe.local.module_app[scrub(module)] + +def get_app_publisher(module): + app = frappe.local.module_app[scrub(module)] + if not app: + frappe.throw(_("App not found")) + app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0] + return app_publisher + +def make_boilerplate(template, doc, opts=None): + target_path = get_doc_path(doc.module, doc.doctype, doc.name) + template_name = template.replace("controller", scrub(doc.name)) + target_file_path = os.path.join(target_path, template_name) + + app_publisher = get_app_publisher(doc.module) + + if not os.path.exists(target_file_path): + if not opts: + opts = {} + + with open(target_file_path, 'w') as target: + with open(os.path.join(get_module_path("core"), "doctype", scrub(doc.doctype), + "boilerplate", template), 'r') as source: + target.write(source.read().format(app_publisher=app_publisher, + classname=doc.name.replace(" ", ""), doctype=doc.name, **opts)) diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 87db8652aa..bb78544508 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, os, json -from frappe.modules import scrub, get_module_path, scrub_dt_dn +from frappe.modules import get_module_path, scrub_dt_dn from frappe.utils import get_datetime_str def import_files(module, dt=None, dn=None, force=False): @@ -74,6 +74,7 @@ def read_doc_from_file(path): ignore_values = { "Report": ["disabled"], + "Print Format": ["disabled"] } ignore_doctypes = ["Page Role", "DocPerm"] @@ -106,5 +107,5 @@ def import_doc(docdict, force=False): doc.ignore_validate = True doc.ignore_permissions = True doc.ignore_mandatory = True - doc.ignore_restrictions = True + doc.ignore_user_permissions = True doc.insert() diff --git a/frappe/modules/patch_handler.py b/frappe/modules/patch_handler.py index e7dbd01ff5..1a86cfebbf 100644 --- a/frappe/modules/patch_handler.py +++ b/frappe/modules/patch_handler.py @@ -53,9 +53,9 @@ def run_single(patchmodule=None, method=None, methodargs=None, force=False): def execute_patch(patchmodule, method=None, methodargs=None): """execute the patch""" - success = False block_user(True) frappe.db.begin() + try: log('Executing {patch} in {site} ({db})'.format(patch=patchmodule or str(methodargs), site=frappe.local.site, db=frappe.db.cur_db_name)) @@ -63,22 +63,21 @@ def execute_patch(patchmodule, method=None, methodargs=None): if patchmodule.startswith("execute:"): exec patchmodule.split("execute:")[1] in globals() else: - frappe.get_attr(patchmodule + ".execute")() + frappe.get_attr(patchmodule.split()[0] + ".execute")() update_patch_log(patchmodule) elif method: method(**methodargs) - frappe.db.commit() - success = True - except Exception, e: + except Exception: frappe.db.rollback() - tb = frappe.get_traceback() - log(tb) + raise - block_user(False) - if success: + else: + frappe.db.commit() + block_user(False) log('Success') - return success + + return True def update_patch_log(patchmodule): """update patch_file in patch log""" diff --git a/frappe/patches.txt b/frappe/patches.txt index 3c8dbd23bd..6e8c2fa47e 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -1,32 +1,56 @@ -execute:import inlinestyler # new requirement - execute:frappe.db.sql("""update `tabPatch Log` set patch=replace(patch, '.4_0.', '.v4_0.')""") #2014-05-12 execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2014-01-24 execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2014-03-01 -execute:frappe.reload_doc('core', 'doctype', 'docperm') #2013-13-26 +execute:frappe.reload_doc('core', 'doctype', 'docperm') #2014-06-04 execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26 -execute:frappe.reload_doc('core', 'doctype', 'report') #2013-13-26 +execute:frappe.reload_doc('core', 'doctype', 'report') #2014-06-03 execute:frappe.reload_doc('core', 'doctype', 'version') #2014-02-21 +execute:frappe.reload_doc('core', 'doctype', 'email_alert') #2014-07-15 execute:frappe.db.sql("alter table `tabSessions` modify `user` varchar(255), engine=InnoDB") +execute:frappe.db.sql("delete from `tabDocField` where parent='0'") +frappe.patches.v4_0.change_varchar_length -frappe.patches.v4_0.remove_old_parent -frappe.patches.v4_0.remove_index_sitemap -frappe.patches.v4_0.add_delete_permission -frappe.patches.v4_0.move_match_to_restricted -frappe.patches.v4_0.set_todo_checked_as_closed -frappe.patches.v4_0.website_sitemap_hierarchy frappe.patches.v4_0.webnotes_to_frappe execute:frappe.reset_perms("Module Def") -frappe.patches.v4_0.rename_sitemap_to_route +execute:import frappe.installer;frappe.installer.make_site_dirs() #2014-02-19 frappe.patches.v4_0.rename_profile_to_user +frappe.patches.v4_0.deprecate_control_panel +frappe.patches.v4_0.remove_old_parent +frappe.patches.v4_0.rename_sitemap_to_route +frappe.patches.v4_0.website_sitemap_hierarchy +frappe.patches.v4_0.remove_index_sitemap frappe.patches.v4_0.set_website_route_idx -execute:import frappe.installer;frappe.installer.make_site_dirs() #2014-02-19 +frappe.patches.v4_0.add_delete_permission +frappe.patches.v4_0.set_todo_checked_as_closed frappe.patches.v4_0.private_backups frappe.patches.v4_0.set_module_in_report frappe.patches.v4_0.update_datetime -frappe.patches.v4_0.deprecate_control_panel frappe.patches.v4_0.file_manager_hooks execute:frappe.get_doc("User", "Guest").save() +frappe.patches.v4_0.update_custom_field_insert_after frappe.patches.v4_0.deprecate_link_selects frappe.patches.v4_0.set_user_gravatar -frappe.patches.v4_0.update_custom_field_insert_after +frappe.patches.v4_0.set_user_permissions +frappe.patches.v4_0.create_custom_field_for_owner_match +frappe.patches.v4_0.enable_scheduler_in_system_settings +execute:frappe.db.sql("update tabReport set apply_user_permissions=1") #2014-06-03 +frappe.patches.v4_0.replace_deprecated_timezones +execute:import frappe.website.render; frappe.website.render.clear_cache("login"); #2014-06-10 +frappe.patches.v4_0.fix_attach_field_file_url +execute:frappe.reset_perms("User") #2014-06-13 +execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '')='' or ifnull(`role`, '')=''""") #2014-08-18 +frappe.patches.v4_0.remove_user_owner_custom_field +execute:frappe.delete_doc("DocType", "Website Template") +execute:frappe.db.sql("""update `tabProperty Setter` set property_type='Text' where property in ('options', 'default')""") #2014-06-20 +frappe.patches.v4_1.enable_outgoing_email_settings +execute:frappe.db.sql("""update `tabSingles` set `value`=`doctype` where `field`='name'""") #2014-07-04 +frappe.patches.v4_1.enable_print_as_pdf #2014-06-17 +execute:frappe.db.sql("""update `tabDocPerm` set email=1 where parent='User' and permlevel=0 and `role`='All' and `read`=1 and apply_user_permissions=1""") #2014-07-15 +execute:frappe.db.sql("""update `tabPrint Format` set print_format_type='Client' where ifnull(print_format_type, '')=''""") #2014-07-28 +frappe.patches.v4_1.file_manager_fix +frappe.patches.v4_2.print_with_letterhead +execute:frappe.delete_doc("DocType", "Control Panel", force=1) +execute:frappe.reload_doc('website', 'doctype', 'web_form') #2014-09-04 +execute:frappe.reload_doc('website', 'doctype', 'web_form_field') #2014-09-04 +frappe.patches.v4_2.refactor_website_routing +frappe.patches.v4_2.set_assign_in_doc diff --git a/frappe/patches/v4_0/change_varchar_length.py b/frappe/patches/v4_0/change_varchar_length.py new file mode 100644 index 0000000000..d9da8221e9 --- /dev/null +++ b/frappe/patches/v4_0/change_varchar_length.py @@ -0,0 +1,23 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + for dt in frappe.db.sql_list("""select name from `tabDocType` where ifnull(issingle, 0)=0"""): + desc = dict((d["Field"], d) for d in frappe.db.sql("desc `tab{}`".format(dt), as_dict=True)) + alter_table = [] + + if desc["name"]["Type"] != "varchar(255)": + alter_table.append("change `name` `name` varchar(255) not null") + + for fieldname in ("modified_by", "owner", "parent", "parentfield", "parenttype"): + if desc[fieldname]["Type"] != "varchar(255)": + alter_table.append("change `{fieldname}` `{fieldname}` varchar(255)".format(fieldname=fieldname)) + + if alter_table: + alter_table_query = "alter table `tab{doctype}` {alter_table}".format(doctype=dt, alter_table=",\n".join(alter_table)) + # print alter_table_query + frappe.db.sql_ddl(alter_table_query) + diff --git a/frappe/patches/v4_0/create_custom_field_for_owner_match.py b/frappe/patches/v4_0/create_custom_field_for_owner_match.py new file mode 100644 index 0000000000..2ac51c18ef --- /dev/null +++ b/frappe/patches/v4_0/create_custom_field_for_owner_match.py @@ -0,0 +1,39 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.core.doctype.custom_field.custom_field import create_custom_field + +def execute(): + if "match" in frappe.db.get_table_columns("DocPerm"): + create_custom_field_for_owner_match() + +def create_custom_field_for_owner_match(): + frappe.db.sql("""update `tabDocPerm` set apply_user_permissions=1 where `match`='owner'""") + + for dt in frappe.db.sql_list("""select distinct parent from `tabDocPerm` + where `match`='owner' and permlevel=0 and parent != 'User'"""): + + # a link field pointing to User already exists + if (frappe.db.get_value("DocField", {"parent": dt, "fieldtype": "Link", "options": "User", "default": "__user"}) + or frappe.db.get_value("Custom Field", {"dt": dt, "fieldtype": "Link", "options": "User", "default": "__user"})): + print "User link field already exists for", dt + continue + + fieldname = "{}_owner".format(frappe.scrub(dt)) + + create_custom_field(dt, frappe._dict({ + "permlevel": 0, + "label": "{} Owner".format(dt), + "fieldname": fieldname, + "fieldtype": "Link", + "options": "User", + "default": "__user" + })) + + frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=owner""".format(doctype=dt, + fieldname=fieldname)) + + # commit is required so that we don't lose these changes because of an error in next loop's ddl + frappe.db.commit() diff --git a/frappe/patches/v4_0/enable_scheduler_in_system_settings.py b/frappe/patches/v4_0/enable_scheduler_in_system_settings.py new file mode 100644 index 0000000000..bc2f167ece --- /dev/null +++ b/frappe/patches/v4_0/enable_scheduler_in_system_settings.py @@ -0,0 +1,14 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils.scheduler import disable_scheduler, enable_scheduler +from frappe.utils import cint + +def execute(): + frappe.reload_doc("core", "doctype", "system_settings") + if cint(frappe.db.get_global("disable_scheduler")): + disable_scheduler() + else: + enable_scheduler() diff --git a/frappe/patches/v4_0/fix_attach_field_file_url.py b/frappe/patches/v4_0/fix_attach_field_file_url.py new file mode 100644 index 0000000000..3c5a6aa03c --- /dev/null +++ b/frappe/patches/v4_0/fix_attach_field_file_url.py @@ -0,0 +1,13 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + attach_fields = (frappe.db.sql("""select parent, fieldname from `tabDocField` where fieldtype='Attach'""") + + frappe.db.sql("""select dt, fieldname from `tabCustom Field` where fieldtype='Attach'""")) + + for doctype, fieldname in attach_fields: + frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=concat("/", `{fieldname}`) + where `{fieldname}` like 'files/%'""".format(doctype=doctype, fieldname=fieldname)) diff --git a/frappe/patches/v4_0/remove_index_sitemap.py b/frappe/patches/v4_0/remove_index_sitemap.py index 7f5124813c..00d37eaf2e 100644 --- a/frappe/patches/v4_0/remove_index_sitemap.py +++ b/frappe/patches/v4_0/remove_index_sitemap.py @@ -1,6 +1,4 @@ import frappe def execute(): - if frappe.db.exists("Website Route", "index"): - frappe.delete_doc("Website Route", "index", ignore_permissions=True) - \ No newline at end of file + pass diff --git a/frappe/patches/v4_0/move_match_to_restricted.py b/frappe/patches/v4_0/remove_user_owner_custom_field.py similarity index 54% rename from frappe/patches/v4_0/move_match_to_restricted.py rename to frappe/patches/v4_0/remove_user_owner_custom_field.py index e21b12c034..4f3e478f98 100644 --- a/frappe/patches/v4_0/move_match_to_restricted.py +++ b/frappe/patches/v4_0/remove_user_owner_custom_field.py @@ -5,5 +5,6 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("core", "doctype", "docperm") - frappe.db.sql("""update `tabDocPerm` set restricted=1 where `match`='owner'""") \ No newline at end of file + user_owner = frappe.db.get_value("Custom Field", {"fieldname": "user_owner"}) + if user_owner: + frappe.delete_doc("Custom Field", user_owner) diff --git a/frappe/patches/v4_0/rename_profile_to_user.py b/frappe/patches/v4_0/rename_profile_to_user.py index 66b8752da6..bcb910b80a 100644 --- a/frappe/patches/v4_0/rename_profile_to_user.py +++ b/frappe/patches/v4_0/rename_profile_to_user.py @@ -13,4 +13,6 @@ def execute(): if "profile" in get_table_columns("Website Route Permission"): rename_field("Website Route Permission", "profile", "user") frappe.reload_doc("website", "doctype", "blogger") - rename_field("Blogger", "profile", "user") + + if "profile" in get_table_columns("Blogger"): + rename_field("Blogger", "profile", "user") diff --git a/frappe/patches/v4_0/rename_sitemap_to_route.py b/frappe/patches/v4_0/rename_sitemap_to_route.py index 823d5d4b2e..991edf1f7c 100644 --- a/frappe/patches/v4_0/rename_sitemap_to_route.py +++ b/frappe/patches/v4_0/rename_sitemap_to_route.py @@ -4,25 +4,28 @@ from frappe.model import rename_field def execute(): tables = frappe.db.sql_list("show tables") - if "tabWebsite Route" not in tables: - frappe.rename_doc("DocType", "Website Sitemap", "Website Route", force=True) - - if "tabWebsite Template" not in tables: - frappe.rename_doc("DocType", "Website Sitemap Config", "Website Template", force=True) - - try: - if "tabWebsite Route Permission" not in tables: - frappe.rename_doc("DocType", "Website Sitemap Permission", "Website Route Permission", force=True) - - for d in ("Blog Category", "Blog Post", "Web Page", "Website Route", "Website Group"): - frappe.reload_doc("website", "doctype", frappe.scrub(d)) - rename_field(d, "parent_website_sitemap", "parent_website_route") - - rename_field("Website Route", "website_sitemap_config", "website_template") - rename_field("Website Route Permission", "website_sitemap", "website_route") + for doctype in ("Website Sitemap", "Website Sitemap Config"): + if "tab{}".format(doctype) in tables: + frappe.delete_doc("DocType", doctype, force=1) + frappe.db.sql("drop table `tab{}`".format(doctype)) + + if "tabWebsite Route Permission" not in tables: + frappe.rename_doc("DocType", "Website Sitemap Permission", "Website Route Permission", force=True) + + for d in ("Blog Category", "Blog Post", "Web Page", "Website Group"): + frappe.reload_doc("website", "doctype", frappe.scrub(d)) + rename_field_if_exists(d, "parent_website_sitemap", "parent_website_route") + + frappe.reload_doc("website", "doctype", "website_route_permission") + + rename_field_if_exists("Website Route Permission", "website_sitemap", "website_route") + + for d in ("blog_category", "blog_post", "web_page", "website_group", "post", "user_vote"): + frappe.reload_doc("website", "doctype", d) + +def rename_field_if_exists(doctype, old_fieldname, new_fieldname): + try: + rename_field(doctype, old_fieldname, new_fieldname) except Exception, e: if e.args[0] != 1054: raise - - for d in ("blog_category", "blog_post", "web_page", "website_route", "website_group"): - frappe.reload_doc("website", "doctype", d) diff --git a/frappe/patches/v4_0/replace_deprecated_timezones.py b/frappe/patches/v4_0/replace_deprecated_timezones.py new file mode 100644 index 0000000000..4574dc19f8 --- /dev/null +++ b/frappe/patches/v4_0/replace_deprecated_timezones.py @@ -0,0 +1,21 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils.momentjs import data as momentjs_data + +def execute(): + frappe.reload_doc("core", "doctype", "user") + + ss = frappe.get_doc("System Settings", "System Settings") + if ss.time_zone in momentjs_data.get("links"): + ss.time_zone = momentjs_data["links"][ss.time_zone] + ss.ignore_mandatory = True + ss.save() + + for user, time_zone in frappe.db.sql("select name, time_zone from `tabUser` where ifnull(time_zone, '')!=''"): + if time_zone in momentjs_data.get("links"): + user = frappe.get_doc("User", user) + user.time_zone = momentjs_data["links"][user.time_zone] + user.save() diff --git a/frappe/patches/v4_0/set_user_permissions.py b/frappe/patches/v4_0/set_user_permissions.py new file mode 100644 index 0000000000..b25f68c09c --- /dev/null +++ b/frappe/patches/v4_0/set_user_permissions.py @@ -0,0 +1,24 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +import frappe.permissions + +def execute(): + frappe.reload_doc("core", "doctype", "docperm") + table_columns = frappe.db.get_table_columns("DocPerm") + + if "restricted" in table_columns: + frappe.db.sql("""update `tabDocPerm` set apply_user_permissions=1 where ifnull(apply_user_permissions, 0)=0 + and restricted=1""") + + if "match" in table_columns: + frappe.db.sql("""update `tabDocPerm` set apply_user_permissions=1 + where ifnull(apply_user_permissions, 0)=0 and ifnull(`match`, '')!=''""") + + # change Restriction to User Permission in tabDefaultValue + frappe.db.sql("""update `tabDefaultValue` set parenttype='User Permission' where parenttype='Restriction'""") + + frappe.clear_cache() + diff --git a/frappe/patches/v4_0/set_website_route_idx.py b/frappe/patches/v4_0/set_website_route_idx.py index eb5fc1ebd6..82f64756a4 100644 --- a/frappe/patches/v4_0/set_website_route_idx.py +++ b/frappe/patches/v4_0/set_website_route_idx.py @@ -1,23 +1,24 @@ import frappe def execute(): - from frappe.website.doctype.website_template.website_template import \ - get_pages_and_generators, get_template_controller - - frappe.reload_doc("website", "doctype", "website_template") - frappe.reload_doc("website", "doctype", "website_route") - - for app in frappe.get_installed_apps(): - pages, generators = get_pages_and_generators(app) - for g in generators: - doctype = frappe.get_attr(get_template_controller(app, g["path"], g["fname"]) + ".doctype") - module = frappe.db.get_value("DocType", doctype, "module") - frappe.reload_doc(frappe.scrub(module), "doctype", frappe.scrub(doctype)) - - frappe.db.sql("""update `tabBlog Category` set `title`=`name` where ifnull(`title`, '')=''""") - frappe.db.sql("""update `tabWebsite Route` set idx=null""") - for doctype in ["Blog Category", "Blog Post", "Web Page", "Website Group"]: - frappe.db.sql("""update `tab{}` set idx=null""".format(doctype)) - - from frappe.website.doctype.website_template.website_template import rebuild_website_template - rebuild_website_template() \ No newline at end of file + pass + # from frappe.website.doctype.website_template.website_template import \ + # get_pages_and_generators, get_template_controller + # + # frappe.reload_doc("website", "doctype", "website_template") + # frappe.reload_doc("website", "doctype", "website_route") + # + # for app in frappe.get_installed_apps(): + # pages, generators = get_pages_and_generators(app) + # for g in generators: + # doctype = frappe.get_attr(get_template_controller(app, g["path"], g["fname"]) + ".doctype") + # module = frappe.db.get_value("DocType", doctype, "module") + # frappe.reload_doc(frappe.scrub(module), "doctype", frappe.scrub(doctype)) + # + # frappe.db.sql("""update `tabBlog Category` set `title`=`name` where ifnull(`title`, '')=''""") + # frappe.db.sql("""update `tabWebsite Route` set idx=null""") + # for doctype in ["Blog Category", "Blog Post", "Web Page", "Website Group"]: + # frappe.db.sql("""update `tab{}` set idx=null""".format(doctype)) + # + # from frappe.website.doctype.website_template.website_template import rebuild_website_template + # rebuild_website_template() diff --git a/frappe/patches/v4_0/webnotes_to_frappe.py b/frappe/patches/v4_0/webnotes_to_frappe.py index 0fa8bdf1dc..c70b2fdbde 100644 --- a/frappe/patches/v4_0/webnotes_to_frappe.py +++ b/frappe/patches/v4_0/webnotes_to_frappe.py @@ -1,10 +1,11 @@ import frappe, json def execute(): + frappe.clear_cache() installed = frappe.get_installed_apps() if "webnotes" in installed: installed.remove("webnotes") if "frappe" not in installed: installed = ["frappe"] + installed frappe.db.set_global("installed_apps", json.dumps(installed)) - frappe.clear_cache() \ No newline at end of file + frappe.clear_cache() diff --git a/frappe/patches/v4_0/website_sitemap_hierarchy.py b/frappe/patches/v4_0/website_sitemap_hierarchy.py index 90fead9502..324b13ea4a 100644 --- a/frappe/patches/v4_0/website_sitemap_hierarchy.py +++ b/frappe/patches/v4_0/website_sitemap_hierarchy.py @@ -6,19 +6,16 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("website", "doctype", "website_template") - frappe.reload_doc("website", "doctype", "website_route") - frappe.reload_doc("website", "doctype", "website_route_permission") - frappe.reload_doc("website", "doctype", "website_group") - frappe.reload_doc("website", "doctype", "post") - frappe.reload_doc("website", "doctype", "user_vote") - - frappe.db.sql("""update `tabWebsite Route` ws set ref_doctype=(select wsc.ref_doctype - from `tabWebsite Template` wsc where wsc.name=ws.website_template) - where ifnull(page_or_generator, '')!='Page'""") - + # frappe.db.sql("""update `tabWebsite Route` ws set ref_doctype=(select wsc.ref_doctype + # from `tabWebsite Template` wsc where wsc.name=ws.website_template) + # where ifnull(page_or_generator, '')!='Page'""") + frappe.reload_doc("website", "doctype", "website_settings") - home_page = frappe.db.get_value("Website Settings", "Website Settings", "home_page") - home_page = frappe.db.get_value("Website Route", {"docname": home_page}) or home_page - frappe.db.set_value("Website Settings", "Website Settings", "home_page", - home_page) + + # original_home_page = frappe.db.get_value("Website Settings", "Website Settings", "home_page") + # + # home_page = frappe.db.sql("""select name from `tabWebsite Route` + # where (name=%s or docname=%s) and name!='index'""", (original_home_page, original_home_page)) + # home_page = home_page[0][0] if home_page else original_home_page + # + # frappe.db.set_value("Website Settings", "Website Settings", "home_page", home_page) diff --git a/frappe/patches/v4_1/__init__.py b/frappe/patches/v4_1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/patches/v4_1/enable_outgoing_email_settings.py b/frappe/patches/v4_1/enable_outgoing_email_settings.py new file mode 100644 index 0000000000..472a8109e3 --- /dev/null +++ b/frappe/patches/v4_1/enable_outgoing_email_settings.py @@ -0,0 +1,10 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("core", "doctype", "outgoing_email_settings") + if (frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "mail_server") or "").strip(): + frappe.db.set_value("Outgoing Email Settings", "Outgoing Email Settings", "enabled", 1) diff --git a/frappe/patches/v4_1/enable_print_as_pdf.py b/frappe/patches/v4_1/enable_print_as_pdf.py new file mode 100644 index 0000000000..877a2b61f7 --- /dev/null +++ b/frappe/patches/v4_1/enable_print_as_pdf.py @@ -0,0 +1,26 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("core", "doctype", "print_settings") + print_settings = frappe.get_doc("Print Settings") + print_settings.print_style = "Modern" + + try: + import pdfkit + except ImportError: + pass + else: + # if someone has already configured in Outgoing Email Settings + outgoing_email_settings = frappe.db.get_singles_dict("Outgoing Email Settings") + if "send_print_as_pdf" in outgoing_email_settings: + print_settings.send_print_as_pdf = outgoing_email_settings.send_print_as_pdf + print_settings.pdf_page_size = outgoing_email_settings.pdf_page_size + + else: + print_settings.send_print_as_pdf = 1 + + print_settings.save() diff --git a/frappe/patches/v4_1/file_manager_fix.py b/frappe/patches/v4_1/file_manager_fix.py new file mode 100644 index 0000000000..5315284109 --- /dev/null +++ b/frappe/patches/v4_1/file_manager_fix.py @@ -0,0 +1,98 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals + +import frappe +import os +from frappe.utils.file_manager import get_content_hash, get_file, get_file_name +from frappe.utils import get_files_path, get_site_path + +# The files missed by the previous patch might have been replaced with new files +# with the same filename +# +# This patch does the following, +# * Detect which files were replaced and rename them with name{hash:5}.extn and +# update filedata record for the new file +# +# * make missing_files.txt in site dir with files that should be recovered from +# a backup from a time before version 3 migration +# +# * Patch remaining unpatched file data records. + +def execute(): + frappe.db.auto_commit_on_many_writes = True + rename_replacing_files() + for name, file_name, file_url in frappe.db.sql( + """select name, file_name, file_url from `tabFile Data` + where ifnull(file_name, '')!='' and ifnull(content_hash, '')=''"""): + b = frappe.get_doc('File Data', name) + old_file_name = b.file_name + b.file_name = os.path.basename(old_file_name) + if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): + b.file_url = os.path.normpath('/' + old_file_name) + else: + b.file_url = os.path.normpath('/files/' + old_file_name) + try: + _file_name, content = get_file(name) + b.content_hash = get_content_hash(content) + except IOError: + print 'Warning: Error processing ', name + b.content_hash = None + b.ignore_duplicate_entry_error = True + b.save() + frappe.db.auto_commit_on_many_writes = False + +def get_replaced_files(): + ret = [] + new_files = dict(frappe.db.sql("select name, file_name from `tabFile Data` where file_name not like 'files/%'")) + old_files = dict(frappe.db.sql("select name, file_name from `tabFile Data` where ifnull(content_hash, '')=''")) + invfiles = invert_dict(new_files) + + for nname, nfilename in new_files.iteritems(): + if 'files/' + nfilename in old_files.values(): + ret.append((nfilename, invfiles[nfilename])) + return ret + +def rename_replacing_files(): + replaced_files = get_replaced_files() + if len(replaced_files): + missing_files = [v[0] for v in replaced_files] + with open(get_site_path('missing_files.txt'), 'w') as f: + f.write(('\n'.join(missing_files) + '\n').encode('utf-8')) + + for file_name, file_datas in replaced_files: + print 'processing ' + file_name + content_hash = frappe.db.get_value('File Data', file_datas[0], 'content_hash') + if not content_hash: + continue + new_file_name = get_file_name(file_name, content_hash) + if os.path.exists(get_files_path(new_file_name)): + continue + print 'skipping ' + file_name + try: + os.rename(get_files_path(file_name), get_files_path(new_file_name)) + except OSError: + print 'Error renaming ', file_name + for name in file_datas: + f = frappe.get_doc('File Data', name) + f.file_name = new_file_name + f.file_url = '/files/' + new_file_name + f.save() + +def invert_dict(ddict): + ret = {} + for k,v in ddict.iteritems(): + if not ret.get(v): + ret[v] = [k] + else: + ret[v].append(k) + return ret + +def get_file_name(fname, hash): + if '.' in fname: + partial, extn = fname.rsplit('.', 1) + else: + partial = fname + extn = '' + return '{partial}{suffix}.{extn}'.format(partial=partial, extn=extn, suffix=hash[:5]) diff --git a/frappe/patches/v4_2/__init__.py b/frappe/patches/v4_2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/patches/v4_2/print_with_letterhead.py b/frappe/patches/v4_2/print_with_letterhead.py new file mode 100644 index 0000000000..ad2a144d95 --- /dev/null +++ b/frappe/patches/v4_2/print_with_letterhead.py @@ -0,0 +1,11 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("core", "doctype", "print_settings") + print_settings = frappe.get_doc("Print Settings") + print_settings.with_letterhead = 1 + print_settings.save() diff --git a/frappe/patches/v4_2/refactor_website_routing.py b/frappe/patches/v4_2/refactor_website_routing.py new file mode 100644 index 0000000000..b3ad5c8761 --- /dev/null +++ b/frappe/patches/v4_2/refactor_website_routing.py @@ -0,0 +1,7 @@ +import frappe + +def execute(): + # clear all static web pages + frappe.delete_doc("DocType", "Website Route", force=1) + frappe.delete_doc("Page", "sitemap-browser", force=1) + frappe.db.sql("drop table `tabWebsite Route`") diff --git a/frappe/patches/v4_2/set_assign_in_doc.py b/frappe/patches/v4_2/set_assign_in_doc.py new file mode 100644 index 0000000000..664c997eaf --- /dev/null +++ b/frappe/patches/v4_2/set_assign_in_doc.py @@ -0,0 +1,10 @@ +import frappe + +def execute(): + for name in frappe.db.sql_list("""select name from `tabToDo` + where ifnull(reference_type, '')!='' and ifnull(reference_name, '')!=''"""): + try: + frappe.get_doc("ToDo", name).on_update() + except Exception, e: + if e.args[0]!=1146: + raise diff --git a/frappe/permissions.py b/frappe/permissions.py index 466318cd4f..30cdbee603 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -2,20 +2,23 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe +import frappe, copy, json from frappe import _, msgprint from frappe.utils import cint -rights = ("read", "write", "create", "submit", "cancel", "amend", - "report", "import", "export", "print", "email", "restrict", "delete", "restricted") +rights = ("read", "write", "create", "delete", "submit", "cancel", "amend", + "print", "email", "report", "import", "export", "set_user_permissions") -def check_admin_or_system_manager(): - if ("System Manager" not in frappe.get_roles()) and \ - (frappe.session.user!="Administrator"): +def check_admin_or_system_manager(user=None): + if not user: user = frappe.session.user + + if ("System Manager" not in frappe.get_roles(user)) and (user!="Administrator"): frappe.throw(_("Not permitted"), frappe.PermissionError) -def has_permission(doctype, ptype="read", doc=None, verbose=True): +def has_permission(doctype, ptype="read", doc=None, verbose=True, user=None): """check if user has permission""" + if not user: user = frappe.session.user + if frappe.is_table(doctype): return True @@ -27,134 +30,173 @@ def has_permission(doctype, ptype="read", doc=None, verbose=True): if ptype=="import" and not cint(meta.allow_import): return False - if frappe.session.user=="Administrator": + if user=="Administrator": return True - # get user permissions - if not get_user_perms(meta).get(ptype): + role_permissions = get_role_permissions(meta, user=user) + if not role_permissions.get(ptype): return False if doc: if isinstance(doc, basestring): doc = frappe.get_doc(meta.name, doc) - if not has_unrestricted_access(doc, verbose=verbose): - return False + if role_permissions["apply_user_permissions"].get(ptype): + if not user_has_permission(doc, verbose=verbose, user=user, + user_permission_doctypes=role_permissions.get("user_permission_doctypes")): + return False - if not has_controller_permissions(doc): + if not has_controller_permissions(doc, ptype, user=user): return False return True -def get_user_perms(meta, user=None): - if not user: - user = frappe.session.user +def get_doc_permissions(doc, verbose=False, user=None): + if not user: user = frappe.session.user + + if frappe.is_table(doc.doctype): + return {"read":1, "write":1} + + meta = frappe.get_meta(doc.doctype) + + role_permissions = copy.deepcopy(get_role_permissions(meta, user=user)) + + if not cint(meta.is_submittable): + role_permissions["submit"] = 0 + + if not cint(meta.allow_import): + role_permissions["import"] = 0 + + if role_permissions.get("apply_user_permissions") and not user_has_permission(doc, verbose=verbose, user=user, + user_permission_doctypes=role_permissions.get("user_permission_doctypes")): + # no user permissions, switch off all user-level permissions + for ptype in role_permissions: + if role_permissions["apply_user_permissions"].get(ptype): + role_permissions[ptype] = 0 + + return role_permissions + +def get_role_permissions(meta, user=None): + if not user: user = frappe.session.user cache_key = (meta.name, user) - if not frappe.local.user_perms.get(cache_key): - perms = frappe._dict() + + if not frappe.local.role_permissions.get(cache_key): + perms = frappe._dict({ "apply_user_permissions": {} }) user_roles = frappe.get_roles(user) for p in meta.permissions: if cint(p.permlevel)==0 and (p.role in user_roles): for ptype in rights: - if ptype == "restricted": - perms[ptype] = perms.get(ptype, 1) and cint(p.get(ptype)) - else: - perms[ptype] = perms.get(ptype, 0) or cint(p.get(ptype)) - - frappe.local.user_perms[cache_key] = perms - - return frappe.local.user_perms[cache_key] - -def has_unrestricted_access(doc, verbose=True): - from frappe.defaults import get_restrictions - restrictions = get_restrictions() - - meta = frappe.get_meta(doc.get("doctype")) - user_perms = get_user_perms(meta) - if get_user_perms(meta).restricted: - if doc.owner == frappe.session.user: - # owner is always allowed for restricted permissions - return True - elif not (restrictions and restrictions.get(doc.get("doctype"))): - return False - else: - if not restrictions: - return True + perms[ptype] = perms.get(ptype, 0) or cint(p.get(ptype)) + + if ptype != "set_user_permissions" and p.get(ptype): + perms["apply_user_permissions"][ptype] = (perms["apply_user_permissions"].get(ptype, 1) + and p.get("apply_user_permissions")) + + if p.apply_user_permissions: + # set user_permission_doctypes in perms + user_permission_doctypes = (json.loads(p.user_permission_doctypes) + if p.user_permission_doctypes else None) + + if user_permission_doctypes and user_permission_doctypes not in perms.get("user_permission_doctypes", []): + # perms["user_permission_doctypes"] would be a list of list like [["User", "Blog Post"], ["User"]] + perms.setdefault("user_permission_doctypes", []).append(user_permission_doctypes) - def _has_unrestricted_access(d): + for key, value in perms.get("apply_user_permissions").items(): + if not value: + del perms["apply_user_permissions"][key] + + frappe.local.role_permissions[cache_key] = perms + + return frappe.local.role_permissions[cache_key] + +def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=None): + from frappe.defaults import get_user_permissions + user_permissions = get_user_permissions(user) + user_permission_doctypes = get_user_permission_doctypes(user_permission_doctypes, user_permissions) + + def check_user_permission(d): meta = frappe.get_meta(d.get("doctype")) + end_result = False + + messages = {} - # evaluate specific restrictions - fields_to_check = meta.get_restricted_fields(restrictions.keys()) + # check multiple sets of user_permission_doctypes using OR condition + for doctypes in user_permission_doctypes: + result = True - _has_restricted_data = False - for df in fields_to_check: - if d.get(df.fieldname) and d.get(df.fieldname) not in restrictions[df.options]: - if verbose: - msg = _("Not allowed to access {0} with {1} = {2}").format(df.options, _(df.label), d.get(df.fieldname)) + for df in meta.get_fields_to_check_permissions(doctypes): + if (df.options in user_permissions and d.get(df.fieldname) + and d.get(df.fieldname) not in user_permissions[df.options]): + result = False - if d.parentfield: - msg = "{doctype}, {row} #{idx}, ".format(doctype=_(d.doctype), - row=_("Row"), idx=d.idx) + msg + if verbose: + msg = _("Not allowed to access {0} with {1} = {2}").format(df.options, _(df.label), d.get(df.fieldname)) + if d.parentfield: + msg = "{doctype}, {row} #{idx}, ".format(doctype=_(d.doctype), + row=_("Row"), idx=d.idx) + msg - msgprint(msg) + messages[df.fieldname] = msg - _has_restricted_data = True + end_result = end_result or result - return _has_restricted_data + if not end_result and messages: + for fieldname, msg in messages.items(): + msgprint(msg) - has_restricted_data = _has_unrestricted_access(doc) + return end_result + + _user_has_permission = check_user_permission(doc) for d in doc.get_all_children(): - has_restricted_data = _has_unrestricted_access(d) or has_restricted_data + _user_has_permission = check_user_permission(d) and _user_has_permission + + return _user_has_permission - # check all restrictions before returning - return False if has_restricted_data else True +def has_controller_permissions(doc, ptype, user=None): + if not user: user = frappe.session.user -def has_controller_permissions(doc): for method in frappe.get_hooks("has_permission").get(doc.doctype, []): - if not frappe.call(frappe.get_attr(method), doc=doc): + if not frappe.call(frappe.get_attr(method), doc=doc, ptype=ptype, user=user): return False return True -def can_restrict_user(user, doctype, docname=None): - if not can_restrict(doctype, docname): - return False - - # check if target user does not have restrict permission - if has_only_non_restrict_role(doctype, user): - return True - - return False - -def can_restrict(doctype, docname=None): - # System Manager can always restrict +def can_set_user_permissions(doctype, docname=None): + # System Manager can always set user permissions if "System Manager" in frappe.get_roles(): return True + meta = frappe.get_meta(doctype) # check if current user has read permission for docname if docname and not has_permission(doctype, "read", docname): return False - # check if current user has a role with restrict permission - if not has_restrict_permission(meta): + # check if current user has a role that can set permission + if get_role_permissions(meta).set_user_permissions!=1: return False return True -def has_restrict_permission(meta=None, user=None): - return get_user_perms(meta, user).restrict==1 +def set_user_permission_if_allowed(doctype, name, user, with_message=False): + if get_role_permissions(frappe.get_meta(doctype), user).set_user_permissions!=1: + add_user_permission(doctype, name, user, with_message) -def has_only_non_restrict_role(doctype, user): - meta = frappe.get_meta(doctype) - # check if target user does not have restrict permission - if has_restrict_permission(meta, user): - return False +def add_user_permission(doctype, name, user, with_message=False): + if name not in frappe.defaults.get_user_permissions(user).get(doctype, []): + if not frappe.db.exists(doctype, name): + frappe.throw(_("{0} {1} not found").format(_(doctype), name), frappe.DoesNotExistError) + + frappe.defaults.add_default(doctype, name, user, "User Permission") + elif with_message: + msgprint(_("Permission already set")) - # and has non-restrict role - return get_user_perms(meta, user).restrict==0 +def remove_user_permission(doctype, name, user, default_value_name=None): + frappe.defaults.clear_default(key=doctype, value=name, parent=user, parenttype="User Permission", + name=default_value_name) + +def clear_user_permissions_for_doctype(doctype): + frappe.defaults.clear_default(parenttype="User Permission", key=doctype) def can_import(doctype, raise_exception=False): if not ("System Manager" in frappe.get_roles() or has_permission(doctype, "import")): @@ -171,3 +213,40 @@ def can_export(doctype, raise_exception=False): else: return False return True + +def apply_user_permissions(doctype, ptype, user=None): + """Check if apply_user_permissions is checked for a doctype, perm type, user combination""" + role_permissions = get_role_permissions(frappe.get_meta(doctype), user=user) + return role_permissions.get("apply_user_permissions", {}).get(ptype) + +def get_user_permission_doctypes(user_permission_doctypes, user_permissions): + """returns a list of list like [["User", "Blog Post"], ["User"]]""" + if user_permission_doctypes: + # select those user permission doctypes for which user permissions exist! + user_permission_doctypes = [list(set(doctypes).intersection(set(user_permissions.keys()))) + for doctypes in user_permission_doctypes] + + else: + user_permission_doctypes = [user_permissions.keys()] + + + if len(user_permission_doctypes) > 1: + # OPTIMIZATION + # if intersection exists, use that to reduce the amount of querying + # for example, [["Blogger", "Blog Category"], ["Blogger"]], should only search in [["Blogger"]] as the first and condition becomes redundant + + common = user_permission_doctypes[0] + for i in xrange(1, len(user_permission_doctypes), 1): + common = list(set(common).intersection(set(user_permission_doctypes[i]))) + if not common: + break + + if common: + # is common one of the user_permission_doctypes set? + for doctypes in user_permission_doctypes: + # are these lists equal? + if set(common) == set(doctypes): + user_permission_doctypes = [common] + break + + return user_permission_doctypes diff --git a/frappe/public/build.json b/frappe/public/build.json index d108ce90d0..6c4b1acf11 100644 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -12,6 +12,7 @@ "public/js/lib/nprogress.js", "public/js/frappe/translate.js", "public/js/frappe/misc/pretty_date.js", + "public/js/lib/moment/moment.min.js", "website/js/website.js", "website/js/website_group.js" ], @@ -32,8 +33,8 @@ "public/css/tag-it.css", "public/css/bootstrap.css", - "public/css/bootstrap-responsive.css", "public/css/font-awesome.css", + "public/css/octicons/octicons.css", "public/css/desk.css", "public/css/appframe.css", "public/css/app_icon.css", @@ -52,6 +53,8 @@ "public/js/lib/bootstrap.min.js", "public/js/lib/nprogress.js", "public/js/lib/beautify-html.js", + "public/js/lib/moment/moment.min.js", + "public/js/lib/moment/moment-timezone.min.js", "public/js/frappe/format.js", "public/js/frappe/provide.js", @@ -65,13 +68,15 @@ "public/js/frappe/router.js", "public/js/frappe/desk.js", "public/js/frappe/defaults.js", + "public/js/lib/microtemplate.js", + + "public/html/print_template.html", + "public/html/list_info_template.html", "public/js/legacy/globals.js", "public/js/legacy/datatype.js", - "public/js/legacy/datetime.js", "public/js/legacy/dom.js", "public/js/legacy/handler.js", - "public/js/legacy/printElement.js", "public/js/legacy/loaders.js", "public/js/frappe/ui/appframe.js", @@ -117,19 +122,17 @@ "public/js/frappe/views/test_runner.js", "public/js/frappe/form/formatters.js", - "public/js/legacy/layout.js", - "public/js/frappe/ui/toolbar/selector_dialog.js", "public/js/frappe/ui/toolbar/new.js", "public/js/frappe/ui/toolbar/search.js", "public/js/frappe/ui/toolbar/report.js", "public/js/frappe/ui/toolbar/recent.js", "public/js/frappe/ui/toolbar/bookmarks.js", + "public/js/frappe/ui/toolbar/about.js", "public/js/frappe/ui/toolbar/toolbar.js", "public/js/frappe/ui/editor.js", "public/js/legacy/form.js", - "public/js/legacy/print_format.js", "public/js/legacy/clientscriptAPI.js", "public/js/frappe/form/toolbar.js", @@ -146,7 +149,12 @@ "public/js/frappe/form/linked_with.js", "public/js/frappe/form/workflow.js", "public/js/frappe/form/assign_to.js", - "public/js/frappe/print/print_table.js" + "public/js/frappe/form/print.js" + ], + "js/print_format_v3.min.js": [ + "public/js/legacy/layout.js", + "public/js/legacy/print_table.js", + "public/js/legacy/print_format.js" ], "js/slickgrid.min.js": [ "public/js/lib/slickgrid/jquery.event.drag.js", diff --git a/frappe/public/css/appframe.css b/frappe/public/css/appframe.css index 6101946ff7..e4c694526a 100644 --- a/frappe/public/css/appframe.css +++ b/frappe/public/css/appframe.css @@ -5,28 +5,41 @@ padding-bottom: 15px; } +.appframe-wrapper { + background-color: #fff; + min-height: 400px; + /*box-shadow: 1px 0px 1px rgba(0,0,0,0.4);*/ +} + .appframe-titlebar { border-bottom: 1px solid #c7c7c7; } -.appframe-titlebar, .appframe-footer { - background: url(/assets/frappe/images/ui/sos.png) repeat; - -webkit-box-shadow: inset 0 0 7px rgba(0, 0, 0, .07); - box-shadow: inset 0 0 7px rgba(0, 0, 0, .07); +.appframe-titlebar, .appframe-iconbar, .appframe-form, .appframe-primary-actions { + /*background-color: rgba(255, 255, 255, 0.7);*/ + background-color: #f9f9f9; +} + +.appframe-primary-actions { + border-bottom: 1px solid #c7c7c7; +} + +.appframe-primary-actions .btn { + margin: 10px; + margin-left: 0px; } .appframe-iconbar { - border-bottom: 1px solid #eee; + border-bottom: 1px solid #c7c7c7; } .titlebar-item { - padding-top: 15px; - padding-bottom: 15px; + padding-top: 10px; + padding-bottom: 10px; } -.titlebar-item h2 { +.titlebar-item h3 { display: inline-block; - font-weight: normal; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -34,9 +47,10 @@ margin: 0px; } -.titlebar-item.text-left { - +.titlebar-item.text-right { + margin-top: 3px; } + .titlebar-left-item { float: left; width: 30px; @@ -51,6 +65,13 @@ h2.titlebar-left-item { width: 90%; } +.title-sub { + font-size: 50%; + color: #888; + margin-left: 34px; + margin-top: 4px; +} + @media (max-width: 768px) { .titlebar-center-item { width: 80%; @@ -101,7 +122,7 @@ h2.titlebar-left-item { } .appframe-form { - padding: 5px 0px; + padding: 2px 0px 5px 0px; } .appframe-form input, .appframe-form select, .appframe-form label { @@ -116,14 +137,19 @@ h2.titlebar-left-item { .appframe-form .form-control { height: 28px; + margin-top: 0px; } - .iconbar { display: inline-block; padding: 9px 0px; } +.iconbar-4 { + border-left: 1px solid #c7c7c7; + padding-left: 4px; +} + .iconbar ul { list-style: none; margin: 0 0 0 0; @@ -157,11 +183,22 @@ h2.titlebar-left-item { color: orange; } +.workflow-button-area { + margin-bottom: 15px; +} -.appframe-footer { - margin-top: 15px; +.appframe-control-label { + font-size: 75% !important; + margin: 0px auto !important; + padding: 0px 4px !important; } -.workflow-button-area { - margin-bottom: 15px; +.appframe-only-label { + margin-top: 21px !important; + text-align: center; +} + +.appframe-form .checkbox { + margin-top: 15px !important; + margin-bottom: -7px !important; } diff --git a/frappe/public/css/avatar.css b/frappe/public/css/avatar.css index a55cef4987..39af6d08b3 100644 --- a/frappe/public/css/avatar.css +++ b/frappe/public/css/avatar.css @@ -1,10 +1,12 @@ .avatar { - display: inline-block; + display: inline-block; vertical-align: middle; - border-radius: 50%; + border-radius: 5px; overflow: hidden; background-color: #ddd; border: 1px solid #eee; + width: 50px; + height: 50px; } .avatar img { @@ -23,3 +25,12 @@ width: 72px; height: 72px; } + +.avatar-xs { + margin-right: 3px; + margin-top: -2px; + width: 17px; + height: 17px; + border: none; + border-radius: 3px; +} diff --git a/frappe/public/css/bootstrap.css b/frappe/public/css/bootstrap.css index 49a9f383a1..c6e36fc3e6 100644 --- a/frappe/public/css/bootstrap.css +++ b/frappe/public/css/bootstrap.css @@ -1,12 +1,10 @@ - /*! - * Bootstrap v3.1.0 (http://getbootstrap.com) + * Bootstrap v3.1.1 (http://getbootstrap.com) * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ -/*! normalize.css v3.0.0 | MIT License | git.io/normalize */ - +/*! normalize.css v3.0.1 | MIT License | git.io/normalize */ html { font-family: sans-serif; -webkit-text-size-adjust: 100%; @@ -96,8 +94,9 @@ figure { } hr { height: 0; - -moz-box-sizing: content-box; - box-sizing: content-box; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; } pre { overflow: auto; @@ -146,7 +145,9 @@ input { } input[type="checkbox"], input[type="radio"] { - box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; padding: 0; } input[type="number"]::-webkit-inner-spin-button, @@ -188,10 +189,11 @@ th { } @media print { * { - color: #000 !important; +/* color: #000 !important;*/ text-shadow: none !important; - background: transparent !important; - box-shadow: none !important; +/* background: transparent !important;*/ + -webkit-box-shadow: none !important; + box-shadow: none !important; } a, a:visited { @@ -209,7 +211,7 @@ th { } pre, blockquote { - border: 1px solid #999; +/* border: 1px solid #999;*/ page-break-inside: avoid; } @@ -239,2733 +241,2824 @@ th { .navbar { display: none; } - .table td, +/* .table td, .table th { background-color: #fff !important; } - .btn > .caret, +*/ .btn > .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } - .label { +/* .label { border: 1px solid #000; } - .table { +*/ .table { border-collapse: collapse !important; } - .table-bordered th, + /*.table-bordered th, .table-bordered td { border: 1px solid #ddd !important; - } -} -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; + }*/ } -*:before, -*:after { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); } -html { - font-size: 62.5%; +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -body { - font-family: "Open Sans", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.6; - /*line-height: 1.42857143;*/ - color: #333; - background-color: #fff; +.glyphicon-asterisk:before { + content: "\2a"; } -input, -button, -select, -textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; +.glyphicon-plus:before { + content: "\2b"; } -a { - color: #2980b9; - text-decoration: none; +.glyphicon-euro:before { + content: "\20ac"; } -a:hover, -a:focus { - color: #1b557a; - text-decoration: underline; +.glyphicon-minus:before { + content: "\2212"; } -a:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; +.glyphicon-cloud:before { + content: "\2601"; } -figure { - margin: 0; +.glyphicon-envelope:before { + content: "\2709"; } -img { - vertical-align: middle; +.glyphicon-pencil:before { + content: "\270f"; } -.img-responsive { - display: block; - max-width: 100%; - height: auto; +.glyphicon-glass:before { + content: "\e001"; } -.img-rounded { - border-radius: 6px; +.glyphicon-music:before { + content: "\e002"; } -.img-thumbnail { - display: inline-block; - max-width: 100%; - height: auto; - padding: 4px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; +.glyphicon-search:before { + content: "\e003"; } -.img-circle { - border-radius: 50%; +.glyphicon-heart:before { + content: "\e005"; } -hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eee; +.glyphicon-star:before { + content: "\e006"; } -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; +.glyphicon-star-empty:before { + content: "\e007"; } -h1, -h2, -h3, -h4, -h5, -h6, -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - font-family: inherit; - font-weight: 500; - line-height: 1.1; - color: inherit; +.glyphicon-user:before { + content: "\e008"; } -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small, -.h1 small, -.h2 small, -.h3 small, -.h4 small, -.h5 small, -.h6 small, -h1 .small, -h2 .small, -h3 .small, -h4 .small, -h5 .small, -h6 .small, -.h1 .small, -.h2 .small, -.h3 .small, -.h4 .small, -.h5 .small, -.h6 .small { - font-weight: normal; - line-height: 1; - color: #999; +.glyphicon-film:before { + content: "\e009"; } -h1, -.h1, -h2, -.h2, -h3, -.h3 { - margin-top: 20px; - margin-bottom: 10px; +.glyphicon-th-large:before { + content: "\e010"; } -h1 small, -.h1 small, -h2 small, -.h2 small, -h3 small, -.h3 small, -h1 .small, -.h1 .small, -h2 .small, -.h2 .small, -h3 .small, -.h3 .small { - font-size: 65%; +.glyphicon-th:before { + content: "\e011"; } -h4, -.h4, -h5, -.h5, -h6, -.h6 { - margin-top: 10px; - margin-bottom: 10px; +.glyphicon-th-list:before { + content: "\e012"; } -h4 small, -.h4 small, -h5 small, -.h5 small, -h6 small, -.h6 small, -h4 .small, -.h4 .small, -h5 .small, -.h5 .small, -h6 .small, -.h6 .small { - font-size: 75%; +.glyphicon-ok:before { + content: "\e013"; } -h1, -.h1 { - font-size: 36px; +.glyphicon-remove:before { + content: "\e014"; } -h2, -.h2 { - font-size: 30px; +.glyphicon-zoom-in:before { + content: "\e015"; } -h3, -.h3 { - font-size: 24px; +.glyphicon-zoom-out:before { + content: "\e016"; } -h4, -.h4 { - font-size: 18px; +.glyphicon-off:before { + content: "\e017"; } -h5, -.h5 { - font-size: 14px; +.glyphicon-signal:before { + content: "\e018"; } -h6, -.h6 { - font-size: 12px; +.glyphicon-cog:before { + content: "\e019"; } -p { - margin: 0 0 10px; +.glyphicon-trash:before { + content: "\e020"; } -.lead { - margin-bottom: 20px; - font-size: 16px; - font-weight: 200; - line-height: 1.4; +.glyphicon-home:before { + content: "\e021"; } -@media (min-width: 768px) { - .lead { - font-size: 21px; - } +.glyphicon-file:before { + content: "\e022"; } -small, -.small { - font-size: 85%; +.glyphicon-time:before { + content: "\e023"; } -cite { - font-style: normal; +.glyphicon-road:before { + content: "\e024"; } -.text-left { - text-align: left; +.glyphicon-download-alt:before { + content: "\e025"; } -.text-right { - text-align: right; +.glyphicon-download:before { + content: "\e026"; } -.text-center { - text-align: center; +.glyphicon-upload:before { + content: "\e027"; } -.text-justify { - text-align: justify; +.glyphicon-inbox:before { + content: "\e028"; } -.text-muted { - color: #999; +.glyphicon-play-circle:before { + content: "\e029"; } -.text-primary { - color: #2980b9; +.glyphicon-repeat:before { + content: "\e030"; } -a.text-primary:hover { - color: #20638f; +.glyphicon-refresh:before { + content: "\e031"; } -.text-success { - color: #3c763d; +.glyphicon-list-alt:before { + content: "\e032"; } -a.text-success:hover { - color: #2b542c; +.glyphicon-lock:before { + content: "\e033"; } -.text-info { - color: #31708f; +.glyphicon-flag:before { + content: "\e034"; } -a.text-info:hover { - color: #245269; +.glyphicon-headphones:before { + content: "\e035"; } -.text-warning { - color: #f39c12; +.glyphicon-volume-off:before { + content: "\e036"; } -a.text-warning:hover { - color: #c87f0a; +.glyphicon-volume-down:before { + content: "\e037"; } -.text-danger { - color: #c0392b; +.glyphicon-volume-up:before { + content: "\e038"; } -a.text-danger:hover { - color: #962d22; +.glyphicon-qrcode:before { + content: "\e039"; } -.bg-primary { - color: #fff; - background-color: #2980b9; +.glyphicon-barcode:before { + content: "\e040"; } -a.bg-primary:hover { - background-color: #20638f; +.glyphicon-tag:before { + content: "\e041"; } -.bg-success { - background-color: #dff0d8; +.glyphicon-tags:before { + content: "\e042"; } -a.bg-success:hover { - background-color: #c1e2b3; +.glyphicon-book:before { + content: "\e043"; } -.bg-info { - background-color: #d9edf7; +.glyphicon-bookmark:before { + content: "\e044"; } -a.bg-info:hover { - background-color: #afd9ee; +.glyphicon-print:before { + content: "\e045"; } -.bg-warning { - background-color: #fcf8e3; +.glyphicon-camera:before { + content: "\e046"; } -a.bg-warning:hover { - background-color: #f7ecb5; +.glyphicon-font:before { + content: "\e047"; } -.bg-danger { - background-color: #f2dede; +.glyphicon-bold:before { + content: "\e048"; } -a.bg-danger:hover { - background-color: #e4b9b9; +.glyphicon-italic:before { + content: "\e049"; } -.page-header { - padding-bottom: 9px; - margin: 40px 0 20px; - border-bottom: 1px solid #eee; +.glyphicon-text-height:before { + content: "\e050"; } -ul, -ol { - margin-top: 0; - margin-bottom: 10px; +.glyphicon-text-width:before { + content: "\e051"; } -ul ul, -ol ul, -ul ol, -ol ol { - margin-bottom: 0; +.glyphicon-align-left:before { + content: "\e052"; } -.list-unstyled { - padding-left: 0; - list-style: none; +.glyphicon-align-center:before { + content: "\e053"; } -.list-inline { - padding-left: 0; - list-style: none; +.glyphicon-align-right:before { + content: "\e054"; } -.list-inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px; +.glyphicon-align-justify:before { + content: "\e055"; } -.list-inline > li:first-child { - padding-left: 0; +.glyphicon-list:before { + content: "\e056"; } -dl { - margin-top: 0; - margin-bottom: 20px; +.glyphicon-indent-left:before { + content: "\e057"; } -dt, -dd { - line-height: 1.42857143; +.glyphicon-indent-right:before { + content: "\e058"; } -dt { - font-weight: bold; +.glyphicon-facetime-video:before { + content: "\e059"; } -dd { - margin-left: 0; +.glyphicon-picture:before { + content: "\e060"; } -@media (min-width: 768px) { - .dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - } - .dl-horizontal dd { - margin-left: 180px; - } +.glyphicon-map-marker:before { + content: "\e062"; } -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #999; +.glyphicon-adjust:before { + content: "\e063"; } -.initialism { - font-size: 90%; - text-transform: uppercase; +.glyphicon-tint:before { + content: "\e064"; } -blockquote { - padding: 10px 20px; - margin: 0 0 20px; - font-size: 17.5px; - border-left: 5px solid #eee; +.glyphicon-edit:before { + content: "\e065"; } -blockquote p:last-child, -blockquote ul:last-child, -blockquote ol:last-child { - margin-bottom: 0; +.glyphicon-share:before { + content: "\e066"; } -blockquote footer, -blockquote small, -blockquote .small { - display: block; - font-size: 80%; - line-height: 1.42857143; - color: #999; +.glyphicon-check:before { + content: "\e067"; } -blockquote footer:before, -blockquote small:before, -blockquote .small:before { - content: '\2014 \00A0'; +.glyphicon-move:before { + content: "\e068"; } -.blockquote-reverse, -blockquote.pull-right { - padding-right: 15px; - padding-left: 0; - text-align: right; - border-right: 5px solid #eee; - border-left: 0; +.glyphicon-step-backward:before { + content: "\e069"; } -.blockquote-reverse footer:before, -blockquote.pull-right footer:before, -.blockquote-reverse small:before, -blockquote.pull-right small:before, -.blockquote-reverse .small:before, -blockquote.pull-right .small:before { - content: ''; +.glyphicon-fast-backward:before { + content: "\e070"; } -.blockquote-reverse footer:after, -blockquote.pull-right footer:after, -.blockquote-reverse small:after, -blockquote.pull-right small:after, -.blockquote-reverse .small:after, -blockquote.pull-right .small:after { - content: '\00A0 \2014'; +.glyphicon-backward:before { + content: "\e071"; } -blockquote:before, -blockquote:after { - content: ""; +.glyphicon-play:before { + content: "\e072"; } -address { - margin-bottom: 20px; - font-style: normal; - line-height: 1.42857143; +.glyphicon-pause:before { + content: "\e073"; } -code, -kbd, -pre, -samp { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +.glyphicon-stop:before { + content: "\e074"; } -code { - padding: 2px 4px; - font-size: 90%; - color: #c7254e; - white-space: nowrap; - background-color: #f9f2f4; - border-radius: 4px; +.glyphicon-forward:before { + content: "\e075"; } -kbd { - padding: 2px 4px; - font-size: 90%; - color: #fff; - background-color: #333; - border-radius: 3px; - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +.glyphicon-fast-forward:before { + content: "\e076"; } -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 1.42857143; - color: #333; - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - border: 1px solid #ccc; - border-radius: 4px; +.glyphicon-step-forward:before { + content: "\e077"; } -pre code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0; +.glyphicon-eject:before { + content: "\e078"; } -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; +.glyphicon-chevron-left:before { + content: "\e079"; } -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; +.glyphicon-chevron-right:before { + content: "\e080"; } -@media (min-width: 768px) { - .container { - width: 750px; - } +.glyphicon-plus-sign:before { + content: "\e081"; } -@media (min-width: 992px) { - .container { - width: 970px; - } +.glyphicon-minus-sign:before { + content: "\e082"; } -@media (min-width: 1200px) { - .container { - width: 1170px; - } +.glyphicon-remove-sign:before { + content: "\e083"; } -.container-fluid { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; +.glyphicon-ok-sign:before { + content: "\e084"; } -.row { - margin-right: -15px; - margin-left: -15px; +.glyphicon-question-sign:before { + content: "\e085"; } -.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; +.glyphicon-info-sign:before { + content: "\e086"; } -.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { - float: left; +.glyphicon-screenshot:before { + content: "\e087"; } -.col-xs-12 { - width: 100%; +.glyphicon-remove-circle:before { + content: "\e088"; } -.col-xs-11 { - width: 91.66666667%; +.glyphicon-ok-circle:before { + content: "\e089"; } -.col-xs-10 { - width: 83.33333333%; +.glyphicon-ban-circle:before { + content: "\e090"; } -.col-xs-9 { - width: 75%; +.glyphicon-arrow-left:before { + content: "\e091"; } -.col-xs-8 { - width: 66.66666667%; +.glyphicon-arrow-right:before { + content: "\e092"; } -.col-xs-7 { - width: 58.33333333%; +.glyphicon-arrow-up:before { + content: "\e093"; } -.col-xs-6 { - width: 50%; +.glyphicon-arrow-down:before { + content: "\e094"; } -.col-xs-5 { - width: 41.66666667%; +.glyphicon-share-alt:before { + content: "\e095"; } -.col-xs-4 { - width: 33.33333333%; +.glyphicon-resize-full:before { + content: "\e096"; } -.col-xs-3 { - width: 25%; +.glyphicon-resize-small:before { + content: "\e097"; } -.col-xs-2 { - width: 16.66666667%; +.glyphicon-exclamation-sign:before { + content: "\e101"; } -.col-xs-1 { - width: 8.33333333%; +.glyphicon-gift:before { + content: "\e102"; } -.col-xs-pull-12 { - right: 100%; +.glyphicon-leaf:before { + content: "\e103"; } -.col-xs-pull-11 { - right: 91.66666667%; +.glyphicon-fire:before { + content: "\e104"; } -.col-xs-pull-10 { - right: 83.33333333%; +.glyphicon-eye-open:before { + content: "\e105"; } -.col-xs-pull-9 { - right: 75%; +.glyphicon-eye-close:before { + content: "\e106"; } -.col-xs-pull-8 { - right: 66.66666667%; +.glyphicon-warning-sign:before { + content: "\e107"; } -.col-xs-pull-7 { - right: 58.33333333%; +.glyphicon-plane:before { + content: "\e108"; } -.col-xs-pull-6 { - right: 50%; +.glyphicon-calendar:before { + content: "\e109"; } -.col-xs-pull-5 { - right: 41.66666667%; +.glyphicon-random:before { + content: "\e110"; } -.col-xs-pull-4 { - right: 33.33333333%; +.glyphicon-comment:before { + content: "\e111"; } -.col-xs-pull-3 { - right: 25%; +.glyphicon-magnet:before { + content: "\e112"; } -.col-xs-pull-2 { - right: 16.66666667%; +.glyphicon-chevron-up:before { + content: "\e113"; } -.col-xs-pull-1 { - right: 8.33333333%; +.glyphicon-chevron-down:before { + content: "\e114"; } -.col-xs-pull-0 { - right: 0; +.glyphicon-retweet:before { + content: "\e115"; } -.col-xs-push-12 { - left: 100%; +.glyphicon-shopping-cart:before { + content: "\e116"; } -.col-xs-push-11 { - left: 91.66666667%; +.glyphicon-folder-close:before { + content: "\e117"; } -.col-xs-push-10 { - left: 83.33333333%; +.glyphicon-folder-open:before { + content: "\e118"; } -.col-xs-push-9 { - left: 75%; +.glyphicon-resize-vertical:before { + content: "\e119"; } -.col-xs-push-8 { - left: 66.66666667%; +.glyphicon-resize-horizontal:before { + content: "\e120"; } -.col-xs-push-7 { - left: 58.33333333%; +.glyphicon-hdd:before { + content: "\e121"; } -.col-xs-push-6 { - left: 50%; +.glyphicon-bullhorn:before { + content: "\e122"; } -.col-xs-push-5 { - left: 41.66666667%; +.glyphicon-bell:before { + content: "\e123"; } -.col-xs-push-4 { - left: 33.33333333%; +.glyphicon-certificate:before { + content: "\e124"; } -.col-xs-push-3 { - left: 25%; +.glyphicon-thumbs-up:before { + content: "\e125"; } -.col-xs-push-2 { - left: 16.66666667%; +.glyphicon-thumbs-down:before { + content: "\e126"; } -.col-xs-push-1 { - left: 8.33333333%; +.glyphicon-hand-right:before { + content: "\e127"; } -.col-xs-push-0 { - left: 0; +.glyphicon-hand-left:before { + content: "\e128"; } -.col-xs-offset-12 { - margin-left: 100%; +.glyphicon-hand-up:before { + content: "\e129"; } -.col-xs-offset-11 { - margin-left: 91.66666667%; +.glyphicon-hand-down:before { + content: "\e130"; } -.col-xs-offset-10 { - margin-left: 83.33333333%; +.glyphicon-circle-arrow-right:before { + content: "\e131"; } -.col-xs-offset-9 { - margin-left: 75%; +.glyphicon-circle-arrow-left:before { + content: "\e132"; } -.col-xs-offset-8 { - margin-left: 66.66666667%; +.glyphicon-circle-arrow-up:before { + content: "\e133"; } -.col-xs-offset-7 { - margin-left: 58.33333333%; +.glyphicon-circle-arrow-down:before { + content: "\e134"; } -.col-xs-offset-6 { - margin-left: 50%; +.glyphicon-globe:before { + content: "\e135"; } -.col-xs-offset-5 { - margin-left: 41.66666667%; +.glyphicon-wrench:before { + content: "\e136"; } -.col-xs-offset-4 { - margin-left: 33.33333333%; +.glyphicon-tasks:before { + content: "\e137"; } -.col-xs-offset-3 { - margin-left: 25%; +.glyphicon-filter:before { + content: "\e138"; } -.col-xs-offset-2 { - margin-left: 16.66666667%; +.glyphicon-briefcase:before { + content: "\e139"; } -.col-xs-offset-1 { - margin-left: 8.33333333%; +.glyphicon-fullscreen:before { + content: "\e140"; } -.col-xs-offset-0 { - margin-left: 0; +.glyphicon-dashboard:before { + content: "\e141"; } -@media (min-width: 768px) { - .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { - float: left; - } - .col-sm-12 { - width: 100%; - } - .col-sm-11 { - width: 91.66666667%; - } - .col-sm-10 { - width: 83.33333333%; - } - .col-sm-9 { - width: 75%; - } - .col-sm-8 { - width: 66.66666667%; - } - .col-sm-7 { - width: 58.33333333%; - } - .col-sm-6 { - width: 50%; - } - .col-sm-5 { - width: 41.66666667%; - } - .col-sm-4 { - width: 33.33333333%; - } - .col-sm-3 { - width: 25%; - } - .col-sm-2 { - width: 16.66666667%; - } - .col-sm-1 { - width: 8.33333333%; - } - .col-sm-pull-12 { - right: 100%; - } - .col-sm-pull-11 { - right: 91.66666667%; - } - .col-sm-pull-10 { - right: 83.33333333%; - } - .col-sm-pull-9 { - right: 75%; - } - .col-sm-pull-8 { - right: 66.66666667%; - } - .col-sm-pull-7 { - right: 58.33333333%; - } - .col-sm-pull-6 { - right: 50%; - } - .col-sm-pull-5 { - right: 41.66666667%; - } - .col-sm-pull-4 { - right: 33.33333333%; - } - .col-sm-pull-3 { - right: 25%; - } - .col-sm-pull-2 { - right: 16.66666667%; - } - .col-sm-pull-1 { - right: 8.33333333%; - } - .col-sm-pull-0 { - right: 0; - } - .col-sm-push-12 { - left: 100%; - } - .col-sm-push-11 { - left: 91.66666667%; - } - .col-sm-push-10 { - left: 83.33333333%; - } - .col-sm-push-9 { - left: 75%; - } - .col-sm-push-8 { - left: 66.66666667%; - } - .col-sm-push-7 { - left: 58.33333333%; - } - .col-sm-push-6 { - left: 50%; - } - .col-sm-push-5 { - left: 41.66666667%; - } - .col-sm-push-4 { - left: 33.33333333%; - } - .col-sm-push-3 { - left: 25%; - } - .col-sm-push-2 { - left: 16.66666667%; - } - .col-sm-push-1 { - left: 8.33333333%; - } - .col-sm-push-0 { - left: 0; - } - .col-sm-offset-12 { - margin-left: 100%; - } - .col-sm-offset-11 { - margin-left: 91.66666667%; - } - .col-sm-offset-10 { - margin-left: 83.33333333%; - } - .col-sm-offset-9 { - margin-left: 75%; - } - .col-sm-offset-8 { - margin-left: 66.66666667%; - } - .col-sm-offset-7 { - margin-left: 58.33333333%; - } - .col-sm-offset-6 { - margin-left: 50%; - } - .col-sm-offset-5 { - margin-left: 41.66666667%; - } - .col-sm-offset-4 { - margin-left: 33.33333333%; - } - .col-sm-offset-3 { - margin-left: 25%; - } - .col-sm-offset-2 { - margin-left: 16.66666667%; - } - .col-sm-offset-1 { - margin-left: 8.33333333%; - } - .col-sm-offset-0 { - margin-left: 0; - } +.glyphicon-paperclip:before { + content: "\e142"; } -@media (min-width: 992px) { - .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { - float: left; - } - .col-md-12 { - width: 100%; - } - .col-md-11 { - width: 91.66666667%; - } - .col-md-10 { - width: 83.33333333%; - } - .col-md-9 { - width: 75%; - } - .col-md-8 { - width: 66.66666667%; - } - .col-md-7 { - width: 58.33333333%; - } - .col-md-6 { - width: 50%; - } - .col-md-5 { - width: 41.66666667%; - } - .col-md-4 { - width: 33.33333333%; - } - .col-md-3 { - width: 25%; - } - .col-md-2 { - width: 16.66666667%; - } - .col-md-1 { - width: 8.33333333%; - } - .col-md-pull-12 { - right: 100%; - } - .col-md-pull-11 { - right: 91.66666667%; - } - .col-md-pull-10 { - right: 83.33333333%; - } - .col-md-pull-9 { - right: 75%; - } - .col-md-pull-8 { - right: 66.66666667%; - } - .col-md-pull-7 { - right: 58.33333333%; - } - .col-md-pull-6 { - right: 50%; - } - .col-md-pull-5 { - right: 41.66666667%; - } - .col-md-pull-4 { - right: 33.33333333%; - } - .col-md-pull-3 { - right: 25%; - } - .col-md-pull-2 { - right: 16.66666667%; - } - .col-md-pull-1 { - right: 8.33333333%; - } - .col-md-pull-0 { - right: 0; - } - .col-md-push-12 { - left: 100%; - } - .col-md-push-11 { - left: 91.66666667%; - } - .col-md-push-10 { - left: 83.33333333%; - } - .col-md-push-9 { - left: 75%; - } - .col-md-push-8 { - left: 66.66666667%; - } - .col-md-push-7 { - left: 58.33333333%; - } - .col-md-push-6 { - left: 50%; - } - .col-md-push-5 { - left: 41.66666667%; - } - .col-md-push-4 { - left: 33.33333333%; - } - .col-md-push-3 { - left: 25%; - } - .col-md-push-2 { - left: 16.66666667%; - } - .col-md-push-1 { - left: 8.33333333%; - } - .col-md-push-0 { - left: 0; - } - .col-md-offset-12 { - margin-left: 100%; - } - .col-md-offset-11 { - margin-left: 91.66666667%; - } - .col-md-offset-10 { - margin-left: 83.33333333%; - } - .col-md-offset-9 { - margin-left: 75%; - } - .col-md-offset-8 { - margin-left: 66.66666667%; - } - .col-md-offset-7 { - margin-left: 58.33333333%; - } - .col-md-offset-6 { - margin-left: 50%; - } - .col-md-offset-5 { - margin-left: 41.66666667%; - } - .col-md-offset-4 { - margin-left: 33.33333333%; - } - .col-md-offset-3 { - margin-left: 25%; - } - .col-md-offset-2 { - margin-left: 16.66666667%; - } - .col-md-offset-1 { - margin-left: 8.33333333%; - } - .col-md-offset-0 { - margin-left: 0; - } +.glyphicon-heart-empty:before { + content: "\e143"; } -@media (min-width: 1200px) { - .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { - float: left; - } - .col-lg-12 { - width: 100%; - } - .col-lg-11 { - width: 91.66666667%; - } - .col-lg-10 { - width: 83.33333333%; - } - .col-lg-9 { - width: 75%; - } - .col-lg-8 { - width: 66.66666667%; - } - .col-lg-7 { - width: 58.33333333%; - } - .col-lg-6 { - width: 50%; - } - .col-lg-5 { - width: 41.66666667%; - } - .col-lg-4 { - width: 33.33333333%; - } - .col-lg-3 { - width: 25%; - } - .col-lg-2 { - width: 16.66666667%; - } - .col-lg-1 { - width: 8.33333333%; - } - .col-lg-pull-12 { - right: 100%; - } - .col-lg-pull-11 { - right: 91.66666667%; - } - .col-lg-pull-10 { - right: 83.33333333%; - } - .col-lg-pull-9 { - right: 75%; - } - .col-lg-pull-8 { - right: 66.66666667%; - } - .col-lg-pull-7 { - right: 58.33333333%; - } - .col-lg-pull-6 { - right: 50%; - } - .col-lg-pull-5 { - right: 41.66666667%; - } - .col-lg-pull-4 { - right: 33.33333333%; - } - .col-lg-pull-3 { - right: 25%; - } - .col-lg-pull-2 { - right: 16.66666667%; - } - .col-lg-pull-1 { - right: 8.33333333%; - } - .col-lg-pull-0 { - right: 0; - } - .col-lg-push-12 { - left: 100%; - } - .col-lg-push-11 { - left: 91.66666667%; - } - .col-lg-push-10 { - left: 83.33333333%; - } - .col-lg-push-9 { - left: 75%; - } - .col-lg-push-8 { - left: 66.66666667%; - } - .col-lg-push-7 { - left: 58.33333333%; - } - .col-lg-push-6 { - left: 50%; - } - .col-lg-push-5 { - left: 41.66666667%; - } - .col-lg-push-4 { - left: 33.33333333%; - } - .col-lg-push-3 { - left: 25%; - } - .col-lg-push-2 { - left: 16.66666667%; - } - .col-lg-push-1 { - left: 8.33333333%; - } - .col-lg-push-0 { - left: 0; - } - .col-lg-offset-12 { - margin-left: 100%; - } - .col-lg-offset-11 { - margin-left: 91.66666667%; - } - .col-lg-offset-10 { - margin-left: 83.33333333%; - } - .col-lg-offset-9 { - margin-left: 75%; - } - .col-lg-offset-8 { - margin-left: 66.66666667%; - } - .col-lg-offset-7 { - margin-left: 58.33333333%; - } - .col-lg-offset-6 { - margin-left: 50%; - } - .col-lg-offset-5 { - margin-left: 41.66666667%; - } - .col-lg-offset-4 { - margin-left: 33.33333333%; - } - .col-lg-offset-3 { - margin-left: 25%; - } - .col-lg-offset-2 { - margin-left: 16.66666667%; - } - .col-lg-offset-1 { - margin-left: 8.33333333%; - } - .col-lg-offset-0 { - margin-left: 0; - } +.glyphicon-link:before { + content: "\e144"; } -table { - max-width: 100%; - background-color: transparent; +.glyphicon-phone:before { + content: "\e145"; } -th { - text-align: left; +.glyphicon-pushpin:before { + content: "\e146"; } -.table { - width: 100%; - margin-bottom: 20px; +.glyphicon-usd:before { + content: "\e148"; } -.table > thead > tr > th, -.table > tbody > tr > th, -.table > tfoot > tr > th, -.table > thead > tr > td, -.table > tbody > tr > td, -.table > tfoot > tr > td { - padding: 8px; - line-height: 1.42857143; - vertical-align: top; - border-top: 1px solid #ddd; +.glyphicon-gbp:before { + content: "\e149"; } -.table > thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid #ddd; +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; } -.table > caption + thead > tr:first-child > th, -.table > colgroup + thead > tr:first-child > th, -.table > thead:first-child > tr:first-child > th, -.table > caption + thead > tr:first-child > td, -.table > colgroup + thead > tr:first-child > td, -.table > thead:first-child > tr:first-child > td { - border-top: 0; +.glyphicon-expand:before { + content: "\e158"; } -.table > tbody + tbody { - border-top: 2px solid #ddd; +.glyphicon-collapse-down:before { + content: "\e159"; } -.table .table { - background-color: #fff; +.glyphicon-collapse-up:before { + content: "\e160"; } -.table-condensed > thead > tr > th, -.table-condensed > tbody > tr > th, -.table-condensed > tfoot > tr > th, -.table-condensed > thead > tr > td, -.table-condensed > tbody > tr > td, -.table-condensed > tfoot > tr > td { - padding: 5px; +.glyphicon-log-in:before { + content: "\e161"; } -.table-bordered { - border: 1px solid #ddd; +.glyphicon-flash:before { + content: "\e162"; } -.table-bordered > thead > tr > th, -.table-bordered > tbody > tr > th, -.table-bordered > tfoot > tr > th, -.table-bordered > thead > tr > td, -.table-bordered > tbody > tr > td, -.table-bordered > tfoot > tr > td { - border: 1px solid #ddd; +.glyphicon-log-out:before { + content: "\e163"; } -.table-bordered > thead > tr > th, -.table-bordered > thead > tr > td { - border-bottom-width: 2px; +.glyphicon-new-window:before { + content: "\e164"; } -.table-striped > tbody > tr:nth-child(odd) > td, -.table-striped > tbody > tr:nth-child(odd) > th { - background-color: #f9f9f9; +.glyphicon-record:before { + content: "\e165"; } -.table-hover > tbody > tr:hover > td, -.table-hover > tbody > tr:hover > th { - background-color: #f5f5f5; +.glyphicon-save:before { + content: "\e166"; } -table col[class*="col-"] { - position: static; - display: table-column; - float: none; +.glyphicon-open:before { + content: "\e167"; } -table td[class*="col-"], -table th[class*="col-"] { - position: static; - display: table-cell; - float: none; +.glyphicon-saved:before { + content: "\e168"; } -.table > thead > tr > td.active, -.table > tbody > tr > td.active, -.table > tfoot > tr > td.active, -.table > thead > tr > th.active, -.table > tbody > tr > th.active, -.table > tfoot > tr > th.active, -.table > thead > tr.active > td, -.table > tbody > tr.active > td, -.table > tfoot > tr.active > td, -.table > thead > tr.active > th, -.table > tbody > tr.active > th, -.table > tfoot > tr.active > th { - background-color: #f5f5f5; +.glyphicon-import:before { + content: "\e169"; } -.table-hover > tbody > tr > td.active:hover, -.table-hover > tbody > tr > th.active:hover, -.table-hover > tbody > tr.active:hover > td, -.table-hover > tbody > tr.active:hover > th { - background-color: #e8e8e8; +.glyphicon-export:before { + content: "\e170"; } -.table > thead > tr > td.success, -.table > tbody > tr > td.success, -.table > tfoot > tr > td.success, -.table > thead > tr > th.success, -.table > tbody > tr > th.success, -.table > tfoot > tr > th.success, -.table > thead > tr.success > td, -.table > tbody > tr.success > td, -.table > tfoot > tr.success > td, -.table > thead > tr.success > th, -.table > tbody > tr.success > th, -.table > tfoot > tr.success > th { - background-color: #dff0d8; +.glyphicon-send:before { + content: "\e171"; } -.table-hover > tbody > tr > td.success:hover, -.table-hover > tbody > tr > th.success:hover, -.table-hover > tbody > tr.success:hover > td, -.table-hover > tbody > tr.success:hover > th { - background-color: #d0e9c6; +.glyphicon-floppy-disk:before { + content: "\e172"; } -.table > thead > tr > td.info, -.table > tbody > tr > td.info, -.table > tfoot > tr > td.info, -.table > thead > tr > th.info, -.table > tbody > tr > th.info, -.table > tfoot > tr > th.info, -.table > thead > tr.info > td, -.table > tbody > tr.info > td, -.table > tfoot > tr.info > td, -.table > thead > tr.info > th, -.table > tbody > tr.info > th, -.table > tfoot > tr.info > th { - background-color: #d9edf7; +.glyphicon-floppy-saved:before { + content: "\e173"; } -.table-hover > tbody > tr > td.info:hover, -.table-hover > tbody > tr > th.info:hover, -.table-hover > tbody > tr.info:hover > td, -.table-hover > tbody > tr.info:hover > th { - background-color: #c4e3f3; +.glyphicon-floppy-remove:before { + content: "\e174"; } -.table > thead > tr > td.warning, -.table > tbody > tr > td.warning, -.table > tfoot > tr > td.warning, -.table > thead > tr > th.warning, -.table > tbody > tr > th.warning, -.table > tfoot > tr > th.warning, -.table > thead > tr.warning > td, -.table > tbody > tr.warning > td, -.table > tfoot > tr.warning > td, -.table > thead > tr.warning > th, -.table > tbody > tr.warning > th, -.table > tfoot > tr.warning > th { - background-color: #fcf8e3; +.glyphicon-floppy-save:before { + content: "\e175"; } -.table-hover > tbody > tr > td.warning:hover, -.table-hover > tbody > tr > th.warning:hover, -.table-hover > tbody > tr.warning:hover > td, -.table-hover > tbody > tr.warning:hover > th { - background-color: #faf2cc; +.glyphicon-floppy-open:before { + content: "\e176"; } -.table > thead > tr > td.danger, -.table > tbody > tr > td.danger, -.table > tfoot > tr > td.danger, -.table > thead > tr > th.danger, -.table > tbody > tr > th.danger, -.table > tfoot > tr > th.danger, -.table > thead > tr.danger > td, -.table > tbody > tr.danger > td, -.table > tfoot > tr.danger > td, -.table > thead > tr.danger > th, -.table > tbody > tr.danger > th, -.table > tfoot > tr.danger > th { - background-color: #f2dede; +.glyphicon-credit-card:before { + content: "\e177"; } -.table-hover > tbody > tr > td.danger:hover, -.table-hover > tbody > tr > th.danger:hover, -.table-hover > tbody > tr.danger:hover > td, -.table-hover > tbody > tr.danger:hover > th { - background-color: #ebcccc; +.glyphicon-transfer:before { + content: "\e178"; } -@media (max-width: 767px) { - .table-responsive { - width: 100%; - margin-bottom: 15px; - overflow-x: scroll; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - border: 1px solid #ddd; - } - .table-responsive > .table { - margin-bottom: 0; - } - .table-responsive > .table > thead > tr > th, - .table-responsive > .table > tbody > tr > th, - .table-responsive > .table > tfoot > tr > th, - .table-responsive > .table > thead > tr > td, - .table-responsive > .table > tbody > tr > td, - .table-responsive > .table > tfoot > tr > td { - white-space: nowrap; - } - .table-responsive > .table-bordered { - border: 0; - } - .table-responsive > .table-bordered > thead > tr > th:first-child, - .table-responsive > .table-bordered > tbody > tr > th:first-child, - .table-responsive > .table-bordered > tfoot > tr > th:first-child, - .table-responsive > .table-bordered > thead > tr > td:first-child, - .table-responsive > .table-bordered > tbody > tr > td:first-child, - .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; - } - .table-responsive > .table-bordered > thead > tr > th:last-child, - .table-responsive > .table-bordered > tbody > tr > th:last-child, - .table-responsive > .table-bordered > tfoot > tr > th:last-child, - .table-responsive > .table-bordered > thead > tr > td:last-child, - .table-responsive > .table-bordered > tbody > tr > td:last-child, - .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; - } - .table-responsive > .table-bordered > tbody > tr:last-child > th, - .table-responsive > .table-bordered > tfoot > tr:last-child > th, - .table-responsive > .table-bordered > tbody > tr:last-child > td, - .table-responsive > .table-bordered > tfoot > tr:last-child > td { - border-bottom: 0; - } +.glyphicon-cutlery:before { + content: "\e179"; } -fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; +.glyphicon-header:before { + content: "\e180"; } -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: inherit; - color: #333; - border: 0; - border-bottom: 1px solid #e5e5e5; +.glyphicon-compressed:before { + content: "\e181"; } -label { - display: inline-block; - margin-bottom: 5px; - font-weight: bold; +.glyphicon-earphone:before { + content: "\e182"; } -input[type="search"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; +.glyphicon-phone-alt:before { + content: "\e183"; } -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - /* IE8-9 */ - line-height: normal; +.glyphicon-tower:before { + content: "\e184"; } -input[type="file"] { - display: block; +.glyphicon-stats:before { + content: "\e185"; } -input[type="range"] { - display: block; - width: 100%; +.glyphicon-sd-video:before { + content: "\e186"; } -select[multiple], -select[size] { - height: auto; +.glyphicon-hd-video:before { + content: "\e187"; } -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; +.glyphicon-subtitles:before { + content: "\e188"; } -output { - display: block; - padding-top: 7px; - font-size: 14px; - line-height: 1.42857143; - color: #555; +.glyphicon-sound-stereo:before { + content: "\e189"; } -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; +.glyphicon-sound-dolby:before { + content: "\e190"; } -.form-control:focus { - border-color: #000; - outline: 0; +.glyphicon-sound-5-1:before { + content: "\e191"; } -.form-control:-moz-placeholder { - color: #999; +.glyphicon-sound-6-1:before { + content: "\e192"; } -.form-control::-moz-placeholder { - color: #999; - opacity: 1; +.glyphicon-sound-7-1:before { + content: "\e193"; } -.form-control:-ms-input-placeholder { - color: #999; +.glyphicon-copyright-mark:before { + content: "\e194"; } -.form-control::-webkit-input-placeholder { - color: #999; +.glyphicon-registration-mark:before { + content: "\e195"; } -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] .form-control { - cursor: not-allowed; - background-color: #eee; - opacity: 1; +.glyphicon-cloud-download:before { + content: "\e197"; } -textarea.form-control { - height: auto; +.glyphicon-cloud-upload:before { + content: "\e198"; } -input[type="date"] { - line-height: 34px; +.glyphicon-tree-conifer:before { + content: "\e199"; } -.form-group { - margin-bottom: 15px; +.glyphicon-tree-deciduous:before { + content: "\e200"; } -.radio, -.checkbox { - display: block; - min-height: 20px; - padding-left: 20px; - margin-top: 10px; - margin-bottom: 10px; +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } -.radio label, -.checkbox label { - display: inline; - font-weight: normal; - cursor: pointer; +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } -.radio input[type="radio"], -.radio-inline input[type="radio"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - float: left; - margin-left: -20px; +html { + font-size: 62.5%; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } -.radio + .radio, -.checkbox + .checkbox { - margin-top: -5px; +body { + font-family: "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; } -.radio-inline, -.checkbox-inline { - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - vertical-align: middle; - cursor: pointer; +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; } -.radio-inline + .radio-inline, -.checkbox-inline + .checkbox-inline { - margin-top: 0; - margin-left: 10px; +a { + color: #000; + text-decoration: none; } -input[type="radio"][disabled], -input[type="checkbox"][disabled], -.radio[disabled], -.radio-inline[disabled], -.checkbox[disabled], -.checkbox-inline[disabled], -fieldset[disabled] input[type="radio"], -fieldset[disabled] input[type="checkbox"], -fieldset[disabled] .radio, -fieldset[disabled] .radio-inline, -fieldset[disabled] .checkbox, -fieldset[disabled] .checkbox-inline { - cursor: not-allowed; +a:hover, +a:focus { + color: #000; + text-decoration: underline; } -.input-sm { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } -select.input-sm { - height: 30px; - line-height: 30px; +figure { + margin: 0; } -textarea.input-sm, -select[multiple].input-sm { +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; height: auto; } -.input-lg { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; +.img-rounded { border-radius: 6px; } -select.input-lg { - height: 46px; - line-height: 46px; -} -textarea.input-lg, -select[multiple].input-lg { +.img-thumbnail { + display: inline-block; + max-width: 100%; height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; } -.has-feedback { - position: relative; +.img-circle { + border-radius: 50%; } -.has-feedback .form-control { - padding-right: 42.5px; +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; } -.has-feedback .form-control-feedback { +.sr-only { position: absolute; - top: 25px; - right: 0; - display: block; - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; -} -.has-success .help-block, -.has-success .control-label, -.has-success .radio, -.has-success .checkbox, -.has-success .radio-inline, -.has-success .checkbox-inline { - color: #3c763d; -} -.has-success .form-control { - border-color: #3c763d; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; } -.has-success .form-control:focus { - border-color: #2b542c; +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; } -.has-success .input-group-addon { - color: #3c763d; - background-color: #dff0d8; - border-color: #3c763d; +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; } -.has-success .form-control-feedback { - color: #3c763d; +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #999; } -.has-warning .help-block, -.has-warning .control-label, -.has-warning .radio, -.has-warning .checkbox, -.has-warning .radio-inline, -.has-warning .checkbox-inline { - color: #f39c12; +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; } -.has-warning .form-control { - border-color: #f39c12; +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; } -.has-warning .form-control:focus { - border-color: #c87f0a; +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; } -.has-warning .input-group-addon { - color: #f39c12; - background-color: #fcf8e3; - border-color: #f39c12; +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; } -.has-warning .form-control-feedback { - color: #f39c12; +h1, +.h1 { + font-size: 36px; } -.has-error .help-block, -.has-error .control-label, -.has-error .radio, -.has-error .checkbox, -.has-error .radio-inline, -.has-error .checkbox-inline { - color: #c0392b; +h2, +.h2 { + font-size: 30px; } -.has-error .form-control { - border-color: #c0392b; +h3, +.h3 { + font-size: 24px; } -.has-error .form-control:focus { - border-color: #962d22; +h4, +.h4 { + font-size: 18px; } -.has-error .input-group-addon { - color: #c0392b; - background-color: #f2dede; - border-color: #c0392b; +h5, +.h5 { + font-size: 14px; } -.has-error .form-control-feedback { - color: #c0392b; +h6, +.h6 { + font-size: 12px; } -.form-control-static { - margin-bottom: 0; +p { + margin: 0 0 10px; } -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373; +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; } @media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .form-inline .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio, - .form-inline .checkbox { - display: inline-block; - padding-left: 0; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio input[type="radio"], - .form-inline .checkbox input[type="checkbox"] { - float: none; - margin-left: 0; - } - .form-inline .has-feedback .form-control-feedback { - top: 0; + .lead { + font-size: 21px; } } -.form-horizontal .control-label, -.form-horizontal .radio, -.form-horizontal .checkbox, -.form-horizontal .radio-inline, -.form-horizontal .checkbox-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0; -} -.form-horizontal .radio, -.form-horizontal .checkbox { - min-height: 27px; +small, +.small { + font-size: 85%; } -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px; +cite { + font-style: normal; } -.form-horizontal .form-control-static { - padding-top: 7px; +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; } -@media (min-width: 768px) { - .form-horizontal .control-label { - text-align: right; - } +.text-left { + text-align: left; } -.form-horizontal .has-feedback .form-control-feedback { - top: 0; - right: 15px; +.text-right { + text-align: right; } -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: normal; - line-height: 1.42857143; +.text-center { text-align: center; - white-space: nowrap; - vertical-align: middle; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; } -.btn:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; +.text-justify { + text-align: justify; +} +.text-muted { + color: #999; +} +.text-primary { + color: #2980b9; +} +a.text-primary:hover { + color: #20638f; } -.btn:hover, -.btn:focus { - color: #333; - text-decoration: none; +.text-success { + color: #3c763d; } -.btn:active, -.btn.active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +a.text-success:hover { + color: #2b542c; } -.btn.disabled, -.btn[disabled], -fieldset[disabled] .btn { - pointer-events: none; - cursor: not-allowed; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - opacity: .65; +.text-info { + color: #31708f; } -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc; +a.text-info:hover { + color: #245269; } -.btn-default:hover, -.btn-default:focus, -.btn-default:active, -.btn-default.active, -.open .dropdown-toggle.btn-default { - color: #333; - background-color: #ebebeb; - border-color: #adadad; +.text-warning { + color: #f39c12; } -.btn-default:active, -.btn-default.active, -.open .dropdown-toggle.btn-default { - background-image: none; +a.text-warning:hover { + color: #c87f0a; } -.btn-default.disabled, -.btn-default[disabled], -fieldset[disabled] .btn-default, -.btn-default.disabled:hover, -.btn-default[disabled]:hover, -fieldset[disabled] .btn-default:hover, -.btn-default.disabled:focus, -.btn-default[disabled]:focus, -fieldset[disabled] .btn-default:focus, -.btn-default.disabled:active, -.btn-default[disabled]:active, -fieldset[disabled] .btn-default:active, -.btn-default.disabled.active, -.btn-default[disabled].active, -fieldset[disabled] .btn-default.active { - background-color: #fff; - border-color: #ccc; +.text-danger { + color: #c0392b; } -.btn-default .badge { - color: #fff; - background-color: #333; +a.text-danger:hover { + color: #962d22; } -.btn-primary { +.bg-primary { color: #fff; background-color: #2980b9; - border-color: #2472a4; } -.btn-primary:hover, -.btn-primary:focus, -.btn-primary:active, -.btn-primary.active, -.open .dropdown-toggle.btn-primary { - color: #fff; - background-color: #226998; - border-color: #194f72; +a.bg-primary:hover { + background-color: #20638f; } -.btn-primary:active, -.btn-primary.active, -.open .dropdown-toggle.btn-primary { - background-image: none; +.bg-success { + background-color: #dff0d8; } -.btn-primary.disabled, -.btn-primary[disabled], -fieldset[disabled] .btn-primary, -.btn-primary.disabled:hover, -.btn-primary[disabled]:hover, -fieldset[disabled] .btn-primary:hover, -.btn-primary.disabled:focus, -.btn-primary[disabled]:focus, -fieldset[disabled] .btn-primary:focus, -.btn-primary.disabled:active, -.btn-primary[disabled]:active, -fieldset[disabled] .btn-primary:active, -.btn-primary.disabled.active, -.btn-primary[disabled].active, -fieldset[disabled] .btn-primary.active { - background-color: #2980b9; - border-color: #2472a4; +a.bg-success:hover { + background-color: #c1e2b3; } -.btn-primary .badge { - color: #2980b9; - background-color: #fff; +.bg-info { + background-color: #d9edf7; } -.btn-success { - color: #fff; - background-color: #5cb85c; - border-color: #4cae4c; +a.bg-info:hover { + background-color: #afd9ee; } -.btn-success:hover, -.btn-success:focus, -.btn-success:active, -.btn-success.active, -.open .dropdown-toggle.btn-success { - color: #fff; - background-color: #47a447; - border-color: #398439; +.bg-warning { + background-color: #fcf8e3; } -.btn-success:active, -.btn-success.active, -.open .dropdown-toggle.btn-success { - background-image: none; +a.bg-warning:hover { + background-color: #f7ecb5; } -.btn-success.disabled, -.btn-success[disabled], -fieldset[disabled] .btn-success, -.btn-success.disabled:hover, -.btn-success[disabled]:hover, -fieldset[disabled] .btn-success:hover, -.btn-success.disabled:focus, -.btn-success[disabled]:focus, -fieldset[disabled] .btn-success:focus, -.btn-success.disabled:active, -.btn-success[disabled]:active, -fieldset[disabled] .btn-success:active, -.btn-success.disabled.active, -.btn-success[disabled].active, -fieldset[disabled] .btn-success.active { - background-color: #5cb85c; - border-color: #4cae4c; +.bg-danger { + background-color: #f2dede; } -.btn-success .badge { - color: #5cb85c; - background-color: #fff; +a.bg-danger:hover { + background-color: #e4b9b9; } -.btn-info { - color: #fff; - background-color: #5bc0de; - border-color: #46b8da; +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; } -.btn-info:hover, -.btn-info:focus, -.btn-info:active, -.btn-info.active, -.open .dropdown-toggle.btn-info { - color: #fff; - background-color: #39b3d7; - border-color: #269abc; +ul, +ol { + margin-top: 0; + margin-bottom: 10px; } -.btn-info:active, -.btn-info.active, -.open .dropdown-toggle.btn-info { - background-image: none; +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; } -.btn-info.disabled, -.btn-info[disabled], -fieldset[disabled] .btn-info, -.btn-info.disabled:hover, -.btn-info[disabled]:hover, -fieldset[disabled] .btn-info:hover, -.btn-info.disabled:focus, -.btn-info[disabled]:focus, -fieldset[disabled] .btn-info:focus, -.btn-info.disabled:active, -.btn-info[disabled]:active, -fieldset[disabled] .btn-info:active, -.btn-info.disabled.active, -.btn-info[disabled].active, -fieldset[disabled] .btn-info.active { - background-color: #5bc0de; - border-color: #46b8da; +.list-unstyled { + padding-left: 0; + list-style: none; } -.btn-info .badge { - color: #5bc0de; - background-color: #fff; +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; } -.btn-warning { - color: #fff; - background-color: #f39c12; - border-color: #e08e0b; +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; } -.btn-warning:hover, -.btn-warning:focus, -.btn-warning:active, -.btn-warning.active, -.open .dropdown-toggle.btn-warning { - color: #fff; - background-color: #d2850b; - border-color: #a66908; +dl { + margin-top: 0; + margin-bottom: 20px; } -.btn-warning:active, -.btn-warning.active, -.open .dropdown-toggle.btn-warning { - background-image: none; +dt, +dd { + line-height: 1.42857143; } -.btn-warning.disabled, -.btn-warning[disabled], -fieldset[disabled] .btn-warning, -.btn-warning.disabled:hover, -.btn-warning[disabled]:hover, -fieldset[disabled] .btn-warning:hover, -.btn-warning.disabled:focus, -.btn-warning[disabled]:focus, -fieldset[disabled] .btn-warning:focus, -.btn-warning.disabled:active, -.btn-warning[disabled]:active, -fieldset[disabled] .btn-warning:active, -.btn-warning.disabled.active, -.btn-warning[disabled].active, -fieldset[disabled] .btn-warning.active { - background-color: #f39c12; - border-color: #e08e0b; +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } } -.btn-warning .badge { - color: #f39c12; - background-color: #fff; +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999; } -.btn-danger { - color: #fff; - background-color: #c0392b; - border-color: #ab3326; +.initialism { + font-size: 90%; + text-transform: uppercase; } -.btn-danger:hover, -.btn-danger:focus, -.btn-danger:active, -.btn-danger.active, -.open .dropdown-toggle.btn-danger { - color: #fff; - background-color: #9f2f24; - border-color: #79241b; +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; } -.btn-danger:active, -.btn-danger.active, -.open .dropdown-toggle.btn-danger { - background-image: none; +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; } -.btn-danger.disabled, -.btn-danger[disabled], -fieldset[disabled] .btn-danger, -.btn-danger.disabled:hover, -.btn-danger[disabled]:hover, -fieldset[disabled] .btn-danger:hover, -.btn-danger.disabled:focus, -.btn-danger[disabled]:focus, -fieldset[disabled] .btn-danger:focus, -.btn-danger.disabled:active, -.btn-danger[disabled]:active, -fieldset[disabled] .btn-danger:active, -.btn-danger.disabled.active, -.btn-danger[disabled].active, -fieldset[disabled] .btn-danger.active { - background-color: #c0392b; - border-color: #ab3326; +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #999; } -.btn-danger .badge { - color: #c0392b; - background-color: #fff; +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; } -.btn-link { - font-weight: normal; - color: #2980b9; - cursor: pointer; - border-radius: 0; +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; } -.btn-link, -.btn-link:active, -.btn-link[disabled], -fieldset[disabled] .btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; } -.btn-link, -.btn-link:hover, -.btn-link:focus, -.btn-link:active { - border-color: transparent; +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; } -.btn-link:hover, -.btn-link:focus { - color: #1b557a; - text-decoration: underline; - background-color: transparent; +blockquote:before, +blockquote:after { + content: ""; } -.btn-link[disabled]:hover, -fieldset[disabled] .btn-link:hover, -.btn-link[disabled]:focus, -fieldset[disabled] .btn-link:focus { - color: #999; - text-decoration: none; +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; } -.btn-lg { - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } -.btn-sm { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; } -.btn-xs { - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); } -.btn-block { +pre { display: block; - width: 100%; - padding-right: 0; - padding-left: 0; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; } -.btn-block + .btn-block { - margin-top: 5px; +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; } -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; } -.fade { - opacity: 0; - -webkit-transition: opacity .15s linear; - transition: opacity .15s linear; +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; } -.fade.in { - opacity: 1; +@media (min-width: 768px) { + .container { + width: 750px; + } } -.collapse { - display: none; +@media (min-width: 992px) { + .container { + width: 970px; + } } -.collapse.in { - display: block; +@media (min-width: 1200px) { + .container { + width: 1170px; + } } -.collapsing { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition: height .35s ease; - transition: height .35s ease; +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; } -@font-face { - font-family: 'Glyphicons Halflings'; - - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +.row { + margin-right: -15px; + margin-left: -15px; } -.glyphicon { +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; } -.glyphicon-asterisk:before { - content: "\2a"; +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; } -.glyphicon-plus:before { - content: "\2b"; +.col-xs-12 { + width: 100%; } -.glyphicon-euro:before { - content: "\20ac"; +.col-xs-11 { + width: 91.66666667%; } -.glyphicon-minus:before { - content: "\2212"; +.col-xs-10 { + width: 83.33333333%; } -.glyphicon-cloud:before { - content: "\2601"; +.col-xs-9 { + width: 75%; } -.glyphicon-envelope:before { - content: "\2709"; +.col-xs-8 { + width: 66.66666667%; } -.glyphicon-pencil:before { - content: "\270f"; +.col-xs-7 { + width: 58.33333333%; } -.glyphicon-glass:before { - content: "\e001"; +.col-xs-6 { + width: 50%; } -.glyphicon-music:before { - content: "\e002"; +.col-xs-5 { + width: 41.66666667%; } -.glyphicon-search:before { - content: "\e003"; +.col-xs-4 { + width: 33.33333333%; } -.glyphicon-heart:before { - content: "\e005"; +.col-xs-3 { + width: 25%; } -.glyphicon-star:before { - content: "\e006"; +.col-xs-2 { + width: 16.66666667%; } -.glyphicon-star-empty:before { - content: "\e007"; +.col-xs-1 { + width: 8.33333333%; } -.glyphicon-user:before { - content: "\e008"; +.col-xs-pull-12 { + right: 100%; } -.glyphicon-film:before { - content: "\e009"; +.col-xs-pull-11 { + right: 91.66666667%; } -.glyphicon-th-large:before { - content: "\e010"; +.col-xs-pull-10 { + right: 83.33333333%; } -.glyphicon-th:before { - content: "\e011"; +.col-xs-pull-9 { + right: 75%; } -.glyphicon-th-list:before { - content: "\e012"; +.col-xs-pull-8 { + right: 66.66666667%; } -.glyphicon-ok:before { - content: "\e013"; +.col-xs-pull-7 { + right: 58.33333333%; } -.glyphicon-remove:before { - content: "\e014"; +.col-xs-pull-6 { + right: 50%; } -.glyphicon-zoom-in:before { - content: "\e015"; +.col-xs-pull-5 { + right: 41.66666667%; } -.glyphicon-zoom-out:before { - content: "\e016"; +.col-xs-pull-4 { + right: 33.33333333%; } -.glyphicon-off:before { - content: "\e017"; +.col-xs-pull-3 { + right: 25%; } -.glyphicon-signal:before { - content: "\e018"; +.col-xs-pull-2 { + right: 16.66666667%; } -.glyphicon-cog:before { - content: "\e019"; +.col-xs-pull-1 { + right: 8.33333333%; } -.glyphicon-trash:before { - content: "\e020"; +.col-xs-pull-0 { + right: auto; } -.glyphicon-home:before { - content: "\e021"; +.col-xs-push-12 { + left: 100%; } -.glyphicon-file:before { - content: "\e022"; +.col-xs-push-11 { + left: 91.66666667%; } -.glyphicon-time:before { - content: "\e023"; +.col-xs-push-10 { + left: 83.33333333%; } -.glyphicon-road:before { - content: "\e024"; +.col-xs-push-9 { + left: 75%; } -.glyphicon-download-alt:before { - content: "\e025"; +.col-xs-push-8 { + left: 66.66666667%; } -.glyphicon-download:before { - content: "\e026"; +.col-xs-push-7 { + left: 58.33333333%; } -.glyphicon-upload:before { - content: "\e027"; +.col-xs-push-6 { + left: 50%; } -.glyphicon-inbox:before { - content: "\e028"; +.col-xs-push-5 { + left: 41.66666667%; } -.glyphicon-play-circle:before { - content: "\e029"; +.col-xs-push-4 { + left: 33.33333333%; } -.glyphicon-repeat:before { - content: "\e030"; +.col-xs-push-3 { + left: 25%; } -.glyphicon-refresh:before { - content: "\e031"; +.col-xs-push-2 { + left: 16.66666667%; } -.glyphicon-list-alt:before { - content: "\e032"; +.col-xs-push-1 { + left: 8.33333333%; } -.glyphicon-lock:before { - content: "\e033"; +.col-xs-push-0 { + left: auto; } -.glyphicon-flag:before { - content: "\e034"; +.col-xs-offset-12 { + margin-left: 100%; } -.glyphicon-headphones:before { - content: "\e035"; +.col-xs-offset-11 { + margin-left: 91.66666667%; } -.glyphicon-volume-off:before { - content: "\e036"; +.col-xs-offset-10 { + margin-left: 83.33333333%; } -.glyphicon-volume-down:before { - content: "\e037"; +.col-xs-offset-9 { + margin-left: 75%; } -.glyphicon-volume-up:before { - content: "\e038"; +.col-xs-offset-8 { + margin-left: 66.66666667%; } -.glyphicon-qrcode:before { - content: "\e039"; +.col-xs-offset-7 { + margin-left: 58.33333333%; } -.glyphicon-barcode:before { - content: "\e040"; +.col-xs-offset-6 { + margin-left: 50%; } -.glyphicon-tag:before { - content: "\e041"; +.col-xs-offset-5 { + margin-left: 41.66666667%; } -.glyphicon-tags:before { - content: "\e042"; +.col-xs-offset-4 { + margin-left: 33.33333333%; } -.glyphicon-book:before { - content: "\e043"; +.col-xs-offset-3 { + margin-left: 25%; } -.glyphicon-bookmark:before { - content: "\e044"; +.col-xs-offset-2 { + margin-left: 16.66666667%; } -.glyphicon-print:before { - content: "\e045"; +.col-xs-offset-1 { + margin-left: 8.33333333%; } -.glyphicon-camera:before { - content: "\e046"; +.col-xs-offset-0 { + margin-left: 0; } -.glyphicon-font:before { - content: "\e047"; +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } } -.glyphicon-bold:before { - content: "\e048"; +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } } -.glyphicon-italic:before { - content: "\e049"; +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } } -.glyphicon-text-height:before { - content: "\e050"; +table { + max-width: 100%; + background-color: transparent; } -.glyphicon-text-width:before { - content: "\e051"; +th { + text-align: left; } -.glyphicon-align-left:before { - content: "\e052"; +.table { + width: 100%; + margin-bottom: 20px; } -.glyphicon-align-center:before { - content: "\e053"; +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; } -.glyphicon-align-right:before { - content: "\e054"; +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; } -.glyphicon-align-justify:before { - content: "\e055"; +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; } -.glyphicon-list:before { - content: "\e056"; +.table > tbody + tbody { + border-top: 2px solid #ddd; } -.glyphicon-indent-left:before { - content: "\e057"; +.table .table { + background-color: #fff; } -.glyphicon-indent-right:before { - content: "\e058"; +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; } -.glyphicon-facetime-video:before { - content: "\e059"; +.table-bordered { + border: 1px solid #ddd; } -.glyphicon-picture:before { - content: "\e060"; +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; } -.glyphicon-map-marker:before { - content: "\e062"; +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; } -.glyphicon-adjust:before { - content: "\e063"; +.table-striped > tbody > tr:nth-child(odd) > td, +.table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; } -.glyphicon-tint:before { - content: "\e064"; +.table-hover > tbody > tr:hover > td, +.table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; } -.glyphicon-edit:before { - content: "\e065"; +table col[class*="col-"] { + position: static; + display: table-column; + float: none; } -.glyphicon-share:before { - content: "\e066"; +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; } -.glyphicon-check:before { - content: "\e067"; +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; } -.glyphicon-move:before { - content: "\e068"; +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; } -.glyphicon-step-backward:before { - content: "\e069"; +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; } -.glyphicon-fast-backward:before { - content: "\e070"; +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; } -.glyphicon-backward:before { - content: "\e071"; +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; } -.glyphicon-play:before { - content: "\e072"; +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; } -.glyphicon-pause:before { - content: "\e073"; +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; } -.glyphicon-stop:before { - content: "\e074"; +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; } -.glyphicon-forward:before { - content: "\e075"; +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; } -.glyphicon-fast-forward:before { - content: "\e076"; +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; } -.glyphicon-step-forward:before { - content: "\e077"; +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-x: scroll; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } } -.glyphicon-eject:before { - content: "\e078"; +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; } -.glyphicon-chevron-left:before { - content: "\e079"; +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; } -.glyphicon-chevron-right:before { - content: "\e080"; +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; } -.glyphicon-plus-sign:before { - content: "\e081"; +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } -.glyphicon-minus-sign:before { - content: "\e082"; +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; } -.glyphicon-remove-sign:before { - content: "\e083"; +input[type="file"] { + display: block; } -.glyphicon-ok-sign:before { - content: "\e084"; +input[type="range"] { + display: block; + width: 100%; } -.glyphicon-question-sign:before { - content: "\e085"; +select[multiple], +select[size] { + height: auto; } -.glyphicon-info-sign:before { - content: "\e086"; +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } -.glyphicon-screenshot:before { - content: "\e087"; +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; } -.glyphicon-remove-circle:before { - content: "\e088"; +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; } -.glyphicon-ok-circle:before { - content: "\e089"; +.form-control:focus { + border-color: #000; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(0, 0, 0, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(0, 0, 0, .6); } -.glyphicon-ban-circle:before { - content: "\e090"; +.form-control::-moz-placeholder { + color: #999; + opacity: 1; } -.glyphicon-arrow-left:before { - content: "\e091"; +.form-control:-ms-input-placeholder { + color: #999; } -.glyphicon-arrow-right:before { - content: "\e092"; +.form-control::-webkit-input-placeholder { + color: #999; } -.glyphicon-arrow-up:before { - content: "\e093"; +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eee; + opacity: 1; } -.glyphicon-arrow-down:before { - content: "\e094"; +textarea.form-control { + height: auto; } -.glyphicon-share-alt:before { - content: "\e095"; +input[type="search"] { + -webkit-appearance: none; } -.glyphicon-resize-full:before { - content: "\e096"; +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + line-height: 34px; + line-height: 1.42857143 \0; } -.glyphicon-resize-small:before { - content: "\e097"; +input[type="date"].input-sm, +input[type="time"].input-sm, +input[type="datetime-local"].input-sm, +input[type="month"].input-sm { + line-height: 30px; } -.glyphicon-exclamation-sign:before { - content: "\e101"; +input[type="date"].input-lg, +input[type="time"].input-lg, +input[type="datetime-local"].input-lg, +input[type="month"].input-lg { + line-height: 46px; } -.glyphicon-gift:before { - content: "\e102"; +.form-group { + margin-bottom: 15px; } -.glyphicon-leaf:before { - content: "\e103"; +.radio, +.checkbox { + display: block; + min-height: 20px; + margin-top: 10px; + margin-bottom: 10px; } -.glyphicon-fire:before { - content: "\e104"; +.radio label, +.checkbox label { + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; } -.glyphicon-eye-open:before { - content: "\e105"; +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; } -.glyphicon-eye-close:before { - content: "\e106"; +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; } -.glyphicon-warning-sign:before { - content: "\e107"; +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; } -.glyphicon-plane:before { - content: "\e108"; +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; } -.glyphicon-calendar:before { - content: "\e109"; +input[type="radio"][disabled], +input[type="checkbox"][disabled], +.radio[disabled], +.radio-inline[disabled], +.checkbox[disabled], +.checkbox-inline[disabled], +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"], +fieldset[disabled] .radio, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; } -.glyphicon-random:before { - content: "\e110"; +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; } -.glyphicon-comment:before { - content: "\e111"; +select.input-sm { + height: 30px; + line-height: 30px; } -.glyphicon-magnet:before { - content: "\e112"; +textarea.input-sm, +select[multiple].input-sm { + height: auto; } -.glyphicon-chevron-up:before { - content: "\e113"; +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; } -.glyphicon-chevron-down:before { - content: "\e114"; +select.input-lg { + height: 46px; + line-height: 46px; } -.glyphicon-retweet:before { - content: "\e115"; +textarea.input-lg, +select[multiple].input-lg { + height: auto; } -.glyphicon-shopping-cart:before { - content: "\e116"; +.has-feedback { + position: relative; } -.glyphicon-folder-close:before { - content: "\e117"; +.has-feedback .form-control { + padding-right: 42.5px; } -.glyphicon-folder-open:before { - content: "\e118"; +.form-control-feedback { + position: absolute; + top: 25px; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; } -.glyphicon-resize-vertical:before { - content: "\e119"; +.input-lg + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; } -.glyphicon-resize-horizontal:before { - content: "\e120"; +.input-sm + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; } -.glyphicon-hdd:before { - content: "\e121"; +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline { + color: #3c763d; } -.glyphicon-bullhorn:before { - content: "\e122"; +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); } -.glyphicon-bell:before { - content: "\e123"; +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; } -.glyphicon-certificate:before { - content: "\e124"; +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; } -.glyphicon-thumbs-up:before { - content: "\e125"; +.has-success .form-control-feedback { + color: #3c763d; } -.glyphicon-thumbs-down:before { - content: "\e126"; +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline { + color: #f39c12; } -.glyphicon-hand-right:before { - content: "\e127"; +.has-warning .form-control { + border-color: #f39c12; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); } -.glyphicon-hand-left:before { - content: "\e128"; +.has-warning .form-control:focus { + border-color: #c87f0a; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #f8c573; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #f8c573; } -.glyphicon-hand-up:before { - content: "\e129"; +.has-warning .input-group-addon { + color: #f39c12; + background-color: #fcf8e3; + border-color: #f39c12; } -.glyphicon-hand-down:before { - content: "\e130"; +.has-warning .form-control-feedback { + color: #f39c12; } -.glyphicon-circle-arrow-right:before { - content: "\e131"; +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline { + color: #c0392b; } -.glyphicon-circle-arrow-left:before { - content: "\e132"; +.has-error .form-control { + border-color: #c0392b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); } -.glyphicon-circle-arrow-up:before { - content: "\e133"; +.has-error .form-control:focus { + border-color: #962d22; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #df7c72; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #df7c72; } -.glyphicon-circle-arrow-down:before { - content: "\e134"; +.has-error .input-group-addon { + color: #c0392b; + background-color: #f2dede; + border-color: #c0392b; } -.glyphicon-globe:before { - content: "\e135"; +.has-error .form-control-feedback { + color: #c0392b; } -.glyphicon-wrench:before { - content: "\e136"; +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; } -.glyphicon-tasks:before { - content: "\e137"; +.form-control-static { + margin-bottom: 0; } -.glyphicon-filter:before { - content: "\e138"; +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; } -.glyphicon-briefcase:before { - content: "\e139"; +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } } -.glyphicon-fullscreen:before { - content: "\e140"; +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; } -.glyphicon-dashboard:before { - content: "\e141"; +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; } -.glyphicon-paperclip:before { - content: "\e142"; +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; } -.glyphicon-heart-empty:before { - content: "\e143"; +.form-horizontal .form-control-static { + padding-top: 7px; + padding-bottom: 7px; } -.glyphicon-link:before { - content: "\e144"; +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } } -.glyphicon-phone:before { - content: "\e145"; +.form-horizontal .has-feedback .form-control-feedback { + top: 0; + right: 15px; } -.glyphicon-pushpin:before { - content: "\e146"; +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; } -.glyphicon-usd:before { - content: "\e148"; +.btn:focus, +.btn:active:focus, +.btn.active:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } -.glyphicon-gbp:before { - content: "\e149"; +.btn:hover, +.btn:focus { + color: #333; + text-decoration: none; } -.glyphicon-sort:before { - content: "\e150"; +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); } -.glyphicon-sort-by-alphabet:before { - content: "\e151"; +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; } -.glyphicon-sort-by-alphabet-alt:before { - content: "\e152"; +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; } -.glyphicon-sort-by-order:before { - content: "\e153"; +.btn-default:hover, +.btn-default:focus, +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; } -.glyphicon-sort-by-order-alt:before { - content: "\e154"; +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; } -.glyphicon-sort-by-attributes:before { - content: "\e155"; +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc; } -.glyphicon-sort-by-attributes-alt:before { - content: "\e156"; +.btn-default .badge { + color: #fff; + background-color: #333; } -.glyphicon-unchecked:before { - content: "\e157"; +.btn-primary { + color: #fff; + background-color: #2980b9; + border-color: #2472a4; } -.glyphicon-expand:before { - content: "\e158"; +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #20638f; + border-color: #194f72; } -.glyphicon-collapse-down:before { - content: "\e159"; +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; } -.glyphicon-collapse-up:before { - content: "\e160"; +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #2980b9; + border-color: #2472a4; } -.glyphicon-log-in:before { - content: "\e161"; +.btn-primary .badge { + color: #2980b9; + background-color: #fff; } -.glyphicon-flash:before { - content: "\e162"; +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; } -.glyphicon-log-out:before { - content: "\e163"; +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; } -.glyphicon-new-window:before { - content: "\e164"; +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; } -.glyphicon-record:before { - content: "\e165"; +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; } -.glyphicon-save:before { - content: "\e166"; +.btn-success .badge { + color: #5cb85c; + background-color: #fff; } -.glyphicon-open:before { - content: "\e167"; +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; } -.glyphicon-saved:before { - content: "\e168"; +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; } -.glyphicon-import:before { - content: "\e169"; +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; } -.glyphicon-export:before { - content: "\e170"; +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; } -.glyphicon-send:before { - content: "\e171"; +.btn-info .badge { + color: #5bc0de; + background-color: #fff; } -.glyphicon-floppy-disk:before { - content: "\e172"; +.btn-warning { + color: #fff; + background-color: #f39c12; + border-color: #e08e0b; } -.glyphicon-floppy-saved:before { - content: "\e173"; +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #c87f0a; + border-color: #a66908; } -.glyphicon-floppy-remove:before { - content: "\e174"; +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; } -.glyphicon-floppy-save:before { - content: "\e175"; +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f39c12; + border-color: #e08e0b; } -.glyphicon-floppy-open:before { - content: "\e176"; +.btn-warning .badge { + color: #f39c12; + background-color: #fff; } -.glyphicon-credit-card:before { - content: "\e177"; +.btn-danger { + color: #fff; + background-color: #c0392b; + border-color: #ab3326; } -.glyphicon-transfer:before { - content: "\e178"; +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #962d22; + border-color: #79241b; } -.glyphicon-cutlery:before { - content: "\e179"; +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; } -.glyphicon-header:before { - content: "\e180"; +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #c0392b; + border-color: #ab3326; } -.glyphicon-compressed:before { - content: "\e181"; +.btn-danger .badge { + color: #c0392b; + background-color: #fff; } -.glyphicon-earphone:before { - content: "\e182"; +.btn-link { + font-weight: normal; + color: #000; + cursor: pointer; + border-radius: 0; } -.glyphicon-phone-alt:before { - content: "\e183"; +.btn-link, +.btn-link:active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; } -.glyphicon-tower:before { - content: "\e184"; +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; } -.glyphicon-stats:before { - content: "\e185"; +.btn-link:hover, +.btn-link:focus { + color: #000; + text-decoration: underline; + background-color: transparent; } -.glyphicon-sd-video:before { - content: "\e186"; +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #999; + text-decoration: none; } -.glyphicon-hd-video:before { - content: "\e187"; +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; } -.glyphicon-subtitles:before { - content: "\e188"; +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; } -.glyphicon-sound-stereo:before { - content: "\e189"; +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; } -.glyphicon-sound-dolby:before { - content: "\e190"; +.btn-block { + display: block; + width: 100%; + padding-right: 0; + padding-left: 0; } -.glyphicon-sound-5-1:before { - content: "\e191"; +.btn-block + .btn-block { + margin-top: 5px; } -.glyphicon-sound-6-1:before { - content: "\e192"; +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; } -.glyphicon-sound-7-1:before { - content: "\e193"; +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; } -.glyphicon-copyright-mark:before { - content: "\e194"; +.fade.in { + opacity: 1; } -.glyphicon-registration-mark:before { - content: "\e195"; +.collapse { + display: none; } -.glyphicon-cloud-download:before { - content: "\e197"; +.collapse.in { + display: block; } -.glyphicon-cloud-upload:before { - content: "\e198"; +tr.collapse.in { + display: table-row; } -.glyphicon-tree-conifer:before { - content: "\e199"; +tbody.collapse.in { + display: table-row-group; } -.glyphicon-tree-deciduous:before { - content: "\e200"; +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height .35s ease; + -o-transition: height .35s ease; + transition: height .35s ease; } .caret { display: inline-block; @@ -2994,9 +3087,11 @@ input[type="button"].btn-block { padding: 5px 0; margin: 2px 0 0; font-size: 14px; + text-align: left; list-style: none; background-color: #fff; - background-clip: padding-box; + -webkit-background-clip: padding-box; + background-clip: padding-box; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, .15); border-radius: 4px; @@ -3127,7 +3222,7 @@ input[type="button"].btn-block { } .btn-group > .btn:focus, .btn-group-vertical > .btn:focus { - outline: none; + outline: 0; } .btn-group .btn + .btn, .btn-group .btn + .btn-group, @@ -3181,24 +3276,6 @@ input[type="button"].btn-block { .btn-group.open .dropdown-toggle { outline: 0; } -.btn-group-xs > .btn { - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-group-sm > .btn { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-group-lg > .btn { - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} .btn-group > .btn + .dropdown-toggle { padding-right: 8px; padding-left: 8px; @@ -3283,9 +3360,15 @@ input[type="button"].btn-block { .btn-group-justified > .btn-group .btn { width: 100%; } +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} [data-toggle="buttons"] > .btn > input[type="radio"], [data-toggle="buttons"] > .btn > input[type="checkbox"] { - display: none; + position: absolute; + z-index: -1; + filter: alpha(opacity=0); + opacity: 0; } .input-group { position: relative; @@ -3298,6 +3381,8 @@ input[type="button"].btn-block { padding-left: 0; } .input-group .form-control { + position: relative; + z-index: 2; float: left; width: 100%; margin-bottom: 0; @@ -3472,7 +3557,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .nav .open > a:hover, .nav .open > a:focus { background-color: #eee; - border-color: #2980b9; + border-color: #000; } .nav .nav-divider { height: 1px; @@ -3648,13 +3733,13 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } } .navbar-collapse { - max-height: 340px; padding-right: 15px; padding-left: 15px; overflow-x: visible; -webkit-overflow-scrolling: touch; border-top: 1px solid transparent; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); } .navbar-collapse.in { overflow-y: auto; @@ -3663,7 +3748,8 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .navbar-collapse { width: auto; border-top: 0; - box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; } .navbar-collapse.collapse { display: block !important; @@ -3681,6 +3767,16 @@ select[multiple].input-group-sm > .input-group-btn > .btn { padding-left: 0; } } +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} .container > .navbar-header, .container-fluid > .navbar-header, .container > .navbar-collapse, @@ -3730,7 +3826,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .navbar-brand { float: left; - height: 20px; + height: 36px; padding: 8px 15px; font-size: 18px; line-height: 20px; @@ -3758,7 +3854,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { border-radius: 4px; } .navbar-toggle:focus { - outline: none; + outline: 0; } .navbar-toggle .icon-bar { display: block; @@ -3790,7 +3886,8 @@ select[multiple].input-group-sm > .input-group-btn > .btn { margin-top: 0; background-color: transparent; border: 0; - box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; } .navbar-nav .open .dropdown-menu > li > a, .navbar-nav .open .dropdown-menu .dropdown-header { @@ -3850,6 +3947,18 @@ select[multiple].input-group-sm > .input-group-btn > .btn { width: auto; vertical-align: middle; } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } .navbar-form .control-label { margin-bottom: 0; vertical-align: middle; @@ -4009,6 +4118,19 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .navbar-default .navbar-link:hover { color: #333; } +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} .navbar-inverse { background-color: rgba(0, 0, 0, .7); border-color: rgba(0, 0, 0, .7); @@ -4098,6 +4220,19 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .navbar-inverse .navbar-link:hover { color: #fff; } +.navbar-inverse .btn-link { + color: #eee; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} .breadcrumb { padding: 8px 15px; margin-bottom: 20px; @@ -4132,7 +4267,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { padding: 6px 12px; margin-left: -1px; line-height: 1.42857143; - color: #2980b9; + color: #000; text-decoration: none; background-color: #fff; border: 1px solid #ddd; @@ -4152,7 +4287,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .pagination > li > span:hover, .pagination > li > a:focus, .pagination > li > span:focus { - color: #1b557a; + color: #000; background-color: #eee; border-color: #ddd; } @@ -4259,8 +4394,8 @@ select[multiple].input-group-sm > .input-group-btn > .btn { vertical-align: baseline; border-radius: .25em; } -.label[href]:hover, -.label[href]:focus { +a.label:hover, +a.label:focus { color: #fff; text-decoration: none; cursor: pointer; @@ -4347,7 +4482,7 @@ a.badge:focus { } a.list-group-item.active > .badge, .nav-pills > .active > a > .badge { - color: #2980b9; + color: #000; background-color: #fff; } .nav-pills > li > a > .badge { @@ -4368,6 +4503,9 @@ a.list-group-item.active > .badge, font-size: 21px; font-weight: 200; } +.jumbotron > hr { + border-top-color: #d5d5d5; +} .container .jumbotron { border-radius: 6px; } @@ -4397,20 +4535,18 @@ a.list-group-item.active > .badge, border: 1px solid #ddd; border-radius: 4px; -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; transition: all .2s ease-in-out; } .thumbnail > img, .thumbnail a > img { - display: block; - max-width: 100%; - height: auto; margin-right: auto; margin-left: auto; } a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active { - border-color: #2980b9; + border-color: #000; } .thumbnail .caption { padding: 9px; @@ -4491,15 +4627,7 @@ a.thumbnail.active { } @-webkit-keyframes progress-bar-stripes { from { - background-position: 10px 0; - } - to { - background-position: 0 0; - } -} -@-moz-keyframes progress-bar-stripes { - from { - background-position: 10px 0; + background-position: 40px 0; } to { background-position: 0 0; @@ -4507,22 +4635,22 @@ a.thumbnail.active { } @-o-keyframes progress-bar-stripes { from { - background-position: 0 0; + background-position: 40px 0; } to { - background-position: 10px 0; + background-position: 0 0; } } @keyframes progress-bar-stripes { from { - background-position: 10px 0; + background-position: 40px 0; } to { background-position: 0 0; } } .progress { - height: 10px; + height: 20px; margin-bottom: 20px; overflow: hidden; background-color: #f5f5f5; @@ -4535,29 +4663,46 @@ a.thumbnail.active { width: 0; height: 100%; font-size: 12px; - line-height: 10px; + line-height: 20px; color: #fff; text-align: center; background-color: #2980b9; -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); -webkit-transition: width .6s ease; + -o-transition: width .6s ease; transition: width .6s ease; } .progress-striped .progress-bar { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-size: 10px 10px; + -webkit-background-size: 40px 40px; + background-size: 40px 40px; } .progress.active .progress-bar { -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; animation: progress-bar-stripes 2s linear infinite; } +.progress-bar[aria-valuenow="1"], +.progress-bar[aria-valuenow="2"] { + min-width: 30px; +} +.progress-bar[aria-valuenow="0"] { + min-width: 30px; + color: #999; + background-color: transparent; + background-image: none; + -webkit-box-shadow: none; + box-shadow: none; +} .progress-bar-success { background-color: #5cb85c; } .progress-striped .progress-bar-success { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .progress-bar-info { @@ -4565,6 +4710,7 @@ a.thumbnail.active { } .progress-striped .progress-bar-info { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .progress-bar-warning { @@ -4572,6 +4718,7 @@ a.thumbnail.active { } .progress-striped .progress-bar-warning { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .progress-bar-danger { @@ -4579,6 +4726,7 @@ a.thumbnail.active { } .progress-striped .progress-bar-danger { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .media, @@ -4644,25 +4792,42 @@ a.list-group-item .list-group-item-heading { } a.list-group-item:hover, a.list-group-item:focus { + color: #555; text-decoration: none; background-color: #f5f5f5; } -a.list-group-item.active, -a.list-group-item.active:hover, -a.list-group-item.active:focus { +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #999; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #999; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { z-index: 2; color: #fff; background-color: #2980b9; border-color: #2980b9; } -a.list-group-item.active .list-group-item-heading, -a.list-group-item.active:hover .list-group-item-heading, -a.list-group-item.active:focus .list-group-item-heading { +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading { color: inherit; } -a.list-group-item.active .list-group-item-text, -a.list-group-item.active:hover .list-group-item-text, -a.list-group-item.active:focus .list-group-item-text { +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { color: #bedcf0; } .list-group-item-success { @@ -4772,6 +4937,31 @@ a.list-group-item-danger.active:focus { .panel-body { padding: 15px; } +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} .panel > .list-group { margin-bottom: 0; } @@ -4779,17 +4969,13 @@ a.list-group-item-danger.active:focus { border-width: 1px 0; border-radius: 0; } -.panel > .list-group .list-group-item:first-child { - border-top: 0; -} -.panel > .list-group .list-group-item:last-child { - border-bottom: 0; -} .panel > .list-group:first-child .list-group-item:first-child { + border-top: 0; border-top-left-radius: 3px; border-top-right-radius: 3px; } .panel > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } @@ -4800,6 +4986,11 @@ a.list-group-item-danger.active:focus { .panel > .table-responsive > .table { margin-bottom: 0; } +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} .panel > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, @@ -4820,6 +5011,11 @@ a.list-group-item-danger.active:focus { .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { border-top-right-radius: 3px; } +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} .panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, @@ -4880,69 +5076,35 @@ a.list-group-item-danger.active:focus { .panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { border-right: 0; } -.panel > .table-bordered > thead > tr:first-child > th, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, -.panel > .table-bordered > tbody > tr:first-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th, -.panel > .table-bordered > tfoot > tr:first-child > th, -.panel > .table-responsive > .table-bordered > tfoot > tr:first-child > th, .panel > .table-bordered > thead > tr:first-child > td, .panel > .table-responsive > .table-bordered > thead > tr:first-child > td, .panel > .table-bordered > tbody > tr:first-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, -.panel > .table-bordered > tfoot > tr:first-child > td, -.panel > .table-responsive > .table-bordered > tfoot > tr:first-child > td { - border-top: 0; +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; } -.panel > .table-bordered > thead > tr:last-child > th, -.panel > .table-responsive > .table-bordered > thead > tr:last-child > th, -.panel > .table-bordered > tbody > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, -.panel > .table-bordered > tfoot > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th, -.panel > .table-bordered > thead > tr:last-child > td, -.panel > .table-responsive > .table-bordered > thead > tr:last-child > td, .panel > .table-bordered > tbody > tr:last-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, .panel > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td { +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { border-bottom: 0; } .panel > .table-responsive { margin-bottom: 0; border: 0; } -.panel-heading { - padding: 10px 15px; - border-bottom: 1px solid transparent; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel-heading > .dropdown .dropdown-toggle { - color: inherit; -} -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; - color: inherit; -} -.panel-title > a { - color: inherit; -} -.panel-footer { - padding: 10px 15px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} .panel-group { margin-bottom: 20px; } .panel-group .panel { margin-bottom: 0; - overflow: hidden; border-radius: 4px; } .panel-group .panel + .panel { @@ -4968,10 +5130,10 @@ a.list-group-item-danger.active:focus { background-color: #f5f5f5; border-color: #ddd; } -.panel-default > .panel-heading + .panel-collapse .panel-body { +.panel-default > .panel-heading + .panel-collapse > .panel-body { border-top-color: #ddd; } -.panel-default > .panel-footer + .panel-collapse .panel-body { +.panel-default > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #ddd; } .panel-primary { @@ -4982,10 +5144,10 @@ a.list-group-item-danger.active:focus { background-color: #2980b9; border-color: #2980b9; } -.panel-primary > .panel-heading + .panel-collapse .panel-body { +.panel-primary > .panel-heading + .panel-collapse > .panel-body { border-top-color: #2980b9; } -.panel-primary > .panel-footer + .panel-collapse .panel-body { +.panel-primary > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #2980b9; } .panel-success { @@ -4996,10 +5158,10 @@ a.list-group-item-danger.active:focus { background-color: #dff0d8; border-color: #d6e9c6; } -.panel-success > .panel-heading + .panel-collapse .panel-body { +.panel-success > .panel-heading + .panel-collapse > .panel-body { border-top-color: #d6e9c6; } -.panel-success > .panel-footer + .panel-collapse .panel-body { +.panel-success > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #d6e9c6; } .panel-info { @@ -5010,10 +5172,10 @@ a.list-group-item-danger.active:focus { background-color: #d9edf7; border-color: #bce8f1; } -.panel-info > .panel-heading + .panel-collapse .panel-body { +.panel-info > .panel-heading + .panel-collapse > .panel-body { border-top-color: #bce8f1; } -.panel-info > .panel-footer + .panel-collapse .panel-body { +.panel-info > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #bce8f1; } .panel-warning { @@ -5024,10 +5186,10 @@ a.list-group-item-danger.active:focus { background-color: #fcf8e3; border-color: #faebcc; } -.panel-warning > .panel-heading + .panel-collapse .panel-body { +.panel-warning > .panel-heading + .panel-collapse > .panel-body { border-top-color: #faebcc; } -.panel-warning > .panel-footer + .panel-collapse .panel-body { +.panel-warning > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #faebcc; } .panel-danger { @@ -5038,12 +5200,37 @@ a.list-group-item-danger.active:focus { background-color: #f2dede; border-color: #ebccd1; } -.panel-danger > .panel-heading + .panel-collapse .panel-body { +.panel-danger > .panel-heading + .panel-collapse > .panel-body { border-top-color: #ebccd1; } -.panel-danger > .panel-footer + .panel-collapse .panel-body { +.panel-danger > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #ebccd1; } +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive.embed-responsive-4by3 { + padding-bottom: 75%; +} .well { min-height: 20px; padding: 19px; @@ -5109,16 +5296,17 @@ button.close { } .modal.fade .modal-dialog { -webkit-transition: -webkit-transform .3s ease-out; - -moz-transition: -moz-transform .3s ease-out; -o-transition: -o-transform .3s ease-out; transition: transform .3s ease-out; -webkit-transform: translate(0, -25%); -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); transform: translate(0, -25%); } .modal.in .modal-dialog { -webkit-transform: translate(0, 0); -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); transform: translate(0, 0); } .modal-dialog { @@ -5129,11 +5317,12 @@ button.close { .modal-content { position: relative; background-color: #fff; - background-clip: padding-box; + -webkit-background-clip: padding-box; + background-clip: padding-box; border: 1px solid #999; border: 1px solid rgba(0, 0, 0, .2); border-radius: 6px; - outline: none; + outline: 0; -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); box-shadow: 0 3px 9px rgba(0, 0, 0, .5); } @@ -5168,11 +5357,10 @@ button.close { } .modal-body { position: relative; - padding: 20px; + padding: 15px; } .modal-footer { - padding: 19px 20px 20px; - margin-top: 15px; + padding: 15px; text-align: right; border-top: 1px solid #e5e5e5; } @@ -5186,6 +5374,13 @@ button.close { .modal-footer .btn-block + .btn-block { margin-left: 0; } +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} @media (min-width: 768px) { .modal-dialog { width: 600px; @@ -5198,13 +5393,15 @@ button.close { .modal-sm { width: 300px; } +} +@media (min-width: 992px) { .modal-lg { width: 900px; } } .tooltip { position: absolute; - z-index: 1030; + z-index: 1070; display: block; font-size: 12px; line-height: 1.4; @@ -5304,14 +5501,15 @@ button.close { position: absolute; top: 0; left: 0; - z-index: 1010; + z-index: 1060; display: none; max-width: 276px; padding: 1px; text-align: left; white-space: normal; background-color: #fff; - background-clip: padding-box; + -webkit-background-clip: padding-box; + background-clip: padding-box; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, .2); border-radius: 6px; @@ -5343,8 +5541,8 @@ button.close { .popover-content { padding: 9px 14px; } -.popover .arrow, -.popover .arrow:after { +.popover > .arrow, +.popover > .arrow:after { position: absolute; display: block; width: 0; @@ -5352,14 +5550,14 @@ button.close { border-color: transparent; border-style: solid; } -.popover .arrow { +.popover > .arrow { border-width: 11px; } -.popover .arrow:after { +.popover > .arrow:after { content: ""; border-width: 10px; } -.popover.top .arrow { +.popover.top > .arrow { bottom: -11px; left: 50%; margin-left: -11px; @@ -5367,14 +5565,14 @@ button.close { border-top-color: rgba(0, 0, 0, .25); border-bottom-width: 0; } -.popover.top .arrow:after { +.popover.top > .arrow:after { bottom: 1px; margin-left: -10px; content: " "; border-top-color: #fff; border-bottom-width: 0; } -.popover.right .arrow { +.popover.right > .arrow { top: 50%; left: -11px; margin-top: -11px; @@ -5382,14 +5580,14 @@ button.close { border-right-color: rgba(0, 0, 0, .25); border-left-width: 0; } -.popover.right .arrow:after { +.popover.right > .arrow:after { bottom: -10px; left: 1px; content: " "; border-right-color: #fff; border-left-width: 0; } -.popover.bottom .arrow { +.popover.bottom > .arrow { top: -11px; left: 50%; margin-left: -11px; @@ -5397,14 +5595,14 @@ button.close { border-bottom-color: #999; border-bottom-color: rgba(0, 0, 0, .25); } -.popover.bottom .arrow:after { +.popover.bottom > .arrow:after { top: 1px; margin-left: -10px; content: " "; border-top-width: 0; border-bottom-color: #fff; } -.popover.left .arrow { +.popover.left > .arrow { top: 50%; right: -11px; margin-top: -11px; @@ -5412,7 +5610,7 @@ button.close { border-left-color: #999; border-left-color: rgba(0, 0, 0, .25); } -.popover.left .arrow:after { +.popover.left > .arrow:after { right: 1px; bottom: -10px; content: " "; @@ -5431,13 +5629,11 @@ button.close { position: relative; display: none; -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; transition: .6s ease-in-out left; } .carousel-inner > .item > img, .carousel-inner > .item > a > img { - display: block; - max-width: 100%; - height: auto; line-height: 1; } .carousel-inner > .active, @@ -5485,6 +5681,9 @@ button.close { } .carousel-control.left { background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, .5) 0%), color-stop(rgba(0, 0, 0, .0001) 100%)); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); background-repeat: repeat-x; @@ -5493,6 +5692,9 @@ button.close { right: 0; left: auto; background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, .0001) 0%), color-stop(rgba(0, 0, 0, .5) 100%)); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); background-repeat: repeat-x; @@ -5502,7 +5704,7 @@ button.close { color: #fff; text-decoration: none; filter: alpha(opacity=90); - outline: none; + outline: 0; opacity: .9; } .carousel-control .icon-prev, @@ -5517,17 +5719,18 @@ button.close { .carousel-control .icon-prev, .carousel-control .glyphicon-chevron-left { left: 50%; + margin-left: -10px; } .carousel-control .icon-next, .carousel-control .glyphicon-chevron-right { right: 50%; + margin-right: -10px; } .carousel-control .icon-prev, .carousel-control .icon-next { width: 20px; height: 20px; margin-top: -10px; - margin-left: -10px; font-family: serif; } .carousel-control .icon-prev:before { @@ -5581,16 +5784,23 @@ button.close { text-shadow: none; } @media screen and (min-width: 768px) { - .carousel-control .glyphicons-chevron-left, - .carousel-control .glyphicons-chevron-right, + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, .carousel-control .icon-prev, .carousel-control .icon-next { width: 30px; height: 30px; margin-top: -15px; - margin-left: -15px; font-size: 30px; } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -15px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -15px; + } .carousel-caption { right: 20%; left: 20%; @@ -5602,6 +5812,8 @@ button.close { } .clearfix:before, .clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, .container:before, .container:after, .container-fluid:before, @@ -5632,6 +5844,7 @@ button.close { content: " "; } .clearfix:after, +.dl-horizontal dd:after, .container:after, .container-fluid:after, .row:after, @@ -5685,9 +5898,23 @@ button.close { width: device-width; } .visible-xs, -tr.visible-xs, -th.visible-xs, -td.visible-xs { +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { display: none !important; } @media (max-width: 767px) { @@ -5705,11 +5932,20 @@ td.visible-xs { display: table-cell !important; } } -.visible-sm, -tr.visible-sm, -th.visible-sm, -td.visible-sm { - display: none !important; +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm { @@ -5726,11 +5962,20 @@ td.visible-sm { display: table-cell !important; } } -.visible-md, -tr.visible-md, -th.visible-md, -td.visible-md { - display: none !important; +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md { @@ -5747,11 +5992,20 @@ td.visible-md { display: table-cell !important; } } -.visible-lg, -tr.visible-lg, -th.visible-lg, -td.visible-lg { - display: none !important; +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } } @media (min-width: 1200px) { .visible-lg { @@ -5768,42 +6022,42 @@ td.visible-lg { display: table-cell !important; } } +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} @media (max-width: 767px) { - .hidden-xs, - tr.hidden-xs, - th.hidden-xs, - td.hidden-xs { + .hidden-xs { display: none !important; } } @media (min-width: 768px) and (max-width: 991px) { - .hidden-sm, - tr.hidden-sm, - th.hidden-sm, - td.hidden-sm { + .hidden-sm { display: none !important; } } @media (min-width: 992px) and (max-width: 1199px) { - .hidden-md, - tr.hidden-md, - th.hidden-md, - td.hidden-md { + .hidden-md { display: none !important; } } @media (min-width: 1200px) { - .hidden-lg, - tr.hidden-lg, - th.hidden-lg, - td.hidden-lg { + .hidden-lg { display: none !important; } } -.visible-print, -tr.visible-print, -th.visible-print, -td.visible-print { +.visible-print { display: none !important; } @media print { @@ -5821,11 +6075,32 @@ td.visible-print { display: table-cell !important; } } +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} @media print { - .hidden-print, - tr.hidden-print, - th.hidden-print, - td.hidden-print { + .hidden-print { display: none !important; } } @@ -5835,11 +6110,6 @@ td.visible-print { .list-group-item { padding: 8px 15px; } -.panel { - border: 1px solid transparent; - -webkit-box-shadow: none; - box-shadow: none; -} h3, h4 { font-weight: bold; @@ -5848,3 +6118,4 @@ ul.with-margin li, ol.with-margin li { margin: 7px auto; } +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 4668115949..93265bb994 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -1,3 +1,5 @@ +@import url(http://fonts.googleapis.com/css?family=Noto+Sans:400,700); + html { min-height: 100%; position: relative; @@ -13,12 +15,22 @@ a { cursor: pointer; } +.navbar-inverse { + background-color: #444; + border-bottom: 0px; +} + a.disabled, a.disabled:hover { color: #888; cursor: default; text-decoration: none; } +a.form-link { + font-weight: bold; + font-size: 102%; +} + .layout-main { padding-bottom: 10px; } @@ -28,7 +40,6 @@ a.disabled, a.disabled:hover { } .text-ellipsis { - display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -55,8 +66,20 @@ div#freeze { text-align: center; } -/* listing */ +.app-page { + border: 1px solid #c7c7c7; + border-radius: 4px; + margin-top: 15px; + padding: 0px; + overflow: hidden; +} + +.page-container .container { + max-width: 970px; +} + +/* listing */ .show_filters { padding-top: 15px; padding-bottom: 15px; @@ -64,8 +87,13 @@ div#freeze { border-bottom: 1px solid #c7c7c7; } +.set-filters .btn-group { + margin-bottom: 10px; + margin-right: 10px; +} + .list-row { - padding: 5px 15px; + padding: 5px 15px 10px; margin: 0px -15px; border-bottom: 1px solid #c7c7c7; } @@ -91,21 +119,23 @@ div#freeze { margin-top: 3px; } -.icon-in-circle { - color: #b7b7b7; - background-color: white; - border-radius:50%; - text-align: center; - width: 20px; - height: 20px; - font-size: 20px; - display: inline-block; -} - .form-layout { /*margin-top: -15px;*/ } +.form-print-wrapper { + margin: 0px -15px -15px -15px; + background-color: #ddd; + padding: 0px 15px 30px 15px; +} + +.form-page-header { + border-top: 1px solid #eee; + margin: 15px -15px -15px -15px; + padding: 10px 15px; + background-color: #f9f9f9; +} + .form-control { padding: 6px 8px; } @@ -136,22 +166,43 @@ div#freeze { /* list */ +.progress { + height: 10px; +} + .doclist-row { position: relative; padding-top: 5px; - padding-bottom: 3px; + padding-bottom: 6px; border-bottom: "1px solid #eee"; } -.doclist-row .col { - padding-left: 7px; - padding-right: 7px; +.doclist-row .progress { + margin-top: 12px; +} + +.filterable { + cursor: pointer; +} + +.doclist-row .label { + margin-right: 8px; } .list-timestamp { position: absolute; - right: 5px; + right: 15px; + bottom: 3px; + font-size: 70%; + color: #888; +} + +.list-doc-name { + position: absolute; bottom: 2px; +} + +.list-doc-name a { font-size: 70%; color: #888; } @@ -199,7 +250,7 @@ div#freeze { position: fixed; bottom: 8px; right: 8px; - z-index: 10; + z-index: 1050; } #alert-container .alert { @@ -225,6 +276,43 @@ div#freeze { } /* form */ +.comment-connector { + height: 30px; + margin-left: 70px; + border-left: 1px solid #d7d7d7; +} + +.comment-body { + border-left: 1px solid #d7d7d7; + padding: 5px 15px 15px 30px; +} + +.comment-body p { + margin-bottom: 5px; +} + +.comment-icon { + margin-right: -14px !important; + margin-left: 15px; + z-index: 1; +} + +.icon-timeline { + color: #fff; + height: 29px; + width: 29px; + padding: 8px 9px 7px 9px; + border-radius: 50%; + background-color: #d7d7d7; + text-align: center; + display: inline-block; + float: left; +} + +.comment { + margin-top: 0px; +} + .frappe-editor { cursor: text; } @@ -257,33 +345,56 @@ ul.linked-with-list li { } .grid-heading-row { - padding: 8px; - border-bottom: 1px solid #dddddd; + padding: 8px 15px; + border-bottom: 1px solid #c7c7c7; + background-color: #f9f9f9; + font-weight: bold; } .rows .grid-row .divider { padding-bottom: 5px; margin-bottom: 5px; - border-bottom: 1px solid #dddddd; + margin-top: 8px; + border-bottom: 1px solid #c7c7c7; } .rows .grid-row .data-row, .rows .grid-row .panel-heading { cursor: pointer; } +.data-row { + margin-left: -20px; +} + +.row-index { + text-align: right; +} + .grid-row .panel { background-color: #fffff8; } +.grid-row td { + vertical-align: top; +} + +.grid-row p { + margin-bottom: 5px; +} + /* form footer */ .form-footer { - padding-top: 15px; - padding-bottom: 15px; - color: #888; + padding-bottom: 30px; /*box-shadow: 0px -1px 6px rgba(0,0,0,0.3);*/ } +.form-footer h5 { + margin: 15px 0px; + font-weight: bold; +} + + .like-disabled-input { background-color: #f8f8f8; padding: 6px; @@ -352,6 +463,7 @@ ul.linked-with-list li { margin-bottom: 7px; } + /* hack */ .ui-datepicker { z-index: 9999999 !important; } .ui-autocomplete { @@ -364,9 +476,10 @@ ul.linked-with-list li { } .print-preview { - padding: 50px 20px; - margin: 0px -15px; - box-shadow: 1px 1px 5px rgba(0,0,0,0.5); + padding: 0px; + max-width: 8.3in; + margin: auto; + min-height: 11.69in; } .module-view-layout { diff --git a/frappe/public/css/hljs-github.css b/frappe/public/css/hljs-github.css new file mode 100644 index 0000000000..71967a3739 --- /dev/null +++ b/frappe/public/css/hljs-github.css @@ -0,0 +1,125 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; padding: 0.5em; + color: #333; + background: #f8f8f8 +} + +.hljs-comment, +.hljs-template_comment, +.diff .hljs-header, +.hljs-javadoc { + color: #998; + font-style: italic +} + +.hljs-keyword, +.css .rule .hljs-keyword, +.hljs-winutils, +.javascript .hljs-title, +.nginx .hljs-title, +.hljs-subst, +.hljs-request, +.hljs-status { + color: #333; + font-weight: bold +} + +.hljs-number, +.hljs-hexcolor, +.ruby .hljs-constant { + color: #099; +} + +.hljs-string, +.hljs-tag .hljs-value, +.hljs-phpdoc, +.tex .hljs-formula { + color: #d14 +} + +.hljs-title, +.hljs-id, +.coffeescript .hljs-params, +.scss .hljs-preprocessor { + color: #900; + font-weight: bold +} + +.javascript .hljs-title, +.lisp .hljs-title, +.clojure .hljs-title, +.hljs-subst { + font-weight: normal +} + +.hljs-class .hljs-title, +.haskell .hljs-type, +.vhdl .hljs-literal, +.tex .hljs-command { + color: #458; + font-weight: bold +} + +.hljs-tag, +.hljs-tag .hljs-title, +.hljs-rules .hljs-property, +.django .hljs-tag .hljs-keyword { + color: #000080; + font-weight: normal +} + +.hljs-attribute, +.hljs-variable, +.lisp .hljs-body { + color: #008080 +} + +.hljs-regexp { + color: #009926 +} + +.hljs-symbol, +.ruby .hljs-symbol .hljs-string, +.lisp .hljs-keyword, +.tex .hljs-special, +.hljs-prompt { + color: #990073 +} + +.hljs-built_in, +.lisp .hljs-title, +.clojure .hljs-built_in { + color: #0086b3 +} + +.hljs-preprocessor, +.hljs-pragma, +.hljs-pi, +.hljs-doctype, +.hljs-shebang, +.hljs-cdata { + color: #999; + font-weight: bold +} + +.hljs-deletion { + background: #fdd +} + +.hljs-addition { + background: #dfd +} + +.diff .hljs-change { + background: #0086b3 +} + +.hljs-chunk { + color: #aaa +} diff --git a/frappe/public/css/less/frappe.less b/frappe/public/css/less/frappe.less index 6c218e541b..0f3578357b 100644 --- a/frappe/public/css/less/frappe.less +++ b/frappe/public/css/less/frappe.less @@ -12,6 +12,9 @@ @alert-padding: 10px; @nav-link-padding: 8px 15px; +@link-color: #000; +@font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + .alert { border: 1px solid transparent; } @@ -20,12 +23,6 @@ padding: 8px 15px; } -.panel { - border: 1px solid transparent; - -webkit-box-shadow: none; - box-shadow: none; -} - h3, h4 { font-weight: bold; } @@ -33,4 +30,4 @@ h3, h4 { ul.with-margin li, ol.with-margin li { margin: 7px auto; -} \ No newline at end of file +} diff --git a/frappe/public/css/octicons/LICENSE.txt b/frappe/public/css/octicons/LICENSE.txt new file mode 100755 index 0000000000..259b43d14d --- /dev/null +++ b/frappe/public/css/octicons/LICENSE.txt @@ -0,0 +1,9 @@ +(c) 2012-2014 GitHub + +When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos) + +Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) +Applies to all font files + +Code License: MIT (http://choosealicense.com/licenses/mit/) +Applies to all other files diff --git a/frappe/public/css/octicons/README.md b/frappe/public/css/octicons/README.md new file mode 100755 index 0000000000..1007073350 --- /dev/null +++ b/frappe/public/css/octicons/README.md @@ -0,0 +1 @@ +If you intend to install Octicons locally, install `octicons-local.ttf`. It should appear as “github-octicons” in your font list. It is specially designed not to conflict with GitHub's web fonts. diff --git a/frappe/public/css/octicons/octicons-local.ttf b/frappe/public/css/octicons/octicons-local.ttf new file mode 100755 index 0000000000..62bcbb3ae0 Binary files /dev/null and b/frappe/public/css/octicons/octicons-local.ttf differ diff --git a/frappe/public/css/octicons/octicons.css b/frappe/public/css/octicons/octicons.css new file mode 100755 index 0000000000..dd05c96320 --- /dev/null +++ b/frappe/public/css/octicons/octicons.css @@ -0,0 +1,245 @@ +@font-face { + font-family: 'octicons'; + src: url('/assets/frappe/css/octicons/octicons.eot?#iefix') format('embedded-opentype'), + url('/assets/frappe/css/octicons/octicons.woff') format('woff'), + url('/assets/frappe/css/octicons/octicons.ttf') format('truetype'), + url('/assets/frappe/css/octicons/octicons.svg#octicons') format('svg'); + font-weight: normal; + font-style: normal; +} + +/* + +.octicon is optimized for 16px. +.mega-octicon is optimized for 32px but can be used larger. + +*/ +.octicon { + font: normal normal 16px octicons; + line-height: 1; + display: inline-block; + text-decoration: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { + font: normal normal 32px octicons; + line-height: 1; + display: inline-block; + text-decoration: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.octicon-alert:before { content: '\f02d'} /*  */ +.octicon-alignment-align:before { content: '\f08a'} /*  */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */ +.octicon-alignment-unalign:before { content: '\f08b'} /*  */ +.octicon-arrow-down:before { content: '\f03f'} /*  */ +.octicon-arrow-left:before { content: '\f040'} /*  */ +.octicon-arrow-right:before { content: '\f03e'} /*  */ +.octicon-arrow-small-down:before { content: '\f0a0'} /*  */ +.octicon-arrow-small-left:before { content: '\f0a1'} /*  */ +.octicon-arrow-small-right:before { content: '\f071'} /*  */ +.octicon-arrow-small-up:before { content: '\f09f'} /*  */ +.octicon-arrow-up:before { content: '\f03d'} /*  */ +.octicon-beer:before { content: '\f069'} /*  */ +.octicon-book:before { content: '\f007'} /*  */ +.octicon-bookmark:before { content: '\f07b'} /*  */ +.octicon-briefcase:before { content: '\f0d3'} /*  */ +.octicon-broadcast:before { content: '\f048'} /*  */ +.octicon-browser:before { content: '\f0c5'} /*  */ +.octicon-bug:before { content: '\f091'} /*  */ +.octicon-calendar:before { content: '\f068'} /*  */ +.octicon-check:before { content: '\f03a'} /*  */ +.octicon-checklist:before { content: '\f076'} /*  */ +.octicon-chevron-down:before { content: '\f0a3'} /*  */ +.octicon-chevron-left:before { content: '\f0a4'} /*  */ +.octicon-chevron-right:before { content: '\f078'} /*  */ +.octicon-chevron-up:before { content: '\f0a2'} /*  */ +.octicon-circle-slash:before { content: '\f084'} /*  */ +.octicon-circuit-board:before { content: '\f0d6'} /*  */ +.octicon-clippy:before { content: '\f035'} /*  */ +.octicon-clock:before { content: '\f046'} /*  */ +.octicon-cloud-download:before { content: '\f00b'} /*  */ +.octicon-cloud-upload:before { content: '\f00c'} /*  */ +.octicon-code:before { content: '\f05f'} /*  */ +.octicon-color-mode:before { content: '\f065'} /*  */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /*  */ +.octicon-comment-discussion:before { content: '\f04f'} /*  */ +.octicon-credit-card:before { content: '\f045'} /*  */ +.octicon-dash:before { content: '\f0ca'} /*  */ +.octicon-dashboard:before { content: '\f07d'} /*  */ +.octicon-database:before { content: '\f096'} /*  */ +.octicon-device-camera:before { content: '\f056'} /*  */ +.octicon-device-camera-video:before { content: '\f057'} /*  */ +.octicon-device-desktop:before { content: '\f27c'} /*  */ +.octicon-device-mobile:before { content: '\f038'} /*  */ +.octicon-diff:before { content: '\f04d'} /*  */ +.octicon-diff-added:before { content: '\f06b'} /*  */ +.octicon-diff-ignored:before { content: '\f099'} /*  */ +.octicon-diff-modified:before { content: '\f06d'} /*  */ +.octicon-diff-removed:before { content: '\f06c'} /*  */ +.octicon-diff-renamed:before { content: '\f06e'} /*  */ +.octicon-ellipsis:before { content: '\f09a'} /*  */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /*  */ +.octicon-file-binary:before { content: '\f094'} /*  */ +.octicon-file-code:before { content: '\f010'} /*  */ +.octicon-file-directory:before { content: '\f016'} /*  */ +.octicon-file-media:before { content: '\f012'} /*  */ +.octicon-file-pdf:before { content: '\f014'} /*  */ +.octicon-file-submodule:before { content: '\f017'} /*  */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */ +.octicon-file-symlink-file:before { content: '\f0b0'} /*  */ +.octicon-file-text:before { content: '\f011'} /*  */ +.octicon-file-zip:before { content: '\f013'} /*  */ +.octicon-flame:before { content: '\f0d2'} /*  */ +.octicon-fold:before { content: '\f0cc'} /*  */ +.octicon-gear:before { content: '\f02f'} /*  */ +.octicon-gift:before { content: '\f042'} /*  */ +.octicon-gist:before { content: '\f00e'} /*  */ +.octicon-gist-secret:before { content: '\f08c'} /*  */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /*  */ +.octicon-git-commit:before { content: '\f01f'} /*  */ +.octicon-git-compare:before { content: '\f0ac'} /*  */ +.octicon-git-merge:before { content: '\f023'} /*  */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /*  */ +.octicon-globe:before { content: '\f0b6'} /*  */ +.octicon-graph:before { content: '\f043'} /*  */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /*  */ +.octicon-home:before { content: '\f08d'} /*  */ +.octicon-horizontal-rule:before { content: '\f070'} /*  */ +.octicon-hourglass:before { content: '\f09e'} /*  */ +.octicon-hubot:before { content: '\f09d'} /*  */ +.octicon-inbox:before { content: '\f0cf'} /*  */ +.octicon-info:before { content: '\f059'} /*  */ +.octicon-issue-closed:before { content: '\f028'} /*  */ +.octicon-issue-opened:before { content: '\f026'} /*  */ +.octicon-issue-reopened:before { content: '\f027'} /*  */ +.octicon-jersey:before { content: '\f019'} /*  */ +.octicon-jump-down:before { content: '\f072'} /*  */ +.octicon-jump-left:before { content: '\f0a5'} /*  */ +.octicon-jump-right:before { content: '\f0a6'} /*  */ +.octicon-jump-up:before { content: '\f073'} /*  */ +.octicon-key:before { content: '\f049'} /*  */ +.octicon-keyboard:before { content: '\f00d'} /*  */ +.octicon-law:before { content: '\f0d8'} /* */ +.octicon-light-bulb:before { content: '\f000'} /*  */ +.octicon-link:before { content: '\f05c'} /*  */ +.octicon-link-external:before { content: '\f07f'} /*  */ +.octicon-list-ordered:before { content: '\f062'} /*  */ +.octicon-list-unordered:before { content: '\f061'} /*  */ +.octicon-location:before { content: '\f060'} /*  */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /*  */ +.octicon-logo-github:before { content: '\f092'} /*  */ +.octicon-mail:before { content: '\f03b'} /*  */ +.octicon-mail-read:before { content: '\f03c'} /*  */ +.octicon-mail-reply:before { content: '\f051'} /*  */ +.octicon-mark-github:before { content: '\f00a'} /*  */ +.octicon-markdown:before { content: '\f0c9'} /*  */ +.octicon-megaphone:before { content: '\f077'} /*  */ +.octicon-mention:before { content: '\f0be'} /*  */ +.octicon-microscope:before { content: '\f089'} /*  */ +.octicon-milestone:before { content: '\f075'} /*  */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /*  */ +.octicon-mortar-board:before { content: '\f0d7'} /* */ +.octicon-move-down:before { content: '\f0a8'} /*  */ +.octicon-move-left:before { content: '\f074'} /*  */ +.octicon-move-right:before { content: '\f0a9'} /*  */ +.octicon-move-up:before { content: '\f0a7'} /*  */ +.octicon-mute:before { content: '\f080'} /*  */ +.octicon-no-newline:before { content: '\f09c'} /*  */ +.octicon-octoface:before { content: '\f008'} /*  */ +.octicon-organization:before { content: '\f037'} /*  */ +.octicon-package:before { content: '\f0c4'} /*  */ +.octicon-paintcan:before { content: '\f0d1'} /*  */ +.octicon-pencil:before { content: '\f058'} /*  */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /*  */ +.octicon-pin:before { content: '\f041'} /*  */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */ +.octicon-playback-pause:before { content: '\f0bb'} /*  */ +.octicon-playback-play:before { content: '\f0bf'} /*  */ +.octicon-playback-rewind:before { content: '\f0bc'} /*  */ +.octicon-plug:before { content: '\f0d4'} /*  */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /*  */ +.octicon-podium:before { content: '\f0af'} /*  */ +.octicon-primitive-dot:before { content: '\f052'} /*  */ +.octicon-primitive-square:before { content: '\f053'} /*  */ +.octicon-pulse:before { content: '\f085'} /*  */ +.octicon-puzzle:before { content: '\f0c0'} /*  */ +.octicon-question:before { content: '\f02c'} /*  */ +.octicon-quote:before { content: '\f063'} /*  */ +.octicon-radio-tower:before { content: '\f030'} /*  */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /*  */ +.octicon-repo-clone:before { content: '\f04c'} /*  */ +.octicon-repo-force-push:before { content: '\f04a'} /*  */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /*  */ +.octicon-repo-pull:before { content: '\f006'} /*  */ +.octicon-repo-push:before { content: '\f005'} /*  */ +.octicon-rocket:before { content: '\f033'} /*  */ +.octicon-rss:before { content: '\f034'} /*  */ +.octicon-ruby:before { content: '\f047'} /*  */ +.octicon-screen-full:before { content: '\f066'} /*  */ +.octicon-screen-normal:before { content: '\f067'} /*  */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /*  */ +.octicon-server:before { content: '\f097'} /*  */ +.octicon-settings:before { content: '\f07c'} /*  */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /*  */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /*  */ +.octicon-split:before { content: '\f0c6'} /*  */ +.octicon-squirrel:before { content: '\f0b2'} /*  */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /*  */ +.octicon-steps:before { content: '\f0c7'} /*  */ +.octicon-stop:before { content: '\f08f'} /*  */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /*  */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /*  */ +.octicon-telescope:before { content: '\f088'} /*  */ +.octicon-terminal:before { content: '\f0c8'} /*  */ +.octicon-three-bars:before { content: '\f05e'} /*  */ +.octicon-tools:before { content: '\f031'} /*  */ +.octicon-trashcan:before { content: '\f0d0'} /*  */ +.octicon-triangle-down:before { content: '\f05b'} /*  */ +.octicon-triangle-left:before { content: '\f044'} /*  */ +.octicon-triangle-right:before { content: '\f05a'} /*  */ +.octicon-triangle-up:before { content: '\f0aa'} /*  */ +.octicon-unfold:before { content: '\f039'} /*  */ +.octicon-unmute:before { content: '\f0ba'} /*  */ +.octicon-versions:before { content: '\f064'} /*  */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /*  */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/frappe/public/css/octicons/octicons.eot b/frappe/public/css/octicons/octicons.eot new file mode 100755 index 0000000000..a3aef43c6e Binary files /dev/null and b/frappe/public/css/octicons/octicons.eot differ diff --git a/frappe/public/css/octicons/octicons.less b/frappe/public/css/octicons/octicons.less new file mode 100755 index 0000000000..78ef46ead8 --- /dev/null +++ b/frappe/public/css/octicons/octicons.less @@ -0,0 +1,244 @@ +@octicons-font-path: "."; +@octicons-version: "897b19cdb9c4473f9166329e039ba8337c77d561"; + +@font-face { + font-family: 'octicons'; + src: ~"url('@{octicons-font-path}/octicons.eot?#iefix&v=@{octicons-version}') format('embedded-opentype')", + ~"url('@{octicons-font-path}/octicons.woff?v=@{octicons-version}') format('woff')", + ~"url('@{octicons-font-path}/octicons.ttf?v=@{octicons-version}') format('truetype')", + ~"url('@{octicons-font-path}/octicons.svg?v=@{octicons-version}#octicons') format('svg')"; + font-weight: normal; + font-style: normal; +} + +// .octicon is optimized for 16px. +// .mega-octicon is optimized for 32px but can be used larger. +.octicon { + font: normal normal 16px octicons; + line-height: 1; + display: inline-block; + text-decoration: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { + font: normal normal 32px octicons; + line-height: 1; + display: inline-block; + text-decoration: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.octicon-alert:before { content: '\f02d'} /*  */ +.octicon-alignment-align:before { content: '\f08a'} /*  */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */ +.octicon-alignment-unalign:before { content: '\f08b'} /*  */ +.octicon-arrow-down:before { content: '\f03f'} /*  */ +.octicon-arrow-left:before { content: '\f040'} /*  */ +.octicon-arrow-right:before { content: '\f03e'} /*  */ +.octicon-arrow-small-down:before { content: '\f0a0'} /*  */ +.octicon-arrow-small-left:before { content: '\f0a1'} /*  */ +.octicon-arrow-small-right:before { content: '\f071'} /*  */ +.octicon-arrow-small-up:before { content: '\f09f'} /*  */ +.octicon-arrow-up:before { content: '\f03d'} /*  */ +.octicon-beer:before { content: '\f069'} /*  */ +.octicon-book:before { content: '\f007'} /*  */ +.octicon-bookmark:before { content: '\f07b'} /*  */ +.octicon-briefcase:before { content: '\f0d3'} /*  */ +.octicon-broadcast:before { content: '\f048'} /*  */ +.octicon-browser:before { content: '\f0c5'} /*  */ +.octicon-bug:before { content: '\f091'} /*  */ +.octicon-calendar:before { content: '\f068'} /*  */ +.octicon-check:before { content: '\f03a'} /*  */ +.octicon-checklist:before { content: '\f076'} /*  */ +.octicon-chevron-down:before { content: '\f0a3'} /*  */ +.octicon-chevron-left:before { content: '\f0a4'} /*  */ +.octicon-chevron-right:before { content: '\f078'} /*  */ +.octicon-chevron-up:before { content: '\f0a2'} /*  */ +.octicon-circle-slash:before { content: '\f084'} /*  */ +.octicon-circuit-board:before { content: '\f0d6'} /*  */ +.octicon-clippy:before { content: '\f035'} /*  */ +.octicon-clock:before { content: '\f046'} /*  */ +.octicon-cloud-download:before { content: '\f00b'} /*  */ +.octicon-cloud-upload:before { content: '\f00c'} /*  */ +.octicon-code:before { content: '\f05f'} /*  */ +.octicon-color-mode:before { content: '\f065'} /*  */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /*  */ +.octicon-comment-discussion:before { content: '\f04f'} /*  */ +.octicon-credit-card:before { content: '\f045'} /*  */ +.octicon-dash:before { content: '\f0ca'} /*  */ +.octicon-dashboard:before { content: '\f07d'} /*  */ +.octicon-database:before { content: '\f096'} /*  */ +.octicon-device-camera:before { content: '\f056'} /*  */ +.octicon-device-camera-video:before { content: '\f057'} /*  */ +.octicon-device-desktop:before { content: '\f27c'} /*  */ +.octicon-device-mobile:before { content: '\f038'} /*  */ +.octicon-diff:before { content: '\f04d'} /*  */ +.octicon-diff-added:before { content: '\f06b'} /*  */ +.octicon-diff-ignored:before { content: '\f099'} /*  */ +.octicon-diff-modified:before { content: '\f06d'} /*  */ +.octicon-diff-removed:before { content: '\f06c'} /*  */ +.octicon-diff-renamed:before { content: '\f06e'} /*  */ +.octicon-ellipsis:before { content: '\f09a'} /*  */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /*  */ +.octicon-file-binary:before { content: '\f094'} /*  */ +.octicon-file-code:before { content: '\f010'} /*  */ +.octicon-file-directory:before { content: '\f016'} /*  */ +.octicon-file-media:before { content: '\f012'} /*  */ +.octicon-file-pdf:before { content: '\f014'} /*  */ +.octicon-file-submodule:before { content: '\f017'} /*  */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */ +.octicon-file-symlink-file:before { content: '\f0b0'} /*  */ +.octicon-file-text:before { content: '\f011'} /*  */ +.octicon-file-zip:before { content: '\f013'} /*  */ +.octicon-flame:before { content: '\f0d2'} /*  */ +.octicon-fold:before { content: '\f0cc'} /*  */ +.octicon-gear:before { content: '\f02f'} /*  */ +.octicon-gift:before { content: '\f042'} /*  */ +.octicon-gist:before { content: '\f00e'} /*  */ +.octicon-gist-secret:before { content: '\f08c'} /*  */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /*  */ +.octicon-git-commit:before { content: '\f01f'} /*  */ +.octicon-git-compare:before { content: '\f0ac'} /*  */ +.octicon-git-merge:before { content: '\f023'} /*  */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /*  */ +.octicon-globe:before { content: '\f0b6'} /*  */ +.octicon-graph:before { content: '\f043'} /*  */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /*  */ +.octicon-home:before { content: '\f08d'} /*  */ +.octicon-horizontal-rule:before { content: '\f070'} /*  */ +.octicon-hourglass:before { content: '\f09e'} /*  */ +.octicon-hubot:before { content: '\f09d'} /*  */ +.octicon-inbox:before { content: '\f0cf'} /*  */ +.octicon-info:before { content: '\f059'} /*  */ +.octicon-issue-closed:before { content: '\f028'} /*  */ +.octicon-issue-opened:before { content: '\f026'} /*  */ +.octicon-issue-reopened:before { content: '\f027'} /*  */ +.octicon-jersey:before { content: '\f019'} /*  */ +.octicon-jump-down:before { content: '\f072'} /*  */ +.octicon-jump-left:before { content: '\f0a5'} /*  */ +.octicon-jump-right:before { content: '\f0a6'} /*  */ +.octicon-jump-up:before { content: '\f073'} /*  */ +.octicon-key:before { content: '\f049'} /*  */ +.octicon-keyboard:before { content: '\f00d'} /*  */ +.octicon-law:before { content: '\f0d8'} /* */ +.octicon-light-bulb:before { content: '\f000'} /*  */ +.octicon-link:before { content: '\f05c'} /*  */ +.octicon-link-external:before { content: '\f07f'} /*  */ +.octicon-list-ordered:before { content: '\f062'} /*  */ +.octicon-list-unordered:before { content: '\f061'} /*  */ +.octicon-location:before { content: '\f060'} /*  */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /*  */ +.octicon-logo-github:before { content: '\f092'} /*  */ +.octicon-mail:before { content: '\f03b'} /*  */ +.octicon-mail-read:before { content: '\f03c'} /*  */ +.octicon-mail-reply:before { content: '\f051'} /*  */ +.octicon-mark-github:before { content: '\f00a'} /*  */ +.octicon-markdown:before { content: '\f0c9'} /*  */ +.octicon-megaphone:before { content: '\f077'} /*  */ +.octicon-mention:before { content: '\f0be'} /*  */ +.octicon-microscope:before { content: '\f089'} /*  */ +.octicon-milestone:before { content: '\f075'} /*  */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /*  */ +.octicon-mortar-board:before { content: '\f0d7'} /* */ +.octicon-move-down:before { content: '\f0a8'} /*  */ +.octicon-move-left:before { content: '\f074'} /*  */ +.octicon-move-right:before { content: '\f0a9'} /*  */ +.octicon-move-up:before { content: '\f0a7'} /*  */ +.octicon-mute:before { content: '\f080'} /*  */ +.octicon-no-newline:before { content: '\f09c'} /*  */ +.octicon-octoface:before { content: '\f008'} /*  */ +.octicon-organization:before { content: '\f037'} /*  */ +.octicon-package:before { content: '\f0c4'} /*  */ +.octicon-paintcan:before { content: '\f0d1'} /*  */ +.octicon-pencil:before { content: '\f058'} /*  */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /*  */ +.octicon-pin:before { content: '\f041'} /*  */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */ +.octicon-playback-pause:before { content: '\f0bb'} /*  */ +.octicon-playback-play:before { content: '\f0bf'} /*  */ +.octicon-playback-rewind:before { content: '\f0bc'} /*  */ +.octicon-plug:before { content: '\f0d4'} /*  */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /*  */ +.octicon-podium:before { content: '\f0af'} /*  */ +.octicon-primitive-dot:before { content: '\f052'} /*  */ +.octicon-primitive-square:before { content: '\f053'} /*  */ +.octicon-pulse:before { content: '\f085'} /*  */ +.octicon-puzzle:before { content: '\f0c0'} /*  */ +.octicon-question:before { content: '\f02c'} /*  */ +.octicon-quote:before { content: '\f063'} /*  */ +.octicon-radio-tower:before { content: '\f030'} /*  */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /*  */ +.octicon-repo-clone:before { content: '\f04c'} /*  */ +.octicon-repo-force-push:before { content: '\f04a'} /*  */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /*  */ +.octicon-repo-pull:before { content: '\f006'} /*  */ +.octicon-repo-push:before { content: '\f005'} /*  */ +.octicon-rocket:before { content: '\f033'} /*  */ +.octicon-rss:before { content: '\f034'} /*  */ +.octicon-ruby:before { content: '\f047'} /*  */ +.octicon-screen-full:before { content: '\f066'} /*  */ +.octicon-screen-normal:before { content: '\f067'} /*  */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /*  */ +.octicon-server:before { content: '\f097'} /*  */ +.octicon-settings:before { content: '\f07c'} /*  */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /*  */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /*  */ +.octicon-split:before { content: '\f0c6'} /*  */ +.octicon-squirrel:before { content: '\f0b2'} /*  */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /*  */ +.octicon-steps:before { content: '\f0c7'} /*  */ +.octicon-stop:before { content: '\f08f'} /*  */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /*  */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /*  */ +.octicon-telescope:before { content: '\f088'} /*  */ +.octicon-terminal:before { content: '\f0c8'} /*  */ +.octicon-three-bars:before { content: '\f05e'} /*  */ +.octicon-tools:before { content: '\f031'} /*  */ +.octicon-trashcan:before { content: '\f0d0'} /*  */ +.octicon-triangle-down:before { content: '\f05b'} /*  */ +.octicon-triangle-left:before { content: '\f044'} /*  */ +.octicon-triangle-right:before { content: '\f05a'} /*  */ +.octicon-triangle-up:before { content: '\f0aa'} /*  */ +.octicon-unfold:before { content: '\f039'} /*  */ +.octicon-unmute:before { content: '\f0ba'} /*  */ +.octicon-versions:before { content: '\f064'} /*  */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /*  */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/frappe/public/css/octicons/octicons.svg b/frappe/public/css/octicons/octicons.svg new file mode 100755 index 0000000000..ea3e0f1615 --- /dev/null +++ b/frappe/public/css/octicons/octicons.svg @@ -0,0 +1,198 @@ + + + + +(c) 2012-2014 GitHub + +When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos) + +Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) +Applies to all font files + +Code License: MIT (http://choosealicense.com/licenses/mit/) +Applies to all other files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frappe/public/css/octicons/octicons.ttf b/frappe/public/css/octicons/octicons.ttf new file mode 100755 index 0000000000..7111762d8b Binary files /dev/null and b/frappe/public/css/octicons/octicons.ttf differ diff --git a/frappe/public/css/octicons/octicons.woff b/frappe/public/css/octicons/octicons.woff new file mode 100755 index 0000000000..a3989c84a3 Binary files /dev/null and b/frappe/public/css/octicons/octicons.woff differ diff --git a/frappe/public/css/octicons/sprockets-octicons.scss b/frappe/public/css/octicons/sprockets-octicons.scss new file mode 100755 index 0000000000..f2c1664ede --- /dev/null +++ b/frappe/public/css/octicons/sprockets-octicons.scss @@ -0,0 +1,241 @@ +@font-face { + font-family: 'octicons'; + src: font-url('octicons.eot?#iefix') format('embedded-opentype'), + font-url('octicons.woff') format('woff'), + font-url('octicons.ttf') format('truetype'), + font-url('octicons.svg#octicons') format('svg'); + font-weight: normal; + font-style: normal; +} + +// .octicon is optimized for 16px. +// .mega-octicon is optimized for 32px but can be used larger. +.octicon { + font: normal normal 16px octicons; + line-height: 1; + display: inline-block; + text-decoration: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { + font: normal normal 32px octicons; + line-height: 1; + display: inline-block; + text-decoration: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.octicon-alert:before { content: '\f02d'} /*  */ +.octicon-alignment-align:before { content: '\f08a'} /*  */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */ +.octicon-alignment-unalign:before { content: '\f08b'} /*  */ +.octicon-arrow-down:before { content: '\f03f'} /*  */ +.octicon-arrow-left:before { content: '\f040'} /*  */ +.octicon-arrow-right:before { content: '\f03e'} /*  */ +.octicon-arrow-small-down:before { content: '\f0a0'} /*  */ +.octicon-arrow-small-left:before { content: '\f0a1'} /*  */ +.octicon-arrow-small-right:before { content: '\f071'} /*  */ +.octicon-arrow-small-up:before { content: '\f09f'} /*  */ +.octicon-arrow-up:before { content: '\f03d'} /*  */ +.octicon-beer:before { content: '\f069'} /*  */ +.octicon-book:before { content: '\f007'} /*  */ +.octicon-bookmark:before { content: '\f07b'} /*  */ +.octicon-briefcase:before { content: '\f0d3'} /*  */ +.octicon-broadcast:before { content: '\f048'} /*  */ +.octicon-browser:before { content: '\f0c5'} /*  */ +.octicon-bug:before { content: '\f091'} /*  */ +.octicon-calendar:before { content: '\f068'} /*  */ +.octicon-check:before { content: '\f03a'} /*  */ +.octicon-checklist:before { content: '\f076'} /*  */ +.octicon-chevron-down:before { content: '\f0a3'} /*  */ +.octicon-chevron-left:before { content: '\f0a4'} /*  */ +.octicon-chevron-right:before { content: '\f078'} /*  */ +.octicon-chevron-up:before { content: '\f0a2'} /*  */ +.octicon-circle-slash:before { content: '\f084'} /*  */ +.octicon-circuit-board:before { content: '\f0d6'} /*  */ +.octicon-clippy:before { content: '\f035'} /*  */ +.octicon-clock:before { content: '\f046'} /*  */ +.octicon-cloud-download:before { content: '\f00b'} /*  */ +.octicon-cloud-upload:before { content: '\f00c'} /*  */ +.octicon-code:before { content: '\f05f'} /*  */ +.octicon-color-mode:before { content: '\f065'} /*  */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /*  */ +.octicon-comment-discussion:before { content: '\f04f'} /*  */ +.octicon-credit-card:before { content: '\f045'} /*  */ +.octicon-dash:before { content: '\f0ca'} /*  */ +.octicon-dashboard:before { content: '\f07d'} /*  */ +.octicon-database:before { content: '\f096'} /*  */ +.octicon-device-camera:before { content: '\f056'} /*  */ +.octicon-device-camera-video:before { content: '\f057'} /*  */ +.octicon-device-desktop:before { content: '\f27c'} /*  */ +.octicon-device-mobile:before { content: '\f038'} /*  */ +.octicon-diff:before { content: '\f04d'} /*  */ +.octicon-diff-added:before { content: '\f06b'} /*  */ +.octicon-diff-ignored:before { content: '\f099'} /*  */ +.octicon-diff-modified:before { content: '\f06d'} /*  */ +.octicon-diff-removed:before { content: '\f06c'} /*  */ +.octicon-diff-renamed:before { content: '\f06e'} /*  */ +.octicon-ellipsis:before { content: '\f09a'} /*  */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /*  */ +.octicon-file-binary:before { content: '\f094'} /*  */ +.octicon-file-code:before { content: '\f010'} /*  */ +.octicon-file-directory:before { content: '\f016'} /*  */ +.octicon-file-media:before { content: '\f012'} /*  */ +.octicon-file-pdf:before { content: '\f014'} /*  */ +.octicon-file-submodule:before { content: '\f017'} /*  */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */ +.octicon-file-symlink-file:before { content: '\f0b0'} /*  */ +.octicon-file-text:before { content: '\f011'} /*  */ +.octicon-file-zip:before { content: '\f013'} /*  */ +.octicon-flame:before { content: '\f0d2'} /*  */ +.octicon-fold:before { content: '\f0cc'} /*  */ +.octicon-gear:before { content: '\f02f'} /*  */ +.octicon-gift:before { content: '\f042'} /*  */ +.octicon-gist:before { content: '\f00e'} /*  */ +.octicon-gist-secret:before { content: '\f08c'} /*  */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /*  */ +.octicon-git-commit:before { content: '\f01f'} /*  */ +.octicon-git-compare:before { content: '\f0ac'} /*  */ +.octicon-git-merge:before { content: '\f023'} /*  */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /*  */ +.octicon-globe:before { content: '\f0b6'} /*  */ +.octicon-graph:before { content: '\f043'} /*  */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /*  */ +.octicon-home:before { content: '\f08d'} /*  */ +.octicon-horizontal-rule:before { content: '\f070'} /*  */ +.octicon-hourglass:before { content: '\f09e'} /*  */ +.octicon-hubot:before { content: '\f09d'} /*  */ +.octicon-inbox:before { content: '\f0cf'} /*  */ +.octicon-info:before { content: '\f059'} /*  */ +.octicon-issue-closed:before { content: '\f028'} /*  */ +.octicon-issue-opened:before { content: '\f026'} /*  */ +.octicon-issue-reopened:before { content: '\f027'} /*  */ +.octicon-jersey:before { content: '\f019'} /*  */ +.octicon-jump-down:before { content: '\f072'} /*  */ +.octicon-jump-left:before { content: '\f0a5'} /*  */ +.octicon-jump-right:before { content: '\f0a6'} /*  */ +.octicon-jump-up:before { content: '\f073'} /*  */ +.octicon-key:before { content: '\f049'} /*  */ +.octicon-keyboard:before { content: '\f00d'} /*  */ +.octicon-law:before { content: '\f0d8'} /* */ +.octicon-light-bulb:before { content: '\f000'} /*  */ +.octicon-link:before { content: '\f05c'} /*  */ +.octicon-link-external:before { content: '\f07f'} /*  */ +.octicon-list-ordered:before { content: '\f062'} /*  */ +.octicon-list-unordered:before { content: '\f061'} /*  */ +.octicon-location:before { content: '\f060'} /*  */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /*  */ +.octicon-logo-github:before { content: '\f092'} /*  */ +.octicon-mail:before { content: '\f03b'} /*  */ +.octicon-mail-read:before { content: '\f03c'} /*  */ +.octicon-mail-reply:before { content: '\f051'} /*  */ +.octicon-mark-github:before { content: '\f00a'} /*  */ +.octicon-markdown:before { content: '\f0c9'} /*  */ +.octicon-megaphone:before { content: '\f077'} /*  */ +.octicon-mention:before { content: '\f0be'} /*  */ +.octicon-microscope:before { content: '\f089'} /*  */ +.octicon-milestone:before { content: '\f075'} /*  */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /*  */ +.octicon-mortar-board:before { content: '\f0d7'} /* */ +.octicon-move-down:before { content: '\f0a8'} /*  */ +.octicon-move-left:before { content: '\f074'} /*  */ +.octicon-move-right:before { content: '\f0a9'} /*  */ +.octicon-move-up:before { content: '\f0a7'} /*  */ +.octicon-mute:before { content: '\f080'} /*  */ +.octicon-no-newline:before { content: '\f09c'} /*  */ +.octicon-octoface:before { content: '\f008'} /*  */ +.octicon-organization:before { content: '\f037'} /*  */ +.octicon-package:before { content: '\f0c4'} /*  */ +.octicon-paintcan:before { content: '\f0d1'} /*  */ +.octicon-pencil:before { content: '\f058'} /*  */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /*  */ +.octicon-pin:before { content: '\f041'} /*  */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */ +.octicon-playback-pause:before { content: '\f0bb'} /*  */ +.octicon-playback-play:before { content: '\f0bf'} /*  */ +.octicon-playback-rewind:before { content: '\f0bc'} /*  */ +.octicon-plug:before { content: '\f0d4'} /*  */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /*  */ +.octicon-podium:before { content: '\f0af'} /*  */ +.octicon-primitive-dot:before { content: '\f052'} /*  */ +.octicon-primitive-square:before { content: '\f053'} /*  */ +.octicon-pulse:before { content: '\f085'} /*  */ +.octicon-puzzle:before { content: '\f0c0'} /*  */ +.octicon-question:before { content: '\f02c'} /*  */ +.octicon-quote:before { content: '\f063'} /*  */ +.octicon-radio-tower:before { content: '\f030'} /*  */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /*  */ +.octicon-repo-clone:before { content: '\f04c'} /*  */ +.octicon-repo-force-push:before { content: '\f04a'} /*  */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /*  */ +.octicon-repo-pull:before { content: '\f006'} /*  */ +.octicon-repo-push:before { content: '\f005'} /*  */ +.octicon-rocket:before { content: '\f033'} /*  */ +.octicon-rss:before { content: '\f034'} /*  */ +.octicon-ruby:before { content: '\f047'} /*  */ +.octicon-screen-full:before { content: '\f066'} /*  */ +.octicon-screen-normal:before { content: '\f067'} /*  */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /*  */ +.octicon-server:before { content: '\f097'} /*  */ +.octicon-settings:before { content: '\f07c'} /*  */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /*  */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /*  */ +.octicon-split:before { content: '\f0c6'} /*  */ +.octicon-squirrel:before { content: '\f0b2'} /*  */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /*  */ +.octicon-steps:before { content: '\f0c7'} /*  */ +.octicon-stop:before { content: '\f08f'} /*  */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /*  */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /*  */ +.octicon-telescope:before { content: '\f088'} /*  */ +.octicon-terminal:before { content: '\f0c8'} /*  */ +.octicon-three-bars:before { content: '\f05e'} /*  */ +.octicon-tools:before { content: '\f031'} /*  */ +.octicon-trashcan:before { content: '\f0d0'} /*  */ +.octicon-triangle-down:before { content: '\f05b'} /*  */ +.octicon-triangle-left:before { content: '\f044'} /*  */ +.octicon-triangle-right:before { content: '\f05a'} /*  */ +.octicon-triangle-up:before { content: '\f0aa'} /*  */ +.octicon-unfold:before { content: '\f039'} /*  */ +.octicon-unmute:before { content: '\f0ba'} /*  */ +.octicon-versions:before { content: '\f064'} /*  */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /*  */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/frappe/public/css/tree_grid.css b/frappe/public/css/tree_grid.css index c91e17584b..5ce94263cf 100644 --- a/frappe/public/css/tree_grid.css +++ b/frappe/public/css/tree_grid.css @@ -13,9 +13,9 @@ } .toggle.expand { - background: url("../lib/images/icons/expand.gif") no-repeat center center; + background: url("../frappe/js/lib/slickgrid/images/expand.gif") no-repeat center center; } .toggle.collapse { - background: url("../lib/images/icons/collapse.gif") no-repeat center center; -} \ No newline at end of file + background: url("../frappe/js/lib/slickgrid/images/collapse.gif") no-repeat center center; +} diff --git a/frappe/public/html/list_info_template.html b/frappe/public/html/list_info_template.html new file mode 100644 index 0000000000..61ffdcc143 --- /dev/null +++ b/frappe/public/html/list_info_template.html @@ -0,0 +1,17 @@ +{% if (tags.length) { %} + + {%= tags.join(", ") %} +{% } %} +{% if (comments.length) { %} + + {%= comments.length %} + +{% } %} +{% if (assign.length) { %} + {% for (var i=0, l=assign.length; i + {%= frappe.avatar(assign[i], "avatar-xs") %} + {% }%} +{% } %} +{%= comment_when(data.modified) %} diff --git a/frappe/public/html/print_template.html b/frappe/public/html/print_template.html new file mode 100644 index 0000000000..740261397a --- /dev/null +++ b/frappe/public/html/print_template.html @@ -0,0 +1,22 @@ + + + + + + + + + {%= title %} + + + + +

+ + diff --git a/frappe/public/images/help/print-style-classic.png b/frappe/public/images/help/print-style-classic.png new file mode 100644 index 0000000000..e1817aad28 Binary files /dev/null and b/frappe/public/images/help/print-style-classic.png differ diff --git a/frappe/public/images/help/print-style-modern.png b/frappe/public/images/help/print-style-modern.png new file mode 100644 index 0000000000..57917de063 Binary files /dev/null and b/frappe/public/images/help/print-style-modern.png differ diff --git a/frappe/public/images/help/print-style-monochrome.png b/frappe/public/images/help/print-style-monochrome.png new file mode 100644 index 0000000000..ed0a301deb Binary files /dev/null and b/frappe/public/images/help/print-style-monochrome.png differ diff --git a/frappe/public/images/help/print-style-standard.png b/frappe/public/images/help/print-style-standard.png new file mode 100644 index 0000000000..db1c37044e Binary files /dev/null and b/frappe/public/images/help/print-style-standard.png differ diff --git a/frappe/public/images/help/style-settings-help.png b/frappe/public/images/help/style-settings-help.png new file mode 100644 index 0000000000..cc49373b59 Binary files /dev/null and b/frappe/public/images/help/style-settings-help.png differ diff --git a/frappe/public/js/frappe/defaults.js b/frappe/public/js/frappe/defaults.js index cdaeca429e..886b2c2bca 100644 --- a/frappe/public/js/frappe/defaults.js +++ b/frappe/public/js/frappe/defaults.js @@ -46,11 +46,11 @@ frappe.defaults = { } } }, - get_restrictions: function() { - return frappe.defaults.restrictions; + get_user_permissions: function() { + return frappe.defaults.user_permissions; }, - set_restrictions: function(restrictions) { - if(!restrictions) return; - frappe.defaults.restrictions = $.extend(frappe.defaults.restrictions || {}, restrictions); + set_user_permissions: function(user_permissions) { + if(!user_permissions) return; + frappe.defaults.user_permissions = $.extend(frappe.defaults.user_permissions || {}, user_permissions); } } \ No newline at end of file diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 7d2ed46f0e..21e7510964 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -1,14 +1,6 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt -if(!console) { - var console = { - log: function(txt) { - // suppress - } - } -} - $(document).ready(function() { frappe.assets.check(); frappe.provide('frappe.app'); @@ -19,7 +11,7 @@ frappe.Application = Class.extend({ init: function() { this.load_startup(); }, - + load_startup: function() { var me = this; if(window.app) { @@ -37,56 +29,70 @@ frappe.Application = Class.extend({ }); } else { this.startup(); - } + } }, startup: function() { // load boot info this.load_bootinfo(); - // page container - this.make_page_container(); - + if(user!="Guest") this.set_user_display_settings(); + // navbar this.make_nav_bar(); - + // favicon this.set_favicon(); - - if(user!="Guest") this.set_user_display_settings(); - + this.setup_keyboard_shortcuts(); - + // control panel startup code this.run_startup_js(); + if(frappe.boot) { - // route to home page - frappe.route(); + if(localStorage.getItem("session_lost_route")) { + window.location.hash = localStorage.getItem("session_lost_route"); + localStorage.removeItem("session_lost_route"); + } + } - + + // page container + this.make_page_container(); + + // route to home page + frappe.route(); + // trigger app startup $(document).trigger('startup'); this.start_notification_updates(); - + $(document).trigger('app_ready'); }, - + set_user_display_settings: function() { - frappe.ui.set_user_background(frappe.boot.user.background_image); + frappe.ui.set_user_background(frappe.boot.user.background_image, null, + frappe.boot.user.background_style); }, - + load_bootinfo: function() { if(frappe.boot) { frappe.modules = frappe.boot.modules; this.check_metadata_cache_status(); this.set_globals(); this.sync_pages(); + if(frappe.boot.timezone_info) { + moment.tz.add(frappe.boot.timezone_info); + } + if(frappe.boot.print_css) { + frappe.dom.set_style(frappe.boot.print_css) + } } else { this.set_as_guest(); } }, - + check_metadata_cache_status: function() { if(frappe.boot.metadata_version != localStorage.metadata_version) { localStorage.clear(); @@ -94,22 +100,22 @@ frappe.Application = Class.extend({ frappe.assets.init_local_storage(); } }, - + start_notification_updates: function() { var me = this; setInterval(function() { me.refresh_notifications(); }, 30000); - + // first time loaded in boot $(document).trigger("notification-update"); - + // refresh notifications if user is back after sometime $(document).on("session_alive", function() { me.refresh_notifications(); }) }, - + refresh_notifications: function() { if(frappe.session_alive) { return frappe.call({ @@ -124,7 +130,7 @@ frappe.Application = Class.extend({ }); } }, - + set_globals: function() { // for backward compatibility user = frappe.boot.user.name; @@ -132,15 +138,15 @@ frappe.Application = Class.extend({ user_defaults = frappe.boot.user.defaults; user_roles = frappe.boot.user.roles; user_email = frappe.boot.user.email; - sys_defaults = frappe.boot.sysdefaults; + sys_defaults = frappe.boot.sysdefaults; }, sync_pages: function() { // clear cached pages if timestamp is not found if(localStorage["page_info"]) { frappe.boot.allowed_pages = []; page_info = JSON.parse(localStorage["page_info"]); - $.each(frappe.boot.page_info, function(name, modified) { - if(page_info[name]!=modified) { + $.each(frappe.boot.page_info, function(name, p) { + if(!page_info[name] || (page_info[name].modified != p.modified)) { delete localStorage["_page:" + name]; } frappe.boot.allowed_pages.push(name); @@ -171,7 +177,7 @@ frappe.Application = Class.extend({ make_nav_bar: function() { // toolbar if(frappe.boot) { - frappe.container.frappe_toolbar = new frappe.ui.toolbar.Toolbar(); + frappe.frappe_toolbar = new frappe.ui.toolbar.Toolbar(); } }, logout: function() { @@ -189,14 +195,14 @@ frappe.Application = Class.extend({ }) }, redirect_to_login: function() { - window.location.href = 'index.html'; + window.location.href = '/'; }, set_favicon: function() { var link = $('link[type="image/x-icon"]').remove().attr("href"); $('').appendTo("head"); $('').appendTo("head"); }, - + setup_keyboard_shortcuts: function() { $(document) .keydown("meta+g ctrl+g", function(e) { @@ -246,9 +252,9 @@ frappe.Application = Class.extend({ }) }, - + run_startup_js: function() { if(frappe.boot.startup_js) eval(frappe.boot.startup_js); } -}) \ No newline at end of file +}) diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index 180fabf93e..cdaa62defc 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -116,6 +116,10 @@ frappe.get_modal = function(title, body_html) { \ \ + \ \ \ ').appendTo(document.body); @@ -170,52 +174,6 @@ frappe.get_shade = function(color, factor) { + get_hex(get_int(color.substr(4,2)) + factor) } -frappe.get_gradient_css = function(col, diff) { - if(!diff) diff = 10 - var col1 = frappe.get_shade(col, diff); - var col2 = frappe.get_shade(col, -diff); - return "\nbackground-color: " + col + " !important;" - +"\nbackground: -moz-linear-gradient(top, #"+col1+" 0%, #"+col2+" 99%) !important;" - +"\nbackground:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#"+col1+"), color-stop(99%,#"+col2+")) !important;" - +"\nbackground:-webkit-linear-gradient(top, #"+col1+" 0%,#"+col2+" 99%) !important;" - +"\nbackground:-o-linear-gradient(top, #"+col1+" 0%,#"+col2+" 99%) !important;" - +"\nbackground:-ms-linear-gradient(top, #"+col1+" 0%,#"+col2+" 99%) !important;" - +"\nbackground:-o-linear-gradient(top, #"+col1+" 0%,#"+col2+" 99%) !important;" - +"\nbackground:linear-gradient(top, #"+col1+" 0%,#%"+col2+" 99%) !important;" - +"\nfilter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#"+col1+"', endColorstr='#"+col1+"',GradientType=0 ) !important;" -} - -$.fn.gradientify = function(col) { - if(!col) col = this.css("background-color"); - var col1 = frappe.get_shade(col, 1.05); - var col2 = frappe.get_shade(col, 0.95); - - this.css({ - "background": "-moz-linear-gradient(top, #"+col1+" 0%, #"+col2+" 99%)" - }); - this.css({ - "background": "-webkit-gradient(linear, left top, left bottom, color-stop(0%,#"+col1+"), color-stop(99%,#"+col2+"))" - }); - this.css({ - "background": "-webkit-linear-gradient(top, #"+col1+" 0%,#"+col2+" 99%)" - }); - this.css({ - "background": "-o-linear-gradient(top, #"+col1+" 0%,#"+col2+" 99%);" - }); - this.css({ - "background": "-ms-linear-gradient(top, #"+col1+" 0%,#"+col2+" 99%);" - }); - this.css({ - "background": "-o-linear-gradient(top, #"+col1+" 0%,#"+col2+" 99%);" - }); - this.css({ - "background": "linear-gradient(top, #"+col1+" 0%,#%"+col2+" 99%);" - }); - this.css({ - "filter": "progid:DXImageTransform.Microsoft.gradient( startColorstr='#"+col1+"', endColorstr='#"+col1+"',GradientType=0 )" - }); -} - frappe.get_cookie = function(c) { var clist = (document.cookie+'').split(';'); var cookies = {}; @@ -239,30 +197,31 @@ frappe.dom.set_box_shadow = function(ele, spread) { // create options for(var i=0; i').html(label).attr('value', value).appendTo(this); + if (is_null(v)) { + var value = null; + var label = null; + } else { + var is_value_null = is_null(v.value); + var is_label_null = is_null(v.label); + + if (is_value_null && is_label_null) { + var value = v; + var label = __(v); + } else { + var value = is_value_null ? "" : v.value; + var label = is_label_null ? __(value) : __(v.label); + } + } + $('
- - - - {% for tdf in table_meta.fields %} - {% if tdf.fieldtype not in ("Column Break", "Section Break") - and tdf.label %} - - {% endif %} - {% endfor %} - - - - {% for d in doc[df.fieldname] %} - - - {% for tdf in table_meta.fields %} - {% if tdf.fieldtype not in ("Column Break", "Section Break") %} - - {% endif %} - {% endfor %} - - {% endfor %} - -
Sr - {{ tdf.label }}
{{ d.idx }}{{ print_value(tdf, d, table_meta) }}
-

- {% else %} -
-
- {% if df.fieldtype not in ("Image","HTML") %} - - {% endif %} -
-
- {{ print_value(df, doc, meta) }} -
-
- {% endif %} - {% endif %} -{% endfor %} -{% endif %} -{% endblock %} diff --git a/frappe/templates/pages/view.py b/frappe/templates/pages/view.py deleted file mode 100644 index 7e72a8895c..0000000000 --- a/frappe/templates/pages/view.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -no_cache = 1 -no_sitemap = 1 - -def get_context(context): - doc = frappe.get_doc(frappe.local.form_dict.doctype, frappe.local.form_dict.name) - doc.run_method("make_view") - return { - "doc": doc, - "meta": doc.meta - } diff --git a/frappe/templates/print_formats/standard.html b/frappe/templates/print_formats/standard.html index e69de29bb2..c4c97adae9 100644 --- a/frappe/templates/print_formats/standard.html +++ b/frappe/templates/print_formats/standard.html @@ -0,0 +1,26 @@ +{%- from "templates/print_formats/standard_macros.html" import add_header, + render_field -%} + +{% for page in layout %} +
+ {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead) }} + {% for section in page %} +
+ {% for column in section %} +
+ {% for df in column %} + {{ render_field(df, doc) }} + {% endfor %} +
+ {% endfor %} +
+ {% endfor %} +
+{% endfor %} + +{%- if trigger_print -%} + +{%- endif -%} diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html new file mode 100644 index 0000000000..da644fe655 --- /dev/null +++ b/frappe/templates/print_formats/standard_macros.html @@ -0,0 +1,121 @@ +{% macro render_field(df, doc) -%} + {%- if df.fieldtype=="Table" -%} + {{ render_table(df, doc) }} + {%- elif df.fieldtype=="HTML" -%} +
{{ df.options or "" }}
+ {%- elif df.fieldtype in ("Text", "Text Editor", "Code") -%} + {{ render_text_field(df, doc) }} + {%- else -%} + {{ render_field_with_label(df, doc) }} + {%- endif -%} +{%- endmacro -%} + +{%- macro render_table(df, doc) -%} + {%- set table_meta = frappe.get_meta(df.options) -%} + {%- set data = doc.get(df.fieldname)[df.start:df.end] -%} + {%- if doc.table_print_templates and + doc.table_print_templates.get(df.fieldname) -%} + {% include doc.table_print_templates[df.fieldname] %} + {%- else -%} + {%- if data -%} + {%- set visible_columns = get_visible_columns(doc.get(df.fieldname), + table_meta) -%} +
+ + + + + {% for tdf in visible_columns %} + + {% endfor %} + + + + {% for d in data %} + + + {% for tdf in visible_columns %} + + {% endfor %} + + {% endfor %} + +
Sr + {{ _(tdf.label) }}
{{ d.idx }}{{ print_value(tdf, d, doc) }}
+
+ {%- endif -%} + {%- endif -%} +{%- endmacro -%} + +{%- macro render_field_with_label(df, doc) -%} +
+
+ {% if df.fieldtype not in ("Image","HTML") and + doc.get(df.fieldname) != None %} + + {% endif %} +
+
+ {% if doc.get(df.fieldname) != None -%} + {{ print_value(df, doc) }}{% endif %} +
+
+{%- endmacro -%} + +{%- macro render_text_field(df, doc) -%} +{%- if doc.get(df.fieldname) != None -%} +
+ {%- if df.fieldtype in ("Text", "Code") %}{%- endif %} + {%- if df.fieldtype=="Code" %} +
{{ doc.get(df.fieldname) }}
+ {% else -%} + {{ doc.get_formatted(df.fieldname, parent_doc or doc) }} + {% endif -%} +
+{%- endif -%} +{%- endmacro -%} + +{%- macro print_value(df, doc, parent_doc=None) -%} + {% if df.fieldtype=="Check" %} + + {% elif df.fieldtype=="Image" %} + + {% else %} + {{ doc.get_formatted(df.fieldname, parent_doc or doc) }} + {% endif %} +{%- endmacro %} + +{% macro get_width(df) -%} + {%- if df.print_width -%}{{ (df.print_width|str).replace("px", "") }} + {%- elif df.fieldtype in ("Int", "Check", "Float", "Currency") -%}{{ 80 }} + {%- else -%}{{ 150 }}{% endif -%} +{%- endmacro %} + +{% macro get_align_class(fieldtype) %} + {%- if fieldtype in ("Int", "Check", "Float", "Currency") -%}{{ "text-right" }} + {%- else -%}{{ "" }} + {%- endif -%} +{% endmacro %} + +{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead) -%} + {% if letter_head and not no_letterhead %} +
{{ letter_head }}
+ {% endif %} + + {%- if doc.meta.is_submittable and doc.docstatus==0-%} +
+

{{ _("DRAFT") }}

+ {%- endif -%} + {%- if doc.meta.is_submittable and doc.docstatus==2-%} +
+

{{ _("CANCELLED") }}

+ {%- endif -%} + {% if max_pages > 1 %} +

{{ _("Page #{0} of {1}").format(page_num, max_pages) }}

+ {% endif %} +{%- endmacro -%} diff --git a/frappe/templates/styles/classic.css b/frappe/templates/styles/classic.css new file mode 100644 index 0000000000..b35e1044fd --- /dev/null +++ b/frappe/templates/styles/classic.css @@ -0,0 +1,16 @@ +/* + common style for whole page + This should include: + + page size related settings + + font family settings + + line spacing settings +*/ +.print-format div, +.print-format span, +.print-format td, +.print-format h1, +.print-format h2, +.print-format h3, +.print-format h4 { + font-family: serif; +} diff --git a/frappe/templates/styles/modern.css b/frappe/templates/styles/modern.css new file mode 100644 index 0000000000..b5c73df971 --- /dev/null +++ b/frappe/templates/styles/modern.css @@ -0,0 +1,17 @@ +.print-heading { + text-align: right; + text-transform: uppercase; + font-color: #888; + font-weight: bold; + padding-bottom: 7px; + margin-bottom: 15px; + border-bottom: 3px solid #aaa; +} + +table.table-bordered { + border-top: 3px solid #aaa; +} + +th { + background-color: #eee; +} diff --git a/frappe/templates/styles/monochrome.css b/frappe/templates/styles/monochrome.css new file mode 100644 index 0000000000..0e68c34e29 --- /dev/null +++ b/frappe/templates/styles/monochrome.css @@ -0,0 +1,26 @@ +.print-format * { + color: #000 !important; +} + +.print-format .alert { + background-color: inherit; + border: 1px dashed #333; +} + +.print-format .table-bordered, +.print-format .table-bordered > thead > tr > th, +.print-format .table-bordered > tbody > tr > th, +.print-format .table-bordered > tfoot > tr > th, +.print-format .table-bordered > thead > tr > td, +.print-format .table-bordered > tbody > tr > td, +.print-format .table-bordered > tfoot > tr > td { + border: 1px solid #333; +} + +.print-format hr { + border-top: 1px solid #333; +} + +.print-heading { + border-bottom: 2px solid #333; +} diff --git a/frappe/templates/styles/standard.css b/frappe/templates/styles/standard.css new file mode 100644 index 0000000000..0ba413b3f0 --- /dev/null +++ b/frappe/templates/styles/standard.css @@ -0,0 +1,88 @@ +@import url(https://fonts.googleapis.com/css?family=Noto+Sans:400,700); + +@media screen { + .print-format-gutter { + background-color: #ddd; + padding: 15px 0px; + } + .print-format { + background-color: white; + box-shadow: 0px 0px 9px rgba(0,0,0,0.5); + max-width: 8.3in; + min-height: 11.69in; + padding: 0.75in; + margin: auto; + } + + .page-break { + padding: 30px 0px; + border-bottom: 1px dashed #888; + } + + .page-break:first-child { + padding-top: 0px; + } + + .page-break:last-child { + border-bottom: 0px; + } +} + +@media print { + .print-format p { + margin-left: 1px; + margin-right: 1px; + } +} + +.print-format { + font-size: 9pt; + font-family: 'Noto Sans', sans-serif; + -webkit-print-color-adjust:exact; +} + +.page-break { + page-break-after: always; +} + +.print-heading { + border-bottom: 2px solid #aaa; + margin-bottom: 10px; +} + +.print-heading h2 { + margin: 0px; +} +.print-heading h4 { + margin-top: 5px; +} + +table.no-border, table.no-border td { + border: 0px; +} + +.print-format label { + /* wkhtmltopdf breaks label into multiple lines when it is inline-block */ + display: block; +} + +.print-format img { + max-width: 100%; +} + +.print-format table td > .primary:first-child { + font-weight: bold; +} + +.print-format td, .print-format th { + vertical-align: top !important; + padding: 6px !important; +} + +.print-format p { + margin: 3px 0px 3px; +} + +.print-format { + font-size: {{ print_settings.font_size|flt or 9 }}pt; +} diff --git a/frappe/templates/website_group/forum.py b/frappe/templates/website_group/forum.py index 70fae3ee97..b4f3c1f17f 100644 --- a/frappe/templates/website_group/forum.py +++ b/frappe/templates/website_group/forum.py @@ -8,58 +8,57 @@ from frappe.website.permissions import get_access @frappe.whitelist(allow_guest=True) def get_post_list_html(group, view, limit_start=0, limit_length=20): - from frappe.templates.generators.website_group import get_views - + from frappe.website.doctype.website_group.website_group import get_views + # verify permission for paging if frappe.local.form_dict.cmd == "get_post_list_html": - pathname = frappe.db.get_value("Website Route", - {"ref_doctype": "Website Group", "docname": group}) - access = get_access(pathname) - + doc = frappe.get_doc("Website Group", group) + access = get_access(doc, doc.get_route()) + if not access.get("read"): return frappe.PermissionError - + conditions = "" values = [group] - + group_type = frappe.db.get_value("Website Group", group, "group_type") if group_type == "Events": # should show based on time upto precision of hour # because the current hour should also be in upcoming values.append(now_datetime().replace(minute=0, second=0, microsecond=0)) - + if view in ("feed", "closed"): order_by = "p.creation desc" - + if view == "closed": conditions += " and p.is_task=1 and p.status='Closed'" - + elif view in ("popular", "open"): now = get_datetime_str(now_datetime()) - order_by = """(p.upvotes + post_reply_count - (timestampdiff(hour, p.creation, \"{}\") / 2)) desc, + order_by = """(p.upvotes + post_reply_count - (timestampdiff(hour, p.creation, \"{}\") / 2)) desc, p.creation desc""".format(now) - + if view == "open": conditions += " and p.is_task=1 and p.status='Open'" - + elif view == "upcoming": conditions += " and p.is_event=1 and p.event_datetime >= %s" order_by = "p.event_datetime asc" - + elif view == "past": conditions += " and p.is_event=1 and p.event_datetime < %s" order_by = "p.event_datetime desc" - + values += [int(limit_start), int(limit_length)] - + posts = frappe.db.sql("""select p.*, pr.user_image, pr.first_name, pr.last_name, (select count(pc.name) from `tabPost` pc where pc.parent_post=p.name) as post_reply_count from `tabPost` p, `tabUser` pr - where p.website_group = %s and pr.name = p.owner and ifnull(p.parent_post, '')='' + where p.website_group = %s and pr.name = p.owner and ifnull(p.parent_post, '')='' {conditions} order by {order_by} limit %s, %s""".format(conditions=conditions, order_by=order_by), - tuple(values), as_dict=True, debug=True) - + tuple(values), as_dict=True) + context = { "posts": posts, "limit_start": limit_start, "view": get_views(group_type)[view] } - + return frappe.get_template("templates/includes/post_list.html").render(context) - + diff --git a/frappe/templates/website_group/post.py b/frappe/templates/website_group/post.py index 93fbe6221b..50e3577850 100644 --- a/frappe/templates/website_group/post.py +++ b/frappe/templates/website_group/post.py @@ -7,7 +7,6 @@ from frappe import _ from frappe.utils import get_fullname from frappe.website.permissions import get_access from frappe.utils.file_manager import save_file -from frappe.templates.generators.website_group import get_pathname def get_post_context(context): post = frappe.get_doc("Post", frappe.form_dict.name) @@ -58,7 +57,8 @@ def clear_post_cache(post=None): def add_post(group, content, picture, picture_name, title=None, parent_post=None, assigned_to=None, status=None, event_datetime=None): - access = get_access(get_pathname(group)) + doc = frappe.get_doc("Website Group", group) + access = get_access(doc, doc.get_route()) if not access.get("write"): raise frappe.PermissionError @@ -100,7 +100,8 @@ def save_post(post, content, picture=None, picture_name=None, title=None, assigned_to=None, status=None, event_datetime=None): post = frappe.get_doc("Post", post) - access = get_access(get_pathname(post.website_group)) + group = frappe.get_doc("Website Group", post.website_group) + access = get_access(group, group.get_route()) if not access.get("write"): raise frappe.PermissionError @@ -130,7 +131,7 @@ def save_post(post, content, picture=None, picture_name=None, title=None, return post.parent_post or post.name def process_picture(post, picture_name, picture): - from frappe.templates.generators.website_group import clear_cache + from frappe.website.doctype.website_group.website_group import clear_cache post.picture_url = save_file(picture_name, picture, "Post", post.name, decode=True).file_url frappe.db.set_value("Post", post.name, "picture_url", post.picture_url) diff --git a/frappe/templates/website_group/settings.py b/frappe/templates/website_group/settings.py index d7397f7be9..8c8a649fa0 100644 --- a/frappe/templates/website_group/settings.py +++ b/frappe/templates/website_group/settings.py @@ -3,26 +3,26 @@ from __future__ import unicode_literals import frappe -from frappe.website.permissions import get_access, clear_permissions -from frappe.templates.generators.website_group import get_pathname +from frappe.website.permissions import get_access from frappe.utils.email_lib.bulk import send @frappe.whitelist() def suggest_user(term, group): - pathname = get_pathname(group) - if not get_access(pathname).get("admin"): + doc = frappe.get_doc("Website Group", group) + pathname = doc.get_route() + if not get_access(doc, pathname).get("admin"): raise frappe.PermissionError - - users = frappe.db.sql("""select pr.name, pr.first_name, pr.last_name, + + users = frappe.db.sql("""select pr.name, pr.first_name, pr.last_name, pr.user_image, pr.location - from `tabUser` pr + from `tabUser` pr where (pr.first_name like %(term)s or pr.last_name like %(term)s) and pr.user_type = "Website User" and pr.user_image is not null and pr.enabled=1 - and not exists(select wsp.name from `tabWebsite Route Permission` wsp - where wsp.website_route=%(group)s and wsp.user=pr.name)""", + and not exists(select wsp.name from `tabWebsite Route Permission` wsp + where wsp.website_route=%(group)s and wsp.user=pr.name)""", {"term": "%{}%".format(term), "group": pathname}, as_dict=True) - + template = frappe.get_template("templates/includes/user_display.html") return [{ "value": "{} {}".format(pr.first_name or "", pr.last_name or ""), @@ -32,10 +32,11 @@ def suggest_user(term, group): @frappe.whitelist() def add_sitemap_permission(group, user): - pathname = get_pathname(group) - if not get_access(pathname).get("admin"): + doc = frappe.get_doc("Website Group", group) + pathname = doc.get_route() + if not get_access(doc, pathname).get("admin"): raise frappe.PermissionError - + permission = frappe.get_doc({ "doctype": "Website Route Permission", "website_route": pathname, @@ -43,32 +44,32 @@ def add_sitemap_permission(group, user): "read": 1 }) permission.insert(ignore_permissions=True) - + user = permission.as_dict() - user.update(frappe.db.get_value("User", user.user, + user.update(frappe.db.get_value("User", user.user, ["name", "first_name", "last_name", "user_image", "location"], as_dict=True)) - + return frappe.get_template("templates/includes/sitemap_permission.html").render({ "user": user }) @frappe.whitelist() def update_permission(group, user, perm, value): - pathname = get_pathname(group) - if not get_access(pathname).get("admin"): + doc = frappe.get_doc("Website Group", group) + pathname = doc.get_route() + if not get_access(doc, pathname).get("admin"): raise frappe.PermissionError - - permission = frappe.get_doc("Website Route Permission", {"website_route": pathname, "user": user}) + + permission = frappe.get_doc("Website Route Permission", + {"website_route": pathname, "user": user, "reference": group}) permission.set(perm, int(value)) permission.save(ignore_permissions=True) - + # send email if perm=="admin" and int(value): - group_title = frappe.db.get_value("Website Route", pathname, "page_title") - - subject = "You have been made Administrator of Group " + group_title - - send(recipients=[user], + subject = "You have been made Administrator of Group " + doc.group_title + + send(recipients=[user], subject= subject, add_unsubscribe_link=False, message="""

Group Notification

\

%s

\ @@ -76,27 +77,28 @@ def update_permission(group, user, perm, value): @frappe.whitelist() def update_description(group, description): - if not get_access(get_pathname(group)).get("admin"): + doc = frappe.get_doc("Website Group", group) + pathname = doc.get_route() + if not get_access(doc, pathname).get("admin"): raise frappe.PermissionError group = frappe.get_doc("Website Group", group) group.group_description = description group.save(ignore_permissions=True) - + @frappe.whitelist() def add_website_group(group, new_group, public_read, public_write, group_type="Forum"): - if not get_access(get_pathname(group)).get("admin"): + doc = frappe.get_doc("Website Group", group) + pathname = doc.get_route() + if not get_access(doc, pathname).get("admin"): raise frappe.PermissionError - - parent_website_route = frappe.db.get_value("Website Route", - {"ref_doctype": "Website Group", "docname": group}) - + frappe.get_doc({ "doctype": "Website Group", "group_name": group + "-" + new_group, "group_title": new_group, - "parent_website_route": parent_website_route, + "parent_website_group": group, "group_type": group_type, "public_read": int(public_read), "public_write": int(public_write) - }).insert(ignore_permissions=True) \ No newline at end of file + }).insert(ignore_permissions=True) diff --git a/frappe/test_runner.py b/frappe/test_runner.py index 78ff15dce9..7010258130 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -10,7 +10,7 @@ from frappe.modules import load_doctype_module, get_module_name from frappe.utils import cstr -def main(app=None, module=None, doctype=None, verbose=False, tests=()): +def main(app=None, module=None, doctype=None, verbose=False, tests=(), force=False): frappe.flags.print_messages = verbose frappe.flags.in_test = True @@ -29,7 +29,7 @@ def main(app=None, module=None, doctype=None, verbose=False, tests=()): frappe.get_attr(fn)() if doctype: - ret = run_tests_for_doctype(doctype, verbose=verbose, tests=tests) + ret = run_tests_for_doctype(doctype, verbose=verbose, tests=tests, force=force) elif module: ret = run_tests_for_module(module, verbose=verbose, tests=tests) else: @@ -63,10 +63,13 @@ def run_all_tests(app=None, verbose=False): return unittest.TextTestRunner(verbosity=1+(verbose and 1 or 0)).run(test_suite) -def run_tests_for_doctype(doctype, verbose=False, tests=()): +def run_tests_for_doctype(doctype, verbose=False, tests=(), force=False): module = frappe.db.get_value("DocType", doctype, "module") test_module = get_module_name(doctype, module, "test_") - make_test_records(doctype, verbose=verbose) + if force: + for name in frappe.db.sql_list("select name from `tab%s`" % doctype): + frappe.delete_doc(doctype, name, force=True) + make_test_records(doctype, verbose=verbose, force=force) module = frappe.get_module(test_module) return _run_unittest(module, verbose=verbose, tests=tests) @@ -94,6 +97,10 @@ def _run_unittest(module, verbose=False, tests=()): def _add_test(path, filename, verbose, test_suite=None): import os, imp + if os.path.sep.join(["doctype", "doctype", "boilerplate"]) in path: + # in /doctype/doctype/boilerplate/ + return + if not test_suite: test_suite = unittest.TestSuite() @@ -107,7 +114,7 @@ def _add_test(path, filename, verbose, test_suite=None): module = imp.load_source(filename[:-3], os.path.join(path, filename)) test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module)) -def make_test_records(doctype, verbose=0): +def make_test_records(doctype, verbose=0, force=False): frappe.flags.mute_emails = True if not frappe.db: @@ -119,8 +126,8 @@ def make_test_records(doctype, verbose=0): if options not in frappe.local.test_objects: frappe.local.test_objects[options] = [] - make_test_records(options, verbose) - make_test_records_for_doctype(options, verbose) + make_test_records(options, verbose, force) + make_test_records_for_doctype(options, verbose, force) def get_modules(doctype): module = frappe.db.get_value("DocType", doctype, "module") @@ -155,7 +162,7 @@ def get_dependencies(doctype): return options_list -def make_test_records_for_doctype(doctype, verbose=0): +def make_test_records_for_doctype(doctype, verbose=0, force=False): module, test_module = get_modules(doctype) if verbose: diff --git a/frappe/tests/test_assign.py b/frappe/tests/test_assign.py index 789e135668..eefdfc2bc9 100644 --- a/frappe/tests/test_assign.py +++ b/frappe/tests/test_assign.py @@ -16,7 +16,8 @@ class TestAssign(unittest.TestCase): "name": todo.name, "description": todo.description, }) - self.assertTrue("test@example.com" in added) - + + self.assertTrue("test@example.com" in [d.owner for d in added]) + removed = frappe.widgets.form.assign_to.remove(todo.doctype, todo.name, "test@example.com") - self.assertTrue("test@example.com" not in removed) + self.assertTrue("test@example.com" not in [d.owner for d in removed]) diff --git a/frappe/tests/test_data_import.py b/frappe/tests/test_data_import.py index b7befbafb9..70ef531e8e 100644 --- a/frappe/tests/test_data_import.py +++ b/frappe/tests/test_data_import.py @@ -4,7 +4,7 @@ import frappe, unittest from frappe.core.page.data_import_tool import exporter from frappe.core.page.data_import_tool import importer -from frappe.utils.datautils import read_csv_content +from frappe.utils.csvutils import read_csv_content class TestDataImport(unittest.TestCase): def test_export(self): @@ -46,13 +46,39 @@ class TestDataImport(unittest.TestCase): importer.upload(content, overwrite=True) self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "New Title") + def test_import_only_children(self): + user_email = "test_import_userrole@example.com" + if frappe.db.exists("User", user_email): + frappe.delete_doc("User", user_email) + + frappe.get_doc({"doctype": "User", "email": user_email, "first_name": "Test Import UserRole"}).insert() + + exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No") + content = read_csv_content(frappe.response.result) + content.append(["", "test_import_userrole@example.com", "Blogger"]) + importer.upload(content) + + user = frappe.get_doc("User", user_email) + self.assertEquals(len(user.get("user_roles")), 1) + self.assertTrue(user.get("user_roles")[0].role, "Blogger") + + # overwrite + exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No") + content = read_csv_content(frappe.response.result) + content.append(["", "test_import_userrole@example.com", "Website Manager"]) + importer.upload(content, overwrite=True) + + user = frappe.get_doc("User", user_email) + self.assertEquals(len(user.get("user_roles")), 1) + self.assertTrue(user.get("user_roles")[0].role, "Website Manager") + def test_import_with_children(self): exporter.get_template("Event", all_doctypes="Yes", with_data="No") content = read_csv_content(frappe.response.result) - content.append([""] * len(content[-2])) + content.append([None] * len(content[-2])) content[-1][2] = "__Test Event" content[-1][3] = "Private" - content[-1][3] = "2014-01-01 10:00:00.000000" + content[-1][4] = "2014-01-01 10:00:00.000000" content[-1][content[15].index("person")] = "Administrator" importer.upload(content) diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index 6f875728fe..a658848f0f 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -13,18 +13,18 @@ class TestEmail(unittest.TestCase): def setUp(self): frappe.db.sql("""update tabUser set unsubscribed=0""") frappe.db.sql("""delete from `tabBulk Email`""") - + def test_send(self): from frappe.utils.email_lib import sendmail #sendmail('test@example.com', subject='Test Mail', msg="Test Content") def test_bulk(self): from frappe.utils.email_lib.bulk import send - send(recipients = ['test@example.com', 'test1@example.com'], + send(recipients = ['test@example.com', 'test1@example.com'], sender="admin@example.com", doctype='User', email_field='email', subject='Testing Bulk', message='This is a bulk mail!') - + bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""", as_dict=1) self.assertEquals(len(bulk), 2) self.assertTrue('test@example.com' in [d['recipient'] for d in bulk]) @@ -39,29 +39,29 @@ class TestEmail(unittest.TestCase): self.assertEquals(len(bulk), 2) self.assertTrue('test@example.com' in [d['recipient'] for d in bulk]) self.assertTrue('test1@example.com' in [d['recipient'] for d in bulk]) - + def test_unsubscribe(self): from frappe.utils.email_lib.bulk import unsubscribe, send - frappe.local.form_dict = { + frappe.local.form_dict = frappe._dict({ 'email':'test@example.com', 'type':'User', 'email_field':'email', "from_test": True - } + }) unsubscribe() - send(recipients = ['test@example.com', 'test1@example.com'], + send(recipients = ['test@example.com', 'test1@example.com'], sender="admin@example.com", - doctype='User', email_field='email', + doctype='User', email_field='email', subject='Testing Bulk', message='This is a bulk mail!') - - bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""", + + bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""", as_dict=1) self.assertEquals(len(bulk), 1) self.assertFalse('test@example.com' in [d['recipient'] for d in bulk]) self.assertTrue('test1@example.com' in [d['recipient'] for d in bulk]) self.assertTrue('Unsubscribe' in bulk[0]['message']) - + def test_bulk_limit(self): from frappe.utils.email_lib.bulk import unsubscribe, send, BulkLimitCrossedError self.assertRaises(BulkLimitCrossedError, send, @@ -69,8 +69,8 @@ class TestEmail(unittest.TestCase): sender="admin@example.com", doctype='User', email_field='email', subject='Testing Bulk', message='This is a bulk mail!') - - + + if __name__=='__main__': frappe.connect() - unittest.main() \ No newline at end of file + unittest.main() diff --git a/frappe/translate.py b/frappe/translate.py index 373d6382cb..e3d7ca65f8 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -17,6 +17,8 @@ Contributing: # frappe._ import frappe, os, re, codecs, json +from frappe.utils.jinja import render_include +from jinja2 import TemplateError def guess_language_from_http_header(lang): """set frappe.local.lang from HTTP headers at beginning of request""" @@ -79,7 +81,7 @@ def get_all_languages(): return [a.split()[0] for a in get_lang_info()] def get_lang_dict(): - return dict([[a[1], a[0]] for a in [a.split() for a in get_lang_info()]]) + return dict([[a[1], a[0]] for a in [a.split(None, 1) for a in get_lang_info()]]) def get_lang_info(): return frappe.cache().get_value("langinfo", @@ -106,6 +108,7 @@ def get_dict(fortype, name=None): elif fortype=="boot": messages = get_messages_from_include_files() messages += frappe.db.sql_list("select name from tabDocType") + messages += frappe.db.sql_list("select name from tabRole") messages += frappe.db.sql_list("select name from `tabModule Def`") translation_assets[asset_key] = make_dict_from_messages(messages) @@ -193,6 +196,10 @@ def get_messages_from_doctype(name): messages = [meta.name, meta.module] + if meta.description: + messages.append(meta.description) + + # translations of field labels, description and options for d in meta.get("fields"): messages.extend([d.label, d.description]) @@ -202,9 +209,17 @@ def get_messages_from_doctype(name): if not "icon" in options[0]: messages.extend(options) + # translations of roles + for d in meta.get("permissions"): + if d.role: + messages.append(d.role) + # extract from js, py files doctype_file_path = frappe.get_module_path(meta.module, "doctype", meta.name, meta.name) messages.extend(get_messages_from_file(doctype_file_path + ".js")) + messages.extend(get_messages_from_file(doctype_file_path + "_list.js")) + messages.extend(get_messages_from_file(doctype_file_path + "_list.html")) + messages.extend(get_messages_from_file(doctype_file_path + "_calendar.js")) return clean(messages) def get_messages_from_page(name): @@ -224,6 +239,7 @@ def get_messages_from_page_or_report(doctype, name, module=None): module = frappe.db.get_value(doctype, name, "module") file_path = frappe.get_module_path(module, doctype, name, name) messages = get_messages_from_file(file_path + ".js") + messages += get_messages_from_file(file_path + ".html") return clean(messages) @@ -234,14 +250,14 @@ def get_server_messages(app): if dontwalk in folders: folders.remove(dontwalk) for f in files: - if f.endswith(".py") or f.endswith(".html"): + if f.endswith(".py") or f.endswith(".html") or f.endswith(".js"): messages.extend(get_messages_from_file(os.path.join(basepath, f))) return clean(messages) def get_messages_from_include_files(app_name=None): messages = [] - for file in (frappe.get_hooks("app_include_js") or []) + (frappe.get_hooks("web_include_js") or []): + for file in (frappe.get_hooks("app_include_js", app_name=app_name) or []) + (frappe.get_hooks("web_include_js", app_name=app_name) or []): messages.extend(get_messages_from_file(os.path.join(frappe.local.sites_path, file))) return clean(messages) @@ -255,6 +271,12 @@ def get_messages_from_file(path): return [] def extract_messages_from_code(code, is_py=False): + try: + code = render_include(code) + except TemplateError: + # Exception will occur when it encounters John Resig's microtemplating code + pass + messages = [] messages += re.findall('_\("([^"]*)"', code) messages += re.findall("_\('([^']*)'", code) @@ -300,11 +322,19 @@ def get_untranslated(lang, untranslated_file, get_all=False): for app in apps: messages.extend(get_messages_for_app(app)) + messages = list(set(messages)) + + def escape_newlines(s): + return (s.replace("\\\n", "|||||") + .replace("\\n", "||||") + .replace("\n", "|||")) + if get_all: print str(len(messages)) + " messages" with open(untranslated_file, "w") as f: for m in messages: - f.write((m + "\n").encode("utf-8")) + # replace \n with ||| so that internal linebreaks don't get split + f.write((escape_newlines(m) + os.linesep).encode("utf-8")) else: full_dict = get_full_dict(lang) @@ -316,7 +346,8 @@ def get_untranslated(lang, untranslated_file, get_all=False): print str(len(untranslated)) + " missing translations of " + str(len(messages)) with open(untranslated_file, "w") as f: for m in untranslated: - f.write((m + "\n").encode("utf-8")) + # replace \n with ||| so that internal linebreaks don't get split + f.write((escape_newlines(m) + os.linesep).encode("utf-8")) else: print "all translated!" @@ -324,8 +355,22 @@ def update_translations(lang, untranslated_file, translated_file): clear_cache() full_dict = get_full_dict(lang) - full_dict.update(dict(zip(frappe.get_file_items(untranslated_file), - frappe.get_file_items(translated_file)))) + def restore_newlines(s): + return (s.replace("|||||", "\\\n") + .replace("| | | | |", "\\\n") + .replace("||||", "\\n") + .replace("| | | |", "\\n") + .replace("|||", "\n") + .replace("| | |", "\n")) + + translation_dict = {} + for key, value in zip(frappe.get_file_items(untranslated_file, ignore_empty_lines=False), + frappe.get_file_items(translated_file, ignore_empty_lines=False)): + + # undo hack in get_untranslated + translation_dict[restore_newlines(key)] = restore_newlines(value) + + full_dict.update(translation_dict) for app in frappe.get_all_apps(True): write_translations_file(app, lang, full_dict) @@ -335,8 +380,21 @@ def rebuild_all_translation_files(): for app in frappe.get_all_apps(): write_translations_file(app, lang) -def write_translations_file(app, lang, full_dict=None): +def write_translations_file(app, lang, full_dict=None, app_messages=None): + if not app_messages: + app_messages = get_messages_for_app(app) + + if not app_messages: + return + tpath = frappe.get_pymodule_path(app, "translations") frappe.create_folder(tpath) write_csv_file(os.path.join(tpath, lang + ".csv"), - get_messages_for_app(app), full_dict or get_full_dict(lang)) + app_messages, full_dict or get_full_dict(lang)) + +def send_translations(translation_dict): + """send these translations in response""" + if "__messages" not in frappe.local.response: + frappe.local.response["__messages"] = {} + + frappe.local.response["__messages"].update(translation_dict) diff --git a/frappe/translations/ar.csv b/frappe/translations/ar.csv index 7d27a29829..93bdaa16e8 100644 --- a/frappe/translations/ar.csv +++ b/frappe/translations/ar.csv @@ -1,39 +1,37 @@ - by Role ,بالتخصص + by Role ,حسب الصلاحية is not set,لم يتم تعيين -""" does not exists",""" لا يوجد" """Company History""","نبذة عن تاريخ الشركة" """Team Members"" or ""Management""","أعضاء الفريق" أو "إدارة" 'In List View' not allowed for type {0} in row {1},'في قائمة عرض ' لا يسمح لنوع {0} في {1} الصف -'link:' type Select {0} getting replaced,' الرابط : ' نوع اختيار {0} الحصول على استبدال +0 - Draft; 1 - Submitted; 2 - Cancelled,0 - مشروع؛ 1 - المقدمة؛ 2 - إلغاء +0 is highest,0 هو أعلى "000 is black, fff is white",000 سوداء، بيضاء FFF 2 days ago,2 منذ أيام "[?]"," [ ؟ ] < / A>" -"\ -
  • field:[fieldname] - By Field\ -
  • naming_series: - By Naming Series (field called naming_series must be present\ -
  • Prompt - Prompt user for a name\ -
  • [series] - Series by prefix (separated by a dot); for example PRE.#####\ -')"">Naming Options", -A user can be restricted to multiple records of the same type.,يمكن تقييد للمستخدم ل سجلات متعددة من نفس النوع . +"\
  • field:[fieldname] - By Field\
  • naming_series: - By Naming Series (field called naming_series must be present\
  • Prompt - Prompt user for a name\
  • [series] - Series by prefix (separated by a dot); for example PRE.#####\')"">Naming Options","<وعند _ النقر = ""msgprint ('
      \
    1. قم المجال: [fieldname] - بواسطة فيلد \
    2. قم naming_series: - بواسطة تسمية سلسلة (يجب أن يكون حقل يسمى naming_series الحاضر \
    3. قم موجه - مطالبة المستخدم لاسم \
    4. قم [سلسلة] - سلسلة بواسطة البادئة (مفصولة نقطة)، على سبيل المثال PRE # # # # # \ ') ""> تسمية خيارات " +new type of document, جديدة نوع الوثيقة +"document type..., e.g. customer", نوع الوثيقة ... ، على سبيل المثال العملاء +e.g. (55 + 434) / 4 or =Math.sin(Math.PI/2)..., على سبيل المثال على (55 + 434) / 4 أو على = الرياضيات. خطيئة (رياضياتPI / 2) ... +module name..., اسم الوحدة النمطية ... +text in document type,النص في نوع الوثيقة +A user can be permitted to multiple records of the same DocType.,يمكن أن يسمح للمستخدم ل سجلات متعددة من نفس DOCTYPE . About,حول -About Us Settings,حول بنا إعدادات -About Us Team Member,حول عضو فريق بنا -Action,عمل +About Us Settings,إعدادات صفحة من نحن +About Us Team Member,أعضاء فريق صفحة من نحن +Action,حدث Actions,الإجراءات "Actions for workflow (e.g. Approve, Cancel).",الإجراءات ل سير العمل ( الموافقة على مثل إلغاء) . Add,إضافة Add A New Rule,إضافة قاعدة جديدة -Add A Restriction,إضافة تقييد +Add A User Permission,إضافة صلاحية مستخدم Add Attachments,إضافة مرفقات -Add Bookmark,إضافة علامة +Add Bookmark,إضافة للمفضلة Add CSS,إضافة CSS Add Column,إضافة عمود Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.,إضافة خدمة Google Analytics ID: على سبيل المثال. UA-89XXX57-1. الرجاء بحث المساعدة على تحليلات جوجل لمزيد من المعلومات. Add Message,إضافة رسالة Add New Permission Rule,إضافة قاعدة جديدة إذن Add Reply,إضافة رد -Add Serial No,إضافة رقم المسلسل -Add This To User's Restrictions,هذا إضافة إلى تقييد المستخدم Add Total Row,إضافة صف الإجمالي Add a New Role,إضافة دور جديد Add a banner to the site. (small banners are usually good),إضافة لافتة إلى الموقع. (لافتات صغيرة عادة ما تكون جيدة) @@ -42,10 +40,12 @@ Add attachment,إضافة المرفقات Add code as <script>,إضافة التعليمات البرمجية كما <script> Add custom javascript to forms.,إضافة إلى أشكال العرف جافا سكريبت . Add fields to forms.,إضافة حقول إلى النماذج. +Add multiple rows,إضافة صفوف متعددة Add new row,إضافة صف جديد "Add the name of Google Web Font e.g. ""Open Sans""","إضافة اسم جوجل خط ويب على سبيل المثال "بلا فتح"" Add to To Do,إضافة إلى المهام -Add to To Do List of,إضافة إلى قائمة المهام من +Add to To Do List Of,إضافة إلى قائمة المهام من +Added {0} ({1}),وأضاف {0} ({1}) Adding System Manager to this User as there must be atleast one System Manager,مضيفا إدارة نظام ل هذا العضو كما يجب أن يكون هناك أتلست إدارة نظام واحد Additional Info,معلومات إضافية Additional Permissions,ضوابط إضافية @@ -57,64 +57,68 @@ Address and other legal information you may want to put in the footer.,العن Admin,مشرف All Applications,جميع التطبيقات All Day,كل يوم +All customizations will be removed. Please confirm.,سيتم إزالة كافة التخصيصات. يرجى تأكيد. "All possible Workflow States and roles of the workflow.
      Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""",جميع الدول سير العمل والأدوار الممكنة لسير العمل.
      Docstatus خيارات: هو "المحفوظة" 0، يتم "المقدمة" (1) ويتم "إلغاء" 2 -Allow Attach,تسمح إرفاق -Allow Import,تسمح استيراد -Allow Import via Data Import Tool,تسمح استيراد عبر أداة استيراد البيانات -Allow Rename,تسمح إعادة تسمية -Allow on Submit,السماح على تقديم +Allow Attach,السماح بالإرفاق +Allow Import,السماح بالإستيراد +Allow Import via Data Import Tool,السماح بالإستيراد خلال أداة إستيراد البيانات +Allow Rename,السماح بإعادة التسمية +Allow on Submit,السماح بالإعتماد Allow user to login only after this hour (0-24),تسمح للمستخدم لتسجيل الدخول فقط بعد هذه الساعة (0-24) Allow user to login only before this hour (0-24),تسمح للمستخدم لتسجيل الدخول فقط قبل هذه الساعة (0-24) Allowed,سمح "Allowing DocType, DocType. Be careful!",السماح DOCTYPE ، DOCTYPE . كن حذرا! Already Registered,مسجل بالفعل -Already in todo,بالفعل في تودو +Already in user's To Do list,بالفعل في المستخدم والقيام قائمة Alternative download link,رابط تحميل بديل "Alternatively, you can also specify 'auto_email_id' in site_config.json","بدلا من ذلك، يمكنك أيضا تحديد "" auto_email_id ' في site_config.json" Always use above Login Id as sender,استخدام دائما فوق معرف تسجيل الدخول باسم المرسل Amend,تعديل "An icon file with .ico extension. Should be 16 x 16 px. Generated using a favicon generator. [favicon-generator.org]","ملف رمز التمديد مع منظمة البن الدولية. يجب أن تكون 16 × 16 بكسل. تم إنشاؤها باستخدام مولد فافيكون. [ فافيكون-generator.org ]" "Another {0} with name {1} exists, select another name",آخر {0} مع اسم {1} موجودا، حدد اسم آخر -Anyone Can Read,أي شخص يمكن قراءة -Anyone Can Write,أي شخص يمكن الكتابة -"Apart from System Manager, roles with 'Can Restrict' permission can restrict other users for that Document Type.",وبصرف النظر عن إدارة النظام ، والأدوار مع ' يمكن تقييد ' إذن يمكن تقييد المستخدمين الآخرين لهذا نوع الوثيقة . -"Apart from the existing Permission Rules, you can apply addition restriction based on Type.",وبصرف النظر عن القواعد إذن القائمة ، يمكنك تطبيق قيود بالإضافة إلى ذلك على أساس النوع . -App Already Installed,التطبيق المثبتة مسبقا +Any existing permission will be deleted / overwritten.,سيتم حذف أي إذن الحالية / الكتابة. +Anyone Can Read,أي شخص يمكنه القراءة +Anyone Can Write,أي شخص يمكه الكتابة +"Apart from Role based Permission Rules, you can apply User Permissions based on DocTypes.",وبصرف النظر عن دور أساس القواعد إذن ، يمكنك تطبيق أذونات المستخدم استنادا DocTypes . +"Apart from System Manager, roles with 'Set User Permissions' right can set permissions for other users for that Document Type.","وبصرف النظر عن إدارة النظام ، يمكن أن الأدوار مع حق "" تعيين أذونات المستخدم تعيين أذونات للمستخدمين الآخرين لذلك نوع الوثيقة ." App Name,اسم التطبيق App not found,لم يتم العثور على التطبيق Application Installer,مثبت التطبيق Applications,تطبيقات +Apply Style,تطبيق نمط +Apply User Permissions,تطبيق ضوابط العضو Are you sure you want to delete the attachment?,هل أنت متأكد أنك تريد حذف المرفق؟ Arial,ارييل "As a best practice, do not assign the same set of permission rule to different Roles. Instead, set multiple Roles to the same User.",باعتبارها أفضل الممارسات ، عدم تعيين نفس مجموعة من الحكم إذن ل أدوار مختلفة. بدلا من ذلك ، تعيين أدوار متعددة ل نفس العضو . Ascending,تصاعدي -Assign To,تعيين إلى -Assigned By,يكلفه بها -Assigned To,تعيين ل +Assign To,تكليف إلى +Assigned By,تكليف بواسطة +Assigned To,كلف إلى Assigned To Fullname,تعيين إلى الاسم بالكامل Assigned To/Owner,تعيين ل / المالك Assignment Added,أضيفت الاحالة Assignment Status Changed,الحالة الاحالة تغيير Assignments,تعيينات -Attach,تعلق +Attach,إرفاق Attach Document Print,إرفاق طباعة المستند -Attach as web link,كما نعلق رابط موقع +Attach as web link,إرفاق كرابط إنترنت Attached To DocType,تعلق على DOCTYPE -Attached To Name,تعلق على اسم +Attached To Name,أرفقت للأسم Attachments,المرفقات -Auto Email Id,أرسل بريد الكتروني رقم السيارات -Auto Name,السيارات اسم -Auto generated,ولدت السيارات +Auto Email Id,البريد الإلكتروني التلقائي +Auto Name,الإسم التلقائي +Auto generated,أنشئت تلقائياً Avatar,الصورة الرمزية Back to Login,العودة إلى تسجيل الدخول Background Color,لون الخلفية Background Image,صورة الخلفية +Background Style,خلفية ستايل Banner,راية Banner HTML,راية HTML Banner Image,راية صورة Banner is above the Top Menu Bar.,راية فوق أعلى شريط القوائم. -Base Template Path,مسار قالب قاعدة Begin this page with a slideshow of images,تبدأ هذه الصفحة مع عرض شرائح من الصور +Beginning with,بدءا Belongs to,ينتمي إلى Bio,الحيوية Birth Date,تاريخ الميلاد @@ -126,31 +130,35 @@ Blog Settings,إعدادات بلوق Blog Title,بلوق العنوان Blogger,مدون Bookmarks,الإشارات المرجعية +Both login and password required,كل من تسجيل الدخول وكلمة المرور المطلوبة Brand HTML,العلامة التجارية HTML -Built on,مبنية على Bulk Email,الجزء الأكبر البريد الإلكتروني Bulk email limit {0} crossed,الحد معظم البريد الإلكتروني {0} عبروا Button,زر By,بواسطة +Calculate,حساب Calendar,تقويم -Can Restrict,يمكن تقييد Cancel,إلغاء Cancelled,إلغاء "Cannot Edit {0} directly: To edit {0} properties, create / update {1}, {2} and {3}",لا يمكن تحرير {0} مباشرة : لتعديل خصائص {0} ، وإنشاء / تحديث {1} ، {2} {3} و Cannot Update: Incorrect / Expired Link.,لا يمكن تحديث : رابط غير الصحيحة / منتهية الصلاحية . Cannot Update: Incorrect Password,لا يمكن تحديث : كلمة المرور غير صحيحة Cannot add more than 50 comments,لا يمكن إضافة أكثر من 50 تعليقا +Cannot cancel before submitting. See Transition {0},لا يمكن إلغاء قبل تقديم . Cannot change picture,لا يمكن تغيير الصورة +Cannot change state of Cancelled Document. Transition row {0},لا يمكن تغيير حالة الوثيقة ملغاة . Cannot change {0},لا يمكن تغيير {0} Cannot delete or cancel because {0} {1} is linked with {2} {3},لا يمكن حذف أو إلغاء ل {0} {1} يرتبط مع {2} {3} Cannot delete {0} as it has child nodes,لا يمكن حذف {0} كما أن لديها العقد التابعة -Cannot delete {0} {1} is it is referenced in another record,لا يمكن حذف {0} {1} و المشار إليها في سجل آخر Cannot edit standard fields,لا تستطيع تعديل الحقول القياسية Cannot map because following condition fails: ,لا يمكن تعيين بسبب فشل الشرط التالي: Cannot open instance when its {0} is open,لا يمكن فتح المثال عندما ه {0} مفتوح Cannot open {0} when its instance is open,لا يمكن فتح {0} عندما مثيل لها مفتوح Cannot print cancelled documents,لا يمكن طباعة المستندات إلغاء +Cannot remove permission for DocType: {0} and Name: {1},لا يمكن إزالة إذن ل DOCTYPE : {0} و اسم : {1} Cannot reply to a reply,لا يمكنك الرد على الرد +Cannot set Email Alert on Document Type {0},لا يمكن تعيين تنبيه بالبريد الالكتروني على نوع الوثيقة {0} +Cannot set permission for DocType: {0} and Name: {1},لا يمكن تعيين إذن ل DOCTYPE : {0} و اسم : {1} Categorize blog posts.,تصنيف بلوق وظيفة. Category,فئة Category Name,اسم التصنيف @@ -162,21 +170,22 @@ Check,تحقق Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.,تحقق أدوار ازل / المسندة إلى الملف. انقر على دور لمعرفة ما الأذونات التي الدور الذي. Check this if you want to send emails as this id only (in case of restriction by your email provider).,الاختيار هذا إذا كنت تريد أن ترسل رسائل البريد الإلكتروني في هذا المعرف فقط (في حالة تقييد من قبل مزود البريد الإلكتروني الخاص بك). Check this to make this the default letter head in all prints,التحقق من ذلك لجعل هذه الرسالة الافتراضية الرأس في جميع الطبعات +Check which Documents are readable by a User,تحقق من وثائق قابلة للقراءة من قبل العضو Checked items shown on desktop,العناصر المحددة التي تظهر على سطح المكتب Child Tables are shown as a Grid in other DocTypes.,وتظهر جداول الطفل بأنه في الشبكة DocTypes أخرى. City,مدينة +Classic,كلاسيكي Clear Cache,مسح ذاكرة التخزين المؤقت Clear all roles,مسح كافة الأدوار -Click on a link to get options to expand get options , +Click on a link to get options,انقر على وصلة للحصول على خيارات Click on row to view / edit.,انقر على صف لعرض / تحرير . -Click to Expand / Collapse,انقر لتوسيع / ​​طي -Client,زبون +Client,عميل +Client-side formats are now deprecated,وانتقدت صيغ العميل الآن Close,أغلق Close: {0},وثيقة : {0} Closed,مغلق Code,رمز Collapse,انهيار -Color,اللون Column Break,العمود استراحة Comment,تعليق Comment By,تعليق من جانب @@ -192,7 +201,7 @@ Company History,نبذة عن تاريخ الشركة Company Introduction,الشركة مقدمة Complaint,شكوى Complete By,الكامل من جانب -Condition Field,حالة الميدان +Condition,الحالة Contact Us Settings,الاتصال بنا إعدادات "Contact options, like ""Sales Query, Support Query"" etc each on a new line or separated by commas.",خيارات الاتصال، مثل "الاستعلام المبيعات والدعم الاستعلام" الخ كل على سطر جديد أو مفصولة بفواصل. Content,محتوى @@ -205,22 +214,23 @@ Copyright,حق النشر Core,جوهر Could not connect to outgoing email server,لا يمكن الاتصال بخادم البريد الإلكتروني المنتهية ولايته Could not find {0},لا يمكن أن تجد {0} -Count,عد +Count,عدد Country,بلد -Create,خلق -Created Custom Field {0} in {1},خلق حقل مخصص {0} في {1} -Created Customer Issue,إنشاء العدد العملاء -Created Opportunity,خلق الفرص +Create,انشاء +Create a new {0},خلق جديد {0} +Created Custom Field {0} in {1},إنشاء الحقل المخصص {0} في {1} +Created Customer Issue,إنشاء مشاكل عميل +Created Opportunity,إنشاء فرصة Created Support Ticket,إنشاء تذكرة دعم Creation / Modified By,إنشاء / تم التعديل بواسطة Currency,عملة -Current status,الوضع الحالي +Current status,الحالة الحالية Custom CSS,العرف CSS -Custom Field,مخصص الميدانية -Custom Javascript,العرف الجافا سكريبت +Custom Field,حقل مخصص +Custom Javascript,جافا سكريبت مخصصة Custom Reports,تقارير مخصصة -Custom Script,سيناريو مخصص -Custom?,العرف؟ +Custom Script,سكربت محصص +Custom?,مخصص Customize,تخصيص Customize Form,تخصيص نموذج Customize Form Field,تخصيص حقل نموذج @@ -233,14 +243,18 @@ Data Import / Export Tool,البيانات أداة استيراد / تصدير Data Import Tool,أداة استيراد البيانات Data missing in table,البيانات الناقصة في الجدول Date,تاريخ +Date Change,تاريخ التغيير +Date Changed,تاريخ تغيير Date Format,تنسيق التاريخ Date and Number Format,تاريخ و صيغة رقم Date must be in format: {0},يجب أن تكون الآن في شكل : {0} Datetime,التاريخ والوقت +Days in Advance,أيام مقدما Dear,العزيز Default,الافتراضي Default Print Format,طباعة شكل الافتراضي Default Value,القيمة الافتراضية +Default is system timezone,الافتراضي هو نظام توقيت "Default: ""Contact Us""",الافتراضي: "اتصل بنا" DefaultValue,الافتراضية Defaults,الافتراضات @@ -287,17 +301,24 @@ Document Type,نوع الوثيقة Document is only editable by users of role,الوثيقة للتحرير فقط من قبل المستخدمين من دور Documentation,توثيق Documents,وثائق -Down,إلى Download,تحميل Download Backup,تحميل النسخ الاحتياطي Download link for your backup will be emailed on the following email address:,وسوف تكون عبر البريد الالكتروني وصلة التحميل للنسخ الاحتياطي الخاصة بك على عنوان البريد الإلكتروني التالي : Drafts,الداما Drag to sort columns,اسحب لفرز الأعمدة Due Date,بسبب تاريخ +Duplicate name {0} {1},تكرار اسم {0} {1} +Dynamic Link,الارتباط الحيوي +ERPNext Demo,ERPNext تجريبي Edit,تحرير Edit Permissions,تحرير ضوابط Editable,للتحرير Email,البريد الإلكتروني +Email Alert,تنبيه البريد الإلكتروني +Email Alert Recipient,مستقبل البريد تنبيه +Email Alert Recipients,المستلمين تنبيه البريد الإلكتروني +Email By Document Field,البريد الإلكتروني بواسطة حقل الوثيقة +Email Footer,البريد الإلكتروني تذييل Email Host,البريد الإلكتروني المضيف Email Id,البريد الإلكتروني معرف Email Login,دخول البريد الالكتروني @@ -313,15 +334,19 @@ Email sent to {0},أرسل بريد إلكتروني إلى {0} Email...,البريد الإلكتروني ... Emails are muted,رسائل البريد الإلكتروني هي صامتة Embed image slideshows in website pages.,تضمين عرض الشرائح صورة في صفحات الموقع. +Enable,تمكين Enable Comments,تمكين تعليقات +Enable Scheduled Jobs,تمكين المهام المجدولة Enabled,تمكين Ends on,ينتهي في Enter Form Type,أدخل نوع النموذج Enter Value,أدخل القيمة +Enter at least one permission row,يدخل الصف إذن واحد على الأقل "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set ""match"" permission rules. To see list of fields, go to Customize Form.","الدخول في مجالات القيمة الافتراضية (مفاتيح) والقيم. إذا قمت بإضافة قيم متعددة لحقل، سيتم اختار أول واحد. كما تستخدم هذه الافتراضات لوضع القواعد "مباراة" إذن. لمعرفة قائمة الحقول، انتقل إلى تخصيص الشكل ." "Enter keys to enable login via Facebook, Google, GitHub.",أدخل مفاتيح لتمكين تسجيل الدخول عبر الفيسبوك ، وجوجل، جيثب . -Equals,التساوي +Equals,تساوي Error,خطأ +"Error generating PDF, attachment sent as HTML",توليد الخطأ PDF، HTML المرفقات إرسالها كما Error: Document has been modified after you have opened it,تم تعديل الوثيقة بعد أن كنت قد فتحه: خطأ Event,حدث Event Datetime,الحدث التاريخ والوقت @@ -337,11 +362,13 @@ Every Day,كل يوم Every Month,كل شهر Every Week,كل أسبوع Every Year,كل سنة +Everyone,كل شخص Example:,على سبيل المثال: Expand,وسع Export,تصدير Export not allowed. You need {0} role to export.,الصادرات غير مسموح به. تحتاج {0} دور للتصدير. Exported,تصدير +"Expression, Optional",التعبير والاختياري Facebook,الفيسبوك Facebook Client ID,الفيسبوك عميل معرف Facebook Client Secret,الفيسبوك العميل السري @@ -360,34 +387,40 @@ Field {0} of type {1} cannot be mandatory,الحقل {0} من نوع {1} لا ي Fieldname,Fieldname Fieldname is required in row {0},مطلوب Fieldname في الصف {0} Fieldname not set for Custom Field,Fieldname لم يتم تعيين ل حقل مخصص +Fieldname which will be the DocType for this link field.,Fieldname التي ستكون DOCTYPE لهذا الحقل الارتباط. Fieldname {0} appears multiple times in rows {1},Fieldname {0} تظهر عدة مرات في الصفوف {1} "Fieldname {0} cannot contain letters, numbers or spaces",Fieldname {0} لا يمكن أن تحتوي على حروف وأرقام أو مسافات Fields,الحقول "Fields separated by comma (,) will be included in the
      Search By list of Search dialog box",وسيتم إدراج حقول مفصولة بفواصل (،) في
      البحث حسب قائمة مربع الحوار بحث -Fieldtype must be one of {0} in row {1},Fieldtype يجب أن يكون واحدا من {0} في {1} الصف +Fieldtype cannot be changed from {0} to {1} in row {2},Fieldtype لا يمكن تغييرها من {0} إلى {1} في {2} الصف File,ملف File Data,ملف البيانات File Name,اسم الملف File Size,حجم الملف File URL,ملف URL File not attached,ملف لا تعلق -File size exceeded the maximum allowed size,حجم الملف تجاوز الحجم الأقصى المسموح به -Filter,تحديد +File size exceeded the maximum allowed size of {0} MB,تجاوز حجم الملف الحد الأقصى المسموح به حجم {0} MB +Fill Screen,ملء الشاشة +Filter,تصفية +Filter records based on User Permissions defined for a user,تصفية السجلات استنادا إلى صلاحيات المستخدمين المخصصة للمستخدم +Filters,فلاتر +Find {0} in {1},البحث عن {0} في {1} First Name,الاسم الأول -Float,الطفو -Float Precision,تعويم الدقة +Float,رقم عشري +Float Precision,دقة الرقم العشري Font (Heading),الخط (العنوان) Font (Text),الخط (نص) +Font Size,حجم الخط Font Size (Text),حجم الخط (نص) Fonts,الخطوط Footer,تذييل +Footer Background,تذييل الخلفية Footer Items,تذييل العناصر +Footer Text,تذييل النص For DocType,ل DOCTYPE -"For Links, enter the DocType as range -For Select, enter list of Options separated by comma", +"For Links, enter the DocType as rangeFor Select, enter list of Options separated by comma",لسريعة، أدخل DOCTYPE عن مجموعة لتحديد، قائمة خيارات مفصولة بفواصل دخول "For comparative filters, start with",للمرشحات النسبية، وتبدأ مع For example if you cancel and amend 'INV004' it will become a new document 'INV004-1'. This helps you to keep track of each amendment.,على سبيل المثال إذا قمت بإلغاء وتعديل 'INV004' سوف تصبح الوثيقة الجديدة "INV004-1". هذا يساعدك على تتبع كل تعديل. -"For example, if user X is restricted to company C, user X will not be able to see any transaction that has company C as a linked value.",على سبيل المثال ، إذا كان يتم تقييد المستخدم X ل شركة C، X المستخدم لن يكون قادرا على رؤية أي المعاملة التي لديها شركة C كقيمة مرتبطة. For ranges,للنطاقات For top bar,الشريط العلوي لل For {0} at level {1} in {2} in row {3},ل {0} في {1} مستوى في {2} في {3} الصف @@ -397,15 +430,17 @@ Form,شكل Forum,منتدى Forums,المنتديات Forward To Email Address,انتقل إلى عنوان البريد الإلكتروني +Frappe Framework,الإطار فرابي Friday,الجمعة From Date must be before To Date,يجب أن تكون من تاريخ إلى تاريخ قبل -Full Name,بدر تام +Full Name,الاسم الكامل Gantt Chart,مخطط جانت Gender,جنس Generator,مولد كهربائي Georgia,جورجيا Get,الحصول على Get From ,عليه من +"Get your globally recognized avatar from Gravatar.com","احصل على صورة شخصية معترف بها عالميا من Gravatar.com " GitHub,جيثب GitHub Client ID,جيثب معرف عميل GitHub Client Secret,جيثب سر عميل @@ -421,7 +456,6 @@ Google Web Font (Heading),Google ويب الخط (العنوان) Google Web Font (Text),Google ويب الخط (نص) Greater or equals,أكبر أو يساوي Greater than,أكبر من -"Grid ""","الشبكة """ "Group Added, refreshing...",مجموعة المضافة، منعش ... Group Description,مجموعة الوصف Group Name,اسم المجموعة @@ -434,15 +468,14 @@ Header,رأس Heading,عنوان Heading Text As,عنوان النص Help,مساعدة +Help on Search,تساعد على البحث Helvetica Neue,هلفتيكا نويه -Hey! There should remain at least one System Manager,مهلا! هناك ينبغي أن تظل إدارة نظام واحد على الأقل Hidden,مخفي Hide Actions,إخفاء عمليات Hide Copy,إخفاء نسخة -Hide Email,إخفاء البريد الإلكتروني Hide Heading,إخفاء عنوان -Hide Print,إخفاء طباعة Hide Toolbar,إخفاء شريط الأدوات +Hide the sidebar,إخفاء الشريط الجانبي High,ارتفاع Highlight,تسليط الضوء على History,تاريخ @@ -451,17 +484,14 @@ Home Page is Products,الصفحة الرئيسية المنتجات غير ID (name) of the entity whose property is to be set,ID (اسم) للكيان الذي هو الملكية التي سيتم تحديدها Icon,رمز Icon will appear on the button,سوف تظهر أيقونة على زر -"If 'Restricted' is checked, the owner is always allowed based on Role.",إذا ' المحظورة ' محددا ، ولا يسمح لل مالك دائما على أساس الدور . -"If 'Restricted' is not checked, you can still restrict permissions based on certain values, like Company or Territory in a document by setting User Restrictions. But unless any restriction is set, a user will have permissions based on the Role.",إذا ' المحظورة ' لم يتم تحديد ، لا يزال بإمكانك تقييد الأذونات على أساس قيم معينة ، مثل شركة أو الإقليم في وثيقة من خلال وضع قيود العضو . ولكن ما لم يتم تعيين أي قيود ، وسوف يكون لديك أذونات مستخدم على أساس الدور . "If a Role does not have access at Level 0, then higher levels are meaningless.",إذا لم يكن لديك الوصول دور في المستوى 0، ثم مستويات أعلى لا معنى لها. "If checked, all other workflows become inactive.",إذا تم، جميع مهام سير العمل الأخرى تصبح خاملة. -"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.",إذا تم، ستضاف رسالة بالبريد الالكتروني مع تنسيق HTML المرفقة لجزء من الجسم البريد الإلكتروني، فضلا المرفق. لإرسال كمرفق فقط، قم بإلغاء تحديد هذا. "If checked, the Home page will be the default Item Group for the website.",إذا تم، سيكون في الصفحة الرئيسية يجب أن يكون فريق المدينة الافتراضية للموقع. "If image is selected, color will be ignored (attach first)",إذا تم تحديد الصورة، سيتم تجاهل اللون (إرفاق الأولى) If non standard port (e.g. 587),إذا غير المنفذ القياسي (على سبيل المثال 587) "If these instructions where not helpful, please add in your suggestions on GitHub Issues.",إذا هذه التعليمات حيث لم تكن مفيدة ، يرجى إضافة في اقتراحاتكم بشأن قضايا جيثب . "If you set this, this Item will come in a drop-down under the selected parent.",إذا قمت بتعيين هذا ، فإن هذا البند تأتي في قائمة منسدلة تحت الأصل المحدد . -Ignore Restrictions,تجاهل القيود +Ignore User Permissions,تجاهل أذونات المستخدم "Ignoring Item {0}, because a group exists with the same name!",تجاهل البند {0} ، لأن مجموعة موجودة بنفس الاسم ! Image,صورة Image Link,رابط الصورة @@ -473,13 +503,15 @@ In Dialog,في مربع حوار In Filter,في تصفية In List View,في عرض القائمة In Report Filter,في تصفية التقرير +In points. Default is 9.,في نقطة. الافتراضي هو 9. In response to,ردا على Incorrect value in row {0}: {1} must be {2} {3},قيمة غير صحيحة في الصف {0} : {1} يجب أن يكون {2} {3} -Incorrect value: {1} must be {2} {3},قيمة غير صحيحة : {1} يجب أن يكون {2} {3} +Incorrect value: {0} must be {1} {2},قيمة غير صحيحة: {0} يجب أن يكون {1} {2} Index,مؤشر Individuals,الأفراد Info,معلومات Insert After,إدراج بعد +"Insert After field '{0}' mentioned in Custom Field '{1}', does not exist",إدراج بعد الحقل '{0}' المذكورة في حقل مخصص '{1}' غير موجود Insert Below,إدراج بالأسفل Insert Code,إدراج رمز Insert Row,إدراج صف @@ -495,6 +527,7 @@ Introductory information for the Contact Us Page,المعلومات التمهي Invalid Email: {0},صالح البريد الإلكتروني: {0} Invalid Filter: {0},تصفية الباطلة: {0} Invalid Home Page,الصفحة الرئيسية غير صالحة +Invalid Login,تسجيل الدخول غير صالحة Invalid Outgoing Mail Server or Port,خادم البريد الصادر غير صالحة أو ميناء Invalid login or password,تسجيل الدخول أو كلمة المرور غير صالحة Invalid recipient address,عنوان المستلم غير صالحة @@ -531,6 +564,7 @@ Leave blank to repeat always,اتركه فارغا لتكرار دائما Left,ترك Less or equals,أقل أو يساوي Less than,أقل من +Letter,الحرف Letter Head,رسالة رئيس Letter Head Name,رسالة رئيس الاسم Letter Head in HTML,رسالة رئيس في HTML @@ -538,13 +572,13 @@ Level,مستوى "Level 0 is for document level permissions, higher levels for field level permissions.",0 هو مستوى الأذونات لمستوى الوثيقة، مستويات أعلى للحصول على أذونات المستوى الميداني. Like,مثل Link,رابط -Link Name,اسم الرابط "Link that is the website home page. Standard Links (index, login, products, blog, about, contact)",ربط هذا هو الصفحة الرئيسية ل موقع الويب. روابط القياسية ( مؤشر ، تسجيل الدخول ، والمنتجات، بلوق ، عن، الاتصال ) Link to other pages in the side bar and next section,ربط إلى صفحات أخرى في شريط الجانب والمقطع التالي Link to the page you want to open,الرابط إلى الصفحة التي تريد فتح Linked In Share,ترتبط في حصة Linked With,ترتبط List,قائمة +List a document type,قائمة نوع مستند List of Web Site Forum's Posts.,قائمة مشاركات المنتدى موقع ويب. Loading,تحميل Loading Report,تحميل تقرير @@ -561,9 +595,10 @@ Logout,خروج Long Text,نص طويل Low,منخفض Lucida Grande,سدا غراندي -Mail Password,البريد كلمة المرور +Mail Password,كلمة مرور البريد الإلكتروني Main Section,القسم العام Make a new,جعل جديدة +Make a new record,جعل رقما قياسيا جديدا Male,ذكر Manage cloud backups on Dropbox,إدارة النسخ الاحتياطية سحابة على دروببوإكس Manage uploaded files.,إدارة الملفات التي تم تحميلها. @@ -578,12 +613,14 @@ Medium,متوسط "Menu items in the Top Bar. For setting the color of the Top Bar, go to Style Settings","عناصر القائمة في الشريط العلوي. لتحديد لون الشريط العلوي، انتقل إلى إعدادات نمط" Merging is only possible between Group-to-Group or Leaf Node-to-Leaf Node,دمج الممكن الوحيد بين المجموعة إلى المجموعة أو عقدة ورقة إلى ورقة عقدة Message,رسالة +Message Examples,أمثلة رسالة Messages,رسائل Method,طريقة Middle Name (Optional),الاسم الأوسط (اختياري) Misc,منوعات Miscellaneous,متفرقات Missing Values Required,في عداد المفقودين القيم المطلوبة +Modern,حديث Modified by,تعديلها من قبل Module,وحدة Module Def,وحدة مواطنه @@ -593,7 +630,6 @@ Modules Setup,إعداد وحدات Monday,يوم الاثنين More,أكثر More content for the bottom of the page.,المزيد من المحتوى لأسفل الصفحة. -Move,خطوة Move Down: {0},تحريك لأسفل : {0} Move Up: {0},تحريك لأعلى : {0} Multiple root nodes not allowed.,العقد الجذرية متعددة غير مسموح به. @@ -602,42 +638,45 @@ Must specify a Query to run,يجب تحديد استعلام لتشغيل My Settings,الإعدادات Name,اسم Name Case,اسم القضية -Name Exists,اسم موجود Name and Description,الاسم والوصف Name is required,مطلوب اسم Name not permitted,تسمية غير مسموح +Name not set via Prompt,الأسم: لم تحدد عن طريق موجه +Name of the Document Type (DocType) you want this field to be linked to. e.g. Customer,اسم نوع الوثيقة (DOCTYPE) تريد هذا الحقل لتكون مرتبطة. على سبيل المثال العملاء +Name of {0} cannot be {1},اسم {0} لا يمكن أن يكون {1} Naming,تسمية Naming Series mandatory,تسمية السلسلة إلزامية Nested set error. Please contact the Administrator.,خطأ مجموعة متداخلة . يرجى الاتصال مدير البرنامج. New,جديد New Password,كلمة مرور جديدة -New Record,رقم قياسي جديد +New Record,سجل جديد New comment on {0} {1},تعليق جديد على {0} {1} New password emailed,كلمة مرور جديدة عبر البريد الالكتروني New value to be set,القيمة الجديدة التي سيتم تحديدها +New {0},جديد {0} Next Communcation On,وفي المراسلات القادمة -Next Record,سجل المقبل -Next State,الدولة القادمة +Next Record,السجل التالي +Next State,الحالية التالية Next actions,الإجراءات التالية No,لا -No Action,أي إجراء -No Cache,لا الكاش +No Action,بدون إجراء No Communication tagged with this ,لا الاتصالات المفتاحية هذه No Copy,اي نسخة No Permissions set for this criteria.,لم يحدد ضوابط لهذه المعايير. No Report Loaded. Please use query-report/[Report Name] to run a report.,أي تقرير المحملة. الرجاء استخدام استعلام تقرير / [اسم التقرير] لتشغيل التقرير. No Results,لا نتائج No Sidebar,لا الشريط الجانبي -No Sitemap,لا خريطة الموقع -No User Restrictions found.,لا توجد قيود العضو . +No User Permissions found.,لم يتم العثور على أذونات المستخدم . No data found,لا توجد بيانات -No document selected,أي وثيقة مختارة No file attached,أي ملف مرفق No further records,لا توجد سجلات أخرى -No one,لا احد +No one,لا أحد +No permission to '{0}' {1},لا توجد صلاحية ل '{0} ' {1} No permission to edit,لا توجد صلاحية ل تعديل No permission to write / remove.,لا توجد صلاحية لكتابة / إزالة. -No records tagged.,لا توجد سجلات المعلمة. +No records tagged.,لا توجد سجلات معلمة. +No template found at path: {0},لا توجد في مسار القالب: {0} +No {0} permission,لا {0} إذن None,لا شيء None: End of Workflow,لا شيء: نهاية سير العمل Not Found,لم يتم العثور على @@ -658,35 +697,38 @@ Not equals,لا يساوي Not found,لم يتم العثور على Not in Developer Mode! Set in site_config.json,ليس في الوضع المطور ! تعيين في site_config.json Not permitted,لا يسمح -Not permitted to restrict User {0} for {1} {2},لا يسمح لتقييد المستخدم {0} ل {1} {2} "Note: For best results, images must be of the same size and width must be greater than height.",ملاحظة: للحصول على أفضل النتائج، يجب أن تكون الصور من نفس الحجم والعرض يجب أن تكون أكبر من الارتفاع. Note: Other permission rules may also apply,ملاحظة: قد قواعد أخرى إذن تنطبق أيضا Note: maximum attachment size = 1mb,ملاحظة: الحد الأقصى لحجم المرفقات 1MB = -Nothing to delete,شيئا ل حذف Nothing to show,لا شيء لإظهار Nothing to show for this selection,شيء لاظهار هذا الاختيار Notification Count,عدد إعلام -Notify By Email,إبلاغ عن طريق البريد الإلكتروني +Notify by Email,إعلام عبر البريد الإلكتروني Number Format,عدد تنسيق Old Parent,العمر الرئيسي On,في -"Once you have set this, the users will only be able access documents where the link (e.g Company) exists.",وبمجرد الانتهاء من تعيين هذه ، سوف يكون المستخدمون قادرين فقط الوصول إلى المستندات حيث الارتباط ( مثل الشركة) موجودا. +"Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger).",وبمجرد الانتهاء من تعيين هذه ، سوف يكون المستخدمون قادرين فقط الوصول إلى المستندات (على سبيل المثال مدونة بوست) حيث يوجد رابط (مثل مدون ) . Only Administrator allowed to create Query / Script Reports,المسؤول الوحيد المسموح به لإنشاء تقارير الاستعلام / سكربت Only Administrator can save a standard report. Please rename and save.,مسؤول فقط يمكن حفظ تقرير القياسية. الرجاء إعادة تسمية وحفظ. Only Allow Edit For,السماح فقط للتحرير -Only Restricted Documents,المستندات المقيدة فقط Only allowed {0} rows in one import,فقط يسمح {0} الصفوف في استيراد واحد -Only restricted users can access,يمكن للمستخدمين يقتصر الوصول +Oops! Something went wrong,عفوًا! ذهب شيئا خاطئا Open,فتح Open Count,فتح عدد Open Sans,مفتوحة بلا -Open source ERP built for the web,ERP مفتوحة المصدر بنيت لشبكة الإنترنت +Open Source Web Applications for the Web,مفتوحة المصدر تطبيقات ويب للويب +Open a module or tool,فتح وحدة نمطية أو أداة +Open {0},مفتوحة {0} +Optional: Alert will only be sent if value is a valid email id.,اختياري: سوف يتم إرسال تنبيه فقط إذا كانت القيمة هي هوية بريد إلكتروني صحيح. +Optional: Always send to these ids. Each email id on a new row,اختياري: دائما إرسالها إلى هذه الهوية. كل معرف البريد الإلكتروني على صف جديد +Optional: The alert will be sent if this expression is true,اختياري: التنبيه سيتم إرسال إذا كان هذا التعبير صحيح Options,خيارات +Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType',"'الارتباط الحيوي ""نوع من الخيارات الميدانية يجب أن يشير إلى رابط حقل آخر مع خيارات باسم' DOCTYPE '" Options Help,خيارات مساعدة +Options for select. Each option on a new line. e.g.:
      Option 1
      Option 2
      Option 3
      ,خيارات للاختيار. كل خيار في سطر جديد. على سبيل المثال:
      الخيار 1
      الخيار 2
      الخيار 3
      Options must be a valid DocType for field {0} in row {1},يجب أن تكون الخيارات ل DOCTYPE صالحة لحقل {0} في {1} الصف Options not set for link field {0},خيارات لم يتم تعيين لحقل الرابط {0} Options requried for Link or Table type field {0} in row {1},خيارات ريكارد ل ينك أو نوع الجدول الحقل {0} في {1} الصف -Or Created By,أو إنشاء بواسطة Org History,غزاله التاريخ Org History Heading,غزاله التاريخ مداخل Original Message,رسالة الأصلي @@ -694,24 +736,26 @@ Other,آخر Outgoing Email Settings,إعدادات البريد الإلكتروني المنتهية ولايته Outgoing Mail Server,خادم البريد الصادر Outgoing Mail Server not specified,خادم البريد الصادر غير محدد +Owner,مالك +PDF Page Size,PDF حجم الصفحة +PDF Settings,إعدادات PDF POP3 Mail Server (e.g. pop.gmail.com),POP3 خادم البريد (على سبيل المثال pop.gmail.com) Page,صفحة +Page #{0} of {1},الصفحة # {0} من {1} Page Background,خلفية الصفحة -Page Border,حد الصفحة Page HTML,صفحة HTML -Page Headings,عناوين الصفحة +Page Header,رأس الصفحة +Page Header Background,رأس الصفحة الخلفية +Page Header Text,رأس الصفحة نص Page Links,الصفحة روابط Page Name,الصفحة اسم -Page Name Field,الصفحة اسم الحقل Page Role,الصفحة الدور Page Text,نص الصفحة Page Title,صفحة العنوان Page content,صفحة المحتوى Page not found,لم يتم العثور على الصفحة Page or Generator,الصفحة أو مولد -Page text and background is same color. Please change.,نص الصفحة والخلفية هي نفس اللون. الرجاء تغيير. Page url name (auto-generated),اسم الصفحة رابط ( تم إنشاؤه تلقائيا ) -Page with name {0} already exists,صفحة مع اسم {0} موجود بالفعل Parent Label,الأصل تسمية Parent Post,الوالد مشاركة Parent Website Page,الوالد الموقع الصفحة @@ -730,15 +774,16 @@ Permanently Submit {0}?,إرسال دائم {0} ؟ Permanently delete {0}?,حذف بشكل دائم {0} ؟ Permission Level,إذن المستوى Permission Levels,إذن مستويات -Permission Manager,إذن إدارة Permission Rules,إذن قوانين +Permission already set,إذن بالفعل تعيين Permissions,أذونات Permissions Settings,أذونات إعدادات Permissions are automatically translated to Standard Reports and Searches.,يتم تحويل أذونات تلقائيا إلى التقارير الموحدة و البحث . -"Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Restricted, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Can Restrict.",يتم تعيين الأذونات على الأدوار و أنواع المستندات (وتسمى DocTypes ) من خلال وضع حقوق مثل القراءة، المحظورة ، كتابة ، إنشاء، حذف، إرسال ، الغاء ، تعديل ، تقرير ، استيراد ، تصدير ، طباعة ، البريد الإلكتروني و يمكن تقييد. -Permissions at higher levels are 'Field Level' permissions. All Fields have a 'Permission Level' set against them and the rules defined at that permissions apply to the field. This is useful in case you want to hide or make certain field read-only.,"الأذونات على مستويات أعلى من الأذونات "" على المستوى الميداني . جميع الحقول تحتوي على ' مستوى الأذونات ' مجموعة ضدهم و قواعد محددة في تلك الأذونات تطبيق في الميدان. هذا مفيد في حال كنت ترغب في إخفاء أو جعل مجال معين للقراءة فقط." +"Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.",يتم تعيين الأذونات على الأدوار و أنواع المستندات (وتسمى DocTypes ) من خلال وضع حقوق مثل القراءة والكتابة ، إنشاء، حذف، إرسال ، الغاء ، تعديل ، تقرير ، استيراد ، تصدير ، طباعة ، البريد الإلكتروني و تعيين أذونات العضو . +Permissions at higher levels are 'Field Level' permissions. All Fields have a 'Permission Level' set against them and the rules defined at that permissions apply to the field. This is useful in case you want to hide or make certain field read-only for certain Roles.,"الأذونات على مستويات أعلى من الأذونات "" على المستوى الميداني ." "Permissions at level 0 are 'Document Level' permissions, i.e. they are primary for access to the document.",الأذونات في 0 مستوى الأذونات هي "مستوى الوثيقة، أي أنها الأساسي للوصول إلى المستند. Permissions get applied on Users based on what Roles they are assigned.,الحصول على تطبيق الأذونات على المستخدمين على أساس ما الأدوار التي تم تعيينها . +Permitted Documents For User,المستندات يسمح للمستخدم Person,شخص Personal Info,معلومات شخصية Phone,هاتف @@ -751,17 +796,24 @@ Please attach a file or set a URL,يرجى إرفاق ملف أو تعيين URL Please do not change the rows above {0},من فضلك لا تغيير الصفوف أعلاه {0} Please enable pop-ups,يرجى تمكين النوافذ المنبثقة Please enter Event's Date and Time!,الرجاء إدخال حدث في التاريخ والوقت ! +Please enter both your email and message so that we \ can get back to you. Thanks!,من فضلك ادخل بريدك الإلكتروني على حد سواء والرسالة حتى نتمكن \ يمكن أن نعود اليكم. شكرا! Please enter some text!,الرجاء إدخال النص ! Please enter title!,يرجى إدخال عنوان ! Please login to Upvote!,يرجى الدخول إلى Upvote ! Please make sure that there are no empty columns in the file.,يرجى التأكد من أنه لا توجد أعمدة فارغة في الملف. Please refresh to get the latest document.,يرجى تحديث للحصول على أحدث وثيقة. +Please reply above this line or remove it if you are replying below it,يرجى الرد فوق هذا الخط أو إزالته إذا كنت تقوم بالرد تحته Please select a file or url,يرجى تحديد ملف أو URL Please select atleast 1 column from {0} to sort,يرجى اختيار العمود 1 من {0} لفرز +Please set {0} first,الرجاء تعيين {0} الأولى Please specify,يرجى تحديد Please specify 'Auto Email Id' in Setup > Outgoing Email Settings,"يرجى تحديد "" هوية السيارات البريد الإلكتروني"" في إعداد > إعدادات البريد الإلكتروني المنتهية ولايته" Please specify Event date and time,يرجى تحديد تاريخ ووقت الحدث -Please specify a,الرجاء تحديد +Please specify doctype,يرجى تحديد DOCTYPE +Please specify user,يرجى تحديد المستخدم +Please specify which date field must be checked,يرجى تحديد أي تاريخ الحقل يجب أن يتم التحقق +Please specify which value field must be checked,يرجى تحديد أي يجب فحص حقل القيمة +Please upload using the same template as download.,يرجى تحميل باستخدام نفس القالب التحميل. Plugin,المساعد Port,ميناء Post,بعد @@ -771,13 +823,20 @@ Post already exists. Cannot add again!,آخر موجود بالفعل. لا يم Post does not exist. Please add post!,آخر غير موجود. الرجاء إضافة آخر ! Post to user,أضف إلى مستخدم Posts,المشاركات -Previous Record,سجل السابق +Previous Record,السجل السابق Primary,أساسي Print,طباعة Print Format,طباعة شكل +Print Format Help,تنسيق الطباعة مساعدة Print Format Type,طباعة نوع تنسيق +Print Format {0} does not exist,تنسيق الطباعة {0} غير موجود +Print Format {0} is disabled,تنسيق الطباعة {0} تم تعطيل Print Hide,طباعة إخفاء +Print Settings,إعدادات الطباعة +Print Style,الطباعة ستايل +Print Style Preview,معاينة قبل الطباعة ستايل Print Width,طباعة العرض +"Print with Letterhead, unless unchecked in a particular Document",طباعة مع رأسية، إلا إذا لم يتم التحقق منه في مستند خاص Print...,طباعة ... Printing and Branding,الطباعة و العلامات التجارية Priority,أفضلية @@ -789,20 +848,21 @@ Property Type,نوع الملكية Public,جمهور Published,نشرت Published On,نشرت يوم +Published on website at: {0},نشرت على موقعه على الانترنت على العنوان التالي: {0} Pull Emails from the Inbox and attach them as Communication records (for known contacts).,سحب رسائل البريد الإلكتروني من علبة الوارد وإرفاقها كسجلات الاتصالات (الاتصالات المعروفة لل). Query,سؤال Query Options,خيارات الاستعلام Query Report,الاستعلام عن Query must be a SELECT,يجب أن يكون الاستعلام SELECT -Quick Help for Permission Restrictions,مساعدة سريعة ل تقييد إذن Quick Help for Setting Permissions,مساعدة سريعة لوضع ضوابط +Quick Help for User Permissions,مساعدة سريعة ل ضوابط العضو Re-open,إعادة فتح Read,قرأ Read Only,للقراءة فقط Received,تلقى Recipient,مستلم Recipients,المستلمين -Record does not exist,لا وجود سجل +Record does not exist,سجل غير موجود Ref DocType,المرجع DOCTYPE Ref Name,المرجع اسم Ref Type,المرجع نوع @@ -816,7 +876,6 @@ Refreshing...,منعش ... Registered but disabled.,سجل لكن تعطيل. Registration Details Emailed.,تفاصيل التسجيل عبر البريد الالكتروني. Reload Page,تحديث الصفحة -Remove,نزع Remove Bookmark,أضف إزالة Remove all customizations?,إزالة كافة التخصيصات ؟ Rename many items by uploading a .csv file.,إعادة تسمية العديد من العناصر عن طريق تحميل ملف CSV . @@ -831,22 +890,20 @@ Report Builder reports are managed directly by the report builder. Nothing to do Report Hide,تقرير إخفاء Report Name,تقرير الاسم Report Type,نوع التقرير +Report an Issue,أبلغ عن مشكلة Report cannot be set for Single types,لا يمكن تعيين التقرير لأنواع واحدة +Report this issue,بلغ عن هذه المسألة Report was not saved (there were errors),لم يتم حفظ التقرير (كانت هناك أخطاء) Reqd,Reqd Reset Password Key,إعادة تعيين كلمة المرور الرئيسية Reset Permissions for {0}?,إعادة تعيين أذونات ل {0} ؟ +Response,الإستجابة Restrict IP,تقييد IP -Restrict cannot be set for Single types,تقيد لا يمكن تعيين لأنواع واحدة Restrict user from this IP address only. Multiple IP addresses can be added by separating with commas. Also accepts partial IP addresses like (111.111.111),تقييد المستخدم من هذا العنوان IP فقط. يمكن إضافة عناوين IP متعددة عن طريق فصل بفواصل. يقبل أيضا عناوين IP جزئية مثل (111.111.111) -Restricted,مقيد -Restricted To,المقيدة لل -Restricting Users,تقييد المستخدمين -Restriction,تقييد -Restriction added,وأضاف قيود Right,حق Role,دور Role Name,دور الاسم +Role Permissions Manager,مدير ضوابط دور Role and Level,دور و المستوى Role exists,وجود دور Roles,الأدوار @@ -856,11 +913,15 @@ Roles HTML,الأدوار HTML Roles can be set for users from their User page.,الأدوار يمكن تعيين للمستخدمين من صفحة المستخدم الخاصة بهم. Root {0} cannot be deleted,الجذر {0} لا يمكن حذف Row,صف +Row #{0}:,الصف # {0}: Rules defining transition of state in the workflow.,قواعد تحديد الانتقال من الدولة في سير العمل. "Rules for how states are transitions, like next state and which role is allowed to change state etc.",ويسمح النظام لكيفية قيام الولايات التحولات، مثل الدولة والتي المقبل دور في تغيير الدولة الخ. +Run scheduled jobs only if checked,تشغيل المهام المجدولة إلا إذا دققت +Run the report first,تشغيل التقرير الأول SMTP Server (e.g. smtp.gmail.com),خادم SMTP (smtp.gmail.com مثلا) Sales,مبيعات -Same file has already been attached to the record,وقد تم بالفعل تعلق نفس الملف إلى السجل +Same file has already been attached to the record,نفس الملف تم إرفاقه إلى هذا السجل +Sample,العينة Saturday,السبت Save,حفظ Scheduler Log,جدولة دخول @@ -869,6 +930,8 @@ Script Report,تقرير النصي Script Type,نوع البرنامج النصي Search,البحث Search Fields,البحث الحقول +Search in a document type,بحث في نوع الوثيقة +Search or type a command,بحث أو كتابة أمر Section Break,قسم استراحة Security,أمن Security Settings,إعدادات الأمان @@ -882,19 +945,24 @@ Select Report Name,حدد اسم التقرير Select Role,حدد دور Select To Download:,اختار ل تحميل : Select Type,حدد نوع -Select User or Property to start.,حدد المستخدم أو عقار للبدء. +Select User or DocType to start.,حدد العضو أو DOCTYPE للبدء. Select a Banner Image first.,تحديد صورة بانر الأول. Select an image of approx width 150px with a transparent background for best results.,اختر صورة من تقريبا عرض 150px مع خلفية شفافة للحصول على أفضل النتائج. -Select dates to create a new ,قم بتحديد مواعيد لخلق جديد +Select dates to create a new , "Select modules to be shown (based on permission). If hidden, they will be hidden for all users.",حدد وحدات ليتم عرضها (على أساس إذن ) . إذا مخفي ، وسوف تكون مخفية لجميع المستخدمين. Select or drag across time slots to create a new event.,حدد أو اسحب عبر فتحات الوقت لإنشاء حدث جديد. "Select target = ""_blank"" to open in a new page.","حدد الهدف = "" _blank "" لفتح صفحة جديدة في ." Select the label after which you want to insert new field.,حدد التسمية بعد الذي تريد إدراج حقل جديد. Send,إرسال +Send Alert On,إرسال تنبيه في Send As Email,أرسل للبريد الالكتروني +Send Email,إرسال البريد الإلكتروني +Send Email Print Attachments as PDF (Recommended),إرسال البريد الإلكتروني المرفقات طباعة بصيغة PDF (مستحسن) Send Me A Copy,أرسل لي نسخة Send Password,إرسال كلمة المرور -Send Print in Body and Attachment,إرسال طباعة في الجسم والتعلق +Send Print as PDF,إرسال طباعة بصيغة PDF +Send alert if date matches this field's value,إرسال تنبيه إذا تاريخ مباريات قيمة هذا الحقل +Send alert if this field's value changes,إرسال تنبيه إذا كانت التغييرات قيمة هذا الحقل Send an email reminder in the morning,إرسال رسالة تذكير في الصباح Send download link of a recent backup to System Managers,إرسال رابط التحميل من نسخة احتياطية حديثة ل مديري النظام Send enquiries to this email address,إرسال الاستفسارات إلى عنوان البريد الإلكتروني هذا @@ -903,22 +971,25 @@ Sent,أرسلت Sent Mail,إرسال بريد Sent Quotation,أرسلت اقتباس Sent or Received,المرسلة أو المتلقاة -Serial No,المسلسل لا Series,سلسلة Series {0} already used in {1},سلسلة {0} تستخدم بالفعل في {1} Server,خادم +Server & Credentials,الخادم وثائق التفويض +Server Error: Please check your server logs or contact tech support.,خطأ الخادم : يرجى مراجعة سجلات الخادم الخاص بك أو الاتصال بالدعم التكنولوجيا . Session Expired. Logging you out,انتهى الدورة. تسجيل خروجك Session Expiry,الدورة انتهاء الاشتراك Session Expiry in Hours e.g. 06:00,انتهاء الاشتراك في الدورة ساعات مثلا 06:00 Session Expiry must be in format {0},يجب أن تكون الدورة انتهاء الصلاحية في شكل {0} Set Banner from Image,تعيين راية من الصورة -Set Defaults and Restrictions for Users,تعيين افتراضيات و قيود للمستخدمين Set Link,تعيين لينك Set Login and Password if authentication is required.,تعيين كلمة المرور وتسجيل الدخول إذا كان مطلوبا المصادقة. Set Only Once,تعيين مرة واحدة فقط Set Password,تعيين كلمة المرور Set Permissions on Document Types and Roles,مجموعة ضوابط على أنواع المستندات والأدوار +Set Permissions per User,مجموعة ضوابط لكل مستخدم +Set User Permissions,تعيين أذونات المستخدم Set Value,تعيين القيمة +"Set default format, page size, print style etc.",تعيين تنسيق افتراضي، حجم الصفحة، نمط الطباعة الخ Set numbering series for transactions.,تعيين ترقيم سلسلة للمعاملات . Set outgoing mail server.,تعيين ملقم البريد الصادر . Settings,إعدادات @@ -926,18 +997,19 @@ Settings for About Us Page.,من نحن إعدادات الصفحة. Settings for Contact Us Page.,إعدادات الاتصال بنا الصفحة. Setup,الإعداد Setup > User,الإعداد > العضو -Setup > User Restriction,الإعداد > تقييد العضو +Setup > User Permissions Manager,الإعداد > ضوابط العضو مدير +Setup Email Alert based on various criteria.,إعداد تنبيه البريد الإلكتروني استنادا إلى معايير مختلفة. Setup of fonts and background.,إعداد الخطوط والخلفية. "Setup of top navigation bar, footer and logo.",الإعداد من أعلى الملاحة تذييل وبار والشعار. Short Bio,بيو قصيرة Short Name,الاسم المختصر Shortcut,الاختصار Show / Hide Modules,إظهار / إخفاء وحدات -Show Details,عرض التفاصيل Show Print First,تظهر أولا طباعة -Show Tags,تظهر الكلمات +Show Tags,إظهار الوسوم Show or hide modules globally.,إظهار أو إخفاء وحدات على مستوى العالم. Show rows with zero values,عرض الصفوف مع قيم الصفر +Show tags,تظهر به Show this field as title,تظهر هذا المجال كما لقب Showing only for (if not empty),تظهر فقط ل (إن لم يكن فارغ) Sidebar,الشريط الجانبي @@ -949,7 +1021,6 @@ Sign up,الاشتراك Single Post (article).,مشاركة واحدة (المادة). Single Types have only one record no tables associated. Values are stored in tabSingles,أنواع واحد يكون سجل واحد فقط لا الجداول المرتبطة . يتم تخزين القيم في tabSingles Sitemap Browser,متصفح خريطة الموقع -Sitemap Ordering Error. Index {0} missing for {0},خطأ الترتيب خريطة الموقع. مؤشر {0} في عداد المفقودين ل {0} Slideshow,عرض الشرائح Slideshow Items,عرض الشرائح عناصر Slideshow Name,العرض اسم @@ -959,10 +1030,13 @@ Solid background color (default light gray),لون الخلفية الصلبة ( Sorry we were unable to find what you were looking for.,وآسف نتمكن من العثور على ما كنت تبحث عنه. Sorry you are not permitted to view this page.,عذرا غير مسموح لك بعرض هذه الصفحة. Sort By,فرز حسب +Sort Field,نوع الحقل Sort Order,ترتيب Standard,معيار Standard Print Format cannot be updated,لا يمكن تحديث تنسيق الطباعة القياسية -Standard Reports,تقارير القياسية +Standard Reply,معيار الرد +Standard Reports,تقارير قياسية +Standard replies to common queries.,الردود على الاستفسارات القياسية الشائعة. Start Report For,تقرير عن بدء Starts on,يبدأ يوم State,دولة @@ -981,7 +1055,7 @@ Subject,موضوع Submit,عرض Submit an Issue,يقدم العدد Submitted,المقدمة -Submitted Record cannot be deleted,لا يمكن حذف سجل المقدمة +Submitted Document cannot be converted back to draft. Transition row {0},الوثيقة المقدمة لا يمكن تحويلها إلى مشروع . Success,نجاح Suggestion,اقتراح Sunday,الأحد @@ -1002,33 +1076,37 @@ Target,الهدف Tasks,المهام Team Members,أعضاء الفريق Team Members Heading,الأعضاء فريق عنوان +Template,ال Template Pages cannot be moved,قالب الصفحات لا يمكن نقل -Template Path,مسار القالب Test,اختبار Test Runner,اختبار عداء Text,نص Text Align,محاذاة النص Text Editor,النص محرر +Thank you for your message,شكرا لرسالتك The name of your company / website as you want to appear on browser title bar. All pages will have this as the prefix to the title.,اسم الشركة / الموقع كما تريد أن يظهر على شريط العنوان في المتصفح. وسوف يكون هذا كل الصفحات كما البادئة على اللقب. The system provides many pre-defined roles. You can add new roles to set finer permissions.,ويوفر النظام العديد من أدوار محددة مسبقا. يمكنك إضافة أدوار جديدة لتعيين أذونات الدقيقة. Then By (optional),ثم (اختياري) +There should remain at least one System Manager,ينبغي أن تظل هناك إدارة نظام واحد على الأقل There were errors,كانت هناك أخطاء There were errors while sending email. Please try again.,كانت هناك أخطاء أثناء إرسال البريد الإلكتروني. يرجى المحاولة مرة أخرى. -These restrictions will apply for all transactions linked to the restricted record.,سيتم تطبيق هذه القيود لجميع المعاملات المرتبطة إلى السجل المقيدة. +"There were some errors setting the name, please contact the administrator",كانت هناك بعض الأخطاء تحديد اسم، يرجى الاتصال بمسؤول +"These permissions will apply for all transactions where the permitted record is linked. For example, if Company C is added to User Permissions of user X, user X will only be able to see transactions that has company C as a linked value.",سيتم تطبيق هذه الأذونات لجميع المعاملات حيث يتم ربط السجل المسموح بها. These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.,وسيتم تحديث هذه القيم تلقائيا في المعاملات وأيضا سوف تكون مفيدة لتقييد الأذونات لهذا المستخدم على المعاملات التي تحتوي على هذه القيم. -These will also be set as default values for those links.,كما سيتم تعيين هذه كقيم الافتراضي ل هذه الروابط . +"These will also be set as default values for those links, if only one such permission record is defined.",كما سيتم تعيين هذه كقيم الافتراضي ل هذه الروابط ، إذا تم تعريف سجل إذن واحد فقط من هذا القبيل. Third Party Authentication,مصادقة طرف ثالث -"This field will appear only if the fieldname defined here has value OR the rules are true (examples):
      -myfield -eval:doc.myfield=='My Value'
      -eval:doc.age>18", +This field will appear only if the fieldname defined here has value OR the rules are true (examples):
      myfieldeval:doc.myfield=='My Value'
      eval:doc.age>18,سيظهر هذا المجال إلا إذا كان تعريف fieldname هنا له قيمة أو قواعد صحيحة (أمثلة):
      myfield وحدة التقييم: doc.myfield == 'بلدي القيمة'
      وحدة التقييم: doc.age> 18 This goes above the slideshow.,هذا يذهب فوق عرض الشرائح. This is PERMANENT action and you cannot undo. Continue?,هذا هو العمل الدائم ويمكنك التراجع لا. المتابعة؟ +"This is a standard format. To make changes, please copy it make make a new format.",هذه هي صيغة قياسية. لإجراء تغييرات، يرجى نسخه جعل جعل شكل جديد. This is permanent action and you cannot undo. Continue?,هذا هو العمل الدائم ويمكنك التراجع لا. المتابعة؟ -This role can restrict users for accessing the record.,هذا الدور يمكن تقييد المستخدمين للوصول إلى السجل. +This must be checked if Style Settings are applicable,هذا يجب أن يتم التحقق إذا إعدادات نمط قابلة للتطبيق +This role update User Permissions for a user,هذا الدور ضوابط التحديث العضو لمستخدم Thursday,الخميس +Tile,قرميدة Time,مرة Time Zone,منطقة زمنية +Timezone,التوقيت Title,لقب Title / headline of your page,عنوان / عنوان الصفحة الخاصة بك Title Case,عنوان القضية @@ -1037,10 +1115,9 @@ Title Prefix,عنوان الاختصار Title field must be a valid fieldname,يجب أن يكون حقل العنوان ل fieldname صالحة Title is required,مطلوب عنوان To,إلى -To Do,هل ل +To Do,قائمة المهام "To format columns, give column labels in the query.",لتنسيق الأعمدة، وإعطاء تسميات الأعمدة في الاستعلام. -"To give acess to a role for only specific records, check the 'Restricted' perimssion. User Restriction Records are used to restrict users with such role to specific records.",لإعطاء رصا إلى دور للسجلات معينة فقط ، والتحقق من perimssion ' المحظورة ' . وتستخدم السجلات تقييد المستخدم لتقييد المستخدمين مع هذا الدور لسجلات محددة. -"To report an issue, go to ", +"To give acess to a role for only specific records, check the 'Apply User Permissions'. User Permissions are used to limit users with such role to specific records.","لإعطاء رصا إلى دور للسجلات معينة فقط ، والتحقق من ""تطبيق أذونات المستخدم ' ." "To run a test add the module name in the route after '{0}'. For example, {1}",لتشغيل اختبار إضافة اسم وحدة في الطريق بعد '{0}'. على سبيل المثال، {1} ToDo,قائمة المهام Too many writes in one request. Please send smaller requests,الكثير من يكتب في طلب واحد . يرجى إرسال طلبات أصغر @@ -1062,22 +1139,21 @@ Unable to load: {0},غير قادر على تحميل: {0} Unable to open attached file. Please try again.,غير قادر على فتح الملف المرفق. يرجى المحاولة مرة أخرى. Unable to send emails at this time,غير قادر على إرسال رسائل البريد الإلكتروني في هذا الوقت Unknown Column: {0},غير معروف العمود: {0} -Unknown Print Format: {0},تنسيق طباعة غير معروف: {0} "Unknown file encoding. Tried utf-8, windows-1250, windows-1252.",ترميز الملفات غير معروف . حاول UTF- 8 ، ويندوز 1250، ويندوز 1252 . Unread Messages,رسائل غير مقروءة Unsubscribe,إلغاء الاشتراك Unsubscribed,إلغاء اشتراكك -Up,فوق Upcoming Events for Today,الأحداث القادمة لهذا اليوم Update,تحديث Update Field,تحديث الميدانية -Update Parent,التحديث الرئيسي Update Value,تحديث القيمة Updated,تحديث Upload,تحميل Upload Attachment,تحميل المرفقات +Upload CSV file containing all user permissions in the same format as Download.,تحميل ملف CSV يحتوي على جميع أذونات المستخدم في نفس الشكل كما تحميل. Upload a file,تحميل ملف Upload and Import,تحميل واستيراد +Upload and Sync,تحميل ومزامنة Uploading...,تحميل ... Upvotes,Upvotes Use TLS,استخدام TLS @@ -1087,16 +1163,16 @@ User Cannot Search,لا يمكن للمستخدم البحث User Defaults,الملف الشخصي الافتراضيات User ID of a blog writer.,هوية المستخدم من بلوق كاتبا. User Image,صورة العضو -User Permission Restrictions,تقييد إذن المستخدم -User Properties,خصائص المستخدم -User Restrictions,تقييد المستخدم +User Permission,إذن المستخدم +User Permissions,ضوابط المستخدم +User Permissions Manager,مدير ضوابط المستخدم User Roles,أدوار المستخدم User Tags,الكلمات المستخدم User Type,نوع المستخدم "User Type ""System User"" can access Desktop. ""Website User"" can only be logged into the website and portal pages. ", User Vote,تصويت المستخدم -User not allowed to delete.,المستخدم لا يسمح لحذفه. -User restrictions should not apply for this Link,يجب أن لا يتم فرض قيود المستخدم لهذا الرابط +User not allowed to delete {0}: {1},المستخدم لا يسمح لحذف {0}: {1} +User permissions should not apply for this Link,لا ينبغي تطبيق أذونات المستخدم لهذا الرابط User {0} cannot be deleted,المستخدم {0} لا يمكن حذف User {0} cannot be disabled,المستخدم {0} لا يمكن تعطيل User {0} cannot be renamed,المستخدم {0} لا يمكن إعادة تسمية @@ -1104,7 +1180,11 @@ User {0} does not exist,المستخدم {0} غير موجود UserRole,UserRole Users and Permissions,المستخدمين وأذونات Users with role {0}:,المستخدمين مع دور {0} : +Valid Login id required.,صالحة معرف تسجيل الدخول المطلوبة. +Valid email and name required,بريد إلكتروني صالح واسم المطلوب Value,قيمة +Value Change,قيمة التغيير +Value Changed,قيمة تغيير Value cannot be changed for {0},لا يمكن تغيير القيمة ل {0} Value for a check field can be either 0 or 1,قيمة لحقل الاختيار يمكن أن يكون إما 0 أو 1 Value missing for,قيمة مفقودة لل @@ -1114,6 +1194,7 @@ Version restored,النسخة استعادة View or manage Website Route tree.,عرض أو إدارة شجرة الموقع الطريق . Visit,زيارة Warning,تحذير +Warning: This Print Format is in old style and cannot be generated via the API.,تحذير: هذا تنسيق الطباعة في النمط القديم، ولا يمكن أن تتولد عن طريق API. Web Page,صفحة على الإنترنت Web Site Forum Page.,الموقع على شبكة الإنترنت المنتدى الصفحة . Website,الموقع @@ -1121,10 +1202,9 @@ Website Group,موقع مجموعة Website Route,خريطة الموقع الموقع Website Route Permission,الطريق إذن الموقع Website Script,الموقع سكربت -Website Settings,موقع إعدادات +Website Settings,إعدادات الموقع Website Slideshow,موقع عرض الشرائح Website Slideshow Item,موقع السلعة عرض شرائح -Website Template,الموقع خريطة الموقع التكوين Website User,موقع العضو Wednesday,الأربعاء Welcome email sent,نرحب البريد الإلكتروني المرسلة @@ -1133,6 +1213,7 @@ Width,عرض Will be used in url (usually first name).,وسوف تستخدم في رابط (عادة الاسم الأول). With Groups,مع المجموعات With Ledgers,مع سجلات الحسابات +With Letterhead,مع رأسية Workflow,سير العمل Workflow Action,سير العمل العمل Workflow Action Name,سير العمل اسم العمل @@ -1153,17 +1234,21 @@ Year,عام Yes,نعم Yesterday,أمس You are not allowed to create / edit reports,لا يسمح لك لإنشاء تقارير / تحرير -You are not allowed to create {0},لا يسمح لك لخلق {0} You are not allowed to export the data of: {0}. Downloading empty template.,لا يسمح لك ل تصدير البيانات من : {0} . تحميل قالب فارغ. You are not allowed to export this report,لا يسمح لك لتصدير هذا التقرير You are not allowed to print this document,لا يسمح لك طباعة هذه الوثيقة You are not allowed to send emails related to this document,لا يسمح لك بإرسال رسائل البريد الإلكتروني ذات الصلة لهذه الوثيقة "You can change Submitted documents by cancelling them and then, amending them.",يمكنك تغيير الوثائق المقدمة من إلغائها ، ثم تعديلها . You can use Customize Form to set levels on fields.,يمكنك استخدام نموذج تخصيص ل تحديد مستويات على الحقول . +You can use wildcard %,يمكنك استخدام حرف البدل٪ +You cannot install this app,لا يمكنك تثبيت هذا التطبيق +You have unsaved changes in this form. Please save before you continue.,لديك تغييرات لم يتم حفظها في هذا النموذج. You need to be logged in and have System Manager Role to be able to access backups.,تحتاج إلى أن تقوم بتسجيل الدخول ولها دور مدير النظام أن يكون قادرا على الوصول إلى النسخ الاحتياطي. You need write permission to rename,كنت بحاجة إلى كتابة إذن لإعادة تسمية +You seem to have written your name instead of your email. \ Please enter a valid email address so that we can get back.,يبدو أنك قد كتبت اسمك بدلا من البريد الإلكتروني الخاص بك. \ الرجاء إدخال عنوان بريد إلكتروني صالح حتى نتمكن من الحصول على الظهر. "Your download is being built, this may take a few moments...",ويجري بناء التنزيل، وهذا قد يستغرق بضع لحظات ... [Label]:[Field Type]/[Options]:[Width],[تسمية]: [نوع الحقل] / [خيارات]: [العرض] +[Optional] Send the email X days in advance of the specified date. 0 equals same day.,[اختياري] إرسال البريد الإلكتروني X أيام قبل التاريخ المحدد. 0 يساوي نفس اليوم. add your own CSS (careful!),إضافة CSS الخاصة بك (careful!) adjust,ضبط align-center,محاذاة الوسط @@ -1202,6 +1287,7 @@ cog,تحكم في comment,تعليق comments,تعليقات dd-mm-yyyy,DD-MM-YYYY +dd.mm.yyyy,dd.mm.yyyy dd/mm/yyyy,اليوم / الشهر / السنة download,تحميل download-alt,تحميل بديل @@ -1223,7 +1309,7 @@ folder-close,المجلد مسافة قريبة folder-open,فتح مجلد font,الخط forward,إلى الأمام -found,أسس +found,موجود fullscreen,ملء الشاشة gift,هدية glass,زجاج @@ -1316,9 +1402,11 @@ tint,لون to,إلى trash,القمامة upload,تحميل +use % as wildcard,استخدام٪ كما البدل user,مستخدم user_image_show,user_image_show values and dates,القيم والتواريخ +values separated by commas,قيم مفصولة بفواصل volume-down,حجم إلى أسفل volume-off,حجم حالا volume-up,حجم المتابعة @@ -1327,21 +1415,28 @@ wrench,وجع yyyy-mm-dd,YYYY-MM-DD zoom-in,التكبير في zoom-out,تكبير المغادرة +{0} List,{0} قائمة +{0} added,{0} أضاف {0} by {1},{0} بواسطة {1} +{0} cannot be set for Single types,{0} لا يمكن تعيين لأنواع واحدة {0} does not exist in row {1},{0} غير موجود في الصف {1} {0} in row {1} cannot have both URL and child items,{0} في {1} الصف لا يمكن أن يكون كل عنوان URL و البنود الأطفال {0} is not a valid email id,{0} ليس معرف بريد إلكتروني صحيح {0} is required,{0} مطلوب {0} is saved,{0} تم حفظها {0} must be one of {1},{0} يجب أن يكون واحدا من {1} +{0} must be set first,{0} يجب تعيين أول +{0} not a valid State,{0} يست دولة صالحة {0} not allowed in fieldname {1},{0} غير مسموح به في fieldname {1} {0} not allowed in name,{0} غير مسموح به في الاسم {0} not allowed to be renamed,{0} غير مسموح به ويجب إعادة تسميته {0} updated,{0} تحديث {0} {1} already exists,{0} {1} موجود بالفعل +"{0} {1} cannot be ""{2}"". It should be one of ""{3}""","{0} {1} لا يمكن أن يكون ""{2}"". ينبغي أن يكون واحدا من ""{3}""" {0} {1} cannot be a leaf node as it has children,{0} {1} لا يمكن أن يكون عقدة ورقة كما فعلت الأطفال "{0} {1} does not exist, select a new target to merge",{0} {1} غير موجود ، حدد هدفا جديدا لدمج -{0} {1} must be a valid {2},{0} {1} يجب أن تكون صالحة {2} +{0} {1} not found,{0} {1} غير موجود +{0} {1}: Submitted Record cannot be deleted.,{0} {1}: لا يمكن حذف سجل نشره. {0}: Cannot set Amend without Cancel,{0} : لا يمكن تعيين تعدل دون الغاء {0}: Cannot set Assign Amend if not Submittable,{0} : لا يمكن تعيين تعيين يعدل إن لم يكن Submittable {0}: Cannot set Assign Submit if not Submittable,{0} : لا يمكن تعيين تعيين إرسال إذا لم Submittable @@ -1351,6 +1446,6 @@ zoom-out,تكبير المغادرة {0}: Cannot set import as {1} is not importable,{0} : لا يمكن تعيين استيراد ك {1} ليس ارداتها "{0}: Create, Submit, Cancel and Amend only valid at level 0",{0} : إنشاء ، إرسال ، تعديل و الغاء صالحة فقط على مستوى 0 {0}: No basic permissions set,{0} : لا تعيين أذونات الأساسية -{0}: Only one rule allowed at a Role and Level,{0} : واحد فقط حكم يسمح في دور و المستوى +"{0}: Only one rule allowed with the same Role, Level and Apply User Permissions",{0} : واحد فقط حكم يسمح بنفس الدور ، و مستوى تطبيق ضوابط العضو {0}: Permission at level 0 must be set before higher levels are set,{0} : إذن على مستوى 0 يجب تعيين قبل أن يتم تحديد مستويات أعلى {app_title},{ app_title } diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv index c1f3f3b440..170ff91341 100644 --- a/frappe/translations/de.csv +++ b/frappe/translations/de.csv @@ -1,770 +1,878 @@ by Role ,von Rolle is not set,nicht gesetzt -""" does not exists",""" Existiert nicht" -"""Company History""",Firmengeschichte -"""Team Members"" or ""Management""","Teammitglieder oder ""Management""" +"""Company History""",„Unternehmensgeschichte“ +"""Team Members"" or ""Management""",„Teammitglieder“ oder „Management“ 'In List View' not allowed for type {0} in row {1},""" In der Listenansicht "" nicht erlaubt für Typ {0} in Zeile {1}" -'link:' type Select {0} getting replaced,""" link: "" Typ Wählen Sie {0} immer ersetzt" -"000 is black, fff is white","000 ist schwarz, weiß fff" -2 days ago,Vor 2 Tagen +0 - Draft; 1 - Submitted; 2 - Cancelled,0 - Entwurf; 1 - eingereicht; 2 - Abgesagt +0 is highest,0 höchsten ist +"000 is black, fff is white","000 ist schwarz, fff ist weiß" +2 days ago,vor 2 Tagen "[?]"," [?] " -"\ -
    5. field:[fieldname] - By Field\ -
    6. naming_series: - By Naming Series (field called naming_series must be present\ -
    7. Prompt - Prompt user for a name\ -
    8. [series] - Series by prefix (separated by a dot); for example PRE.#####\ -
    ')"">Naming Options", -A user can be restricted to multiple records of the same type.,Ein Benutzer kann mehrere Datensätze vom gleichen Typ beschränkt. -About,Über -About Us Settings,Über uns Settings -About Us Team Member,Über uns Team Member +"\
  • field:[fieldname] - By Field\
  • naming_series: - By Naming Series (field called naming_series must be present\
  • Prompt - Prompt user for a name\
  • [series] - Series by prefix (separated by a dot); for example PRE.#####\')"">Naming Options", +new type of document, neue Art von Dokument +"document type..., e.g. customer"," Dokumententyp ... , z. B. Kunden " +e.g. (55 + 434) / 4 or =Math.sin(Math.PI/2)..., zB (55 + 434) / 4 oder = Math.sin (Math.PI / 2) ... +module name..., Modulnamen ... +text in document type, Text in Dokumenttyp +A user can be permitted to multiple records of the same DocType.,Ein Benutzer kann die Genehmigung für mehrere Datensätze des gleichen DocType haben. +About,Info +About Us Settings,Über uns Einstellungen +About Us Team Member,Über uns Team-Mitglied Action,Aktion -Actions,Aktionen "Actions for workflow (e.g. Approve, Cancel).","Aktionen für Workflows (z. B. genehmigen , Abbruch) ." Add,Hinzufügen -Add A New Rule,Fügen Sie eine neue Regel -Add A Restriction,In einem Restriktions -Add Attachments,Anhänge hinzufügen +Add A New Rule,Hinzufügen einer neuen Regel +Add A User Permission,Fügen Sie ein Benutzerberechtigung +Add Attachments,Hinzufügen von Dateianhängen Add Bookmark,Lesezeichen hinzufügen -Add CSS,Fügen Sie CSS +Add CSS,CSS hinzufügen Add Column,Spalte hinzufügen -Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.,In Google Analytics ID: zB. UA-89XXX57-1. Bitte suchen Sie Hilfe zu Google Analytics für weitere Informationen. +Add Filter, +Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.,Google Analytics-ID hinzufügen: z. B. UA-89XXX57-1. Weitere Informationen finden Sie bei Google Analytics. Add Message,Nachricht hinzufügen -Add New Permission Rule,Add New Permission Rule -Add Reply,Fügen Sie Antworten -Add Serial No,In Seriennummer -Add This To User's Restrictions,Fügen Sie diese auf Benutzer- Einschränkungen -Add Total Row,In insgesamt Row +Add New Permission Rule,Neue Berechtigungsregel hinzufügen +Add Reply,Antwort hinzufügen +Add Tag, +Add Total Row,Gesamtzeile hinzufügen Add a New Role,Fügen Sie eine neue Rolle -Add a banner to the site. (small banners are usually good),Hinzufügen einen Banner auf der Website. (Kleine Banner sind in der Regel gut) -Add all roles,In alle Rollen +Add a banner to the site. (small banners are usually good),Der Website ein Werbebanner hinzufügen. (kleine Banner sind in der Regel gut) +Add all roles,Alle Rollen hinzufügen Add attachment,Anhang hinzufügen -Add code as <script>,Fügen Sie Code wie ") - .html(block_data) - .appendTo("body"); - } else if(stype==="script_lib") { - // render once - if(!$("[data-block-html='script_lib'][data-path='"+data.path+"']").length) { - $("") - .html(data.script_lib) - .appendTo("body"); - } - } else { - $section.html(block_data); - } - }); - if(data.title) $("title").html(data.title); + if (data.reload) { + window.location.reload(); + return; + } + + $('[data-html-block]').each(function(i, section) { + var $section = $(section); + var stype = $section.attr("data-html-block"); + + + // handle meta separately + if (stype==="meta_block") return; - // change id of current page - $(".page-container").attr("id", "page-" + data.path); + var block_data = data[stype] || ""; + + // NOTE: use frappe.ready instead of $.ready for reliable execution + if(stype==="script") { + $section.remove(); + $("") + .html(block_data) + .appendTo("body"); + } else if(stype==="script_lib") { + // render once + if(!$("[data-block-html='script_lib'][data-path='"+data.path+"']").length) { + $("") + .html(data.script_lib) + .appendTo("body"); + } + } else { + $section.html(block_data); + } + }); + if(data.title) $("title").html(data.title); - window.ga && ga('send', 'pageview', location.pathname); - $(document).trigger("page-change"); + // change meta tags + $('[data-html-block="meta_block"]').remove(); + if(data.meta_block) { + $("head").append(data.meta_block); } - }, - set_force_reload: function(reload) { - // learned this from twitter's implementation - window.history.replaceState({"reload": reload}, - window.document.title, location.href); + + // change id of current page + $(".page-container").attr("id", "page-" + data.path); + + // clear page-header-right + $(".page-header-right").html(""); + + window.ga && ga('send', 'pageview', location.pathname); + $(document).trigger("page-change"); }, supports_pjax: function() { return (window.history && window.history.pushState && window.history.replaceState && @@ -337,20 +366,19 @@ $.extend(frappe, { $(".navbar a.active").removeClass("active"); $(".navbar a").each(function() { var href = $(this).attr("href"); - if(pathname.indexOf(href)===0) { - var more = pathname.replace(href, ""); - if(!more || more.substr(0, 1)==="/") { - $(this).addClass("active"); - return false; - } + if(href===pathname) { + $(this).addClass("active"); + return false; } }) }, toggle_template_blocks: function() { // this assumes frappe base template - $(".page-header").toggleClass("hidden", !!!$("[data-html-block='header']").text().trim()); - $(".page-footer").toggleClass("hidden", !!!$(".page-footer").text().trim()); - $(".page-header-right").empty(); + $(".page-header").toggleClass("hidden", + !!!$("[data-html-block='header']").text().trim()); + + $(".page-footer").toggleClass("hidden", + !!!$(".page-footer").text().trim()); // hide breadcrumbs if no breadcrumb content or if it is same as the header $("[data-html-block='breadcrumbs'] .breadcrumb").toggleClass("hidden", diff --git a/frappe/website/page/sitemap_browser/sitemap_browser.js b/frappe/website/page/sitemap_browser/sitemap_browser.js deleted file mode 100644 index 392853e341..0000000000 --- a/frappe/website/page/sitemap_browser/sitemap_browser.js +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -// MIT License. See license.txt" - -frappe.pages['sitemap-browser'].onload = function(wrapper) { - frappe.ui.make_app_page({ - parent: wrapper, - title: 'Sitemap Browser', - }); - wrapper.appframe.add_module_icon("Website") - - wrapper.appframe.set_title_right('Refresh', function() { - frappe.website.sitemap.tree.rootnode.reload(); - }); - - $(wrapper) - .find(".layout-side-section") - .html('
    '+ - __('Click on a link to get options to expand get options ') + - __('Add') + ' / ' + __('Edit') + ' / '+ __('Remove') + '.
    ') - - frappe.website.sitemap = new frappe.website.SitemapBrowser( - $(wrapper) - .find(".layout-main-section") - .css({ - "min-height": "300px", - "padding-bottom": "25px" - })); -} - -frappe.provide("frappe.website"); - -frappe.website.SitemapBrowser = Class.extend({ - init: function(parent) { - $(parent).empty(); - var me = this; - this.tree = new frappe.ui.Tree({ - parent: $(parent), - label: "Sitemap", - method: 'frappe.website.page.sitemap_browser.sitemap_browser.get_children', - toolbar: [ - { - toggle_btn: true, - }, - { - label: __("Update Parent"), - click: function(node, btn) { - me.update_parent(); - } - }, - { - label: __("Up"), - click: function(node, btn) { - me.up_or_down("up"); - } - }, - { - label: __("Down"), - click: function(node, btn) { - me.up_or_down("down"); - } - }, - { - label: __("Open"), - click: function(node, btn) { - frappe.set_route("Form", node.data.ref_doctype, node.data.docname); - } - } - ] - - // drop: function(dragged_node, dropped_node, dragged_element, dropped_element) { - // frappe.website.sitemap.update_parent(dragged_node.label, dropped_node.label, function(r) { - // if(!r.exc) { - // dragged_element.remove(); - // dropped_node.reload(); - // } - // }); - // } - }); - this.tree.rootnode.$a - .data('node-data', {value: "Sitemap", expandable:1}) - .click(); - }, - selected_node: function() { - return this.tree.$w.find('.tree-link.selected'); - }, - open: function() { - var node = this.selected_node(); - frappe.set_route("Form", "Website Route", node.data("label")); - }, - up_or_down: function(up_or_down) { - var node = this.tree.get_selected_node(); - frappe.call({ - method: "frappe.website.page.sitemap_browser.sitemap_browser.move", - args: { - "name": node.label, - "up_or_down": up_or_down - }, - callback: function(r) { - if(r.message==="ok") { - node.parent.insertBefore(up_or_down==="up" ? - node.parent.prev() : node.parent.next().next()); - //(node.parent_node || node).reload(); - } - } - }); - }, - update_parent: function() { - var me = this; - if(!this.move_dialog) { - this.move_dialog = new frappe.ui.Dialog({ - title: __("Move"), - fields: [ - { - fieldtype: "Link", - fieldname: "new_parent", - label: "New Parent", - reqd: 1, - options: "Website Route" - }, - { - fieldtype: "Button", - fieldname: "update", - label: "Update", - } - ] - }); - this.move_dialog.get_input("update").on("click", function() { - var node = me.tree.get_selected_node(); - var values = me.move_dialog.get_values(); - if(!values) return; - me._update_parent(node.label, values.new_parent, function(r) { - me.move_dialog.hide(); - (node.parent_node || node).reload(); - }) - }); - } - this.move_dialog.show(); - this.move_dialog.get_input("new_parent").val(""); - }, - _update_parent: function(name, parent, callback) { - frappe.call({ - method: "frappe.website.page.sitemap_browser.sitemap_browser.update_parent", - args: { - "name": name, - "new_parent": parent - }, - callback: function(r) { - callback(r); - } - }); - - } -}); \ No newline at end of file diff --git a/frappe/website/page/sitemap_browser/sitemap_browser.json b/frappe/website/page/sitemap_browser/sitemap_browser.json deleted file mode 100644 index 4cbbdf9bb6..0000000000 --- a/frappe/website/page/sitemap_browser/sitemap_browser.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "creation": "2014-02-18 10:47:22.000000", - "docstatus": 0, - "doctype": "Page", - "icon": "icon-sitemap", - "idx": 1, - "modified": "2014-02-18 10:47:22.000000", - "modified_by": "Administrator", - "module": "Website", - "name": "sitemap-browser", - "owner": "Administrator", - "page_name": "sitemap-browser", - "roles": [ - { - "role": "Website Manager" - }, - { - "role": "System Manager" - } - ], - "standard": "Yes", - "title": "Sitemap Browser" -} \ No newline at end of file diff --git a/frappe/website/page/sitemap_browser/sitemap_browser.py b/frappe/website/page/sitemap_browser/sitemap_browser.py deleted file mode 100644 index 67d85b444b..0000000000 --- a/frappe/website/page/sitemap_browser/sitemap_browser.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.website.render import clear_cache - -@frappe.whitelist() -def get_children(parent=None): - if not frappe.has_permission("Website Route"): - raise frappe.PermissionError - - if parent=="Sitemap": - parent = "" - - return frappe.db.sql("""select name as value, 1 as expandable, ref_doctype, docname - from `tabWebsite Route` where - ifnull(parent_website_route, '')=%s - order by ifnull(idx,0), name asc""", parent, as_dict=True) - -@frappe.whitelist() -def move(name, up_or_down): - ret = None - if not frappe.has_permission("Website Route"): - raise frappe.PermissionError - - sitemap = frappe.get_doc("Website Route", name) - if up_or_down=="up": - if sitemap.idx > 0: - prev = frappe.get_doc("Website Route", { - "parent_website_route": sitemap.parent_website_route, - "idx": sitemap.idx - 1 - }) - if prev.name: - prev.idx = prev.idx + 1 - prev.save() - - sitemap.idx = sitemap.idx - 1 - sitemap.save() - ret = "ok" - - else: - nexts = frappe.get_doc("Website Route", { - "parent_website_route": sitemap.parent_website_route, - "idx": sitemap.idx + 1 - }) - if nexts.name: - nexts.idx = nexts.idx - 1 - nexts.save() - - sitemap.idx = sitemap.idx + 1 - sitemap.save() - ret = "ok" - - clear_cache() - return ret - -@frappe.whitelist() -def update_parent(name, new_parent): - if not frappe.has_permission("Website Route"): - raise frappe.PermissionError - - sitemap = frappe.get_doc("Website Route", name) - - if sitemap.ref_doctype: - generator = frappe.get_doc(sitemap.ref_doctype, sitemap.docname) - if not generator.meta.get_field("parent_website_route"): - frappe.throw(_("Not allowed to move")) - generator.parent_website_route = new_parent - generator.save() - else: - frappe.msgprint(_("Template Pages cannot be moved")) - - clear_cache() diff --git a/frappe/website/permissions.py b/frappe/website/permissions.py index 4c418f2d3f..3c7817e784 100644 --- a/frappe/website/permissions.py +++ b/frappe/website/permissions.py @@ -4,67 +4,66 @@ from __future__ import unicode_literals import frappe - def remove_empty_permissions(): - permissions_cache_to_be_cleared = frappe.db.sql_list("""select distinct user + permissions_cache_to_be_cleared = frappe.db.sql_list("""select distinct user from `tabWebsite Route Permission` where ifnull(`read`, 0)=0 and ifnull(`write`, 0)=0 and ifnull(`admin`, 0)=0""") - + frappe.db.sql("""delete from `tabWebsite Route Permission` where ifnull(`read`, 0)=0 and ifnull(`write`, 0)=0 and ifnull(`admin`, 0)=0""") - + clear_permissions(permissions_cache_to_be_cleared) -def get_access(sitemap_page, user=None): +def get_access(doc, pathname, user=None): user = user or frappe.session.user key = "website_route_permissions:{}".format(user) - + cache = frappe.cache() permissions = cache.get_value(key) or {} - if not permissions.get(sitemap_page): - permissions[sitemap_page] = _get_access(sitemap_page, user) + if not permissions.get(doc.name): + permissions[doc.name] = _get_access(doc, pathname, user) cache.set_value(key, permissions) - - return permissions.get(sitemap_page) - -def _get_access(sitemap_page, user): - lft, rgt, public_read, public_write, page_or_generator = frappe.db.get_value("Website Route", sitemap_page, - ["lft", "rgt", "public_read", "public_write", "page_or_generator"]) + return permissions.get(doc.name) + +def _get_access(doc, pathname, user): read = write = admin = private_read = 0 - if page_or_generator=="Generator": - - if not (lft and rgt): - raise frappe.ValidationError("Please rebuild Website Route Tree") - - if user == "Guest": - return { "read": public_read, "write": 0, "admin": 0 } - - - if public_write: - read = write = 1 - elif public_read: - read = 1 - - for perm in frappe.db.sql("""select wsp.`read`, wsp.`write`, wsp.`admin`, - ws.lft, ws.rgt, ws.name - from `tabWebsite Route Permission` wsp, `tabWebsite Route` ws - where wsp.user = %s and wsp.website_route = ws.name - order by lft asc""", (user,), as_dict=True): - if perm.lft <= lft and perm.rgt >= rgt: - if not (public_read or private_read): private_read = perm.read - if not read: read = perm.read - if not write: write = perm.write - if not admin: admin = perm.admin - if write: read = write - - if read and write and admin: - break - + if user == "Guest": + return { "read": doc.public_read, "write": 0, "admin": 0 } + + if doc.public_write: + read = write = 1 + elif doc.public_read: + read = 1 + + for perm in frappe.db.sql("""select + `tabWebsite Route Permission`.`read`, + `tabWebsite Route Permission`.`write`, + `tabWebsite Route Permission`.`admin`, + `tabWebsite Group`.lft, + `tabWebsite Group`.rgt + from + `tabWebsite Route Permission`, `tabWebsite Group` + where + `tabWebsite Route Permission`.website_route = %s and + `tabWebsite Route Permission`.user = %s and + `tabWebsite Route Permission`.reference = `tabWebsite Group`.name + order by `tabWebsite Group`.lft asc""", (user, pathname), as_dict=True): + if perm.lft <= doc.lft and perm.rgt >= doc.rgt: + if not (doc.public_read or private_read): + private_read = perm.read + if not read: read = perm.read + if not write: write = perm.write + if not admin: admin = perm.admin + if write: read = write + + if read and write and admin: + break + else: read = write = admin = private_read = 1 - + return { "read": read, "write": write, "admin": admin, "private_read": private_read } def clear_permissions(users=None): @@ -72,7 +71,7 @@ def clear_permissions(users=None): users = [users] elif users is None: users = frappe.db.sql_list("""select name from `tabUser`""") - + cache = frappe.cache() for user in users: cache.delete_value("website_route_permissions:{}".format(user)) diff --git a/frappe/website/render.py b/frappe/website/render.py index 7fa6645569..17c9af9398 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -3,12 +3,15 @@ from __future__ import unicode_literals import frappe +from frappe import _ +from frappe.utils import cstr import mimetypes, json from werkzeug.wrappers import Response from frappe.website.context import get_context -from frappe.website.utils import scrub_relative_urls, get_home_page, can_cache +from frappe.website.utils import scrub_relative_urls, get_home_page, can_cache, delete_page_cache from frappe.website.permissions import clear_permissions +from frappe.website.router import clear_sitemap class PageNotFoundError(Exception): pass @@ -21,7 +24,7 @@ def render(path, http_status_code=None): except frappe.DoesNotExistError, e: doctype, name = get_doctype_from_path(path) if doctype and name: - path = "view" + path = "print" frappe.local.form_dict.doctype = doctype frappe.local.form_dict.name = name elif doctype: @@ -34,10 +37,10 @@ def render(path, http_status_code=None): try: data = render_page(path) except frappe.PermissionError, e: - data, http_status_code = render_403(e) + data, http_status_code = render_403(e, path) except frappe.PermissionError, e: - data, http_status_code = render_403(e) + data, http_status_code = render_403(e, path) except Exception: path = "error" @@ -47,10 +50,13 @@ def render(path, http_status_code=None): return build_response(path, data, http_status_code or 200) -def render_403(e): +def render_403(e, pathname): path = "message" - frappe.local.message = "Did you log out?" - frappe.local.message_title = "Not Permitted" + frappe.local.message = """

    {error}

    +

    + %s order by lft asc""", (sitemap_options.lft, sitemap_options.rgt), as_dict=True) - - if not sitemap_options.no_sidebar: - sitemap_options.children = get_route_children(sitemap_options.pathname, home_page) - - if not sitemap_options.children and sitemap_options.parent_website_route \ - and sitemap_options.parent_website_route!=home_page: - sitemap_options.children = get_route_children(sitemap_options.parent_website_route, home_page) - - # determine templates to be used - if not sitemap_options.base_template_path: - app_base = frappe.get_hooks("base_template") - sitemap_options.base_template_path = app_base[0] if app_base else "templates/base.html" - - return sitemap_options - -def get_route_children(pathname, home_page=None): - if not home_page: - home_page = get_home_page() - - if pathname==home_page or not pathname: - children = frappe.db.sql("""select url as name, label as page_title, - 1 as public_read from `tabTop Bar Item` where parentfield='sidebar_items' order by idx""", - as_dict=True) - else: - children = frappe.db.sql("""select * from `tabWebsite Route` - where ifnull(parent_website_route,'')=%s - and public_read=1 - order by idx, page_title asc""", pathname, as_dict=True) - - if children: - # if children are from generator and sort order is specified, then get that condition - website_template = frappe.get_doc("Website Template", children[0].website_template) - if website_template.sort_by!="name": - children = frappe.db.sql("""select t1.* from - `tabWebsite Route` t1, `tab{ref_doctype}` t2 - where ifnull(t1.parent_website_route,'')=%s - and t1.public_read=1 - and t1.docname = t2.name - order by t2.{sort_by} {sort_order}""".format(**website_template.as_dict()), - pathname, as_dict=True) - - children = [frappe.get_doc("Website Route", pathname)] + children - - return children diff --git a/frappe/website/statics.py b/frappe/website/statics.py index aeb55f76e0..e963c522f1 100644 --- a/frappe/website/statics.py +++ b/frappe/website/statics.py @@ -2,55 +2,55 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe, os, time +import frappe, os, time, sys -from frappe import _ -from frappe.utils import cint -from markdown2 import markdown -from frappe.website.sitemap import get_route_children +from frappe.utils import update_progress_bar -def sync_statics(): +def sync_statics(rebuild=False): s = sync() + s.verbose = True while True: - s.start() + s.start(rebuild) frappe.db.commit() time.sleep(2) + rebuild = False class sync(object): - def start(self): + def __init__(self, verbose=False): + self.verbose = verbose + + def start(self, rebuild=False): self.synced = [] + self.synced_paths = [] + self.to_insert = [] + self.to_update = [] self.updated = 0 + self.rebuild = rebuild for app in frappe.get_installed_apps(): self.sync_for_app(app) + self.insert_and_update() self.cleanup() - if self.updated: - print str(self.updated) + " files updated" - def sync_for_app(self, app): self.statics_path = frappe.get_app_path(app, "templates", "statics") if os.path.exists(self.statics_path): for basepath, folders, files in os.walk(self.statics_path): self.sync_folder(basepath, folders, files) - def sync_folder(self, basepath, folders, files): - folder_route = os.path.relpath(basepath, self.statics_path) self.get_index_txt(basepath, files) - self.sync_index_page(basepath, files) + index_found = self.sync_index_page(basepath, files) - if not frappe.db.exists("Website Route", folder_route) and basepath!=self.statics_path: + if not index_found and basepath!=self.statics_path: # not synced either by generator or by index.html return if self.index: self.sync_using_given_index(basepath, folders, files) - else: self.sync_alphabetically(basepath, folders, [filename for filename in files if filename.endswith('html') or filename.endswith('md')]) - def get_index_txt(self, basepath, files): self.index = [] if "index.txt" in files: @@ -62,7 +62,7 @@ class sync(object): fname = "index." + extn if fname in files: self.sync_file(fname, os.path.join(basepath, fname), None) - return + return True def sync_using_given_index(self, basepath, folders, files): for i, page_name in enumerate(self.index): @@ -90,126 +90,102 @@ class sync(object): if not (page_name=="index" and basepath!=self.statics_path): self.sync_file(fname, os.path.join(basepath, fname), None) - def sync_file(self, fname, fpath, priority): - route = os.path.relpath(fpath, self.statics_path).rsplit(".", 1)[0] + def sync_file(self, fname, template_path, priority): + route = os.path.relpath(template_path, self.statics_path).rsplit(".", 1)[0] - if fname.rsplit(".", 1)[0]=="index" and os.path.dirname(fpath) != self.statics_path: + if fname.rsplit(".", 1)[0]=="index" and \ + os.path.dirname(template_path) != self.statics_path: route = os.path.dirname(route) - if route in self.synced: - return - parent_website_route = os.path.dirname(route) page_name = os.path.basename(route) + parent_web_page = os.path.basename(os.path.dirname(route)) + published = 1 + idx = priority + + if page_name in self.synced: + return + + title = self.get_title(template_path) - route_details = frappe.db.get_value("Website Route", route, - ["name", "idx", "static_file_timestamp", "docname"], as_dict=True) + if not frappe.db.get_value("Web Page", {"page_name":page_name}): + web_page = frappe.new_doc("Web Page") + web_page.page_name = page_name + web_page.parent_web_page = parent_web_page + web_page.template_path = template_path + web_page.title = title + web_page.published = published + web_page.idx = idx + web_page.from_website_sync = True + self.to_insert.append(web_page) - if route_details: - self.update_web_page(route_details, fpath, priority, parent_website_route) else: - # Route does not exist, new page - self.insert_web_page(route, fpath, page_name, priority, parent_website_route) + web_page = frappe.get_doc("Web Page", {"page_name":page_name}) + dirty = False + for key in ("parent_web_page", "title", "template_path", "published", "idx"): + if web_page.get(key) != locals().get(key): + web_page.set(key, locals().get(key)) + dirty = True - def insert_web_page(self, route, fpath, page_name, priority, parent_website_route): - page = frappe.get_doc({ - "doctype":"Web Page", - "idx": priority, - "page_name": page_name, - "published": 1, - "parent_website_route": parent_website_route - }) + if dirty: + web_page.from_website_sync = True + self.to_update.append(web_page) - page.update(get_static_content(fpath, page_name, route)) + self.synced.append(page_name) - try: - page.insert() - except frappe.NameError: - # page exists, if deleted static, delete it and try again - old_route = frappe.get_doc("Website Route", {"ref_doctype":"Web Page", - "docname": page.name}) - if old_route.static_file_timestamp and not os.path.exists(os.path.join(self.statics_path, - old_route.name)): + def get_title(self, fpath): + title = os.path.basename(fpath).rsplit(".", 1)[0] + if title =="index": + title = os.path.basename(os.path.dirname(fpath)) - frappe.delete_doc("Web Page", page.name) - page.insert() # retry + title = title.replace("-", " ").replace("_", " ").title() + with open(fpath, "r") as f: + content = unicode(f.read().strip(), "utf-8") - # update timestamp - route_doc = frappe.get_doc("Website Route", {"ref_doctype": "Web Page", - "docname": page.name}) - route_doc.static_file_timestamp = cint(os.path.getmtime(fpath)) - route_doc.save() + if content.startswith("# "): + title = content.splitlines()[0][2:] - self.updated += 1 - print route_doc.name + " inserted" - self.synced.append(route) + if "", 1)[0].strip() - def update_web_page(self, route_details, fpath, priority, parent_website_route): - if str(cint(os.path.getmtime(fpath)))!= route_details.static_file_timestamp \ - or (cint(route_details.idx) != cint(priority) and (priority is not None)): + return title - page = frappe.get_doc("Web Page", route_details.docname) - page.update(get_static_content(fpath, route_details.docname, route_details.name)) - page.idx = priority - page.save() + def insert_and_update(self): + if self.to_insert: + l = len(self.to_insert) + for i, page in enumerate(self.to_insert): + if self.verbose: + print "Inserting " + page.page_name + else: + update_progress_bar("Updating Static Pages", i, l) - route_doc = frappe.get_doc("Website Route", route_details.name) - route_doc.static_file_timestamp = cint(os.path.getmtime(fpath)) - route_doc.save() + page.insert() + if not self.verbose: print "" - print route_doc.name + " updated" - self.updated += 1 + if self.to_update: + for i, page in enumerate(self.to_update): + if not self.verbose: + print "Updating " + page.page_name + else: + sys.stdout.write("\rUpdating statics {0}/{1}".format(i+1, len(self.to_update))) + sys.stdout.flush() - self.synced.append(route_details.name) + page.save() + if not self.verbose: print "" def cleanup(self): if self.synced: - frappe.delete_doc("Web Page", frappe.db.sql_list("""select docname - from `tabWebsite Route` - where ifnull(static_file_timestamp,'')!='' and name not in ({}) - order by (rgt-lft) asc""".format(', '.join(["%s"]*len(self.synced))), - tuple(self.synced))) + # delete static web pages that are not in immediate list + frappe.delete_doc("Web Page", frappe.db.sql_list("""select name + from `tabWeb Page` + where ifnull(template_path,'')!='' + and name not in ({})""".format(', '.join(["%s"]*len(self.synced))), + tuple(self.synced)), force=1) else: - frappe.delete_doc("Web Page", frappe.db.sql_list("""select docname - from `tabWebsite Route` - where ifnull(static_file_timestamp,'')!='' - order by (rgt-lft) asc""")) - - -def get_static_content(fpath, docname, route): - d = frappe._dict({}) - with open(fpath, "r") as contentfile: - content = unicode(contentfile.read(), 'utf-8') - - if fpath.endswith(".md"): - if content: - lines = content.splitlines() - first_line = lines[0].strip() - - if first_line.startswith("# "): - d.title = first_line[2:] - content = "\n".join(lines[1:]) - - if "{index}" in content: - children = get_route_children(route) - html = frappe.get_template("templates/includes/static_index.html").render({ - "items":children}) - content = content.replace("{index}", html) - - content = markdown(content) - - d.main_section = unicode(content.encode("utf-8"), 'utf-8') - if not d.title: - d.title = docname.replace("-", " ").replace("_", " ").title() - - for extn in ("js", "css"): - fpath = fpath.rsplit(".", 1)[0] + "." + extn - if os.path.exists(fpath): - with open(fpath, "r") as f: - d["css" if extn=="css" else "javascript"] = f.read() + # delete all static web pages + frappe.delete_doc("Web Page", frappe.db.sql_list("""select name + from `tabWeb Page` + where ifnull(template_path,'')!=''"""), force=1) - d.insert_style = 1 if d.css else 0 - d.insert_code = 1 if d.javascript else 0 - return d diff --git a/frappe/website/template.py b/frappe/website/template.py index 34326b370d..c37585807f 100644 --- a/frappe/website/template.py +++ b/frappe/website/template.py @@ -27,7 +27,7 @@ def render_blocks(context): for block, render in template.blocks.items(): out[block] = scrub_relative_urls(concat(render(template.new_context(context)))) - _render_blocks(context["template_path"]) + _render_blocks(context["template"]) # default blocks if not found if "title" not in out and out.get("header"): @@ -39,16 +39,39 @@ def render_blocks(context): if "header" not in out and out.get("title"): out["header"] = out["title"] - if not out["header"].startswith("" if "breadcrumbs" not in out: out["breadcrumbs"] = scrub_relative_urls( frappe.get_template("templates/includes/breadcrumbs.html").render(context)) + if "meta_block" not in out: + out["meta_block"] = frappe.get_template("templates/includes/meta_block.html").render(context) + + + out["no_sidebar"] = context.get("no_sidebar", 0) + if "" in out.get("content", ""): out["no_sidebar"] = 1 + if "', out.get("content"))[0].strip() + + if "{index}" in out.get("content", "") and context.get("children"): + html = frappe.get_template("templates/includes/static_index.html").render({ + "items": context["children"]}) + out["content"] = out["content"].replace("{index}", html) + + if "{next}" in out.get("content", ""): + next_item = context.doc.get_next() + if next_item: + if next_item.name[0]!="/": next_item.name = "/" + next_item.name + html = '''


    + {title} +

    '''.format(**next_item) + out["content"] = out["content"].replace("{next}", html) + if "sidebar" not in out and not out.get("no_sidebar"): out["sidebar"] = scrub_relative_urls( frappe.get_template("templates/includes/sidebar.html").render(context)) diff --git a/frappe/website/utils.py b/frappe/website/utils.py index fd605316ec..c2270c9389 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -4,6 +4,14 @@ from __future__ import unicode_literals import frappe, re +def delete_page_cache(path): + if not path: + path = "" + cache = frappe.cache() + cache.delete_value("page:" + path) + cache.delete_value("page_context:" + path) + cache.delete_value("sitemap_options:" + path) + def scrub_relative_urls(html): """prepend a slash before a relative url""" html = re.sub("""(src|href)[^\w'"]*['"](?!http|ftp|/|#|%|{)([^'" >]+)['"]""", '\g<1> = "/\g<2>"', html) @@ -20,6 +28,12 @@ def find_first_image(html): def can_cache(no_cache=False): return not (frappe.conf.disable_website_cache or no_cache) +def get_comment_list(doctype, name): + return frappe.db.sql("""select + comment, comment_by_fullname, creation + from `tabComment` where comment_doctype=%s + and comment_docname=%s order by creation""", (doctype, name), as_dict=1) or [] + def get_home_page(): def _get_home_page(): role_home_page = frappe.get_hooks("role_home_page") diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index 285be473f5..e9cb8678dd 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -5,128 +5,198 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.model.naming import append_number_if_name_exists -from frappe.website.utils import cleanup_page_name +from frappe.website.utils import cleanup_page_name, get_home_page +from frappe.website.render import clear_cache from frappe.utils import now - -from frappe.website.doctype.website_route.website_route import add_to_sitemap, update_sitemap, remove_sitemap +from frappe.modules import get_module_name +from frappe.website.router import get_page_route class WebsiteGenerator(Document): + page_title_field = "name" def autoname(self): - self.setup_generator() - if not self.website_template: return - self.name = self.get_page_name() append_number_if_name_exists(self) - def set_page_name(self): - """set page name based on parent page_name and title""" - page_name = cleanup_page_name(self.get_page_title()) - - if self.is_new(): - self.set(self.website_template.page_name_field, page_name) - else: - frappe.db.set(self, self.website_template.page_name_field, page_name) - - return page_name - - def get_parent_website_route(self): - return self.parent_website_route + def onload(self): + self.get("__onload").website_route = self.get_route() - def setup_generator(self): - if not hasattr(self, "website_template"): - website_template = frappe.db.get_values("Website Template", - {"ref_doctype": self.doctype}, "*") - if website_template: - self.website_template = website_template[0] - else: - self.website_template = None + def validate(self): + self.set_parent_website_route() + if self.website_published() and self.meta.get_field("page_name") and not self.page_name: + self.page_name = self.get_page_name() + self.update_routes_of_descendants() def on_update(self): - self.update_sitemap() + clear_cache(self.get_route()) if getattr(self, "save_versions", False): frappe.add_version(self) - def after_rename(self, olddn, newdn, merge): - self.setup_generator() - if not self.website_template: return - - frappe.db.sql("""update `tabWebsite Route` - set docname=%s where ref_doctype=%s and docname=%s""", (newdn, self.doctype, olddn)) + def get_route(self, doc = None): + if not self.website_published(): + return None - if merge: - remove_sitemap(ref_doctype=self.doctype, docname=olddn) + self.get_page_name() + return make_route(self) - def on_trash(self): - self.setup_generator() - if not self.website_template: return - - remove_sitemap(ref_doctype=self.doctype, docname=self.name) + def get_page_name(self): + return self.get_or_make_page_name() - def update_sitemap(self): - self.setup_generator() - if not self.website_template: return + def get_or_make_page_name(self): + page_name = self.get("page_name") + if not page_name: + page_name = cleanup_page_name(self.get(self.page_title_field)) + self.set("page_name", page_name) - if self.website_template.condition_field and \ - not self.get(self.website_template.condition_field): - # condition field failed, remove and return! - remove_sitemap(ref_doctype=self.doctype, docname=self.name) - return + return page_name - self.add_or_update_sitemap() + def before_rename(self, oldname, name, merge): + self._local = self.get_route() + clear_cache(self.get_route()) - def add_or_update_sitemap(self): - page_name = self.get_page_name() + def after_rename(self, olddn, newdn, merge): + if getattr(self, "_local"): + self.update_routes_of_descendants(self._local) + clear_cache(self.get_route()) - existing_site_map = frappe.db.get_value("Website Route", {"ref_doctype": self.doctype, - "docname": self.name}) + def on_trash(self): + clear_cache(self.get_route()) - if self.modified: - lastmod = frappe.utils.get_datetime(self.modified).strftime("%Y-%m-%d") + def website_published(self): + if hasattr(self, "condition_field"): + return self.get(self.condition_field) and True or False else: - lastmod = now() - - opts = frappe._dict({ + return True + + def set_parent_website_route(self): + if hasattr(self, "parent_website_route_field"): + field = self.meta.get_field(self.parent_website_route_field) + parent = self.get(self.parent_website_route_field) + if parent: + self.parent_website_route = frappe.get_doc(field.options, + parent).get_route() + + def update_routes_of_descendants(self, old_route = None): + if not self.is_new(): + if not old_route: + old_route = frappe.get_doc(self.doctype, self.name).get_route() + + if old_route and old_route != self.get_route(): + frappe.db.sql("""update `tab{0}` set + parent_website_route = replace(parent_website_route, %s, %s) + where parent_website_route like %s""".format(self.doctype), + (old_route, self.get_route(), old_route + "%")) + + def get_website_route(self): + route = frappe._dict() + route.update({ + "doc": self, "page_or_generator": "Generator", "ref_doctype":self.doctype, "idx": self.idx, "docname": self.name, - "page_name": page_name, - "link_name": self.website_template.name, - "lastmod": lastmod, - "parent_website_route": self.get_parent_website_route(), - "page_title": self.get_page_title(), - "public_read": 1 if not self.website_template.no_sidebar else 0 + "page_name": self.get_page_name(), + "controller": get_module_name(self.doctype, self.meta.module), + "template": self.template, + "parent_website_route": self.get("parent_website_route", ""), + "page_title": self.get(self.page_title_field) }) - self.update_permissions(opts) + self.update_permissions(route) - if existing_site_map: - idx = update_sitemap(existing_site_map, opts) - else: - idx = add_to_sitemap(opts) - - if idx!=None and self.idx != idx: - frappe.db.set(self, "idx", idx) + return route - def update_permissions(self, opts): + def update_permissions(self, route): if self.meta.get_field("public_read"): - opts.public_read = self.public_read - opts.public_write = self.public_write + route.public_read = self.public_read + route.public_write = self.public_write else: - opts.public_read = 1 - - def get_page_name(self): - page_name = self._get_page_name() - if not page_name: - page_name = self.set_page_name() + route.public_read = 1 + + def get_parents(self): + parents = [] + parent = self + while parent: + _parent_field = getattr(parent, "parent_website_route_field", None) + _parent_val = parent.get(_parent_field) if _parent_field else None + if _parent_val: + df = parent.meta.get_field(_parent_field) + parent_doc = frappe.get_doc(df.options, _parent_val) + + if not parent_doc.website_published(): + break + + if parent_doc: + parent_info = frappe._dict(name = parent_doc.get_route(), + title= parent_doc.get(getattr(parent_doc, "page_title_field", "name"))) + else: + parent_info = frappe._dict(name=self.parent_website_route, + title=self.parent_website_route.replace("_", " ").title()) + + if parent_info.name in [p.name for p in parents]: + raise frappe.ValidationError, "Recursion in parent link" + + parents.append(parent_info) + parent = parent_doc + else: + # parent route is a page e.g. "blog" + if parent.get("parent_website_route"): + page_route = get_page_route(parent.parent_website_route) + if page_route: + parents.append(frappe._dict(name = page_route.name, + title=page_route.page_title)) + parent = None + + parents.reverse() + return parents + + def get_parent(self): + if hasattr(self, "parent_website_route_field"): + return self.get(self.parent_website_route_field) + + def get_children(self): + if self.get_route()==get_home_page(): + return frappe.db.sql("""select url as name, label as page_title, + 1 as public_read from `tabTop Bar Item` where parentfield='sidebar_items' + order by idx""", as_dict=True) + + if self.meta.get_field("parent_website_route"): + children = self.get_children_of(self.get_route()) + + if not children and self.parent_website_route: + children = self.get_children_of(self.parent_website_route) + + return children + else: + return [] + + def get_children_of(self, route): + children = frappe.db.sql("""select name, page_name, + parent_website_route, {title_field} as title from `tab{doctype}` + where ifnull(parent_website_route,'')=%s + order by {order_by}""".format( + doctype = self.doctype, + title_field = getattr(self, "page_title_field", "name"), + order_by = getattr(self, "order_by", "idx asc")), + route, as_dict=True) + + for c in children: + c.name = make_route(c) + + return children + + def get_next(self): + if self.meta.get_field("parent_website_route") and self.parent_website_route: + route = self.get_route() + siblings = frappe.get_doc(self.doctype, self.get_parent()).get_children() + for i, r in enumerate(siblings): + if i < len(siblings) - 1: + if route==r.name: + return siblings[i+1] + else: + return frappe._dict() - return self._get_page_name() +def make_route(doc): + parent = doc.get("parent_website_route", "") + return ((parent + "/") if parent else "") + doc.page_name - def _get_page_name(self): - if self.meta.get_field(self.website_template.page_name_field): - return self.get(self.website_template.page_name_field) - else: - return cleanup_page_name(self.get_page_title()) - def get_page_title(self): - return self.get("title") or (self.name.replace("-", " ").replace("_", " ").title()) diff --git a/frappe/widgets/form/assign_to.py b/frappe/widgets/form/assign_to.py index d984ec3e68..e4429b247a 100644 --- a/frappe/widgets/form/assign_to.py +++ b/frappe/widgets/form/assign_to.py @@ -14,22 +14,30 @@ def get(args=None): if not args: args = frappe.local.form_dict - get_docinfo(args.get("doctype"), args.get("name")) + get_docinfo(frappe.get_doc(args.get("doctype"), args.get("name"))) - return frappe.db.sql_list("""select owner from `tabToDo` + return frappe.db.sql("""select owner, description from `tabToDo` where reference_type=%(doctype)s and reference_name=%(name)s and status="Open" - order by modified desc limit 5""", args) + order by modified desc limit 5""", args, as_dict=True) @frappe.whitelist() def add(args=None): - """add in someone's to do list""" + """add in someone's to do list + args = { + "assign_to": , + "doctype": , + "name": , + "description": + } + + """ if not args: args = frappe.local.form_dict if frappe.db.sql("""select owner from `tabToDo` where reference_type=%(doctype)s and reference_name=%(name)s and status="Open" and owner=%(assign_to)s""", args): - frappe.msgprint(_("Already in todo"), raise_exception=True) + frappe.msgprint(_("Already in user's To Do list"), raise_exception=True) return else: from frappe.utils import nowdate @@ -50,15 +58,6 @@ def add(args=None): if frappe.get_meta(args['doctype']).get_field("assigned_to"): frappe.db.set_value(args['doctype'], args['name'], "assigned_to", args['assign_to']) - try: - if cint(args.get("restrict")): - from frappe.core.page.user_properties import user_properties - user_properties.add(args['assign_to'], args['doctype'], args['name']) - frappe.msgprint(_("Restriction added")) - except frappe.PermissionError: - frappe.throw(_("Not permitted to restrict User {0} for {1} {2}").format(args["assign_to"], - args["doctype"], args["name"])) - # notify if not args.get("no_notification"): notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN', description=args.get("description"), notify=args.get('notify')) @@ -68,16 +67,19 @@ def add(args=None): @frappe.whitelist() def remove(doctype, name, assign_to): """remove from todo""" - todo = frappe.get_doc("ToDo", {"reference_type":doctype, "reference_name":name, "owner":assign_to, "status":"Open"}) - todo.status = "Closed" - todo.save(ignore_permissions=True) + try: + todo = frappe.get_doc("ToDo", {"reference_type":doctype, "reference_name":name, "owner":assign_to, "status":"Open"}) + todo.status = "Closed" + todo.save(ignore_permissions=True) + + notify_assignment(todo.assigned_by, todo.owner, todo.reference_type, todo.reference_name) + except frappe.DoesNotExistError: + pass # clear assigned_to if field exists if frappe.get_meta(doctype).get_field("assigned_to"): frappe.db.set_value(doctype, name, "assigned_to", None) - notify_assignment(todo.assigned_by, todo.owner, todo.reference_type, todo.reference_name) - return get({"doctype": doctype, "name": name}) def clear(doctype, name): @@ -132,5 +134,4 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', arg["parenttype"] = "Assignment" from frappe.core.page.messages import messages - import json - messages.post(json.dumps(arg)) + messages.post(**arg) diff --git a/frappe/widgets/form/load.py b/frappe/widgets/form/load.py index 915037acab..d9356acc90 100644 --- a/frappe/widgets/form/load.py +++ b/frappe/widgets/form/load.py @@ -6,6 +6,7 @@ import frappe, json import frappe.utils import frappe.defaults import frappe.widgets.form.meta +from frappe.permissions import get_doc_permissions from frappe import _ @frappe.whitelist() @@ -27,14 +28,13 @@ def getdoc(doctype, name, user=None): try: doc = frappe.get_doc(doctype, name) - doc.set("__onload", frappe._dict()) - doc.run_method("onload") + run_onload(doc) if not doc.has_permission("read"): raise frappe.PermissionError, "read" # add file list - get_docinfo(doctype, name) + get_docinfo(doc) except Exception: frappe.errprint(frappe.utils.get_traceback()) @@ -61,7 +61,7 @@ def getdoctype(doctype, with_parent=False, cached_timestamp=None): if not docs: docs = get_meta_bundle(doctype) - frappe.response['restrictions'] = get_restrictions(docs[0]) + frappe.response['user_permissions'] = get_user_permissions(docs[0]) if cached_timestamp and docs[0].modified==cached_timestamp: return "use_cache" @@ -75,21 +75,22 @@ def get_meta_bundle(doctype): bundle.append(frappe.widgets.form.meta.get_meta(df.options)) return bundle -def get_docinfo(doctype, name): +def get_docinfo(doc): frappe.response["docinfo"] = { - "attachments": add_attachments(doctype, name), - "comments": add_comments(doctype, name), - "assignments": add_assignments(doctype, name) + "attachments": get_attachments(doc.doctype, doc.name), + "comments": get_comments(doc.doctype, doc.name), + "assignments": get_assignments(doc.doctype, doc.name), + "permissions": get_doc_permissions(doc) } -def get_restrictions(meta): +def get_user_permissions(meta): out = {} - all_restrictions = frappe.defaults.get_restrictions() - for df in meta.get_restricted_fields(all_restrictions): - out[df.options] = all_restrictions[df.options] + all_user_permissions = frappe.defaults.get_user_permissions() + for df in meta.get_fields_to_check_permissions(all_user_permissions): + out[df.options] = all_user_permissions[df.options] return out -def add_attachments(dt, dn): +def get_attachments(dt, dn): attachments = [] for f in frappe.db.sql("""select name, file_name, file_url from `tabFile Data` where attached_to_name=%s and attached_to_doctype=%s""", @@ -102,20 +103,20 @@ def add_attachments(dt, dn): return attachments -def add_comments(dt, dn, limit=20): - cl = frappe.db.sql("""select name, comment, comment_by, creation from `tabComment` +def get_comments(dt, dn, limit=20): + cl = frappe.db.sql("""select name, comment, comment_by, creation, comment_type from `tabComment` where comment_doctype=%s and comment_docname=%s - order by creation desc limit %s""" % ('%s','%s', limit), (dt, dn), as_dict=1) + order by creation asc limit %s""" % ('%s','%s', limit), (dt, dn), as_dict=1) return cl -def add_assignments(dt, dn): - cl = frappe.db.sql_list("""select owner from `tabToDo` +def get_assignments(dt, dn): + cl = frappe.db.sql("""select owner, description from `tabToDo` where reference_type=%(doctype)s and reference_name=%(name)s and status="Open" order by modified desc limit 5""", { "doctype": dt, "name": dn - }) + }, as_dict=True) return cl @@ -129,3 +130,7 @@ def get_badge_info(doctypes, filters): out[doctype] = frappe.db.get_value(doctype, filters, "count(*)") return out + +def run_onload(doc): + doc.set("__onload", frappe._dict()) + doc.run_method("onload") diff --git a/frappe/widgets/form/meta.py b/frappe/widgets/form/meta.py index 7209738e0a..c751305d90 100644 --- a/frappe/widgets/form/meta.py +++ b/frappe/widgets/form/meta.py @@ -5,10 +5,12 @@ from __future__ import unicode_literals import frappe, os -from frappe.utils import cstr, cint from frappe.model.meta import Meta -from frappe.modules import scrub, get_module_path +from frappe.modules import scrub, get_module_path, load_doctype_module from frappe.model.workflow import get_workflow_name +from frappe.utils import get_html_format +from frappe.translate import make_dict_from_messages, extract_messages_from_code +from frappe.utils.jinja import render_include ###### @@ -19,7 +21,7 @@ def get_meta(doctype, cached=True): meta = FormMeta(doctype) if frappe.local.lang != 'en': - meta.set("__messages", frappe.get_lang_dict("doctype", doctype)) + meta.set_translations(frappe.local.lang) return meta @@ -36,10 +38,13 @@ class FormMeta(Meta): self.add_code() self.load_print_formats() self.load_workflows() + self.load_templates() def as_dict(self, no_nulls=False): d = super(FormMeta, self).as_dict(no_nulls=no_nulls) - for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", "__linked_with", "__messages"): + for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", + "__linked_with", "__messages", "__print_formats", "__workflow_docs", + "__form_grid_templates", "__listview_template"): d[k] = self.get(k) for i, df in enumerate(d.get("fields")): @@ -57,20 +62,30 @@ class FormMeta(Meta): self._add_code(_get_path(self.name + '.css'), "__css") self._add_code(_get_path(self.name + '_list.js'), '__list_js') self._add_code(_get_path(self.name + '_calendar.js'), '__calendar_js') - self._add_code(_get_path(self.name + '_map.js'), '__map_js') - self.add_custom_script() + listview_template = _get_path(self.name + '_list.html') + if os.path.exists(listview_template): + self.set("__listview_template", get_html_format(listview_template)) + self.add_code_via_hook("doctype_js", "__js") + self.add_custom_script() def _add_code(self, path, fieldname): js = frappe.read_file(path) if js: - self.set(fieldname, (self.get(fieldname) or "") + "\n\n" + render_jinja(js)) + self.set(fieldname, (self.get(fieldname) or "") + "\n\n" + render_include(js)) def add_code_via_hook(self, hook, fieldname): - hook = "{}:{}".format(hook, self.name) for app_name in frappe.get_installed_apps(): - for file in frappe.get_hooks(hook, app_name=app_name): + code_hook = frappe.get_hooks(hook, default={}, app_name=app_name) + if not code_hook: + continue + + files = code_hook.get(self.name, []) + if not isinstance(files, list): + files = [files] + + for file in files: path = frappe.get_app_path(app_name, *file.strip("/").split("/")) self._add_code(path, fieldname) @@ -82,11 +97,6 @@ class FormMeta(Meta): self.set("__js", (self.get('__js') or '') + "\n\n" + custom) - def render_jinja(content): - if "{% include" in content: - content = frappe.get_jenv().from_string(content).render() - return content - def add_search_fields(self): """add search fields found in the doctypes indicated by link fields' options""" for df in self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]}): @@ -126,23 +136,44 @@ class FormMeta(Meta): self.set("__linked_with", ret) def load_print_formats(self): - frappe.response.docs.extend(frappe.db.sql("""select * FROM `tabPrint Format` - WHERE doc_type=%s AND docstatus<2""", (self.name,), as_dict=1, update={"doctype":"Print Format"})) + print_formats = frappe.db.sql("""select * FROM `tabPrint Format` + WHERE doc_type=%s AND docstatus<2 and ifnull(disabled, 0)=0""", (self.name,), as_dict=1, + update={"doctype":"Print Format"}) + self.set("__print_formats", print_formats) def load_workflows(self): # get active workflow workflow_name = get_workflow_name(self.name) + workflow_docs = [] if workflow_name and frappe.db.exists("Workflow", workflow_name): workflow = frappe.get_doc("Workflow", workflow_name) - frappe.response.docs.append(workflow) + workflow_docs.append(workflow) for d in workflow.get("workflow_document_states"): - frappe.response.docs.append(frappe.get_doc("Workflow State", d.state)) + workflow_docs.append(frappe.get_doc("Workflow State", d.state)) + + self.set("__workflow_docs", workflow_docs) + + + def load_templates(self): + module = load_doctype_module(self.name) + app = module.__name__.split(".")[0] + templates = {} + if hasattr(module, "form_grid_templates"): + for key, path in module.form_grid_templates.iteritems(): + templates[key] = get_html_format(frappe.get_app_path(app, path)) + + self.set("__form_grid_templates", templates) + + def set_translations(self, lang): + self.set("__messages", frappe.get_lang_dict("doctype", self.name)) + # set translations for grid templates + if self.get("__form_grid_templates"): + for content in self.get("__form_grid_templates").values(): + messages = extract_messages_from_code(content) + messages = make_dict_from_messages(messages) + self.get("__messages").update(messages) -def render_jinja(content): - if "{% include" in content: - content = frappe.get_jenv().from_string(content).render() - return content diff --git a/frappe/widgets/form/save.py b/frappe/widgets/form/save.py index 09b4d34199..dadb51cd92 100644 --- a/frappe/widgets/form/save.py +++ b/frappe/widgets/form/save.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe, json +from frappe.widgets.form.load import run_onload @frappe.whitelist() def savedocs(): @@ -21,6 +22,7 @@ def savedocs(): raise # update recent documents + run_onload(doc) frappe.user.update_recent(doc.doctype, doc.name) send_updated_docs(doc) @@ -44,7 +46,7 @@ def cancel(doctype=None, name=None): def send_updated_docs(doc): from load import get_docinfo - get_docinfo(doc.doctype, doc.name) + get_docinfo(doc) d = doc.as_dict() if hasattr(doc, 'localname'): diff --git a/frappe/widgets/form/utils.py b/frappe/widgets/form/utils.py index 2caa09cf2e..1440671cc6 100644 --- a/frappe/widgets/form/utils.py +++ b/frappe/widgets/form/utils.py @@ -13,7 +13,7 @@ def remove_attach(): """remove attachment""" import frappe.utils.file_manager fid = frappe.form_dict.get('fid') - frappe.utils.file_manager.remove_file(fid) + return frappe.utils.file_manager.remove_file(fid) @frappe.whitelist() def get_fields(): @@ -48,6 +48,9 @@ def validate_link(): # get fetch values if fetch: + # escape with "`" + fetch = ", ".join(("`{0}`".format(f.strip()) for f in fetch.split(","))) + frappe.response['fetch_values'] = [frappe.utils.parse_val(c) \ for c in frappe.db.sql("select %s from `tab%s` where name=%s" \ % (fetch, options, '%s'), (value,))[0]] @@ -63,15 +66,33 @@ def add_comment(doc): return doc.as_dict() @frappe.whitelist() -def get_next(doctype, name, prev): +def get_next(doctype, value, prev, filters=None, order_by="modified desc"): import frappe.widgets.reportview - prev = int(prev) - field = "`tab%s`.name" % doctype + prev = not int(prev) + sort_field, sort_order = order_by.split(" ") + + if not filters: filters = [] + if isinstance(filters, basestring): + filters = json.loads(filters) + + # condition based on sort order + condition = ">" if sort_order.lower()=="desc" else "<" + + # switch the condition + if prev: + condition = "<" if condition==">" else "<" + else: + sort_order = "asc" if sort_order.lower()=="desc" else "desc" + + # add condition for next or prev item + if not order_by[0] in [f[1] for f in filters]: + filters.append([doctype, sort_field, condition, value]) + res = frappe.widgets.reportview.execute(doctype, - fields = [field], - filters = [[doctype, "name", "<" if prev else ">", name]], - order_by = field + " " + ("desc" if prev else "asc"), + fields = ["name"], + filters = filters, + order_by = sort_field + " " + sort_order, limit_start=0, limit_page_length=1, as_list=True) if not res: @@ -81,7 +102,7 @@ def get_next(doctype, name, prev): return res[0][0] @frappe.whitelist() -def get_linked_docs(doctype, name, metadata_loaded=None): +def get_linked_docs(doctype, name, metadata_loaded=None, no_metadata=False): if not metadata_loaded: metadata_loaded = [] meta = frappe.widgets.form.meta.get_meta(doctype) linkinfo = meta.get("__linked_with") @@ -108,7 +129,7 @@ def get_linked_docs(doctype, name, metadata_loaded=None): if ret: results[dt] = ret - if not dt in metadata_loaded: + if not no_metadata and not dt in metadata_loaded: frappe.local.response.docs.extend(link_meta_bundle) diff --git a/frappe/widgets/moduleview.py b/frappe/widgets/moduleview.py index b1adaab896..462e498ae5 100644 --- a/frappe/widgets/moduleview.py +++ b/frappe/widgets/moduleview.py @@ -54,6 +54,9 @@ def build_standard_config(module): from `tabDocType` where module=%s and ifnull(istable, 0)=0 order by document_type desc, name asc""", module, as_dict=True) + for d in doctypes: + d.description = _(d.description or "") + documents = [d for d in doctypes if d.document_type in ("Transaction", "Master", "")] if documents: data.append({ @@ -178,7 +181,7 @@ def get_report_list(module, is_standard="No"): "type": "report", "doctype": r.ref_doctype, "is_query_report": 1 if r.report_type in ("Query Report", "Script Report") else 0, - "description": r.report_type, + "description": _(r.report_type), "label": _(r.name), "name": r.name }) diff --git a/frappe/widgets/page.py b/frappe/widgets/page.py index d586339146..8e1e26cdc2 100644 --- a/frappe/widgets/page.py +++ b/frappe/widgets/page.py @@ -1,8 +1,9 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# MIT License. See license.txt from __future__ import unicode_literals import frappe +from frappe.translate import send_translations @frappe.whitelist() def get(name): @@ -28,14 +29,14 @@ def getpage(): # load translations if frappe.lang != "en": - frappe.response["__messages"] = frappe.get_lang_dict("page", page) + send_translations(frappe.get_lang_dict("page", page)) frappe.response.docs.append(doc) def has_permission(page): if frappe.user.name == "Administrator" or "System Manager" in frappe.user.get_roles(): return True - + page_roles = [d.role for d in page.get("roles")] if page_roles: if frappe.session.user == "Guest" and "Guest" not in page_roles: @@ -43,9 +44,9 @@ def has_permission(page): elif not set(page_roles).intersection(set(frappe.get_roles())): # check if roles match return False - + if not frappe.has_permission("Page", ptype="read", doc=page): - # check if there are any restrictions + # check if there are any user_permissions return False else: # hack for home pages! if no page roles, allow everyone to see! diff --git a/frappe/widgets/query_report.py b/frappe/widgets/query_report.py index d6e5d2f7f3..95b2ce9b64 100644 --- a/frappe/widgets/query_report.py +++ b/frappe/widgets/query_report.py @@ -5,128 +5,145 @@ from __future__ import unicode_literals import frappe import os, json -import types from frappe import _ from frappe.modules import scrub, get_module_path -from frappe.utils import flt, cint +from frappe.utils import flt, cint, get_html_format +from frappe.translate import send_translations import frappe.widgets.reportview def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) if not doc.has_permission("read"): - raise frappe.PermissionError("You don't have access to: {report}".format(report=report_name)) - + frappe.throw(_("You don't have access to Report: {0}").format(report_name), frappe.PermissionError) + if not frappe.has_permission(doc.ref_doctype, "report"): - raise frappe.PermissionError("You don't have access to get a report on: {doctype}".format( - doctype=doc.ref_doctype)) - + frappe.throw(_("You don't have permission to get a report on: {0}").format(doc.ref_doctype), + frappe.PermissionError) + + if doc.disabled: + frappe.throw(_("Report {0} is disabled").format(report_name)) + return doc @frappe.whitelist() def get_script(report_name): report = get_report_doc(report_name) - - module = frappe.db.get_value("DocType", report.ref_doctype, "module") + + module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") module_path = get_module_path(module) report_folder = os.path.join(module_path, "report", scrub(report.name)) script_path = os.path.join(report_folder, scrub(report.name) + ".js") - + print_path = os.path.join(report_folder, scrub(report.name) + ".html") + script = None if os.path.exists(script_path): - with open(script_path, "r") as script: - script = script.read() - + with open(script_path, "r") as f: + script = f.read() + + html_format = get_html_format(print_path) + if not script and report.javascript: script = report.javascript - + if not script: script = "frappe.query_reports['%s']={}" % report_name - + # load translations if frappe.lang != "en": - frappe.response["__messages"] = frappe.get_lang_dict("report", report_name) - - return script + send_translations(frappe.get_lang_dict("report", report_name)) + + return { + "script": script, + "html_format": html_format + } @frappe.whitelist() def run(report_name, filters=()): report = get_report_doc(report_name) - + if filters and isinstance(filters, basestring): filters = json.loads(filters) if not frappe.has_permission(report.ref_doctype, "report"): - frappe.msgprint(_("Must have report permission to access this report."), + frappe.msgprint(_("Must have report permission to access this report."), raise_exception=True) - + + columns, results = [], [] if report.report_type=="Query Report": if not report.query: frappe.msgprint(_("Must specify a Query to run"), raise_exception=True) - - + + if not report.query.lower().startswith("select"): frappe.msgprint(_("Query must be a SELECT"), raise_exception=True) - + result = [list(t) for t in frappe.db.sql(report.query, filters)] columns = [c[0] for c in frappe.db.get_description()] else: - module = frappe.db.get_value("DocType", report.ref_doctype, "module") + module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") if report.is_standard=="Yes": - method_name = frappe.local.module_app[scrub(module)] + "." + scrub(module) \ - + ".report." + scrub(report.name) + "." + scrub(report.name) + ".execute" + method_name = get_report_module_dotted_path(module, report.name) + ".execute" columns, result = frappe.get_attr(method_name)(frappe._dict(filters)) - - result = get_filtered_data(report.ref_doctype, columns, result) - + + if report.apply_user_permissions: + result = get_filtered_data(report.ref_doctype, columns, result) + if cint(report.add_total_row) and result: result = add_total_row(result, columns) - + return { "result": result, "columns": columns } - + +def get_report_module_dotted_path(module, report_name): + return frappe.local.module_app[scrub(module)] + "." + scrub(module) \ + + ".report." + scrub(report_name) + "." + scrub(report_name) + def add_total_row(result, columns): total_row = [""]*len(columns) has_percent = [] for row in result: for i, col in enumerate(columns): - col = col.split(":") - if len(col) > 1: - if col[1] in ["Currency", "Int", "Float", "Percent"] and flt(row[i]): - total_row[i] = flt(total_row[i]) + flt(row[i]) - if col[1] == "Percent" and i not in has_percent: - has_percent.append(i) - + fieldtype = None + if isinstance(col, basestring): + col = col.split(":") + if len(col) > 1: + fieldtype = col[1] + else: + fieldtype = col.get("fieldtype") + + if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(row[i]): + total_row[i] = flt(total_row[i]) + flt(row[i]) + if fieldtype == "Percent" and i not in has_percent: + has_percent.append(i) + for i in has_percent: total_row[i] = total_row[i] / len(result) - - first_col = columns[0].split(":") - if len(first_col) > 1 and first_col[1] not in ["Currency", "Int", "Float", "Percent"]: + + first_col_fieldtype = None + if isinstance(columns[0], basestring): + first_col = columns[0].split(":") + if len(first_col) > 1: + first_col_fieldtype = first_col[1] + else: + first_col_fieldtype = columns[0].get("fieldtype") + + if first_col_fieldtype not in ["Currency", "Int", "Float", "Percent"]: total_row[0] = "Total" - + result.append(total_row) return result def get_filtered_data(ref_doctype, columns, data): result = [] - linked_doctypes = get_linked_doctypes(columns) - match_filters = get_user_match_filters(linked_doctypes, ref_doctype) - - if match_filters: - matched_columns = get_matched_columns(linked_doctypes, match_filters) + match_filters_per_doctype = get_user_match_filters(linked_doctypes, ref_doctype) + + if match_filters_per_doctype: for row in data: - match = True - - if not ("owner" in match_filters and matched_columns.get("user", None)==match_filters["owner"]): - for col, idx in matched_columns.items(): - if row[idx] not in match_filters[col]: - match = False - break - - if match: + if has_match(row, linked_doctypes, match_filters_per_doctype): result.append(row) else: for row in data: @@ -134,35 +151,60 @@ def get_filtered_data(ref_doctype, columns, data): return result +def has_match(row, linked_doctypes, doctype_match_filters): + resultant_match = True + + if not row: + # allow empty rows :) + return resultant_match + + for doctype, filter_list in doctype_match_filters.items(): + matched_for_doctype = False + + for match_filters in filter_list: + match = True + for dt, idx in linked_doctypes.items(): + if dt in match_filters and row[idx] not in match_filters[dt]: + match = False + break + + # each doctype could have multiple conflicting user permission doctypes, hence using OR + # so that even if one of the sets allows a match, it is true + matched_for_doctype = matched_for_doctype or match + + if matched_for_doctype: + break + + # each doctype's user permissions should match the row! hence using AND + resultant_match = resultant_match and matched_for_doctype + + if not resultant_match: + break + + return resultant_match + def get_linked_doctypes(columns): linked_doctypes = {} for idx, col in enumerate(columns): - col = col.split(":") - if len(col) > 1 and col[1].startswith("Link"): - link_dt = col[1].split("/")[1] - linked_doctypes[link_dt] = idx + if isinstance(col, basestring): + col = col.split(":") + if len(col) > 1 and col[1].startswith("Link"): + link_dt = col[1].split("/")[1] + linked_doctypes[link_dt] = idx + + # dict + elif col.get("fieldtype")=="Link" and col.get("options"): + linked_doctypes[col["options"]] = col["fieldname"] return linked_doctypes def get_user_match_filters(doctypes, ref_doctype): match_filters = {} - doctypes_meta = {} - tables = [] for dt in doctypes: - match_filters.update(frappe.widgets.reportview.build_match_conditions(dt, False)) + filter_list = frappe.widgets.reportview.build_match_conditions(dt, False) + if filter_list: + match_filters[dt] = filter_list return match_filters - -def get_matched_columns(linked_doctypes, match_filters): - if "owner" in match_filters: - match_filters["user"] = match_filters["owner"] - - col_idx_map = {} - for dt, idx in linked_doctypes.items(): - link_field = dt.lower().replace(" ", "_") - if link_field in match_filters: - col_idx_map[link_field] = idx - - return col_idx_map \ No newline at end of file diff --git a/frappe/widgets/reportview.py b/frappe/widgets/reportview.py index ad43f60545..304b874f21 100644 --- a/frappe/widgets/reportview.py +++ b/frappe/widgets/reportview.py @@ -123,14 +123,7 @@ def delete_items(): doctype = frappe.form_dict.get('doctype') for d in il: - try: - dt_obj = frappe.get_doc(doctype, d) - if hasattr(dt_obj, 'on_trash'): - dt_obj.on_trash() - frappe.delete_doc(doctype, d) - except Exception, e: - frappe.errprint(frappe.get_traceback()) - pass + frappe.delete_doc(doctype, d) @frappe.whitelist() def get_stats(stats, doctype): @@ -157,6 +150,8 @@ def scrub_user_tags(tagcount): rdict = {} tagdict = dict(tagcount) for t in tagdict: + if not t: + continue alltags = t.split(',') for tag in alltags: if tag: diff --git a/frappe/widgets/search.py b/frappe/widgets/search.py index 91a89baade..5be24c05f2 100644 --- a/frappe/widgets/search.py +++ b/frappe/widgets/search.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import frappe.widgets.reportview -from frappe.utils import cstr +from frappe.utils import cstr, unique # this is called by the Link Field @frappe.whitelist() @@ -62,19 +62,30 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, if txt: if meta.search_fields: for f in meta.get_search_fields(): - or_filters.append([doctype, f.strip(), "like", "%" + txt + "%"]) + or_filters.append([doctype, f.strip(), "like", "%{0}%".format(txt)]) else: - filters.append([doctype, searchfield or "name", "like", - "%" + txt + "%"]) + filters.append([doctype, searchfield or "name", "like", "%{0}%".format(txt)]) + if meta.get("fields", {"fieldname":"enabled", "fieldtype":"Check"}): filters.append([doctype, "enabled", "=", 1]) if meta.get("fields", {"fieldname":"disabled", "fieldtype":"Check"}): filters.append([doctype, "disabled", "!=", 1]) - frappe.response["values"] = frappe.widgets.reportview.execute(doctype, - filters=filters, fields = get_std_fields_list(meta, searchfield or "name"), + fields = get_std_fields_list(meta, searchfield or "name") + + # find relevance as location of search term from the beginning of string `name`. used for sorting results. + fields.append("""locate("{_txt}", `tab{doctype}`.`name`) as `_relevance`""".format( + _txt=frappe.db.escape((txt or "").replace("%", "")), doctype=doctype)) + + values = frappe.widgets.reportview.execute(doctype, + filters=filters, fields=fields, or_filters = or_filters, limit_start = start, - limit_page_length=page_len, as_list=True) + limit_page_length=page_len, + order_by="if(_relevance, _relevance, 99999), name asc".format(doctype), + as_list=True) + + # remove _relevance from results + frappe.response["values"] = [r[:-1] for r in values] def get_std_fields_list(meta, key): # get additional search fields @@ -88,7 +99,7 @@ def get_std_fields_list(meta, key): def build_for_autosuggest(res): results = [] for r in res: - out = {"value": r[0], "description": ", ".join(list(set(cstr(d) for d in r[1:])))} + out = {"value": r[0], "description": ", ".join(unique(cstr(d) for d in r)[1:])} results.append(out) return results diff --git a/frappe/widgets/tags.py b/frappe/widgets/tags.py index 7b0d438d26..1906eb0f82 100644 --- a/frappe/widgets/tags.py +++ b/frappe/widgets/tags.py @@ -1,5 +1,5 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# MIT License. See license.txt from __future__ import unicode_literals """ @@ -33,17 +33,17 @@ def check_user_tags(dt): except Exception, e: if e.args[0] == 1054: DocTags(dt).setup() - + @frappe.whitelist() def add_tag(): "adds a new tag to a record, and creates the Tag master" - + f = frappe.local.form_dict tag, color = f.get('tag'), f.get('color') dt, dn = f.get('dt'), f.get('dn') - + DocTags(dt).add(dn, tag) - + return tag @frappe.whitelist() @@ -51,23 +51,23 @@ def remove_tag(): "removes tag from the record" f = frappe.local.form_dict tag, dt, dn = f.get('tag'), f.get('dt'), f.get('dn') - + DocTags(dt).remove(dn, tag) - + class DocTags: """Tags for a particular doctype""" def __init__(self, dt): self.dt = dt - + def get_tag_fields(self): """returns tag_fields property""" return frappe.db.get_value('DocType', self.dt, 'tag_fields') - + def get_tags(self, dn): """returns tag for a particular item""" - return frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '' + return (frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '').strip() def add(self, dn, tag): """add a new user tag""" @@ -97,15 +97,15 @@ class DocTags: frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \ (self.dt,'%s','%s'), (tags , dn)) except Exception, e: - if e.args[0]==1054: + if e.args[0]==1054: if not tags: # no tags, nothing to do return - + self.setup() self.update(dn, tl) else: raise - + def setup(self): """adds the _user_tags column if not exists""" from frappe.model.db_schema import add_column diff --git a/requirements.txt b/requirements.txt index 76e3d85520..2b1c0cf3ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,10 +16,11 @@ slugify termcolor werkzeug semantic_version -lxml -inlinestyler rauth>=0.6.2 requests==1.2.3 celery redis selenium +pdfkit +babel +ipython diff --git a/setup.py b/setup.py index 0b8c925f7c..877718f0a6 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = '4.0.1' +version = "4.3.0" with open("requirements.txt", "r") as f: install_requires = f.readlines() diff --git a/test_sites/test_site/site_config.json b/test_sites/test_site/site_config.json index 1db5372d32..0d48a7d1be 100644 --- a/test_sites/test_site/site_config.json +++ b/test_sites/test_site/site_config.json @@ -1,4 +1,5 @@ { "db_name": "test_frappe", - "db_password": "test_frappe" + "db_password": "test_frappe", + "auto_email_id": "test@example.com" }