diff --git a/frappe/__init__.py b/frappe/__init__.py index 8d9eed74a3..89d71ce19e 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -16,7 +16,7 @@ import json from .exceptions import * -__version__ = "4.0.0" +__version__ = "4.0.1" local = Local() @@ -215,12 +215,20 @@ def set_user(username): 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): - import frappe.utils.email_lib - if as_markdown: - frappe.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message) +def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", + as_markdown=False, bulk=False): + + 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) + else: - frappe.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message) + import frappe.utils.email_lib + if as_markdown: + frappe.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message) + else: + frappe.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message) logger = None whitelisted = [] @@ -540,13 +548,15 @@ def build_match_conditions(doctype, as_condition=True): import frappe.widgets.reportview return frappe.widgets.reportview.build_match_conditions(doctype, as_condition) -def get_list(doctype, filters=None, fields=None, docstatus=None, +def get_list(doctype, filters=None, fields=None, or_filters=None, docstatus=None, group_by=None, order_by=None, limit_start=0, limit_page_length=None, as_list=False, debug=False, ignore_permissions=False): import frappe.model.db_query - return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters, fields=fields, docstatus=docstatus, - 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) + 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) run_query = get_list diff --git a/frappe/api.py b/frappe/api.py index b29874104e..cfa525eff6 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -44,11 +44,12 @@ def handle(): elif call=="resource": if "run_method" in frappe.local.form_dict: doc = frappe.get_doc(doctype, name) + doc.is_whitelisted(frappe.local.form_dict.run_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(frappe.local.form_dict.run_method, **frappe.local.form_dict) if frappe.local.request.method=="POST": if not doc.has_permission("write"): @@ -69,7 +70,7 @@ def handle(): doc = frappe.get_doc(doctype, name) # Not checking permissions here because it's checked in doc.save doc.update(data) - frappe.local.response.update({ + frappe.local.response.update({ "data": doc.save().as_dict() }) frappe.db.commit() diff --git a/frappe/boot.py b/frappe/boot.py index 868a5b978b..2bb61eefc4 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -34,7 +34,7 @@ def get_bootinfo(): bootinfo.modules = {} for app in frappe.get_installed_apps(): try: - bootinfo.modules.update(frappe.get_attr(app + ".config.desktop.data") or {}) + bootinfo.modules.update(frappe.get_attr(app + ".config.desktop.get_data")() or {}) except ImportError: pass diff --git a/frappe/celery_app.py b/frappe/celery_app.py index c7dedcf249..ecb2313bcb 100644 --- a/frappe/celery_app.py +++ b/frappe/celery_app.py @@ -49,11 +49,8 @@ def setup_celery(app, conf): if conf.celery_error_emails: app.conf.CELERY_SEND_TASK_ERROR_EMAILS = True - app.conf.ADMINS = conf.celery_error_email_recepients - if conf.mail_port: - app.conf.EMAIL_PORT = conf.mail_port - if conf.mail_server: - app.conf.EMAIL_HOST = conf.mail_server + for k, v in conf.celery_error_emails.iteritems(): + setattr(app.conf, k, v) class SiteRouter(object): def route_for_task(self, task, args=None, kwargs=None): diff --git a/frappe/cli.py b/frappe/cli.py index 1cda01b2fa..e961a83b77 100755 --- a/frappe/cli.py +++ b/frappe/cli.py @@ -155,6 +155,7 @@ def setup_test(parser): parser.add_argument("--tests", metavar="TEST FUNCTION", nargs="*", help="Run one or more specific test functions") parser.add_argument("--serve_test", action="store_true", help="Run development server for testing") + parser.add_argument("--driver", nargs="?", help="Run selenium using given driver") def setup_utilities(parser): @@ -231,6 +232,8 @@ def setup_utilities(parser): help="Clear cache, doctype cache and defaults") parser.add_argument("--reset_perms", default=False, action="store_true", help="Reset permissions for all doctypes") + parser.add_argument("--clear_all_sessions", default=False, action="store_true", + help="Clear sessions of all users (logs them out)") # scheduler parser.add_argument("--run_scheduler", default=False, action="store_true", @@ -307,7 +310,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) + add_to_installed_apps(each, rebuild_sitemap=False) frappe.destroy() @cmd @@ -364,11 +367,7 @@ def latest(rebuild_website_config=True, quiet=False): try: # run patches - frappe.local.patch_log_list = [] frappe.modules.patch_handler.run_all() - if verbose: - print "\n".join(frappe.local.patch_log_list) - # sync frappe.model.sync.sync_all(verbose=verbose) sync_fixtures() @@ -381,9 +380,6 @@ def latest(rebuild_website_config=True, quiet=False): frappe.translate.clear_cache() - except frappe.modules.patch_handler.PatchError: - print "\n".join(frappe.local.patch_log_list) - raise finally: frappe.destroy() @@ -399,9 +395,7 @@ def sync_all(force=False, quiet=False): def patch(patch_module, force=False): import frappe.modules.patch_handler frappe.connect() - frappe.local.patch_log_list = [] frappe.modules.patch_handler.run_single(patch_module, force=force) - print "\n".join(frappe.local.patch_log_list) frappe.destroy() @cmd @@ -447,7 +441,6 @@ def backup(with_files=False, backup_path_db=None, backup_path_files=None, quiet= if with_files: print "files backup taken -", odb.backup_path_files, "- on", now() frappe.destroy() - return odb @cmd def move(dest_dir=None, site=None): @@ -509,6 +502,14 @@ def clear_web(): frappe.website.render.clear_cache() frappe.destroy() +@cmd +def clear_all_sessions(): + import frappe.sessions + frappe.connect() + frappe.sessions.clear_all_sessions() + frappe.db.commit() + frappe.destroy() + @cmd def build_sitemap(): from frappe.website import rebuild_config @@ -705,11 +706,11 @@ 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=()): +def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None): import frappe.test_runner from frappe.utils import sel - sel.start(verbose) + sel.start(verbose, driver) ret = 1 try: diff --git a/frappe/config/desktop.py b/frappe/config/desktop.py index deb56d3310..cf12e060ae 100644 --- a/frappe/config/desktop.py +++ b/frappe/config/desktop.py @@ -1,43 +1,50 @@ from frappe import _ -data = { - "Calendar": { - "color": "#2980b9", - "icon": "icon-calendar", - "label": _("Calendar"), - "link": "Calendar/Event", - "type": "view" - }, - "Messages": { - "color": "#9b59b6", - "icon": "icon-comments", - "label": _("Messages"), - "link": "messages", - "type": "page" - }, - "To Do": { - "color": "#f1c40f", - "icon": "icon-check", - "label": _("To Do"), - "link": "List/ToDo", - "doctype": "ToDo", - "type": "list" - }, - "Website": { - "color": "#16a085", - "icon": "icon-globe", - "type": "module" - }, - "Installer": { - "color": "#888", - "icon": "icon-download", - "link": "applications", - "type": "page", - "label": _("Installer") - }, - "Setup": { - "color": "#bdc3c7", - "icon": "icon-wrench", - "type": "module" - }, -} \ No newline at end of file +def get_data(): + return { + "Calendar": { + "color": "#2980b9", + "icon": "icon-calendar", + "label": _("Calendar"), + "link": "Calendar/Event", + "type": "view" + }, + "Messages": { + "color": "#9b59b6", + "icon": "icon-comments", + "label": _("Messages"), + "link": "messages", + "type": "page" + }, + "To Do": { + "color": "#f1c40f", + "icon": "icon-check", + "label": _("To Do"), + "link": "List/ToDo", + "doctype": "ToDo", + "type": "list" + }, + "Website": { + "color": "#16a085", + "icon": "icon-globe", + "type": "module" + }, + "Installer": { + "color": "#888", + "icon": "icon-download", + "link": "applications", + "type": "page", + "label": _("Installer") + }, + "Setup": { + "color": "#bdc3c7", + "icon": "icon-wrench", + "type": "module" + }, + "Core": { + "color": "#589494", + "icon": "icon-cog", + "type": "module", + "system_manager": 1 + }, + } diff --git a/frappe/config/setup.py b/frappe/config/setup.py index 4ffa119167..9a72577d42 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -1,193 +1,191 @@ from frappe import _ from frappe.widgets.moduleview import add_setup_section -data = [ - { - "label": _("Users and Permissions"), - "icon": "icon-group", - "items": [ - { - "type": "doctype", - "name": "User", - "description": _("System and Website Users") - }, - { - "type": "doctype", - "name": "Role", - "description": _("User Roles") - }, - { - "type": "page", - "name": "permission-manager", - "label": "Permission 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") - }, - ] - }, - { - "label": _("Settings"), - "icon": "icon-wrench", - "items": [ - { - "type": "doctype", - "name": "System Settings", - "label": _("System Settings"), - "description": _("Language, Date and Time settings"), - "hide_count": True - }, - { - "type": "page", - "name": "modules_setup", - "label": _("Show / Hide Modules"), - "icon": "icon-upload", - "description": _("Show or hide modules globally.") - }, - { - "type": "doctype", - "name": "Naming Series", - "description": _("Set numbering series for transactions."), - "hide_count": True - }, - ] - }, - { - "label": _("Data"), - "icon": "icon-th", - "items": [ - { - "type": "page", - "name": "data-import-tool", - "label": _("Import / Export Data"), - "icon": "icon-upload", - "description": _("Import / Export Data from .csv files.") - }, - { - "type": "doctype", - "name": "Rename Tool", - "description": _("Rename many items by uploading a .csv file."), - "hide_count": True - }, - { - "type": "doctype", - "name": "File Data", - "description": _("Manage uploaded files.") - } - ] - }, - { - "label": _("Workflow"), - "icon": "icon-random", - "items": [ - { - "type": "doctype", - "name": "Workflow", - "description": _("Define workflows for forms.") - }, - { - "type": "doctype", - "name": "Workflow State", - "description": _("States for workflow (e.g. Draft, Approved, Cancelled).") - }, - { - "type": "doctype", - "name": "Workflow Action", - "description": _("Actions for workflow (e.g. Approve, Cancel).") - }, - ] - }, - { - "label": _("Email"), - "icon": "icon-envelope", - "items": [ - { - "type": "doctype", - "name": "Outgoing Email Settings", - "description": _("Set outgoing mail server.") - }, - ] - }, - { - "label": _("Printing and Branding"), - "icon": "icon-print", - "items": [ - { - "type": "doctype", - "name": "Print Format", - "description": _("Customized HTML Templates for printing transctions.") - }, - ] - }, - { - "label": _("Customize"), - "icon": "icon-glass", - "items": [ - { - "type": "doctype", - "name": "Customize Form", - "description": _("Change field properties (hide, readonly, permission etc.)"), - "hide_count": True - }, - { - "type": "doctype", - "name": "Custom Field", - "description": _("Add fields to forms.") - }, - { - "type": "doctype", - "name": "Custom Script", - "description": _("Add custom javascript to forms.") - } - ] - }, - { - "label": _("System"), - "icon": "icon-cog", - "items": [ - { - "type": "page", - "name": "applications", - "label": _("Application Installer"), - "description": _("Install Applications."), - "icon": "icon-download" - }, - { - "type": "doctype", - "name": "Backup Manager", - "label": _("Download Backup"), - "onclick": "frappe.ui.toolbar.download_backup", - "icon": "icon-download-alt", - "description": _("Send download link of a recent backup to System Managers"), - "hide_count": True - }, - { - "type": "doctype", - "name": "Social Login Keys", - "description": _("Enter keys to enable login via Facebook, Google, GitHub."), - }, - { - "type": "doctype", - "name": "Backup Manager", - "description": _("Manage cloud backups on Dropbox"), - "hide_count": True - }, - { - "type": "doctype", - "name": "Scheduler Log", - "description": _("Log of error on automated events (scheduler).") - }, - ] - } -] - def get_data(): - out = list(data) - add_setup_section(out, "frappe", "website", _("Website"), "icon-globe") - return out + data = [ + { + "label": _("Users and Permissions"), + "icon": "icon-group", + "items": [ + { + "type": "doctype", + "name": "User", + "description": _("System and Website Users") + }, + { + "type": "doctype", + "name": "Role", + "description": _("User Roles") + }, + { + "type": "page", + "name": "permission-manager", + "label": "Permission 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") + }, + ] + }, + { + "label": _("Settings"), + "icon": "icon-wrench", + "items": [ + { + "type": "doctype", + "name": "System Settings", + "label": _("System Settings"), + "description": _("Language, Date and Time settings"), + "hide_count": True + }, + { + "type": "page", + "name": "modules_setup", + "label": _("Show / Hide Modules"), + "icon": "icon-upload", + "description": _("Show or hide modules globally.") + }, + { + "type": "doctype", + "name": "Naming Series", + "description": _("Set numbering series for transactions."), + "hide_count": True + }, + ] + }, + { + "label": _("Data"), + "icon": "icon-th", + "items": [ + { + "type": "page", + "name": "data-import-tool", + "label": _("Import / Export Data"), + "icon": "icon-upload", + "description": _("Import / Export Data from .csv files.") + }, + { + "type": "doctype", + "name": "Rename Tool", + "description": _("Rename many items by uploading a .csv file."), + "hide_count": True + }, + { + "type": "doctype", + "name": "File Data", + "description": _("Manage uploaded files.") + } + ] + }, + { + "label": _("Workflow"), + "icon": "icon-random", + "items": [ + { + "type": "doctype", + "name": "Workflow", + "description": _("Define workflows for forms.") + }, + { + "type": "doctype", + "name": "Workflow State", + "description": _("States for workflow (e.g. Draft, Approved, Cancelled).") + }, + { + "type": "doctype", + "name": "Workflow Action", + "description": _("Actions for workflow (e.g. Approve, Cancel).") + }, + ] + }, + { + "label": _("Email"), + "icon": "icon-envelope", + "items": [ + { + "type": "doctype", + "name": "Outgoing Email Settings", + "description": _("Set outgoing mail server.") + }, + ] + }, + { + "label": _("Printing and Branding"), + "icon": "icon-print", + "items": [ + { + "type": "doctype", + "name": "Print Format", + "description": _("Customized HTML Templates for printing transctions.") + }, + ] + }, + { + "label": _("Customize"), + "icon": "icon-glass", + "items": [ + { + "type": "doctype", + "name": "Customize Form", + "description": _("Change field properties (hide, readonly, permission etc.)"), + "hide_count": True + }, + { + "type": "doctype", + "name": "Custom Field", + "description": _("Add fields to forms.") + }, + { + "type": "doctype", + "name": "Custom Script", + "description": _("Add custom javascript to forms.") + } + ] + }, + { + "label": _("System"), + "icon": "icon-cog", + "items": [ + { + "type": "page", + "name": "applications", + "label": _("Application Installer"), + "description": _("Install Applications."), + "icon": "icon-download" + }, + { + "type": "doctype", + "name": "Backup Manager", + "label": _("Download Backup"), + "onclick": "frappe.ui.toolbar.download_backup", + "icon": "icon-download-alt", + "description": _("Send download link of a recent backup to System Managers"), + "hide_count": True + }, + { + "type": "doctype", + "name": "Social Login Keys", + "description": _("Enter keys to enable login via Facebook, Google, GitHub."), + }, + { + "type": "doctype", + "name": "Backup Manager", + "description": _("Manage cloud backups on Dropbox"), + "hide_count": True + }, + { + "type": "doctype", + "name": "Scheduler Log", + "description": _("Log of error on automated events (scheduler).") + }, + ] + } + ] + add_setup_section(data, "frappe", "website", _("Website"), "icon-globe") + return data diff --git a/frappe/config/website.py b/frappe/config/website.py index ebd8f55a19..4adc17afd9 100644 --- a/frappe/config/website.py +++ b/frappe/config/website.py @@ -1,93 +1,94 @@ from frappe import _ -data = [ - { - "label": _("Documents"), - "icon": "icon-star", - "items": [ - { - "type": "doctype", - "name": "Web Page", - "description": _("Content web page."), - }, - { - "type": "doctype", - "name": "Blog Post", - "description": _("Single Post (article)."), - }, - { - "type": "doctype", - "name": "Blogger", - "description": _("User ID of a blog writer."), - }, - { - "type": "doctype", - "name": "Website Group", - "description": _("Web Site Forum Page."), - }, - { - "type": "doctype", - "name": "Post", - "description": _("List of Web Site Forum's Posts."), - }, - { - "type": "doctype", - "name": "Website Slideshow", - "description": _("Embed image slideshows in website pages."), - }, - ] - }, - { - "label": _("Setup"), - "icon": "icon-cog", - "items": [ - { - "type": "doctype", - "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", - "description": _("Setup of fonts and background."), - }, - { - "type": "doctype", - "name": "Website Script", - "description": _("Javascript to append to the head section of the page."), - }, - { - "type": "doctype", - "name": "Blog Settings", - "description": _("Write titles and introductions to your blog."), - }, - { - "type": "doctype", - "name": "Blog Category", - "description": _("Categorize blog posts."), - }, - { - "type": "doctype", - "name": "About Us Settings", - "description": _("Settings for About Us Page."), - }, - { - "type": "doctype", - "name": "Contact Us Settings", - "description": _("Settings for Contact Us Page."), - }, - { - "type": "doctype", - "name": "Website Page Permission", - "description": _("Define read, write, admin permissions for a Website Page."), - }, - ] - }, -] \ No newline at end of file +def get_data(): + return [ + { + "label": _("Documents"), + "icon": "icon-star", + "items": [ + { + "type": "doctype", + "name": "Web Page", + "description": _("Content web page."), + }, + { + "type": "doctype", + "name": "Blog Post", + "description": _("Single Post (article)."), + }, + { + "type": "doctype", + "name": "Blogger", + "description": _("User ID of a blog writer."), + }, + { + "type": "doctype", + "name": "Website Group", + "description": _("Web Site Forum Page."), + }, + { + "type": "doctype", + "name": "Post", + "description": _("List of Web Site Forum's Posts."), + }, + { + "type": "doctype", + "name": "Website Slideshow", + "description": _("Embed image slideshows in website pages."), + }, + ] + }, + { + "label": _("Setup"), + "icon": "icon-cog", + "items": [ + { + "type": "doctype", + "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", + "description": _("Setup of fonts and background."), + }, + { + "type": "doctype", + "name": "Website Script", + "description": _("Javascript to append to the head section of the page."), + }, + { + "type": "doctype", + "name": "Blog Settings", + "description": _("Write titles and introductions to your blog."), + }, + { + "type": "doctype", + "name": "Blog Category", + "description": _("Categorize blog posts."), + }, + { + "type": "doctype", + "name": "About Us Settings", + "description": _("Settings for About Us Page."), + }, + { + "type": "doctype", + "name": "Contact Us Settings", + "description": _("Settings for Contact Us Page."), + }, + { + "type": "doctype", + "name": "Website Page Permission", + "description": _("Define read, write, admin permissions for a Website Page."), + }, + ] + }, + ] diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 182ad60b00..d55d89cdea 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -10,7 +10,7 @@ from frappe.website.utils import is_signup_enabled from frappe.utils import get_url, cstr from frappe.utils.email_lib.email_body import get_email from frappe.utils.email_lib.smtp import send -from frappe.utils import scrub_urls +from frappe.utils import scrub_urls, cint from frappe import _ from frappe.model.document import Document @@ -78,6 +78,9 @@ def _make(doctype=None, name=None, content=None, subject=None, sent_or_received d.communication_medium = communication_medium + d.idx = cint(frappe.db.sql("""select max(idx) from `tabCommunication` + where parenttype=%s and parent=%s""", (doctype, name))[0][0]) + 1 + comm.ignore_permissions = True comm.insert() @@ -118,7 +121,7 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s 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 not send_print_in_body: + if print_html and not send_print_in_body: d.content += "

Please see attachment for document details.

" mail = get_email(d.recipients, sender=d.sender, subject=d.subject, diff --git a/frappe/core/doctype/custom_field/custom_field.js b/frappe/core/doctype/custom_field/custom_field.js index 9e491dce79..040cb10616 100644 --- a/frappe/core/doctype/custom_field/custom_field.js +++ b/frappe/core/doctype/custom_field/custom_field.js @@ -48,7 +48,7 @@ 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 separated by enter.
Eg.: Field: Country
Options:
China
India
United States

OR
You can also link it to existing Documents.
Eg.: link: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 = ''; } @@ -64,9 +64,10 @@ cur_frm.cscript.dt = function(doc, dt, dn) { args: { doctype: doc.dt, fieldname: doc.fieldname }, callback: function(r, rt) { set_field_options('insert_after', r.message); + var fieldnames = $.map(r.message, function(v) { return v.value; }); - if(insert_after==null || !in_list(r.message.split("\n"), insert_after)) { - insert_after = r.message.split("\n")[0]; + if(insert_after==null || !in_list(fieldnames, insert_after)) { + insert_after = fieldnames[-1]; } cur_frm.set_value('insert_after', insert_after); diff --git a/frappe/core/doctype/custom_field/custom_field.py b/frappe/core/doctype/custom_field/custom_field.py index e83c7df6ad..46b44d0e7d 100644 --- a/frappe/core/doctype/custom_field/custom_field.py +++ b/frappe/core/doctype/custom_field/custom_field.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 @@ -9,7 +9,7 @@ from frappe import _ from frappe.model.document import Document class CustomField(Document): - + def autoname(self): self.set_fieldname() self.name = self.dt + "-" + self.fieldname @@ -19,16 +19,16 @@ class CustomField(Document): if not self.label: frappe.throw(_("Label is mandatory")) # remove special characters from fieldname - self.fieldname = filter(lambda x: x.isdigit() or x.isalpha() or '_', + self.fieldname = filter(lambda x: x.isdigit() or x.isalpha() or '_', cstr(self.label).lower().replace(' ','_')) def validate(self): if not self.idx: self.idx = len(frappe.get_meta(self.dt).get("fields")) + 1 - + if not self.fieldname: frappe.throw(_("Fieldname not set for Custom Field")) - + def on_update(self): # validate field from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype @@ -36,7 +36,7 @@ class CustomField(Document): validate_fields_for_doctype(self.dt) frappe.clear_cache(doctype=self.dt) - + # create property setter to emulate insert after self.create_property_setter() @@ -44,7 +44,7 @@ class CustomField(Document): if not frappe.flags.in_test: from frappe.model.db_schema import updatedb updatedb(self.dt) - + def on_trash(self): # delete property setter entries frappe.db.sql("""\ @@ -57,57 +57,33 @@ class CustomField(Document): def create_property_setter(self): if not self.insert_after: return - idx_label_list, field_list = get_fields_label(self.dt, 0) - label_index = idx_label_list.index(self.insert_after) - if label_index==-1: return - prev_field = field_list[label_index] + dt_meta = frappe.get_meta(self.dt) + if not dt_meta.get_field(self.insert_after): + frappe.throw(_("Insert After field '{0}' mentioned in Custom Field '{1}', does not exist") + .format(dt_meta.get_label(self.insert_after), self.label), frappe.DoesNotExistError) + frappe.db.sql("""\ DELETE FROM `tabProperty Setter` WHERE doc_type = %s AND field_name = %s AND property = 'previous_field'""", (self.dt, self.fieldname)) - + frappe.make_property_setter({ - "doctype":self.dt, - "fieldname": self.fieldname, + "doctype":self.dt, + "fieldname": self.fieldname, "property": "previous_field", - "value": prev_field + "value": self.insert_after }) - + @frappe.whitelist() -def get_fields_label(dt=None, form=1): - """ - if form=1: Returns a string of field labels separated by \n - if form=0: Returns lists of field labels and field names - """ - import frappe - from frappe.utils import cstr - fieldname = None - if not dt: - dt = frappe.form_dict.get('doctype') - fieldname = frappe.form_dict.get('fieldname') - if not dt: return "" - - docfields = frappe.get_meta(dt).get("fields") - - if fieldname: - idx_label_list = [cstr(d.label) or cstr(d.fieldname) or cstr(d.fieldtype) - for d in docfields if d.fieldname != fieldname] - else: - idx_label_list = [cstr(d.label) or cstr(d.fieldname) or cstr(d.fieldtype) - for d in docfields] - if form: - return "\n".join(idx_label_list) - else: - # return idx_label_list, field_list - field_list = [cstr(d.fieldname) for d in docfields] - return idx_label_list, field_list +def get_fields_label(doctype=None): + return [{"value": df.fieldname, "label": _(df.label)} 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}` + 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({ @@ -120,4 +96,4 @@ def create_custom_field_if_values_exist(doctype, df): "options": df.options, "insert_after": df.insert_after }).insert() - + diff --git a/frappe/core/doctype/customize_form/customize_form.json b/frappe/core/doctype/customize_form/customize_form.json index 5db77d1bbf..a95988f208 100644 --- a/frappe/core/doctype/customize_form/customize_form.json +++ b/frappe/core/doctype/customize_form/customize_form.json @@ -1,6 +1,6 @@ { "autoname": "DL.####", - "creation": "2013-01-29 17:55:08.000000", + "creation": "2013-01-29 17:55:08", "docstatus": 0, "doctype": "DocType", "fields": [ @@ -8,6 +8,7 @@ "fieldname": "doc_type", "fieldtype": "Link", "hidden": 0, + "in_list_view": 1, "label": "Enter Form Type", "no_copy": 0, "options": "DocType", @@ -25,23 +26,37 @@ "fieldtype": "Column Break", "permlevel": 0 }, + { + "fieldname": "default_print_format", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Default Print Format", + "no_copy": 0, + "options": "Print Format", + "permlevel": 0, + "search_index": 0 + }, { "description": "Fields separated by comma (,) will be included in the
Search By list of Search dialog box", "fieldname": "search_fields", "fieldtype": "Data", + "in_list_view": 1, "label": "Search Fields", "no_copy": 0, "permlevel": 0, "search_index": 0 }, { - "fieldname": "default_print_format", - "fieldtype": "Link", - "label": "Default Print Format", - "no_copy": 0, - "options": "Print Format", - "permlevel": 0, - "search_index": 0 + "fieldname": "sort_field", + "fieldtype": "Data", + "label": "Sort Field", + "permlevel": 0 + }, + { + "fieldname": "sort_order", + "fieldtype": "Data", + "label": "Sort Order", + "permlevel": 0 }, { "fieldname": "column_break1", @@ -111,7 +126,7 @@ "icon": "icon-glass", "idx": 1, "issingle": 1, - "modified": "2014-01-15 16:16:22.000000", + "modified": "2014-05-08 09:27:44.167026", "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 0af80da2fd..0e0eefdcfa 100644 --- a/frappe/core/doctype/customize_form/customize_form.py +++ b/frappe/core/doctype/customize_form/customize_form.py @@ -14,6 +14,8 @@ from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype class CustomizeForm(Document): doctype_properties = { 'search_fields': 'Data', + 'sort_field': 'Data', + 'sort_order': 'Data', 'default_print_format': 'Data', 'read_only_onload': 'Check', 'allow_attach': 'Check', diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 9b89f80b18..646600a471 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -138,6 +138,17 @@ "options": "\nTitle Case\nUPPER CASE", "permlevel": 0 }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "permlevel": 0, + "reqd": 0, + "search_index": 0 + }, { "fieldname": "column_break_15", "fieldtype": "Column Break", @@ -162,15 +173,20 @@ "search_index": 0 }, { - "fieldname": "description", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "reqd": 0, - "search_index": 0 + "default": "modified", + "description": "", + "fieldname": "sort_field", + "fieldtype": "Data", + "label": "Sort Field", + "permlevel": 0 + }, + { + "default": "DESC", + "fieldname": "sort_order", + "fieldtype": "Select", + "label": "Sort Order", + "options": "ASC\nDESC", + "permlevel": 0 }, { "depends_on": "eval:!doc.istable", @@ -329,7 +345,7 @@ "idx": 1, "issingle": 0, "istable": 0, - "modified": "2014-05-01 05:27:22.582492", + "modified": "2014-05-08 09:23:56.952829", "modified_by": "Administrator", "module": "Core", "name": "DocType", @@ -361,5 +377,7 @@ } ], "read_only": 0, - "search_fields": "module" + "search_fields": "module", + "sort_field": "name", + "sort_order": "ASC" } \ No newline at end of file diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index d8177e5b33..69e66017f0 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -10,6 +10,7 @@ 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 class DocType(Document): def validate(self): @@ -30,7 +31,6 @@ class DocType(Document): validate_permissions(self) self.make_amendable() - self.check_link_replacement_error() def change_modified_of_parent(self): if frappe.flags.in_import: @@ -93,12 +93,6 @@ class DocType(Document): module.on_doctype_update() frappe.clear_cache(doctype=self.name) - def check_link_replacement_error(self): - for d in self.get("fields", {"fieldtype":"Select"}): - if (frappe.db.get_value("DocField", d.name, "options") or "").startswith("link:") \ - and not d.options.startswith("link:"): - frappe.throw(_("'link:' type Select {0} getting replaced").format(d.label)) - 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) @@ -176,7 +170,7 @@ def validate_fields(fields): def check_unique_fieldname(fieldname): duplicates = filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields)) if len(duplicates) > 1: - frappe.throw(_("Fieldname {0} appears multiple times in rows {1}").format(", ".join(duplicates))) + 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: @@ -198,7 +192,8 @@ def validate_fields(fields): def check_min_items_in_list(fields): if len(filter(lambda d: d.in_list_view, fields))==0: for d in fields[:5]: - d.in_list_view = 1 + if d.fieldtype in type_map: + d.in_list_view = 1 def check_width(d): if d.fieldtype == "Currency" and cint(d.width) < 100: diff --git a/frappe/core/doctype/event_user/event_user.json b/frappe/core/doctype/event_user/event_user.json index 8ed3cfe2e0..5886cd21ba 100644 --- a/frappe/core/doctype/event_user/event_user.json +++ b/frappe/core/doctype/event_user/event_user.json @@ -1,17 +1,17 @@ { "autoname": "EVP.#####", - "creation": "2013-02-22 01:27:33.000000", + "creation": "2013-02-22 01:27:33", "docstatus": 0, "doctype": "DocType", "fields": [ { "fieldname": "person", - "fieldtype": "Select", + "fieldtype": "Link", "in_list_view": 1, "label": "Person", "oldfieldname": "person", "oldfieldtype": "Select", - "options": "link:User", + "options": "User", "permlevel": 0, "print_width": "240px", "search_index": 1, @@ -20,9 +20,12 @@ ], "idx": 1, "istable": 1, - "modified": "2013-12-20 19:23:14.000000", + "modified": "2014-05-09 02:12:32.374008", "modified_by": "Administrator", "module": "Core", "name": "Event User", - "owner": "Administrator" + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py index a457a3da5b..4d88a6adca 100644 --- a/frappe/core/doctype/file_data/file_data.py +++ b/frappe/core/doctype/file_data/file_data.py @@ -44,8 +44,8 @@ class FileData(Document): pass # if file not attached to any other record, delete it - if self.file_name and not frappe.db.count("File Data", - {"content_hash": self.content_hash, "name": ["!=", self.name]}): + if self.file_name and self.content_hash and (not frappe.db.count("File Data", + {"content_hash": self.content_hash, "name": ["!=", self.name]})): delete_file_data_content(self) def on_rollback(self): diff --git a/frappe/core/doctype/letter_head/letter_head.json b/frappe/core/doctype/letter_head/letter_head.json index 6ba72a5b6d..115570898b 100644 --- a/frappe/core/doctype/letter_head/letter_head.json +++ b/frappe/core/doctype/letter_head/letter_head.json @@ -1,7 +1,7 @@ { "allow_attach": 1, "autoname": "field:letter_head_name", - "creation": "2012-11-22 17:45:46.000000", + "creation": "2012-11-22 17:45:46", "docstatus": 0, "doctype": "DocType", "fields": [ @@ -9,6 +9,7 @@ "fieldname": "letter_head_name", "fieldtype": "Data", "in_filter": 0, + "in_list_view": 1, "label": "Letter Head Name", "oldfieldname": "letter_head_name", "oldfieldtype": "Data", @@ -19,6 +20,7 @@ "depends_on": "letter_head_name", "fieldname": "disabled", "fieldtype": "Check", + "in_list_view": 1, "label": "Disabled", "oldfieldname": "disabled", "oldfieldtype": "Check", @@ -29,6 +31,7 @@ "description": "Check this to make this the default letter head in all prints", "fieldname": "is_default", "fieldtype": "Check", + "in_list_view": 1, "label": "Is Default", "oldfieldname": "is_default", "oldfieldtype": "Check", @@ -39,6 +42,7 @@ "description": "Letter Head in HTML", "fieldname": "content", "fieldtype": "Text Editor", + "in_list_view": 1, "label": "Content", "oldfieldname": "content", "oldfieldtype": "Text Editor", @@ -48,7 +52,7 @@ "icon": "icon-font", "idx": 1, "max_attachments": 3, - "modified": "2014-01-20 17:48:56.000000", + "modified": "2014-05-07 06:03:07.760995", "modified_by": "Administrator", "module": "Core", "name": "Letter Head", @@ -69,11 +73,11 @@ }, { "delete": 0, - "email": 1, + "email": 0, "permlevel": 0, - "print": 1, + "print": 0, "read": 1, - "report": 1, + "report": 0, "role": "All" } ] diff --git a/frappe/core/doctype/notification_count/notification_count.json b/frappe/core/doctype/notification_count/notification_count.json index 53cd755ee0..e43fcb7686 100644 --- a/frappe/core/doctype/notification_count/notification_count.json +++ b/frappe/core/doctype/notification_count/notification_count.json @@ -1,4 +1,5 @@ { + "autoname": "hash", "creation": "2013-11-18 05:31:03.000000", "docstatus": 0, "doctype": "DocType", @@ -25,9 +26,9 @@ } ], "idx": 1, - "modified": "2013-12-20 19:24:14.000000", + "modified": "2014-05-12 19:24:14.000000", "modified_by": "Administrator", "module": "Core", "name": "Notification Count", "owner": "Administrator" -} \ No newline at end of file +} diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index a9c2e1ddbb..3227e7f9b8 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -71,19 +71,19 @@ class Page(Document): fpath = os.path.join(path, scrub(self.name) + '.js') if os.path.exists(fpath): with open(fpath, 'r') as f: - self.script = f.read() + self.script = unicode(f.read(), "utf-8") # css fpath = os.path.join(path, scrub(self.name) + '.css') if os.path.exists(fpath): with open(fpath, 'r') as f: - self.style = f.read() + self.style = unicode(f.read(), "utf-8") # html fpath = os.path.join(path, scrub(self.name) + '.html') if os.path.exists(fpath): with open(fpath, 'r') as f: - self.content = f.read() + self.content = unicode(f.read(), "utf-8") if frappe.lang != 'en': from frappe.translate import get_lang_js diff --git a/frappe/core/doctype/print_format/print_format.js b/frappe/core/doctype/print_format/print_format.js index 81e8829eed..d09f46c261 100644 --- a/frappe/core/doctype/print_format/print_format.js +++ b/frappe/core/doctype/print_format/print_format.js @@ -1,10 +1,13 @@ -cur_frm.cscript.refresh = function(doc, dt, dn) { - if(user!="Administrator") { - if(doc.standard == 'Yes') { +cur_frm.cscript.refresh = function (doc) { + if (user!="Administrator") { + if (doc.standard == 'Yes') { cur_frm.toggle_enable(["html", "doc_type", "module"], false); cur_frm.disable_save(); + } else { + cur_frm.toggle_enable(["html", "doc_type", "module"], true); + cur_frm.enable_save(); } cur_frm.toggle_enable("standard", false); } -} \ No newline at end of file +} diff --git a/frappe/core/doctype/print_format/print_format.json b/frappe/core/doctype/print_format/print_format.json index 987023bf04..a8bcffd22e 100644 --- a/frappe/core/doctype/print_format/print_format.json +++ b/frappe/core/doctype/print_format/print_format.json @@ -3,21 +3,22 @@ "allow_copy": 0, "allow_rename": 0, "autoname": "Prompt", - "creation": "2013-01-23 19:54:43.000000", + "creation": "2013-01-23 19:54:43", "docstatus": 0, "doctype": "DocType", "fields": [ { "allow_on_submit": 0, "fieldname": "module", - "fieldtype": "Select", + "fieldtype": "Link", "hidden": 0, "in_filter": 1, + "in_list_view": 1, "label": "Module", "no_copy": 0, "oldfieldname": "module", "oldfieldtype": "Select", - "options": "link:Module Def", + "options": "Module Def", "permlevel": 0, "print_hide": 0, "report_hide": 0, @@ -27,10 +28,11 @@ { "description": "Belongs to", "fieldname": "doc_type", - "fieldtype": "Select", + "fieldtype": "Link", "in_filter": 1, + "in_list_view": 1, "label": "DocType", - "options": "link:DocType", + "options": "DocType", "permlevel": 0, "reqd": 0, "search_index": 0 @@ -47,6 +49,7 @@ "fieldtype": "Select", "hidden": 0, "in_filter": 1, + "in_list_view": 1, "label": "Standard", "no_copy": 1, "oldfieldname": "standard", @@ -62,6 +65,7 @@ { "fieldname": "print_format_type", "fieldtype": "Select", + "in_list_view": 1, "label": "Print Format Type", "options": "Client\nServer", "permlevel": 0 @@ -99,7 +103,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2014-01-20 17:49:00.000000", + "modified": "2014-05-09 02:12:39.540952", "modified_by": "Administrator", "module": "Core", "name": "Print Format", @@ -158,5 +162,7 @@ } ], "read_only": 0, - "read_only_onload": 0 + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/frappe/core/doctype/report/report.js b/frappe/core/doctype/report/report.js index 4f3e162577..d8d4bf90cc 100644 --- a/frappe/core/doctype/report/report.js +++ b/frappe/core/doctype/report/report.js @@ -2,7 +2,7 @@ cur_frm.cscript.refresh = function(doc) { cur_frm.add_custom_button("Show Report", function() { switch(doc.report_type) { case "Report Builder": - frappe.set_route("Report", doc.name); + frappe.set_route("Report", doc.ref_doctype, doc.name); break; case "Query Report": frappe.set_route("query-report", doc.name); @@ -12,7 +12,7 @@ cur_frm.cscript.refresh = function(doc) { break; } }, "icon-table") - + cur_frm.set_intro(""); switch(doc.report_type) { case "Report Builder": @@ -30,4 +30,4 @@ cur_frm.cscript.refresh = function(doc) { cur_frm.set_intro(__("Write a Python file in the same folder where this is saved and return column and result.")) break; } -} \ No newline at end of file +} diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index f4a5bde8b2..62e81b08b9 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -1,6 +1,6 @@ { "autoname": "field:report_name", - "creation": "2013-03-09 15:45:57.000000", + "creation": "2013-03-09 15:45:57", "docstatus": 0, "doctype": "DocType", "document_type": "System", @@ -101,7 +101,7 @@ ], "icon": "icon-table", "idx": 1, - "modified": "2014-03-07 15:20:02.000000", + "modified": "2014-05-12 17:08:04.185601", "modified_by": "Administrator", "module": "Core", "name": "Report", @@ -157,5 +157,7 @@ "role": "All", "submit": 0 } - ] + ], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/frappe/core/page/applications/applications.js b/frappe/core/page/applications/applications.js index ce0a0700a2..14c5941900 100644 --- a/frappe/core/page/applications/applications.js +++ b/frappe/core/page/applications/applications.js @@ -1,4 +1,4 @@ -frappe.pages['applications'].onload = function(wrapper) { +frappe.pages['applications'].onload = function(wrapper) { frappe.ui.make_app_page({ parent: wrapper, title: __('Application Installer'), @@ -10,7 +10,7 @@ frappe.pages['applications'].onload = function(wrapper) { method:"frappe.core.page.applications.applications.get_app_list", callback: function(r) { var $main = $(wrapper).find(".layout-main"); - + if(!keys(r.message).length) { $main.html('
No Apps Installed
'); return; @@ -54,7 +54,7 @@ frappe.pages['applications'].onload = function(wrapper) { \ ', app)) $app.appendTo($main) - + if(app.installed) { $btn = $(''); @@ -63,7 +63,7 @@ frappe.pages['applications'].onload = function(wrapper) { .attr("data-app", app.app_name) .on("click", function() { frappe.call({ - method:"frappe.installer.install_app", + method:"frappe.core.page.applications.applications.install_app", args: {name: $(this).attr("data-app")}, callback: function(r) { if(!r.exc) { diff --git a/frappe/core/page/applications/applications.py b/frappe/core/page/applications/applications.py index ade393014c..0162bf4530 100644 --- a/frappe/core/page/applications/applications.py +++ b/frappe/core/page/applications/applications.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals import frappe +import frappe.installer +from frappe import _ @frappe.whitelist() def get_app_list(): @@ -19,8 +21,16 @@ def get_app_list(): "app_publisher", "app_version", "app_url", "app_color"): val = app_hooks.get(key) or [] out[app][key] = val[0] if len(val) else "" - + if app in installed: out[app]["installed"] = 1 - + return out + +@frappe.whitelist() +def install_app(name): + app_hooks = frappe.get_hooks(app_name=name) + if app_hooks.get('hide_in_installer'): + frappe.throw(_("You cannot install this app")) + + frappe.installer.install_app(name) diff --git a/frappe/core/page/data_import_tool/exporter.py b/frappe/core/page/data_import_tool/exporter.py index b64b95b841..ab711e171b 100644 --- a/frappe/core/page/data_import_tool/exporter.py +++ b/frappe/core/page/data_import_tool/exporter.py @@ -108,8 +108,6 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data if docfield.fieldtype == 'Select': if not docfield.options: return '' - elif docfield.options.startswith('link:'): - return 'Valid %s' % docfield.options[5:] else: return 'One of: %s' % ', '.join(filter(None, docfield.options.split('\n'))) elif docfield.fieldtype == 'Link': diff --git a/frappe/core/page/data_import_tool/importer.py b/frappe/core/page/data_import_tool/importer.py index 7c05ceb5fe..36034b01bf 100644 --- a/frappe/core/page/data_import_tool/importer.py +++ b/frappe/core/page/data_import_tool/importer.py @@ -186,9 +186,6 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, doc = None doc = get_doc(row_idx) - if doc.get("name"): - doc["_new_name_set"] = True - try: frappe.local.message_log = [] if doc.get("parentfield"): diff --git a/frappe/core/page/desktop/desktop.js b/frappe/core/page/desktop/desktop.js index 411b9c0ce1..78012c9fec 100644 --- a/frappe/core/page/desktop/desktop.js +++ b/frappe/core/page/desktop/desktop.js @@ -73,7 +73,7 @@ frappe.desktop.render = function() { $.each(modules_list, function(i, m) { var module = frappe.modules[m]; if(module) { - if(m!="Setup" && user_list.indexOf(m)!==-1) + if(!in_list(["Setup", "Core"], m) && user_list.indexOf(m)!==-1) add_icon(m); } }) @@ -82,6 +82,9 @@ frappe.desktop.render = function() { if(user_roles.indexOf('System Manager')!=-1) add_icon('Setup') + if(user_roles.indexOf('Administrator')!=-1) + add_icon('Core') + // all applications frappe.modules["All Applications"] = { icon: "icon-th", diff --git a/frappe/core/page/user_properties/user_properties.py b/frappe/core/page/user_properties/user_properties.py index 47f58cec35..303329dc62 100644 --- a/frappe/core/page/user_properties/user_properties.py +++ b/frappe/core/page/user_properties/user_properties.py @@ -18,17 +18,17 @@ def get_users_and_links(): 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 + + 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() @@ -36,9 +36,9 @@ def get_properties(parent=None, defkey=None, defvalue=None): if p.defkey in doctypes: out.append(p) properties = out - + return properties - + def _build_conditions(filters): conditions = [] values = {} @@ -46,7 +46,7 @@ def _build_conditions(filters): if filters.get(key): conditions.append("and `{key}`=%({key})s".format(key=key)) values[key] = value - + return "\n".join(conditions), values @frappe.whitelist() @@ -54,35 +54,35 @@ 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(name=name) - + + 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 + 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` + 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 + + 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/data/Framework.sql b/frappe/data/Framework.sql index f6055f257c..79c0990491 100644 --- a/frappe/data/Framework.sql +++ b/frappe/data/Framework.sql @@ -8,21 +8,21 @@ DROP TABLE IF EXISTS `tabDocField`; CREATE TABLE `tabDocField` ( - `name` varchar(120) NOT NULL, + `name` varchar(255) NOT NULL, `creation` datetime(6) DEFAULT NULL, `modified` datetime(6) DEFAULT NULL, - `modified_by` varchar(40) DEFAULT NULL, - `owner` varchar(40) DEFAULT NULL, + `modified_by` varchar(255) DEFAULT NULL, + `owner` varchar(255) DEFAULT NULL, `docstatus` int(1) DEFAULT '0', - `parent` varchar(120) DEFAULT NULL, - `parentfield` varchar(120) DEFAULT NULL, - `parenttype` varchar(120) DEFAULT NULL, + `parent` varchar(255) DEFAULT NULL, + `parentfield` varchar(255) DEFAULT NULL, + `parenttype` varchar(255) DEFAULT NULL, `idx` int(8) DEFAULT NULL, - `fieldname` varchar(180) DEFAULT NULL, - `label` varchar(180) DEFAULT NULL, - `oldfieldname` varchar(180) DEFAULT NULL, - `fieldtype` varchar(180) DEFAULT NULL, - `oldfieldtype` varchar(180) DEFAULT NULL, + `fieldname` varchar(255) DEFAULT NULL, + `label` varchar(255) DEFAULT NULL, + `oldfieldname` varchar(255) DEFAULT NULL, + `fieldtype` varchar(255) DEFAULT NULL, + `oldfieldtype` varchar(255) DEFAULT NULL, `options` text, `search_index` int(1) DEFAULT NULL, `hidden` int(1) DEFAULT NULL, @@ -32,12 +32,12 @@ CREATE TABLE `tabDocField` ( `reqd` int(1) DEFAULT NULL, `no_copy` int(1) DEFAULT NULL, `allow_on_submit` int(1) DEFAULT NULL, - `trigger` varchar(180) DEFAULT NULL, - `depends_on` varchar(180) DEFAULT NULL, + `trigger` varchar(255) DEFAULT NULL, + `depends_on` varchar(255) DEFAULT NULL, `permlevel` int(11) DEFAULT '0', `ignore_restrictions` int(1) DEFAULT NULL, - `width` varchar(180) DEFAULT NULL, - `print_width` varchar(180) DEFAULT NULL, + `width` varchar(255) DEFAULT NULL, + `print_width` varchar(255) DEFAULT NULL, `default` text, `description` text, `in_filter` int(1) DEFAULT NULL, @@ -57,22 +57,20 @@ CREATE TABLE `tabDocField` ( -- DROP TABLE IF EXISTS `tabDocPerm`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `tabDocPerm` ( - `name` varchar(120) NOT NULL, + `name` varchar(255) NOT NULL, `creation` datetime(6) DEFAULT NULL, `modified` datetime(6) DEFAULT NULL, - `modified_by` varchar(40) DEFAULT NULL, - `owner` varchar(40) DEFAULT NULL, + `modified_by` varchar(255) DEFAULT NULL, + `owner` varchar(255) DEFAULT NULL, `docstatus` int(1) DEFAULT '0', - `parent` varchar(120) DEFAULT NULL, - `parentfield` varchar(120) DEFAULT NULL, - `parenttype` varchar(120) DEFAULT NULL, + `parent` varchar(255) DEFAULT NULL, + `parentfield` varchar(255) DEFAULT NULL, + `parenttype` varchar(255) DEFAULT NULL, `idx` int(8) DEFAULT NULL, `permlevel` int(11) DEFAULT '0', - `role` varchar(180) DEFAULT NULL, - `match` varchar(180) DEFAULT NULL, + `role` varchar(255) DEFAULT NULL, + `match` varchar(255) DEFAULT NULL, `read` int(1) DEFAULT NULL, `write` int(1) DEFAULT NULL, `create` int(1) DEFAULT NULL, @@ -89,43 +87,42 @@ CREATE TABLE `tabDocPerm` ( PRIMARY KEY (`name`), KEY `parent` (`parent`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `tabDocType` -- DROP TABLE IF EXISTS `tabDocType`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `tabDocType` ( - `name` varchar(180) NOT NULL DEFAULT '', + `name` varchar(255) NOT NULL DEFAULT '', `creation` datetime(6) DEFAULT NULL, `modified` datetime(6) DEFAULT NULL, - `modified_by` varchar(40) DEFAULT NULL, - `owner` varchar(180) DEFAULT NULL, + `modified_by` varchar(255) DEFAULT NULL, + `owner` varchar(255) DEFAULT NULL, `docstatus` int(1) DEFAULT '0', - `parent` varchar(120) DEFAULT NULL, - `parentfield` varchar(120) DEFAULT NULL, - `parenttype` varchar(120) DEFAULT NULL, + `parent` varchar(255) DEFAULT NULL, + `parentfield` varchar(255) DEFAULT NULL, + `parenttype` varchar(255) DEFAULT NULL, `idx` int(8) DEFAULT NULL, - `search_fields` varchar(180) DEFAULT NULL, + `search_fields` varchar(255) DEFAULT NULL, `issingle` int(1) DEFAULT NULL, `istable` int(1) DEFAULT NULL, `version` int(11) DEFAULT NULL, - `module` varchar(180) DEFAULT NULL, - `plugin` varchar(180) DEFAULT NULL, - `autoname` varchar(180) DEFAULT NULL, - `name_case` varchar(180) DEFAULT NULL, - `title_field` varchar(180) DEFAULT NULL, + `module` varchar(255) DEFAULT NULL, + `plugin` varchar(255) DEFAULT NULL, + `autoname` varchar(255) DEFAULT NULL, + `name_case` varchar(255) DEFAULT NULL, + `title_field` varchar(255) DEFAULT NULL, + `sort_field` varchar(255) DEFAULT NULL, + `sort_order` varchar(255) DEFAULT NULL, `description` text, - `colour` varchar(180) DEFAULT NULL, + `colour` varchar(255) DEFAULT NULL, `read_only` int(1) DEFAULT NULL, `in_create` int(1) DEFAULT NULL, `show_in_menu` int(1) DEFAULT NULL, `menu_index` int(11) DEFAULT NULL, - `parent_node` varchar(180) DEFAULT NULL, - `smallicon` varchar(180) DEFAULT NULL, + `parent_node` varchar(255) DEFAULT NULL, + `smallicon` varchar(255) DEFAULT NULL, `allow_print` int(1) DEFAULT NULL, `allow_email` int(1) DEFAULT NULL, `allow_copy` int(1) DEFAULT NULL, @@ -136,47 +133,33 @@ CREATE TABLE `tabDocType` ( `allow_attach` int(1) DEFAULT NULL, `use_template` int(1) DEFAULT NULL, `max_attachments` int(11) DEFAULT NULL, - `section_style` varchar(180) DEFAULT NULL, - `client_script` mediumtext, - `client_script_core` mediumtext, - `server_code` mediumtext, - `server_code_core` mediumtext, - `server_code_compiled` mediumtext, - `client_string` mediumtext, - `server_code_error` varchar(180) DEFAULT NULL, - `print_outline` varchar(180) DEFAULT NULL, - `dt_template` mediumtext, + `print_outline` varchar(255) DEFAULT NULL, `is_transaction_doc` int(1) DEFAULT NULL, - `change_log` mediumtext, `read_only_onload` int(1) DEFAULT NULL, `allow_trash` int(1) DEFAULT NULL, `in_dialog` int(1) DEFAULT NULL, - `document_type` varchar(180) DEFAULT NULL, - `icon` varchar(180) DEFAULT NULL, - `tag_fields` varchar(180) DEFAULT NULL, - `subject` varchar(180) DEFAULT NULL, + `document_type` varchar(255) DEFAULT NULL, + `icon` varchar(255) DEFAULT NULL, + `tag_fields` varchar(255) DEFAULT NULL, + `subject` varchar(255) DEFAULT NULL, `_last_update` varchar(32) DEFAULT NULL, - `default_print_format` varchar(180) DEFAULT NULL, + `default_print_format` varchar(255) DEFAULT NULL, `is_submittable` int(1) DEFAULT NULL, - `_user_tags` varchar(180) DEFAULT NULL, + `_user_tags` varchar(255) DEFAULT NULL, `custom` int(1) DEFAULT NULL, PRIMARY KEY (`name`), KEY `parent` (`parent`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `tabSeries` -- DROP TABLE IF EXISTS `tabSeries`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `tabSeries` ( `name` varchar(100) DEFAULT NULL, `current` int(10) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; -- @@ -184,18 +167,15 @@ CREATE TABLE `tabSeries` ( -- DROP TABLE IF EXISTS `tabSessions`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `tabSessions` ( `user` varchar(255) DEFAULT NULL, - `sid` varchar(120) DEFAULT NULL, + `sid` varchar(255) DEFAULT NULL, `sessiondata` longtext, `ipaddress` varchar(16) DEFAULT NULL, `lastupdate` datetime(6) DEFAULT NULL, `status` varchar(20) DEFAULT NULL, KEY `sid` (`sid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; -- @@ -203,11 +183,9 @@ CREATE TABLE `tabSessions` ( -- DROP TABLE IF EXISTS `tabSingles`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `tabSingles` ( - `doctype` varchar(40) DEFAULT NULL, - `field` varchar(40) DEFAULT NULL, + `doctype` varchar(255) DEFAULT NULL, + `field` varchar(255) DEFAULT NULL, `value` text, KEY `singles_doctype_field_index` (`doctype`, `field`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -217,11 +195,9 @@ CREATE TABLE `tabSingles` ( -- DROP TABLE IF EXISTS `__Auth`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `__Auth` ( - `user` VARCHAR(180) NOT NULL PRIMARY KEY, - `password` VARCHAR(180) NOT NULL, + `user` VARCHAR(255) NOT NULL PRIMARY KEY, + `password` VARCHAR(255) NOT NULL, KEY `user` (`user`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -231,22 +207,22 @@ CREATE TABLE `__Auth` ( DROP TABLE IF EXISTS `tabFile Data`; CREATE TABLE `tabFile Data` ( - `name` varchar(120) NOT NULL, + `name` varchar(255) NOT NULL, `creation` datetime(6) DEFAULT NULL, `modified` datetime(6) DEFAULT NULL, - `modified_by` varchar(40) DEFAULT NULL, - `owner` varchar(40) DEFAULT NULL, + `modified_by` varchar(255) DEFAULT NULL, + `owner` varchar(255) DEFAULT NULL, `docstatus` int(1) DEFAULT '0', - `parent` varchar(120) DEFAULT NULL, - `parentfield` varchar(120) DEFAULT NULL, - `parenttype` varchar(120) DEFAULT NULL, + `parent` varchar(255) DEFAULT NULL, + `parentfield` varchar(255) DEFAULT NULL, + `parenttype` varchar(255) DEFAULT NULL, `idx` int(8) DEFAULT NULL, - `file_name` varchar(180) DEFAULT NULL, - `file_url` varchar(180) DEFAULT NULL, - `module` varchar(180) DEFAULT NULL, - `attached_to_name` varchar(180) DEFAULT NULL, + `file_name` varchar(255) DEFAULT NULL, + `file_url` varchar(255) DEFAULT NULL, + `module` varchar(255) DEFAULT NULL, + `attached_to_name` varchar(255) DEFAULT NULL, `file_size` int(11) DEFAULT NULL, - `attached_to_doctype` varchar(180) DEFAULT NULL, + `attached_to_doctype` varchar(255) DEFAULT NULL, PRIMARY KEY (`name`), KEY `parent` (`parent`), KEY `attached_to_name` (`attached_to_name`), @@ -259,18 +235,18 @@ CREATE TABLE `tabFile Data` ( DROP TABLE IF EXISTS `tabDefaultValue`; CREATE TABLE `tabDefaultValue` ( - `name` varchar(120) NOT NULL, + `name` varchar(255) NOT NULL, `creation` datetime(6) DEFAULT NULL, `modified` datetime(6) DEFAULT NULL, - `modified_by` varchar(40) DEFAULT NULL, - `owner` varchar(40) DEFAULT NULL, + `modified_by` varchar(255) DEFAULT NULL, + `owner` varchar(255) DEFAULT NULL, `docstatus` int(1) DEFAULT '0', - `parent` varchar(120) DEFAULT NULL, - `parentfield` varchar(120) DEFAULT NULL, - `parenttype` varchar(120) DEFAULT NULL, + `parent` varchar(255) DEFAULT NULL, + `parentfield` varchar(255) DEFAULT NULL, + `parenttype` varchar(255) DEFAULT NULL, `idx` int(8) DEFAULT NULL, `defvalue` text, - `defkey` varchar(180) DEFAULT NULL, + `defkey` varchar(255) DEFAULT NULL, PRIMARY KEY (`name`), KEY `parent` (`parent`), KEY `defaultvalue_parent_defkey_index` (`parent`,`defkey`) diff --git a/frappe/data/languages.txt b/frappe/data/languages.txt index a2938d24af..ed686d7802 100644 --- a/frappe/data/languages.txt +++ b/frappe/data/languages.txt @@ -7,11 +7,12 @@ fr français hi हिंदी hr hrvatski it italiano +kn ಕನ್ನಡ nl nederlands pt-BR português brasileiro pt português sr српски ta தமிழ் th ไทย -zh-cn 中国(简体 -zh-tw 中國(繁體 \ No newline at end of file +zh-cn 中国(简体) +zh-tw 中國(繁體) diff --git a/frappe/hooks.py b/frappe/hooks.py index 5b551d841d..95f2299710 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -66,6 +66,7 @@ scheduler_events = { "frappe.utils.email_lib.bulk.clear_outbox", "frappe.core.doctype.notification_count.notification_count.delete_event_notification_count", "frappe.core.doctype.event.event.send_event_digest", + "frappe.sessions.clear_expired_sessions", ], "hourly": [ "frappe.templates.generators.website_group.clear_event_cache" diff --git a/frappe/installer.py b/frappe/installer.py index b5054e3191..2dc37116d1 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -13,6 +13,7 @@ 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 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): @@ -81,7 +82,6 @@ def make_connection(root_login, root_password): root_password = getpass.getpass("MySQL root password: ") return frappe.database.Database(user=root_login, password=root_password) -@frappe.whitelist() def install_app(name, verbose=False, set_as_patched=True): frappe.flags.in_install_app = name frappe.clear_cache() @@ -105,6 +105,7 @@ def install_app(name, verbose=False, set_as_patched=True): if name != "frappe": add_module_defs(name) + sync_for(name, force=True, sync_everything=True, verbose=verbose) add_to_installed_apps(name) @@ -115,6 +116,8 @@ 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() + frappe.flags.in_install_app = False def add_to_installed_apps(app_name, rebuild_sitemap=True): diff --git a/frappe/middlewares.py b/frappe/middlewares.py index 6d5d61cb0e..cb84326249 100644 --- a/frappe/middlewares.py +++ b/frappe/middlewares.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,7 +16,7 @@ class StaticDataMiddleware(SharedDataMiddleware): def get_directory_loader(self, directory): def loader(path): - site = get_site_name(self.environ.get('HTTP_HOST')) + site = get_site_name(frappe.app._site or self.environ.get('HTTP_HOST')) path = os.path.join(directory, site, 'public', 'files', cstr(path)) if os.path.isfile(path): return os.path.basename(path), self._opener(path) diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 5beafb2936..067df6bc27 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -1,9 +1,11 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# MIT License. See license.txt # model __init__.py from __future__ import unicode_literals import frappe +import json + no_value_fields = ['Section Break', 'Column Break', 'HTML', 'Table', 'Button', 'Image'] default_fields = ['doctype','name','owner','creation','modified','modified_by','parent','parentfield','parenttype','idx','docstatus'] @@ -18,10 +20,10 @@ def insert(doclist): d["__islocal"] = 1 else: d.set("__islocal", 1) - + wrapper = frappe.get_doc(doclist) wrapper.save() - + return wrapper def rename(doctype, old, new, debug=False): @@ -29,17 +31,17 @@ def rename(doctype, old, new, debug=False): frappe.model.rename_doc.rename_doc(doctype, old, new, debug) def copytables(srctype, src, srcfield, tartype, tar, tarfield, srcfields, tarfields=[]): - if not tarfields: + if not tarfields: tarfields = srcfields l = [] data = src.get(srcfield) for d in data: newrow = tar.append(tarfield) newrow.idx = d.idx - + for i in range(len(srcfields)): newrow.set(tarfields[i], d.get(srcfields[i])) - + l.append(newrow) return l @@ -60,15 +62,15 @@ def delete_fields(args_dict, delete=0): for dt in args_dict.keys(): fields = args_dict[dt] if not fields: continue - + frappe.db.sql("""\ DELETE FROM `tabDocField` WHERE parent=%s AND fieldname IN (%s) """ % ('%s', ", ".join(['"' + f + '"' for f in fields])), dt) - + # Delete the data / column only if delete is specified if not delete: continue - + if frappe.db.get_value("DocType", dt, "issingle"): frappe.db.sql("""\ DELETE FROM `tabSingles` @@ -84,13 +86,13 @@ def delete_fields(args_dict, delete=0): def rename_field(doctype, old_fieldname, new_fieldname): """This functions assumes that doctype is already synced""" - + meta = frappe.get_meta(doctype) new_field = meta.get_field(new_fieldname) if not new_field: print "rename_field: " + (new_fieldname) + " not found in " + doctype return - + if new_field.fieldtype == "Table": # change parentfield of table mentioned in options frappe.db.sql("""update `tab%s` set parentfield=%s @@ -99,22 +101,82 @@ def rename_field(doctype, old_fieldname, new_fieldname): elif new_field.fieldtype not in no_value_fields: if meta.issingle: frappe.db.sql("""update `tabSingles` set field=%s - where doctype=%s and field=%s""", + where doctype=%s and field=%s""", (new_fieldname, doctype, old_fieldname)) else: # copy field value frappe.db.sql("""update `tab%s` set `%s`=`%s`""" % \ (doctype, new_fieldname, old_fieldname)) - + # update in property setter - frappe.db.sql("""update `tabProperty Setter` set field_name = %s + frappe.db.sql("""update `tabProperty Setter` set field_name = %s 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) - + +def update_reports(doctype, old_fieldname, new_fieldname): + def _get_new_sort_by(report_dict, report, key): + sort_by = report_dict.get(key) or "" + if sort_by: + sort_by = sort_by.split(".") + if len(sort_by) > 1: + if sort_by[0]==doctype and sort_by[1]==old_fieldname: + sort_by = doctype + "." + new_fieldname + report_dict["updated"] = True + elif report.ref_doctype == doctype and sort_by[0]==old_fieldname: + sort_by = doctype + "." + new_fieldname + report_dict["updated"] = True + + if isinstance(sort_by, list): + sort_by = '.'.join(sort_by) + + return sort_by + + reports = frappe.db.sql("""select name, ref_doctype, json from tabReport + where report_type = 'Report Builder' and ifnull(is_standard, 'No') = 'No' + and json like %s and json like %s""", + ('%%%s%%' % old_fieldname , '%%%s%%' % doctype), as_dict=True) + + for r in reports: + report_dict = json.loads(r.json) + + # update filters + new_filters = [] + for f in report_dict.get("filters"): + if f[0] == doctype and f[1] == old_fieldname: + new_filters.append([doctype, new_fieldname, f[2], f[3]]) + report_dict["updated"] = True + else: + new_filters.append(f) + + # update columns + new_columns = [] + for c in report_dict.get("columns"): + if c[0] == old_fieldname and c[1] == doctype: + new_columns.append([new_fieldname, doctype]) + report_dict["updated"] = True + else: + new_columns.append(c) + + # update sort by + new_sort_by = _get_new_sort_by(report_dict, r, "sort_by") + new_sort_by_next = _get_new_sort_by(report_dict, r, "sort_by_next") + + if report_dict.get("updated"): + new_val = json.dumps({ + "filters": new_filters, + "columns": new_columns, + "sort_by": new_sort_by, + "sort_order": report_dict.get("sort_order"), + "sort_by_next": new_sort_by_next, + "sort_order_next": report_dict.get("sort_order_next") + }) + + frappe.db.sql("""update `tabReport` set `json`=%s where name=%s""", (new_val, r.name)) + def update_users_report_view_settings(doctype, ref_fieldname): - import json - user_report_cols = frappe.db.sql("""select defkey, defvalue from `tabDefaultValue` where + user_report_cols = frappe.db.sql("""select defkey, defvalue from `tabDefaultValue` where defkey like '_list_settings:%'""") for key, value in user_report_cols: new_columns = [] @@ -124,5 +186,5 @@ def update_users_report_view_settings(doctype, ref_fieldname): new_columns.append([field, field_doctype]) columns_modified=True if columns_modified: - frappe.db.sql("""update `tabDefaultValue` set defvalue=%s - where defkey=%s""" % ('%s', '%s'), (json.dumps(new_columns), key)) \ No newline at end of file + 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 df303b0699..e723579bce 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -163,6 +163,9 @@ class BaseDocument(object): if self.get("__islocal"): doc["__islocal"] = 1 + elif self.get("__onload"): + doc["__onload"] = self.get("__onload") + return doc def as_json(self): @@ -189,6 +192,7 @@ class BaseDocument(object): except Exception, e: if e.args[0]==1062: type, value, traceback = sys.exc_info() + frappe.msgprint(_("Duplicate name {0} {1}".format(self.doctype, self.name))) raise frappe.NameError, (self.doctype, self.name, e), traceback else: raise @@ -259,9 +263,6 @@ class BaseDocument(object): if not doctype: frappe.throw(_("Options not set for link field {0}").format(df.fieldname)) - elif doctype.lower().startswith("link:"): - doctype = doctype[5:] - 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))) @@ -285,7 +286,7 @@ class BaseDocument(object): current = frappe.db.get_value(self.doctype, self.name, "*", as_dict=True) for key, value in current.iteritems(): df = self.meta.get_field(key) - if df and not df.allow_on_submit and self.get(key) != value: + if df and not df.allow_on_submit and (self.get(key) or value) and self.get(key) != value: frappe.throw(_("Not allowed to change {0} after submission").format(df.label), frappe.UpdateAfterSubmitError) diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py index 79d9ad5d10..ae3d12dc82 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -63,8 +63,7 @@ def get_new_doc(doctype, parent_doc = None, parentfield = None): elif d.fieldtype == "Time": doc.set(d.fieldname, nowtime()) - elif (d.fieldtype == "Select" and d.options and not d.options.startswith("link:") - and d.options != "[Select]"): + elif (d.fieldtype == "Select" and d.options and d.options != "[Select]"): doc.set(d.fieldname, d.options.split("\n")[0]) return doc diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 905d8d53a3..da49f92fde 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -18,15 +18,17 @@ class DatabaseQuery(object): self.ignore_permissions = False self.fields = ["name"] - def execute(self, query=None, filters=None, fields=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): + 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 if fields: self.fields = fields self.filters = filters or [] + self.or_filters = or_filters or [] self.docstatus = docstatus or [] self.group_by = group_by self.order_by = order_by @@ -67,17 +69,19 @@ class DatabaseQuery(object): # query dict args.tables = ', '.join(self.tables) + if self.or_conditions: + self.conditions.append("({0})".format(" or ".join(self.or_conditions))) args.conditions = ' and '.join(self.conditions) args.fields = ', '.join(self.fields) - args.order_by = self.order_by or self.tables[0] + '.modified desc' + self.set_order_by(args) + 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) @@ -134,8 +138,10 @@ class DatabaseQuery(object): def build_conditions(self): self.conditions = [] + self.or_conditions = [] self.add_docstatus_conditions() - self.build_filter_conditions() + self.build_filter_conditions(self.filters, self.conditions) + self.build_filter_conditions(self.or_filters, self.or_conditions) # join parent, child tables for tname in self.tables[1:]: @@ -153,11 +159,13 @@ class DatabaseQuery(object): else: self.conditions.append(self.tables[0] + '.docstatus < 2') - def build_filter_conditions(self): + def build_filter_conditions(self, filters, conditions): """build conditions from user filters""" - for f in self.filters: + if isinstance(filters, dict): + filters = [filters] + for f in filters: if isinstance(f, basestring): - self.conditions.append(f) + conditions.append(f) else: f = self.get_filter_tuple(f) @@ -172,7 +180,7 @@ class DatabaseQuery(object): opts = f[3].split(",") opts = ["'" + t.strip().replace("'", "\\'") + "'" for t in opts] f[3] = "(" + ', '.join(opts) + ")" - self.conditions.append('ifnull(' + tname + '.' + f[1] + ", '') " + f[2] + " " + f[3]) + conditions.append('ifnull(' + tname + '.' + f[1] + ", '') " + f[2] + " " + f[3]) else: df = frappe.get_meta(f[0]).get("fields", {"fieldname": f[1]}) @@ -182,7 +190,7 @@ class DatabaseQuery(object): else: value, default_val = flt(f[3]), 0 - self.conditions.append('ifnull({tname}.{fname}, {default_val}) {operator} {value}'.format( + conditions.append('ifnull({tname}.{fname}, {default_val}) {operator} {value}'.format( tname=tname, fname=f[1], default_val=default_val, operator=f[2], value=value)) @@ -203,7 +211,7 @@ class DatabaseQuery(object): """add match conditions if applicable""" self.match_filters = {} self.match_conditions = [] - self.or_conditions = [] + self.match_or_conditions = [] if not self.tables: self.extract_tables() @@ -214,7 +222,7 @@ class DatabaseQuery(object): restrictions = frappe.defaults.get_restrictions() if restricted_by_user: - self.or_conditions.append('`tab{doctype}`.`owner`="{user}"'.format(doctype=self.doctype, + 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 @@ -247,12 +255,12 @@ class DatabaseQuery(object): if doctype_conditions: conditions += ' and ' + doctype_conditions if conditions else doctype_conditions - if self.or_conditions: + if self.match_or_conditions: if conditions: conditions = '({conditions}) or {or_conditions}'.format(conditions=conditions, - or_conditions = ' or '.join(self.or_conditions)) + or_conditions = ' or '.join(self.match_or_conditions)) else: - conditions = " or ".join(self.or_conditions) + conditions = " or ".join(self.match_or_conditions) return conditions @@ -272,6 +280,14 @@ class DatabaseQuery(object): query = query.replace('%(key)s', 'name') return frappe.db.sql(query, as_dict = (not self.as_list)) + def set_order_by(self, args): + meta = frappe.get_meta(self.doctype) + 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") + def check_sort_by_table(self, order_by): if "." in order_by: tbl = order_by.split('.')[0] diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index 86f7c474e0..127f2de333 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -175,27 +175,37 @@ class DbTable: for col in self.columns.values(): col.check(self.current_columns.get(col.fieldname, None)) + query = [] + for col in self.add_column: - frappe.db.sql("alter table `%s` add column `%s` %s" % (self.name, col.fieldname, col.get_definition())) + query.append("add column `{}` {}".format(col.fieldname, col.get_definition())) for col in self.change_type: - frappe.db.sql("alter table `%s` change `%s` `%s` %s" % (self.name, col.fieldname, col.fieldname, col.get_definition())) + query.append("change `{}` `{}` {}".format(col.fieldname, col.fieldname, col.get_definition())) for col in self.add_index: # if index key not exists if not frappe.db.sql("show index from `%s` where key_name = %s" % (self.name, '%s'), col.fieldname): - frappe.db.sql("alter table `%s` add index `%s`(`%s`)" % (self.name, col.fieldname, col.fieldname)) + query.append("add index `{}`(`{}`)".format(col.fieldname, col.fieldname)) for col in self.drop_index: if col.fieldname != 'name': # primary key # if index key exists if frappe.db.sql("show index from `%s` where key_name = %s" % (self.name, '%s'), col.fieldname): - frappe.db.sql("alter table `%s` drop index `%s`" % (self.name, col.fieldname)) + query.append("drop index `{}`".format(col.fieldname)) - for col in self.set_default: - frappe.db.sql("alter table `%s` alter column `%s` set default %s" % (self.name, col.fieldname, '%s'), (col.default,)) + for col in list(set(self.set_default).difference(set(self.change_type))): + if not col.default: + col_default = "null" + else: + col_default = '"{}"'.format(col.default.replace('"', '\\"')) + + query.append('alter column `{}` set default {}'.format(col.fieldname, col_default)) + + if query: + frappe.db.sql("alter table `{}` {}".format(self.name, ", ".join(query))) class DbColumn: def __init__(self, table, fieldname, fieldtype, length, default, set_index, options): diff --git a/frappe/model/document.py b/frappe/model/document.py index 6118f02d46..8ff78e119d 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -8,6 +8,7 @@ from frappe.utils import flt, cint, cstr, now from frappe.modules import load_doctype_module from frappe.model.base_document import BaseDocument from frappe.model.naming import set_new_name +from werkzeug.exceptions import NotFound, Forbidden # once_only validation # methods @@ -156,6 +157,8 @@ class Document(BaseDocument): self.set_parent_in_children() self.run_before_save_methods() self._validate() + if self._action == "update_after_submit": + self.validate_update_after_submit() # parent if self.meta.issingle: @@ -283,7 +286,6 @@ class Document(BaseDocument): elif docstatus==1: if self.docstatus==1: self._action = "update_after_submit" - self.validate_update_after_submit() if not self.has_permission("submit"): self.raise_no_permission_to("submit") elif self.docstatus==2: @@ -387,6 +389,7 @@ 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): @@ -400,6 +403,11 @@ class Document(BaseDocument): elif self._action=="update_after_submit": self.run_method("on_update_after_submit") + @staticmethod + def whitelist(f): + f.whitelisted = True + return f + @staticmethod def hook(f): def add_to_return_value(self, new_return_value): @@ -433,6 +441,13 @@ class Document(BaseDocument): return composer + def is_whitelisted(self, method): + fn = getattr(self, method, None) + if not fn: + raise NotFound("Method {0} not found".format(method)) + elif not getattr(fn, "whitelisted", False): + raise Forbidden("Method {0} not whitelisted".format(method)) + def validate_value(self, fieldname, condition, val2, doc=None, raise_exception=None): """check that value of fieldname should be 'condition' val2 else throw exception""" diff --git a/frappe/model/meta.py b/frappe/model/meta.py index f4f2c099a8..c0e1ac0bca 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -59,11 +59,7 @@ class Meta(Document): raise def get_link_fields(self): - tmp = self.get("fields", {"fieldtype":"Link", "options":["!=", "[Select]"]}) - for df in self.get("fields", {"fieldtype":"Select", "options": "^link:"}): - tmp.append(frappe._dict({"fieldname":df.fieldname, "label":df.label, - "fieldtype":"Link", "options": df.options[5:]})) - return tmp + return self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]}) def get_table_fields(self): if not hasattr(self, "_table_fields"): @@ -99,6 +95,21 @@ class Meta(Document): def get_options(self, fieldname): return self.get_field(fieldname).options + def get_search_fields(self): + search_fields = self.search_fields or "name" + search_fields = [d.strip() for d in search_fields.split(",")] + if "name" not in search_fields: + search_fields.append("name") + + return search_fields + + def get_list_fields(self): + list_fields = ["name"] + [d.fieldname \ + for d in self.fields if (d.in_list_view and d.fieldtype in type_map)] + if self.title_field and self.title_field not in list_fields: + list_fields.append(self.title_field) + return list_fields + def process(self): # don't process for special doctypes # prevent's circular dependency @@ -203,7 +214,7 @@ doctype_table_fields = [ def is_single(doctype): try: return frappe.db.get_value("DocType", doctype, "issingle") - except IndexError, e: + except IndexError: raise Exception, 'Cannot determine whether %s is single' % doctype def get_parent_dt(dt): diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 782c88a91d..48b551f209 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -7,13 +7,9 @@ import frappe from frappe.utils import now_datetime, cint def set_new_name(doc): - if getattr(doc, "_new_name_set", False): - # already set by doc + if doc.name: return - doc._new_name_set = True - autoname = frappe.get_meta(doc.doctype).autoname - # amendments if getattr(doc, "amended_from", None): return _get_amended_name(doc) @@ -22,31 +18,35 @@ def set_new_name(doc): if tmp and not isinstance(tmp, basestring): # autoname in a function, not a property doc.autoname() - if doc.name: - return + if doc.name: + return + + autoname = frappe.get_meta(doc.doctype).autoname # based on a field - if autoname and autoname.startswith('field:'): - n = doc.get(autoname[6:]) - if not n: - raise Exception, 'Name is required' - doc.name = n.strip() + if autoname: + if autoname.startswith('field:'): + n = doc.get(autoname[6:]) + if not n: + raise Exception, 'Name is required' + doc.name = n.strip() - elif autoname and autoname.startswith("naming_series:"): - if not doc.naming_series: - doc.naming_series = get_default_naming_series(doc.doctype) + elif autoname.startswith("naming_series:"): + if not doc.naming_series: + doc.naming_series = get_default_naming_series(doc.doctype) - if not doc.naming_series: - frappe.msgprint(frappe._("Naming Series mandatory"), raise_exception=True) - doc.name = make_autoname(doc.naming_series+'.#####') + if not doc.naming_series: + frappe.msgprint(frappe._("Naming Series mandatory"), raise_exception=True) + doc.name = make_autoname(doc.naming_series+'.#####') - # call the method! - elif autoname and autoname!='Prompt': - doc.name = make_autoname(autoname, doc.doctype) + # call the method! + elif autoname=='Prompt': + # set from __newname in save.py + if not doc.name: + frappe.throw(frappe._("Name not set via Prompt")) - # given - elif doc.get('__newname', None): - doc.name = doc.get('__newname') + else: + doc.name = make_autoname(autoname, doc.doctype) # default name for table elif doc.meta.istable: diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 32d7072c30..1f4b5fb180 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -11,7 +11,7 @@ from frappe.model.naming import validate_name def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=False): """ Renames a doc(dt, old) to doc(dt, new) and - updates all linked fields of type "Link" or "Select" with "link:" + updates all linked fields of type "Link" """ if not frappe.db.exists(doctype, old): return @@ -138,10 +138,7 @@ def get_link_fields(doctype): where dt.name = df.parent) as issingle from tabDocField df where - df.parent not like "old%%%%" and df.parent != '0' and - ((df.options=%s and df.fieldtype='Link') or - (df.options='link:%s' and df.fieldtype='Select'))""" \ - % ('%s', doctype), (doctype,), as_dict=1) + df.options=%s and df.fieldtype='Link'""", (doctype,), as_dict=1) # get link fields from tabCustom Field custom_link_fields = frappe.db.sql("""\ @@ -150,10 +147,7 @@ def get_link_fields(doctype): where dt.name = df.dt) as issingle from `tabCustom Field` df where - df.dt not like "old%%%%" and df.dt != '0' and - ((df.options=%s and df.fieldtype='Link') or - (df.options='link:%s' and df.fieldtype='Select'))""" \ - % ('%s', doctype), (doctype,), as_dict=1) + df.options=%s and df.fieldtype='Link'""", (doctype,), as_dict=1) # add custom link fields list to link fields list link_fields += custom_link_fields @@ -167,8 +161,7 @@ def get_link_fields(doctype): where ps.property_type='options' and ps.field_name is not null and - (ps.value=%s or ps.value='link:%s')""" \ - % ('%s', doctype), (doctype,), as_dict=1) + ps.value=%s""", (doctype,), as_dict=1) link_fields += property_setter_link_fields @@ -199,10 +192,8 @@ def get_select_fields(old, new): where dt.name = df.parent) as issingle from tabDocField df where - df.parent not like "old%%%%" and df.parent != '0' and df.parent != %s and df.fieldtype = 'Select' and - df.options not like "link:%%%%" and - (df.options like "%%%%%s%%%%")""" \ + df.options like "%%%%%s%%%%" """ \ % ('%s', old), (new,), as_dict=1) # get link fields from tabCustom Field @@ -212,10 +203,8 @@ def get_select_fields(old, new): where dt.name = df.dt) as issingle from `tabCustom Field` df where - df.dt not like "old%%%%" and df.dt != '0' and df.dt != %s and df.fieldtype = 'Select' and - df.options not like "link:%%%%" and - (df.options like "%%%%%s%%%%")""" \ + df.options like "%%%%%s%%%%" """ \ % ('%s', old), (new,), as_dict=1) # add custom link fields list to link fields list @@ -231,8 +220,7 @@ def get_select_fields(old, new): ps.doc_type != %s and ps.property_type='options' and ps.field_name is not null and - ps.value not like "link:%%%%" and - (ps.value like "%%%%%s%%%%")""" \ + ps.value like "%%%%%s%%%%" """ \ % ('%s', old), (new,), as_dict=1) select_fields += property_setter_select_fields @@ -243,16 +231,14 @@ def update_select_field_values(old, new): frappe.db.sql("""\ update `tabDocField` set options=replace(options, %s, %s) where - parent != %s and parent not like "old%%%%" and - fieldtype = 'Select' and options not like "link:%%%%" and + parent != %s and fieldtype = 'Select' and (options like "%%%%\\n%s%%%%" or options like "%%%%%s\\n%%%%")""" % \ ('%s', '%s', '%s', old, old), (old, new, new)) frappe.db.sql("""\ update `tabCustom Field` set options=replace(options, %s, %s) where - dt != %s and dt not like "old%%%%" and - fieldtype = 'Select' and options not like "link:%%%%" and + dt != %s and fieldtype = 'Select' and (options like "%%%%\\n%s%%%%" or options like "%%%%%s\\n%%%%")""" % \ ('%s', '%s', '%s', old, old), (old, new, new)) @@ -260,7 +246,7 @@ def update_select_field_values(old, new): update `tabProperty Setter` set value=replace(value, %s, %s) where doc_type != %s and field_name is not null and - property='options' and value not like "link%%%%" and + property='options' and (value like "%%%%\\n%s%%%%" or value like "%%%%%s\\n%%%%")""" % \ ('%s', '%s', '%s', old, old), (old, new, new)) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 1ad92d78ec..0c285f423a 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -25,14 +25,14 @@ def walk_and_sync(start_path, force=0, sync_everything = False, verbose=False): """walk and sync all doctypes and pages""" modules = [] - - document_type = ['doctype', 'page', 'report'] + + document_type = ['doctype', 'page', 'report', 'print_format'] for path, folders, files in os.walk(start_path): # sort folders so that doctypes are synced before pages or reports for dontwalk in (".git", "locale", "public"): - if dontwalk in folders: + if dontwalk in folders: folders.remove(dontwalk) folders.sort() @@ -47,10 +47,10 @@ def walk_and_sync(start_path, force=0, sync_everything = False, verbose=False): module_name = path.split(os.sep)[-3] doctype = path.split(os.sep)[-2] name = path.split(os.sep)[-1] - + if import_file_by_path(os.path.join(path, f), force=force) and verbose: print module_name + ' | ' + doctype + ' | ' + name frappe.db.commit() - + return modules diff --git a/frappe/modules/__init__.py b/frappe/modules/__init__.py index 50950de9dc..9f8890c482 100644 --- a/frappe/modules/__init__.py +++ b/frappe/modules/__init__.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 """ @@ -8,12 +8,12 @@ from __future__ import unicode_literals import frappe, os import frappe.utils -lower_case_files_for = ['DocType', 'Page', 'Report', - "Workflow", 'Module Def', 'Desktop Item', 'Workflow State', 'Workflow Action'] +lower_case_files_for = ['DocType', 'Page', 'Report', + "Workflow", 'Module Def', 'Desktop Item', 'Workflow State', 'Workflow Action', 'Print Format'] def scrub(txt): return frappe.scrub(txt) - + def scrub_dt_dn(dt, dn): """Returns in lowercase and code friendly names of doctype and name for certain types""" ndt, ndn = dt, dn @@ -21,11 +21,11 @@ def scrub_dt_dn(dt, dn): ndt, ndn = scrub(dt), scrub(dn) return ndt, ndn - + def get_module_path(module): """Returns path of the given module""" return frappe.get_module_path(module) - + def get_doc_path(module, doctype, name): dt, dn = scrub_dt_dn(doctype, name) return os.path.join(get_module_path(module), dt, dn) @@ -48,9 +48,9 @@ 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 return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}'.format(\ - app = scrub(frappe.local.module_app[scrub(module)]), + app = scrub(frappe.local.module_app[scrub(module)]), module = scrub(module), doctype = scrub(doctype), prefix=prefix) diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 9c3a90ddae..87db8652aa 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -47,11 +47,11 @@ def import_file_by_path(path, force=False): original_modified = doc.get("modified") - import_doc(doc) + import_doc(doc, force=force) if original_modified: # since there is a new timestamp on the file, update timestamp in - if doc["doctype"] == doc["name"]: + if doc["doctype"] == doc["name"] and doc["name"]!="DocType": frappe.db.sql("""update tabSingles set value=%s where field="modified" and doctype=%s""", (original_modified, doc["name"])) else: @@ -78,7 +78,7 @@ ignore_values = { ignore_doctypes = ["Page Role", "DocPerm"] -def import_doc(docdict): +def import_doc(docdict, force=False): docdict["__islocal"] = 1 doc = frappe.get_doc(docdict) @@ -87,14 +87,14 @@ def import_doc(docdict): if frappe.db.exists(doc.doctype, doc.name): old_doc = frappe.get_doc(doc.doctype, doc.name) - if doc.doctype in ignore_values: + if doc.doctype in ignore_values and not force: # update ignore values for key in ignore_values.get(doc.doctype) or []: doc.set(key, old_doc.get(key)) # update ignored docs into new doc for df in doc.meta.get_table_fields(): - if df.options in ignore_doctypes: + if df.options in ignore_doctypes and not force: doc.set(df.fieldname, []) ignore.append(df.options) diff --git a/frappe/modules/patch_handler.py b/frappe/modules/patch_handler.py index bd2b18941f..e7dbd01ff5 100644 --- a/frappe/modules/patch_handler.py +++ b/frappe/modules/patch_handler.py @@ -1,15 +1,15 @@ # 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 """ Execute Patch Files To run directly - + python lib/wnf.py patch patch1, patch2 etc python lib/wnf.py patch -f patch1, patch2 etc - + where patch1, patch2 is module name """ import frappe, os @@ -19,21 +19,21 @@ class PatchError(Exception): pass def run_all(): """run all pending patches""" executed = [p[0] for p in frappe.db.sql("""select patch from `tabPatch Log`""")] - + for patch in get_all_patches(): if patch and (patch not in executed): if not run_single(patchmodule = patch): log(patch + ': failed: STOPPED') raise PatchError(patch) - + def get_all_patches(): patches = [] for app in frappe.get_installed_apps(): # 3-to-4 fix - if app=="webnotes": + if app=="webnotes": app="frappe" patches.extend(frappe.get_file_items(frappe.get_pymodule_path(app, "patches.txt"))) - + return patches def reload_doc(args): @@ -42,22 +42,23 @@ def reload_doc(args): def run_single(patchmodule=None, method=None, methodargs=None, force=False): from frappe import conf - + # don't write txt files conf.developer_mode = 0 - + if force or method or not executed(patchmodule): return execute_patch(patchmodule, method, methodargs) else: return True - + def execute_patch(patchmodule, method=None, methodargs=None): """execute the patch""" success = False block_user(True) frappe.db.begin() try: - log('Executing %s in %s' % (patchmodule or str(methodargs), frappe.db.cur_db_name)) + log('Executing {patch} in {site} ({db})'.format(patch=patchmodule or str(methodargs), + site=frappe.local.site, db=frappe.db.cur_db_name)) if patchmodule: if patchmodule.startswith("execute:"): exec patchmodule.split("execute:")[1] in globals() @@ -66,66 +67,43 @@ def execute_patch(patchmodule, method=None, methodargs=None): update_patch_log(patchmodule) elif method: method(**methodargs) - + frappe.db.commit() success = True except Exception, e: frappe.db.rollback() tb = frappe.get_traceback() log(tb) - import os - if frappe.request: - add_to_patch_log(tb) block_user(False) if success: log('Success') return success -def add_to_patch_log(tb): - """add error log to patches/patch.log""" - import conf, os - # TODO use get_site_base_path - with open(os.path.join(os.path.dirname(conf.__file__), 'app', 'patches','patch.log'),'a') as patchlog: - patchlog.write('\n\n' + tb) - def update_patch_log(patchmodule): - """update patch_file in patch log""" - if frappe.db.table_exists("__PatchLog"): - frappe.db.sql("""INSERT INTO `__PatchLog` VALUES (%s, now())""", \ - patchmodule) - else: - frappe.get_doc({"doctype": "Patch Log", "patch": patchmodule}).insert() + """update patch_file in patch log""" + frappe.get_doc({"doctype": "Patch Log", "patch": patchmodule}).insert() def executed(patchmodule): """return True if is executed""" - if frappe.db.table_exists("__PatchLog"): - done = frappe.db.sql("""select patch from __PatchLog where patch=%s""", patchmodule) - else: - done = frappe.db.get_value("Patch Log", {"patch": patchmodule}) - if done: - print "Patch %s already executed in %s" % (patchmodule, frappe.db.cur_db_name) + done = frappe.db.get_value("Patch Log", {"patch": patchmodule}) + # if done: + # print "Patch %s already executed in %s" % (patchmodule, frappe.db.cur_db_name) return done - + def block_user(block): """stop/start execution till patch is run""" + frappe.local.flags.in_patch = block frappe.db.begin() msg = "Patches are being executed in the system. Please try again in a few moments." frappe.db.set_global('__session_status', block and 'stop' or None) frappe.db.set_global('__session_status_message', block and msg or None) frappe.db.commit() - + def check_session_stopped(): if frappe.db.get_global("__session_status")=='stop': frappe.msgprint(frappe.db.get_global("__session_status_message")) raise frappe.SessionStopped('Session Stopped') -def setup(): - frappe.db.sql("""CREATE TABLE IF NOT EXISTS `__PatchLog` ( - patch TEXT, applied_on DATETIME) engine=InnoDB""") - def log(msg): - if getattr(frappe.local, "patch_log_list", None) is None: - frappe.local.patch_log_list = [] - - frappe.local.patch_log_list.append(msg) + print msg diff --git a/frappe/patches.txt b/frappe/patches.txt index e424ab761a..3c8dbd23bd 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -1,5 +1,6 @@ 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 @@ -8,21 +9,24 @@ execute:frappe.reload_doc('core', 'doctype', 'report') #2013-13-26 execute:frappe.reload_doc('core', 'doctype', 'version') #2014-02-21 execute:frappe.db.sql("alter table `tabSessions` modify `user` varchar(255), engine=InnoDB") -frappe.patches.4_0.remove_index_sitemap -frappe.patches.4_0.add_delete_permission -frappe.patches.4_0.move_match_to_restricted -frappe.patches.4_0.set_todo_checked_as_closed -frappe.patches.4_0.website_sitemap_hierarchy -frappe.patches.4_0.webnotes_to_frappe +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.4_0.rename_sitemap_to_route -frappe.patches.4_0.rename_profile_to_user -frappe.patches.4_0.set_website_route_idx +frappe.patches.v4_0.rename_sitemap_to_route +frappe.patches.v4_0.rename_profile_to_user +frappe.patches.v4_0.set_website_route_idx execute:import frappe.installer;frappe.installer.make_site_dirs() #2014-02-19 -frappe.patches.4_0.private_backups -frappe.patches.4_0.set_module_in_report -frappe.patches.4_0.remove_old_parent -frappe.patches.4_0.update_datetime -frappe.patches.4_0.deprecate_control_panel -frappe.patches.4_0.file_manager_hooks +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.deprecate_link_selects +frappe.patches.v4_0.set_user_gravatar +frappe.patches.v4_0.update_custom_field_insert_after diff --git a/frappe/patches/4_0/__init__.py b/frappe/patches/v4_0/__init__.py similarity index 100% rename from frappe/patches/4_0/__init__.py rename to frappe/patches/v4_0/__init__.py diff --git a/frappe/patches/4_0/add_delete_permission.py b/frappe/patches/v4_0/add_delete_permission.py similarity index 100% rename from frappe/patches/4_0/add_delete_permission.py rename to frappe/patches/v4_0/add_delete_permission.py diff --git a/frappe/patches/4_0/deprecate_control_panel.py b/frappe/patches/v4_0/deprecate_control_panel.py similarity index 100% rename from frappe/patches/4_0/deprecate_control_panel.py rename to frappe/patches/v4_0/deprecate_control_panel.py diff --git a/frappe/patches/v4_0/deprecate_link_selects.py b/frappe/patches/v4_0/deprecate_link_selects.py new file mode 100644 index 0000000000..317333b019 --- /dev/null +++ b/frappe/patches/v4_0/deprecate_link_selects.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(): + for name in frappe.db.sql_list("""select name from `tabCustom Field` + where fieldtype="Select" and options like "link:%" """): + custom_field = frappe.get_doc("Custom Field", name) + custom_field.fieldtype = "Link" + custom_field.options = custom_field.options[5:] + custom_field.save() diff --git a/frappe/patches/4_0/file_manager_hooks.py b/frappe/patches/v4_0/file_manager_hooks.py similarity index 72% rename from frappe/patches/4_0/file_manager_hooks.py rename to frappe/patches/v4_0/file_manager_hooks.py index 5c72e62da3..ef043d4d31 100644 --- a/frappe/patches/4_0/file_manager_hooks.py +++ b/frappe/patches/v4_0/file_manager_hooks.py @@ -21,7 +21,16 @@ def execute(): b.file_url = os.path.normpath('/' + old_file_name) else: b.file_url = os.path.normpath('/files/' + old_file_name) - _file_name, content = get_file(name) - b.content_hash = get_content_hash(content) - b.save() + try: + _file_name, content = get_file(name) + b.content_hash = get_content_hash(content) + except IOError: + print 'Warning: Error processing ', name + _file_name = old_file_name + b.content_hash = None + + try: + b.save() + except frappe.DuplicateEntryError: + frappe.delete_doc(b.doctype, b.name) diff --git a/frappe/patches/4_0/move_match_to_restricted.py b/frappe/patches/v4_0/move_match_to_restricted.py similarity index 100% rename from frappe/patches/4_0/move_match_to_restricted.py rename to frappe/patches/v4_0/move_match_to_restricted.py diff --git a/frappe/patches/4_0/private_backups.py b/frappe/patches/v4_0/private_backups.py similarity index 100% rename from frappe/patches/4_0/private_backups.py rename to frappe/patches/v4_0/private_backups.py diff --git a/frappe/patches/4_0/remove_index_sitemap.py b/frappe/patches/v4_0/remove_index_sitemap.py similarity index 100% rename from frappe/patches/4_0/remove_index_sitemap.py rename to frappe/patches/v4_0/remove_index_sitemap.py diff --git a/frappe/patches/4_0/remove_old_parent.py b/frappe/patches/v4_0/remove_old_parent.py similarity index 84% rename from frappe/patches/4_0/remove_old_parent.py rename to frappe/patches/v4_0/remove_old_parent.py index 7c738f43f4..588cda9121 100644 --- a/frappe/patches/4_0/remove_old_parent.py +++ b/frappe/patches/v4_0/remove_old_parent.py @@ -7,3 +7,4 @@ import frappe def execute(): for doctype in frappe.db.sql_list("""select name from `tabDocType` where istable=1"""): frappe.db.sql("""delete from `tab{0}` where parent like "old_par%:%" """.format(doctype)) + frappe.db.sql("""delete from `tabDocField` where parent="0" """) diff --git a/frappe/patches/4_0/rename_profile_to_user.py b/frappe/patches/v4_0/rename_profile_to_user.py similarity index 100% rename from frappe/patches/4_0/rename_profile_to_user.py rename to frappe/patches/v4_0/rename_profile_to_user.py diff --git a/frappe/patches/4_0/rename_sitemap_to_route.py b/frappe/patches/v4_0/rename_sitemap_to_route.py similarity index 100% rename from frappe/patches/4_0/rename_sitemap_to_route.py rename to frappe/patches/v4_0/rename_sitemap_to_route.py diff --git a/frappe/patches/4_0/set_module_in_report.py b/frappe/patches/v4_0/set_module_in_report.py similarity index 100% rename from frappe/patches/4_0/set_module_in_report.py rename to frappe/patches/v4_0/set_module_in_report.py diff --git a/frappe/patches/4_0/set_todo_checked_as_closed.py b/frappe/patches/v4_0/set_todo_checked_as_closed.py similarity index 100% rename from frappe/patches/4_0/set_todo_checked_as_closed.py rename to frappe/patches/v4_0/set_todo_checked_as_closed.py diff --git a/frappe/patches/v4_0/set_user_gravatar.py b/frappe/patches/v4_0/set_user_gravatar.py new file mode 100644 index 0000000000..78bd72f4dc --- /dev/null +++ b/frappe/patches/v4_0/set_user_gravatar.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(): + for name in frappe.db.sql_list("select name from `tabUser` where ifnull(user_image, '')=''"): + user = frappe.get_doc("User", name) + user.update_gravatar() + user.db_set("user_image", user.user_image) diff --git a/frappe/patches/4_0/set_website_route_idx.py b/frappe/patches/v4_0/set_website_route_idx.py similarity index 100% rename from frappe/patches/4_0/set_website_route_idx.py rename to frappe/patches/v4_0/set_website_route_idx.py diff --git a/frappe/patches/v4_0/update_custom_field_insert_after.py b/frappe/patches/v4_0/update_custom_field_insert_after.py new file mode 100644 index 0000000000..0e820cd0e3 --- /dev/null +++ b/frappe/patches/v4_0/update_custom_field_insert_after.py @@ -0,0 +1,18 @@ +# 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 d in frappe.db.sql("""select name, dt, insert_after from `tabCustom Field` + where docstatus < 2""", as_dict=1): + dt_meta = frappe.get_meta(d.dt) + if not dt_meta.get_field(d.insert_after): + cf = frappe.get_doc("Custom Field", d.name) + df = dt_meta.get("fields", {"label": d.insert_after}) + if df: + cf.insert_after = df[0].fieldname + else: + cf.insert_after = None + cf.save() diff --git a/frappe/patches/4_0/update_datetime.py b/frappe/patches/v4_0/update_datetime.py similarity index 100% rename from frappe/patches/4_0/update_datetime.py rename to frappe/patches/v4_0/update_datetime.py diff --git a/frappe/patches/4_0/webnotes_to_frappe.py b/frappe/patches/v4_0/webnotes_to_frappe.py similarity index 100% rename from frappe/patches/4_0/webnotes_to_frappe.py rename to frappe/patches/v4_0/webnotes_to_frappe.py diff --git a/frappe/patches/4_0/website_sitemap_hierarchy.py b/frappe/patches/v4_0/website_sitemap_hierarchy.py similarity index 100% rename from frappe/patches/4_0/website_sitemap_hierarchy.py rename to frappe/patches/v4_0/website_sitemap_hierarchy.py diff --git a/frappe/permissions.py b/frappe/permissions.py index 0663e11f8f..466318cd4f 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -71,11 +71,12 @@ def has_unrestricted_access(doc, verbose=True): 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: + elif not (restrictions and restrictions.get(doc.get("doctype"))): return False else: if not restrictions: diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 0ed2b89c01..4668115949 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -106,6 +106,10 @@ div#freeze { /*margin-top: -15px;*/ } +.form-control { + padding: 6px 8px; +} + .form-headline { margin-bottom: 10px; font-size: 120%; diff --git a/frappe/public/js/frappe/form/assign_to.js b/frappe/public/js/frappe/form/assign_to.js index dac6ebd85b..6bf24bdd76 100644 --- a/frappe/public/js/frappe/form/assign_to.js +++ b/frappe/public/js/frappe/form/assign_to.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 // assign to is lined to todo // refresh - load todos @@ -15,9 +15,9 @@ frappe.ui.form.AssignTo = Class.extend({ this.wrapper = $('
\
\
').appendTo(this.parent); - + this.$list = this.wrapper.find(".alert-list"); - + this.parent.find(".btn").click(function() { me.add(); }); @@ -36,11 +36,11 @@ frappe.ui.form.AssignTo = Class.extend({ this.frm.get_docinfo().assignments = d; this.$list.empty(); if(this.dialog) { - this.dialog.hide(); + this.dialog.hide(); } if(d && d.length) { - for(var i=0; i").appendTo(this.parent); - this.$body = $("
").appendTo(this.$wrapper) - .css({"margin-bottom": "10px", "margin-right": "15px", "float": "right", - "text-align": "right", "max-width": "100%"}) + this.$wrapper = $("
") + .appendTo(this.parent) + .css({"max-width": "600px", "margin": "0px"}); + this.$body = $("
").appendTo(this.$wrapper) + .css({"margin-bottom": "10px", "max-width": "100%"}) this.$wrapper.on("refresh", function() { me.$body.empty(); if(me.df.options && me.frm.doc[me.df.options]) { - me.$img = $("") + me.$img = $("") .appendTo(me.$body); } else { me.$buffer = $("
") @@ -694,7 +695,7 @@ frappe.ui.form.ControlSelect = frappe.ui.form.ControlData.extend({ // set the default option (displayed) var input_value = this.$input.val(); var model_value = frappe.model.get_value(this.doctype, this.docname, this.df.fieldname); - if(model_value == null && input_value != (model_value || "")) { + if(model_value == null && (input_value || "") != (model_value || "")) { this.set_model_value(input_value); } else { this.last_value = value; @@ -784,16 +785,14 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ this.$input.on("focus", function() { setTimeout(function() { if(!me.$input.val()) { - me.$input.val("%").trigger("keydown"); + me.$input.autocomplete("search", ""); } - }, 1000) - }) + }, 500); + }); this.input = this.$input.get(0); this.has_input = true; - //this.bind_change_event(); var me = this; this.setup_buttons(); - //this.setup_typeahead(); this.setup_autocomplete(); }, setup_buttons: function() { @@ -841,10 +840,19 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ if(value!==me.last_value) { me.parse_validate_and_set_in_model(value); } - }}); + } + }); + var cache = {}; this.$input.autocomplete({ + minLength: 0, source: function(request, response) { + if (cache[request.term]!=null) { + // from cache + response(cache[request.term]); + return; + } + var args = { 'txt': request.term, 'doctype': me.df.options, @@ -858,6 +866,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ no_spinner: true, args: args, callback: function(r) { + cache[request.term] = r.results; response(r.results); }, }); @@ -879,17 +888,13 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ } } }).data('ui-autocomplete')._renderItem = function(ul, d) { - var html = ""; - if(keys(d).length > 1) { - d.info = $.map(d, function(val, key) { return ["value", "label"].indexOf(key)!==-1 ? null : val }).join(", ") || ""; - html = repl("%(value)s
%(info)s
", d); - } else { - html = "" + d.value + ""; + var html = "" + d.value + ""; + if(d.value!==d.description) { + html += '
' + d.description + ''; } - return $('
  • ') .data('item.autocomplete', d) - .append(html) + .html('

    ' + html + '

    ') .appendTo(ul); }; // remove accessibility span (for now) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 580db0ca24..53815a57b0 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -109,13 +109,14 @@ frappe.ui.form.Grid = Class.extend({ var me =this; $rows.sortable({ handle: ".data-row, .panel-heading", + helper: 'clone', update: function(event, ui) { - me.frm.doc[me.df.parentfield] = []; + me.frm.doc[me.df.fieldname] = []; $rows.find(".grid-row").each(function(i, item) { var doc = $(item).data("doc"); doc.idx = i + 1; $(this).find(".row-index").html(i + 1); - me.frm.doc[me.df.parentfield].push(doc); + me.frm.doc[me.df.fieldname].push(doc); }); me.frm.dirty(); } diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 472fd57ea5..ad3077bca6 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -122,12 +122,20 @@ frappe.ui.form.Layout = Class.extend({ if(df && df.idx===1) head.css({"margin-top": "0px"}) - if(this.sections.length > 1) - this.section.css({ - "margin-top": "15px", - "border-top": "1px solid #eee" - }); } + + if(df.label || df.show_section_border) { + if(this.sections.length > 1) { + this.section.css("border-top", "1px solid #eee"); + + if (df.label) { + this.section.css("margin-top", "15px"); + } else { + this.section.css("padding-top", "15px"); + } + } + } + if(df.description) { $('
    ' + df.description + '
    ') .css("padding-left", "40px") diff --git a/frappe/public/js/frappe/form/script_manager.js b/frappe/public/js/frappe/form/script_manager.js index 69b9a75a82..93cd5c8663 100644 --- a/frappe/public/js/frappe/form/script_manager.js +++ b/frappe/public/js/frappe/form/script_manager.js @@ -26,7 +26,7 @@ frappe.ui.form.ScriptManager = Class.extend({ name = name || this.frm.docname; handlers = this.get_handlers(event_name, doctype, name, callback); if(callback) handlers.push(callback); - + $.each(handlers, function(i, fn) { fn(); }) @@ -38,7 +38,7 @@ frappe.ui.form.ScriptManager = Class.extend({ $.each(frappe.ui.form.handlers[doctype][event_name], function(i, fn) { handlers.push(function() { fn(me.frm, doctype, name) }); }); - } + } if(this.frm.cscript[event_name]) { handlers.push(function() { me.frm.cscript[event_name](me.frm.doc, doctype, name); }); } @@ -49,6 +49,7 @@ frappe.ui.form.ScriptManager = Class.extend({ }, setup: function() { var doctype = this.frm.meta; + var me = this; // js var cs = doctype.__js; @@ -56,6 +57,15 @@ frappe.ui.form.ScriptManager = Class.extend({ var tmp = eval(cs); } + // setup add fetch + $.each(this.frm.fields, function(i, field) { + var df = field.df; + if(df.fieldtype==="Read Only" && df.options && df.options.indexOf(".")!=-1) { + var parts = df.options.split("."); + me.frm.add_fetch(parts[0], parts[1], df.fieldname); + } + }); + // css doctype.__css && frappe.dom.set_style(doctype.__css); }, @@ -72,25 +82,25 @@ frappe.ui.form.ScriptManager = Class.extend({ }, validate_link_and_fetch: function(df, docname, value, callback) { var me = this; - + if(value) { var fetch = ''; - + if(this.frm && this.frm.fetch_dict[df.fieldname]) fetch = this.frm.fetch_dict[df.fieldname].columns.join(', '); - + return frappe.call({ method:'frappe.widgets.form.utils.validate_link', type: "GET", args: { - 'value': value, - 'options': df.options, + 'value': value, + 'options': df.options, 'fetch': fetch - }, + }, no_spinner: true, callback: function(r) { if(r.message=='Ok') { - if(r.fetch_values) + if(r.fetch_values) me.set_fetch_values(df, docname, r.fetch_values); if(callback) callback(value); } else { @@ -111,9 +121,9 @@ frappe.ui.form.ScriptManager = Class.extend({ copy_from_first_row: function(parentfield, current_row, fieldnames) { var doclist = this.frm.doc[parentfield]; if(doclist.length===1 || doclist[0]===current_row) return; - + $.each(fieldnames, function(i, fieldname) { current_row[fieldname] = doclist[0][fieldname]; }); } -}); \ No newline at end of file +}); diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 1979feb745..b8401c69a8 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -207,7 +207,10 @@ frappe.ui.form.Toolbar = Class.extend({ current = this.appframe.get_title_right_text(), status = null; - this.appframe.clear_primary_action(); + if (!this.frm.doc.__unsaved) { + // don't clear actions menu if dirty + this.appframe.clear_primary_action(); + } if (this.can_submit()) { status = "Submit"; diff --git a/frappe/public/js/frappe/misc/tools.js b/frappe/public/js/frappe/misc/tools.js index 5614181da0..1a8d6195d4 100644 --- a/frappe/public/js/frappe/misc/tools.js +++ b/frappe/public/js/frappe/misc/tools.js @@ -8,17 +8,17 @@ frappe.tools.downloadify = function(data, roles, me) { msgprint(__("Export not allowed. You need {0} role to export.", [frappe.utils.comma_or(roles)])); return; } - + var _get_data = function() { return frappe.tools.to_csv(data); }; var flash_disabled = (navigator.mimeTypes["application/x-shockwave-flash"] == undefined); - + var download_from_server = function() { open_url_post("/", { args: { data: data, filename: me.title }, cmd: "frappe.utils.datautils.send_csv_to_client" }, true); } - + // save file > abt 200 kb using server call if((_get_data().length > 200000) || flash_disabled) { download_from_server(); @@ -36,8 +36,8 @@ frappe.tools.downloadify = function(data, roles, me) { return me.title + '.csv'; }, data: _get_data, - swf: 'lib/js/lib/downloadify/downloadify.swf', - downloadImage: 'lib/js/lib/downloadify/download.png', + swf: 'assets/frappe/js/lib/downloadify/downloadify.swf', + downloadImage: 'assets/frappe/js/lib/downloadify/download.png', onComplete: function(){ $(msgobj.msg_area).html("

    Saved

    ") }, @@ -46,7 +46,7 @@ frappe.tools.downloadify = function(data, roles, me) { width: 100, height: 30, transparent: true, - append: false + append: false }); } }; @@ -56,11 +56,11 @@ frappe.markdown = function(txt) { frappe.require('assets/frappe/js/lib/markdown.js'); frappe.md2html = new Showdown.converter(); } - + while(txt.substr(0,1)==="\n") { txt = txt.substr(1); } - + // remove leading tab (if they exist in the first line) var whitespace_len = 0, first_line = txt.split("\n")[0]; @@ -69,7 +69,7 @@ frappe.markdown = function(txt) { whitespace_len++; first_line = first_line.substr(1); } - + if(whitespace_len && whitespace_len != first_line.length) { var txt1 = []; $.each(txt.split("\n"), function(i, t) { @@ -77,7 +77,7 @@ frappe.markdown = function(txt) { }) txt = txt1.join("\n"); } - + return frappe.md2html.makeHtml(txt); } @@ -109,9 +109,9 @@ frappe.slickgrid_tools = { } row.push(val); }); - + if(!filter || filter(row, d)) { - res.push(row); + res.push(row); } } return [col_row].concat(res); @@ -119,7 +119,7 @@ frappe.slickgrid_tools = { add_property_setter_on_resize: function(grid) { grid.onColumnsResized.subscribe(function(e, args) { $.each(grid.getColumns(), function(i, col) { - if(col.docfield && col.previousWidth != col.width && + if(col.docfield && col.previousWidth != col.width && !in_list(frappe.model.std_fields_list, col.docfield.fieldname) ) { frappe.call({ method:"frappe.client.make_width_property_setter", @@ -131,7 +131,7 @@ frappe.slickgrid_tools = { field_name: col.docfield.fieldname, property: 'width', value: col.width, - "__islocal": 1 + "__islocal": 1 }] } }); @@ -140,5 +140,5 @@ frappe.slickgrid_tools = { } }); }); - } + } }; diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index b2f0afc2c0..839ca14b2b 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -66,7 +66,7 @@ $.extend(frappe.model, { doc[f.fieldname] = v; updated.push(f.fieldname); } else if(f.fieldtype == "Select" && f.options - && f.options.substr(0, 5)!="link:" && f.options!="[Select]") { + && f.options!="[Select]") { doc[f.fieldname] = f.options.split("\n")[0]; } } @@ -166,6 +166,14 @@ $.extend(frappe.model, { }, open_mapped_doc: function(opts) { + if (opts.frm && opts.frm.doc.__unsaved) { + frappe.throw(__("You have unsaved changes in this form. Please save before you continue.")); + + } else if (!opts.source_name && opts.frm) { + opts.source_name = opts.frm.doc.name; + + } + return frappe.call({ type: "GET", method: opts.method, diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 4274950869..3e511d752e 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -22,7 +22,7 @@ frappe.ui.Dialog = frappe.ui.FieldGroup.extend({ init: function(opts) { this.display = false; this.is_dialog = true; - if(!opts.width) opts.width = 600; + if(!opts.width) opts.width = "600px"; $.extend(this, opts); this._super(); diff --git a/frappe/public/js/frappe/ui/editor.js b/frappe/public/js/frappe/ui/editor.js index 2bed8d297a..f4d934ca4d 100644 --- a/frappe/public/js/frappe/ui/editor.js +++ b/frappe/public/js/frappe/ui/editor.js @@ -8,7 +8,7 @@ bsEditor = Class.extend({ init: function(options) { - this.options = $.extend(options || {}, this.default_options); + this.options = $.extend({}, this.default_options, options || {}); this.edit_mode = true; if(this.options.editor) { this.setup_editor(this.options.editor); @@ -100,7 +100,7 @@ bsEditor = Class.extend({ active_toolbar_class: 'btn-info', selection_marker: 'edit-focus-marker', selection_color: 'darkgrey', - remove_typography: true, + remove_typography: false, max_file_size: 1, }, @@ -126,15 +126,18 @@ bsEditor = Class.extend({ var html = this.editor.html() || ""; if(!$.trim(this.editor.text()) && !(this.editor.find("img"))) html = ""; - // html = html.replace(/(
    |\s|

    <\/div>| )*$/, ''); // remove custom typography (use CSS!) if(this.options.remove_typography) { - html = html.replace(/(font-family|font-size|line-height):[^;]*;/g, ''); - html = html.replace(/<[^>]*(font=['"][^'"]*['"])>/g, function(a,b) { return a.replace(b, ''); }); - html = html.replace(/\s*style\s*=\s*["']\s*["']/g, ''); - return html; + var tmp = $("
    ").html(html); + // remove style attributes + tmp.find("*") + .removeAttr("style") + .removeAttr("font"); + html = tmp.html(); } + + return html; }, init_file_drops: function () { diff --git a/frappe/public/js/frappe/ui/filters.js b/frappe/public/js/frappe/ui/filters.js index 50ba37d494..1b9409e1a6 100644 --- a/frappe/public/js/frappe/ui/filters.js +++ b/frappe/public/js/frappe/ui/filters.js @@ -18,7 +18,7 @@ frappe.ui.FilterList = Class.extend({ me.listobj.run(); }); }, - + show_filters: function() { this.$w.find('.show_filters').toggle(); if(!this.filters.length) @@ -29,7 +29,7 @@ frappe.ui.FilterList = Class.extend({ this.filters = []; this.$w.find('.filter_area').empty(); }, - + add_filter: function(tablename, fieldname, condition, value) { this.push_new_filter(tablename, fieldname, condition, value); // list must be expanded @@ -37,7 +37,7 @@ frappe.ui.FilterList = Class.extend({ this.$w.find('.show_filters').toggle(true); } }, - + push_new_filter: function(tablename, fieldname, condition, value) { this.filters.push(new frappe.ui.Filter({ flist: this, @@ -47,7 +47,7 @@ frappe.ui.FilterList = Class.extend({ value: value })); }, - + get_filters: function() { // get filter values as dict var values = []; @@ -57,7 +57,7 @@ frappe.ui.FilterList = Class.extend({ }) return values; }, - + // remove hidden filters update_filters: function() { var fl = []; @@ -66,7 +66,7 @@ frappe.ui.FilterList = Class.extend({ }) this.filters = fl; }, - + get_filter: function(fieldname) { for(var i in this.filters) { if(this.filters[i].field && this.filters[i].field.df.fieldname==fieldname) @@ -109,29 +109,30 @@ frappe.ui.Filter = Class.extend({ make_select: function() { var me = this; this.fieldselect = new frappe.ui.FieldSelect({ - parent: this.$w.find('.fieldname_select_area'), - doctype: this.doctype, - filter_fields: this.filter_fields, + parent: this.$w.find('.fieldname_select_area'), + doctype: this.doctype, + filter_fields: this.filter_fields, select: function(doctype, fieldname) { me.set_field(doctype, fieldname); } }); + this.fieldselect.set_value(this.doctype, this.fieldname); }, set_events: function() { var me = this; - - this.$w.find('a.close').bind('click', function() { + + this.$w.find('a.close').bind('click', function() { me.$w.css('display','none'); var value = me.field.get_parsed_value(); var fieldname = me.field.df.fieldname; me.field = null; - + // hide filter section if(!me.flist.get_filters().length) { me.flist.$w.find('.set_filters').toggle(true); me.flist.$w.find('.show_filters').toggle(false); } - + me.flist.update_filters(); me.flist.listobj.dirty = true; me.flist.listobj.run(); @@ -144,33 +145,33 @@ frappe.ui.Filter = Class.extend({ me.set_field(me.field.df.parent, me.field.df.fieldname, 'Data'); if(!me.field.desc_area) me.field.desc_area = $a(me.field.wrapper, 'span', 'help', null, - 'values separated by comma'); + 'values separated by comma'); } else { - me.set_field(me.field.df.parent, me.field.df.fieldname, null, - me.$w.find('.condition').val()); + me.set_field(me.field.df.parent, me.field.df.fieldname, null, + me.$w.find('.condition').val()); } }); - + // set the field if(me.fieldname) { // presents given (could be via tags!) this.set_values(me.tablename, me.fieldname, me.condition, me.value); } else { me.set_field(me.doctype, 'name'); - } + } }, - + set_values: function(tablename, fieldname, condition, value) { // presents given (could be via tags!) this.set_field(tablename, fieldname); if(condition) this.$w.find('.condition').val(condition).change(); if(value!=null) this.field.set_input(value); }, - + set_field: function(doctype, fieldname, fieldtype, condition) { var me = this; - + // set in fieldname (again) var cur = me.field ? { fieldname: me.field.df.fieldname, @@ -185,13 +186,12 @@ frappe.ui.Filter = Class.extend({ return; } - var df = copy_dict(me.fieldselect.fields_by_name[doctype][fieldname]); this.set_fieldtype(df, fieldtype); - - // called when condition is changed, + + // called when condition is changed, // don't change if all is well - if(me.field && cur.fieldname == fieldname && df.fieldtype == cur.fieldtype && + if(me.field && cur.fieldname == fieldname && df.fieldtype == cur.fieldtype && df.parent == cur.parent) { return; } @@ -199,13 +199,13 @@ frappe.ui.Filter = Class.extend({ // clear field area and make field me.fieldselect.selected_doctype = doctype; me.fieldselect.selected_fieldname = fieldname; - + // save old text var old_text = null; if(me.field) { old_text = me.field.get_parsed_value(); } - + var field_area = me.$w.find('.filter_field').empty().get(0); var f = frappe.ui.form.make_control({ df: df, @@ -213,13 +213,13 @@ frappe.ui.Filter = Class.extend({ only_input: true, }) f.refresh(); - + me.field = f; - if(old_text) + if(old_text && me.field.df.fieldtype===cur.fieldtype) me.field.set_input(old_text); - + if(!condition) this.set_default_condition(df, fieldtype); - + // run on enter $(me.field.wrapper).find(':input').keydown(function(ev) { if(ev.which==13) { @@ -227,33 +227,33 @@ frappe.ui.Filter = Class.extend({ } }) }, - + set_fieldtype: function(df, fieldtype) { // reset if(df.original_type) df.fieldtype = df.original_type; else df.original_type = df.fieldtype; - + df.description = ''; df.reqd = 0; - + // given if(fieldtype) { df.fieldtype = fieldtype; return; - } - + } + // scrub if(df.fieldtype=='Check') { df.fieldtype='Select'; df.options='No\nYes'; } else if(['Text','Small Text','Text Editor','Code','Tags','Comments'].indexOf(df.fieldtype)!=-1) { - df.fieldtype = 'Data'; + df.fieldtype = 'Data'; } else if(df.fieldtype=='Link' && this.$w.find('.condition').val()!="=") { df.fieldtype = 'Data'; } }, - + set_default_condition: function(df, fieldtype) { if(!fieldtype) { // set as "like" for data fields @@ -261,27 +261,27 @@ frappe.ui.Filter = Class.extend({ this.$w.find('.condition').val('like'); } else { this.$w.find('.condition').val('='); - } - } + } + } }, - + get_value: function() { var me = this; var val = me.field.get_parsed_value(); var cond = me.$w.find('.condition').val(); - + if(me.field.df.original_type == 'Check') { val = (val=='Yes' ? 1 :0); } - + if(cond=='like') { // add % only if not there at the end if ((val.length === 0) || (val.lastIndexOf("%") !== (val.length - 1))) { val = (val || "") + '%'; } } else if(val === '%') val = null; - - return [me.fieldselect.selected_doctype, + + return [me.fieldselect.selected_doctype, me.field.df.fieldname, me.$w.find('.condition').val(), val]; } @@ -311,10 +311,10 @@ frappe.ui.FieldSelect = Class.extend({ return false; } }); - + if(this.filter_fields) { for(var i in this.filter_fields) - this.add_field_option(this.filter_fields[i]) + this.add_field_option(this.filter_fields[i]) } else { this.build_options(); } @@ -340,14 +340,14 @@ frappe.ui.FieldSelect = Class.extend({ var me = this; this.clear(); if(!doctype) return; - + // old style if(doctype.indexOf(".")!==-1) { parts = doctype.split("."); doctype = parts[0]; fieldname = parts[1]; } - + $.each(this.options, function(i, v) { if(v.doctype===doctype && v.fieldname===fieldname) { me.selected_doctype = doctype; @@ -365,7 +365,7 @@ frappe.ui.FieldSelect = Class.extend({ if(d.fieldname=="name") opts.options = me.doctype; return $.extend(copy_dict(d), opts); }); - + // add parenttype column var doctype_obj = locals['DocType'][me.doctype]; if(doctype_obj && cint(doctype_obj.istable)) { @@ -376,7 +376,7 @@ frappe.ui.FieldSelect = Class.extend({ parent: me.doctype, }]); } - + // blank if(this.with_blank) { this.options.push({ @@ -414,7 +414,7 @@ frappe.ui.FieldSelect = Class.extend({ var label = df.label + ' (' + df.parent + ')'; var table = df.parent; } - if(frappe.model.no_value_type.indexOf(df.fieldtype)==-1 && + if(frappe.model.no_value_type.indexOf(df.fieldtype)==-1 && !(me.fields_by_name[df.parent] && me.fields_by_name[df.parent][df.fieldname])) { this.options.push({ label: __(label), @@ -423,7 +423,7 @@ frappe.ui.FieldSelect = Class.extend({ doctype: df.parent }) if(!me.fields_by_name[df.parent]) me.fields_by_name[df.parent] = {}; - me.fields_by_name[df.parent][df.fieldname] = df; + me.fields_by_name[df.parent][df.fieldname] = df; } }, }) diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index 63ea4f1d61..b1e54dc8b7 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -257,5 +257,8 @@ frappe.ui.toolbar.show_about = function() { } frappe.ui.toolbar.show_banner = function(msg) { - return $('
    ').html(msg).appendTo($('header')); + $banner = $('
    '+msg+'×
    ') + .appendTo($('header')); + $banner.find(".close").click(function() { $(".toolbar-banner").toggle(false); }); + return $banner; } diff --git a/frappe/public/js/frappe/views/grid_report.js b/frappe/public/js/frappe/views/grid_report.js index d3ef7b70e4..3dc7314cd0 100644 --- a/frappe/public/js/frappe/views/grid_report.js +++ b/frappe/public/js/frappe/views/grid_report.js @@ -20,7 +20,7 @@ $.extend(frappe.report_dump, { $.each(r.message, function(doctype, doctype_data) { frappe.report_dump.set_data(doctype, doctype_data); }); - + // reverse map names $.each(r.message, function(doctype, doctype_data) { // only if not pre-loaded @@ -38,7 +38,7 @@ $.extend(frappe.report_dump, { } } }); - + callback(); }, progress_bar: progress_bar @@ -62,7 +62,7 @@ $.extend(frappe.report_dump, { var row = make_row(d); replace_dict[row.name] = row; }); - + // replace old data $.each(frappe.report_dump.data[doctype], function(i, d) { if(replace_dict[d.name]) { @@ -75,13 +75,13 @@ $.extend(frappe.report_dump, { data.push(d); } }); - + // add new records $.each(replace_dict, function(name, d) { data.push(d); }) } else { - + // first loading $.each(doctype_data.data, function(i, d) { data.push(make_row(d)); @@ -96,20 +96,20 @@ frappe.provide("frappe.views"); frappe.views.GridReport = Class.extend({ init: function(opts) { frappe.require("assets/js/slickgrid.min.js"); - + this.filter_inputs = {}; this.preset_checks = []; this.tree_grid = {show: false}; var me = this; $.extend(this, opts); - + this.wrapper = $('
    ').appendTo(this.parent); - + if(this.filters) { this.make_filters(); } this.make_waiting(); - + this.get_data_and_refresh(); }, bind_show: function() { @@ -118,14 +118,14 @@ frappe.views.GridReport = Class.extend({ // this must be called after init // because "frappe.container.page" will only be set // once "load" event is over. - + var me = this; $(this.page).bind('show', function() { // reapply filters on show frappe.cur_grid_report = me; me.get_data_and_refresh(); }); - + }, get_data_and_refresh: function() { var me = this; @@ -139,7 +139,7 @@ frappe.views.GridReport = Class.extend({ var progress_bar = null; if(!this.setup_filters_done) progress_bar = this.wrapper.find(".progress .progress-bar"); - + frappe.report_dump.with_data(this.doctypes, function() { if(!me.setup_filters_done) { me.setup_filters(); @@ -160,28 +160,28 @@ frappe.views.GridReport = Class.extend({ function(d) { return d.name; }); me.set_autocomplete(v, opts.list); } - }); + }); // refresh - this.filter_inputs.refresh && this.filter_inputs.refresh.click(function() { + this.filter_inputs.refresh && this.filter_inputs.refresh.click(function() { me.get_data(function() { me.refresh(); }); }); - + // reset filters - this.filter_inputs.reset_filters && this.filter_inputs.reset_filters.click(function() { - me.init_filter_values(); + this.filter_inputs.reset_filters && this.filter_inputs.reset_filters.click(function() { + me.init_filter_values(); me.refresh(); }); - + // range this.filter_inputs.range && this.filter_inputs.range.on("change", function() { me.refresh(); }); - + // plot check - if(this.setup_plot_check) + if(this.setup_plot_check) this.setup_plot_check(); }, set_filter: function(key, value) { @@ -223,7 +223,7 @@ frappe.views.GridReport = Class.extend({ filter.val(""); } }); - + this.set_default_values(); }, @@ -232,7 +232,7 @@ frappe.views.GridReport = Class.extend({ from_date: dateutil.str_to_user(sys_defaults.year_start_date), to_date: dateutil.str_to_user(sys_defaults.year_end_date) } - + var me = this; $.each(values, function(i, v) { if(me.filter_inputs[i] && !me.filter_inputs[i].val()) @@ -281,7 +281,7 @@ frappe.views.GridReport = Class.extend({ }); }, make_waiting: function() { - this.waiting = frappe.messages.waiting(this.wrapper, __("Loading Report")+"...", '10'); + this.waiting = frappe.messages.waiting(this.wrapper, __("Loading Report")+"...", '10'); }, load_filter_values: function() { var me = this; @@ -298,12 +298,12 @@ frappe.views.GridReport = Class.extend({ } } }); - + if(this.filter_inputs.from_date && this.filter_inputs.to_date && (this.to_date < this.from_date)) { msgprint(__("From Date must be before To Date")); return; } - + }, make_name_map: function(data, key) { @@ -314,7 +314,7 @@ frappe.views.GridReport = Class.extend({ }) return map; }, - + reset_item_values: function(item) { var me = this; $.each(this.columns, function(i, col) { @@ -323,7 +323,7 @@ frappe.views.GridReport = Class.extend({ } }); }, - + round_item_values: function(item) { var me = this; $.each(this.columns, function(i, col) { @@ -332,17 +332,17 @@ frappe.views.GridReport = Class.extend({ } }); }, - + round_off_data: function() { var me = this; $.each(this.data, function(i, d) { me.round_item_values(d); }); }, - + refresh: function() { this.waiting.toggle(false); - if(!this.grid_wrapper) + if(!this.grid_wrapper) this.make(); this.show_zero = $('.show-zero input:checked').length; this.load_filter_values(); @@ -361,15 +361,15 @@ frappe.views.GridReport = Class.extend({ setup_dataview_columns: function() { this.dataview_columns = $.map(this.columns, function(col) { return !col.hidden ? col : null; - }); + }); }, make: function() { 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(); }); - + // grid wrapper this.grid_wrapper = $("
    ") @@ -392,10 +392,10 @@ frappe.views.GridReport = Class.extend({ +'
    ').appendTo(this.wrapper); this.bind_show(); - + frappe.cur_grid_report = this; $(this.wrapper).trigger('make'); - + }, apply_filters_from_route: function() { var me = this; @@ -432,12 +432,12 @@ frappe.views.GridReport = Class.extend({ me.grid.invalidateRows(args.rows); me.grid.render(); }); - + this.dataView.onRowCountChanged.subscribe(function (e, args) { me.grid.updateRowCount(); me.grid.render(); }); - + this.tree_grid.show && this.add_tree_grid_events(); }, prepare_data_view: function() { @@ -459,13 +459,13 @@ frappe.views.GridReport = Class.extend({ // from all filter_inputs var filters = this.filter_inputs; if(item._show) return true; - + for (i in filters) { if(!this.apply_filter(item, i)) { return false; } } - + return true; }, apply_filter: function(item, fieldname) { @@ -485,11 +485,11 @@ frappe.views.GridReport = Class.extend({ if(col.formatter==me.currency_formatter && !col.hidden) { if(flt(item[col.field]) > 0.001 || flt(item[col.field]) < -0.001) { return true; - } + } } } return false; - } + } return true; }, show_zero_check: function() { @@ -497,7 +497,7 @@ frappe.views.GridReport = Class.extend({ this.wrapper.bind('make', function() { me.wrapper.find('.show-zero').toggle(true).find('input').click(function(){ me.refresh(); - }); + }); }); }, is_default: function(fieldname) { @@ -535,16 +535,16 @@ frappe.views.GridReport = Class.extend({ // link_formatter must have // filter_input, open_btn (true / false), doctype (will be eval'd) if(!value) return ""; - + var me = frappe.cur_grid_report; - + if(dataContext._show) { return repl('%(value)s', { _style: dataContext._style || "", value: value }); } - + // make link to add a filter var link_formatter = me.dataview_columns[cell].link_formatter; if (link_formatter.filter_input) { @@ -562,8 +562,8 @@ frappe.views.GridReport = Class.extend({ // make icon to open form if(link_formatter.open_btn) { - var doctype = link_formatter.doctype - ? eval(link_formatter.doctype) + var doctype = link_formatter.doctype + ? eval(link_formatter.doctype) : dataContext.doctype; html += me.get_link_open_icon(doctype, value); } @@ -576,21 +576,21 @@ frappe.views.GridReport = Class.extend({ return repl(' \ ', { doctype: doctype, - name: encodeURIComponent(name) + name: encodeURIComponent(name) }); }, make_date_range_columns: function() { this.columns = []; - + var me = this; var range = this.filter_inputs.range.val(); this.from_date = dateutil.user_to_str(this.filter_inputs.from_date.val()); this.to_date = dateutil.user_to_str(this.filter_inputs.to_date.val()); var date_diff = dateutil.get_diff(this.to_date, this.from_date); - + me.column_map = {}; me.last_date = null; - + var add_column = function(date) { me.columns.push({ id: date, @@ -600,22 +600,22 @@ frappe.views.GridReport = Class.extend({ width: 100 }); } - + var build_columns = function(condition) { // add column for each date range for(var i=0; i <= date_diff; i++) { var date = dateutil.add_days(me.from_date, i); if(!condition) condition = function() { return true; } - + if(condition(date)) add_column(date); me.last_date = date; - + if(me.columns.length) { me.column_map[date] = me.columns[me.columns.length-1]; } } } - + // make columns for all date ranges if(range=='Daily') { build_columns(); @@ -623,7 +623,7 @@ frappe.views.GridReport = Class.extend({ build_columns(function(date) { if(!me.last_date) return true; return !(dateutil.get_diff(date, me.from_date) % 7) - }); + }); } else if(range=='Monthly') { build_columns(function(date) { if(!me.last_date) return true; @@ -633,7 +633,7 @@ frappe.views.GridReport = Class.extend({ build_columns(function(date) { if(!me.last_date) return true; return dateutil.str_to_obj(date).getDate()==1 && in_list([0,3,6,9], dateutil.str_to_obj(date).getMonth()) - }); + }); } else if(range=='Yearly') { build_columns(function(date) { if(!me.last_date) return true; @@ -641,15 +641,15 @@ frappe.views.GridReport = Class.extend({ return date==v.year_start_date ? true : null; }).length; }); - + } - + // set label as last date of period $.each(this.columns, function(i, col) { col.name = me.columns[i+1] ? dateutil.str_to_user(dateutil.add_days(me.columns[i+1].id, -1)) - : dateutil.str_to_user(me.to_date); - }); + : dateutil.str_to_user(me.to_date); + }); }, trigger_refresh_on_change: function(filters) { var me = this; @@ -670,10 +670,10 @@ frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({ } frappe.require('assets/frappe/js/lib/flot/jquery.flot.js'); frappe.require('assets/frappe/js/lib/flot/jquery.flot.downsample.js'); - + this.plot = $.plot(this.plot_area.toggle(true), plot_data, this.get_plot_options()); - + this.setup_plot_hover(); }, setup_plot_check: function() { @@ -692,7 +692,7 @@ frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({ }); } me.render_plot(); - }); + }); }); }, setup_plot_hover: function() { @@ -718,16 +718,16 @@ frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({ me.previousPoint = item.dataIndex; $("#" + me.tooltip_id).remove(); - showTooltip(item.pageX, item.pageY, + showTooltip(item.pageX, item.pageY, me.get_tooltip_text(item.series.label, item.datapoint[0], item.datapoint[1])); } } else { $("#" + me.tooltip_id).remove(); - me.previousPoint = null; + me.previousPoint = null; } }); - + }, get_tooltip_text: function(label, x, y) { var date = dateutil.obj_to_user(new Date(x)); @@ -749,19 +749,19 @@ frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({ points: {show: true}, lines: {show: true, fill: true}, }); - - // prepend opening - data[data.length-1].data = [[dateutil.str_to_obj(me.from_date).getTime(), + + // prepend opening + data[data.length-1].data = [[dateutil.str_to_obj(me.from_date).getTime(), item.opening]].concat(data[data.length-1].data); } }); - + return data.length ? data : false; }, get_plot_options: function() { return { grid: { hoverable: true, clickable: true }, - xaxis: { mode: "time", + xaxis: { mode: "time", min: dateutil.str_to_obj(this.from_date).getTime(), max: dateutil.str_to_obj(this.to_date).getTime() }, series: { downsample: { threshold: 1000 } } @@ -780,7 +780,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({ }); if (!this.tl) this.tl = {}; if (!this.tl[parent_doctype]) this.tl[parent_doctype] = []; - + $.each(frappe.report_dump.data[parent_doctype], function(i, parent) { if(tmap[parent.name]) { $.each(tmap[parent.name], function(i, d) { @@ -811,15 +811,15 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({ var me = frappe.cur_grid_report; value = value.replace(/&/g,"&").replace(//g,">"); var data = me.data; - var spacer = ""; var idx = me.dataView.getIdxById(dataContext.id); var link = me.tree_grid.formatter(dataContext); - + if(dataContext.doctype) { - link += me.get_link_open_icon(dataContext.doctype, dataContext.name); + link += me.get_link_open_icon(dataContext.doctype, dataContext.name); } - + if (data[idx + 1] && data[idx + 1].indent > data[idx].indent) { if (dataContext._collapsed) { return spacer + "  " + link; @@ -833,7 +833,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({ tree_dataview_filter: function(item) { var me = frappe.cur_grid_report; if(!me.apply_filters(item)) return false; - + var parent = item[me.tree_grid.parent_field]; while (parent) { if (me.item_by_name[parent]._collapsed) { @@ -846,7 +846,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({ prepare_tree: function(item_dt, group_dt) { var group_data = frappe.report_dump.data[group_dt]; var item_data = frappe.report_dump.data[item_dt]; - + // prepare map with child in respective group var me = this; var item_group_map = {}; @@ -855,12 +855,12 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({ var parent = item[me.tree_grid.parent_field]; if(!item_group_map[parent]) item_group_map[parent] = []; if(group_ids.indexOf(item.name)==-1) { - item_group_map[parent].push(item); + item_group_map[parent].push(item); } else { msgprint(__("Ignoring Item {0}, because a group exists with the same name!", [item.name.bold()])); } }); - + // arrange items besides their parent item groups var items = []; $.each(group_data, function(i, group){ @@ -884,7 +884,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({ d.indent = indent; }); }, - + export: function() { var msgbox = msgprint($.format('

    {0}

    \

    {1}

    \ @@ -902,7 +902,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({ var with_groups = $(msgbox.body).find("[name='with_groups']").prop("checked"); var with_ledgers = $(msgbox.body).find("[name='with_ledgers']").prop("checked"); - var data = frappe.slickgrid_tools.get_view_data(me.columns, me.dataView, + var data = frappe.slickgrid_tools.get_view_data(me.columns, me.dataView, function(row, item) { if(with_groups) { // add row @@ -914,15 +914,15 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({ if(with_ledgers && (item.group_or_ledger != "Group" && !item.is_group)) { return true; } - + return false; }); - + frappe.tools.downloadify(data, ["Report Manager", "System Manager"], me); return false; }) return false; }, - + }); diff --git a/frappe/public/js/frappe/views/moduleview.js b/frappe/public/js/frappe/views/moduleview.js index 316de269e3..b5f3f3d218 100644 --- a/frappe/public/js/frappe/views/moduleview.js +++ b/frappe/public/js/frappe/views/moduleview.js @@ -216,15 +216,17 @@ frappe.views.moduleview.ModuleView = Class.extend({ }); } else { var route = item.route; - if(item.type==="doctype") { - route = "List/" + encodeURIComponent(item.name); - } else if(item.type==="page") { - route = item.route || item.link || item.name; - } else if(item.type==="report") { - if(item.is_query_report) { - route = "query-report/" + encodeURIComponent(item.name); - } else { - route = "Report/" + encodeURIComponent(item.doctype) + "/" + encodeURIComponent(item.name); + if(!route) { + if(item.type==="doctype") { + route = "List/" + encodeURIComponent(item.name); + } else if(item.type==="page") { + route = item.route || item.link || item.name; + } else if(item.type==="report") { + if(item.is_query_report) { + route = "query-report/" + encodeURIComponent(item.name); + } else { + route = "Report/" + encodeURIComponent(item.doctype) + "/" + encodeURIComponent(item.name); + } } } diff --git a/frappe/public/js/frappe/views/reportview.js b/frappe/public/js/frappe/views/reportview.js index b1e5bad10b..07960f6bc0 100644 --- a/frappe/public/js/frappe/views/reportview.js +++ b/frappe/public/js/frappe/views/reportview.js @@ -117,7 +117,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ if(!columns) { var columns = [['name', this.doctype],]; $.each(frappe.meta.docfield_list[this.doctype], function(i, df) { - if(df.in_filter && df.fieldname!='naming_series' + if((df.in_filter || df.in_list_view) && df.fieldname!='naming_series' && !in_list(frappe.model.no_value_type, df.fieldname)) { columns.push([df.fieldname, df.parent]); } diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index 7356b4a117..d11da18448 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -719,6 +719,11 @@ _f.Frm.prototype.disable_save = function() { this.appframe.set_title_right("", null); } +_f.Frm.prototype.enable_save = function() { + this.save_disabled = false; + this.toolbar.set_title_right(); +} + _f.Frm.prototype.save_or_update = function() { if(this.save_disabled) return; diff --git a/frappe/public/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css b/frappe/public/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css index 1c84dd3704..486f9891cc 100644 --- a/frappe/public/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css +++ b/frappe/public/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css @@ -12,56 +12,56 @@ /* Layout helpers ----------------------------------*/ .ui-helper-hidden { display: none; } -.ui-helper-hidden-accessible { +.ui-helper-hidden-accessible { border: 0; clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } -.ui-helper-reset { - margin: 0; - padding: 0; - border: 0; - outline: 0; - line-height: 1.3; - text-decoration: none; - font-size: 100%; - list-style: none; +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; } .ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; } -.ui-helper-clearfix:after { - content: "."; - display: block; - height: 0; - clear: both; - visibility: hidden; +.ui-helper-clearfix:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; } -.ui-helper-clearfix { +.ui-helper-clearfix { /*display: inline-block; */ display:block; min-height: 0; /* support: IE7 */ } /* required comment for clearfix to work in Opera \*/ -* html .ui-helper-clearfix { - height:1%; +* html .ui-helper-clearfix { + height:1%; } /* end clearfix */ -.ui-helper-zfix { - width: 100%; - height: 100%; - top: 0; - left: 0; - position: absolute; - opacity: 0; - filter:Alpha(Opacity=0); +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); } .ui-front { z-index: 100; @@ -77,11 +77,11 @@ ----------------------------------*/ /* states and images */ -.ui-icon { - display: block; - text-indent: -99999px; - overflow: hidden; - background-repeat: no-repeat; +.ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; } @@ -89,12 +89,12 @@ ----------------------------------*/ /* Overlays */ -.ui-widget-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; +.ui-widget-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } /* @@ -107,72 +107,72 @@ * http://api.jqueryui.com/resizable/ */ -.ui-resizable { +.ui-resizable { position: relative; } -.ui-resizable-handle { +.ui-resizable-handle { position: absolute; font-size: 0.1px; - z-index: 99999; - display: block; + z-index: 99999; + display: block; } -.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { - display: none; +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { + display: none; } -.ui-resizable-n { - cursor: n-resize; - height: 7px; - width: 100%; - top: -5px; - left: 0; +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; } -.ui-resizable-s { - cursor: s-resize; - height: 7px; - width: 100%; - bottom: -5px; - left: 0; +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; } -.ui-resizable-e { - cursor: e-resize; - width: 7px; - right: -5px; - top: 0; - height: 100%; +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; } -.ui-resizable-w { - cursor: w-resize; - width: 7px; - left: -5px; - top: 0; - height: 100%; +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; } -.ui-resizable-se { - cursor: se-resize; - width: 12px; - height: 12px; - right: 1px; - bottom: 1px; +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; } -.ui-resizable-sw { - cursor: sw-resize; - width: 9px; - height: 9px; - left: -5px; - bottom: -5px; +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; } -.ui-resizable-nw { - cursor: nw-resize; - width: 9px; - height: 9px; - left: -5px; - top: -5px; +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; } -.ui-resizable-ne { - cursor: ne-resize; - width: 9px; - height: 9px; - right: -5px; +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; top: -5px; } @@ -185,10 +185,10 @@ * * http://jqueryui.com/selectable/ */ -.ui-selectable-helper { - position: absolute; - z-index: 100; - border:1px dotted black; +.ui-selectable-helper { + position: absolute; + z-index: 100; + border:1px dotted black; } /* @@ -198,17 +198,18 @@ * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * - * + * * * To view and modify this theme, visit http://jqueryui.com/themeroller/ */ /* Component containers ----------------------------------*/ -.ui-widget { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size:13px; } +/*.ui-widget { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size:13px; }*/ .ui-widget .ui-widget { font-size: 1em; } -.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 1em; } -.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(../frappe/js/lib/jquery/bootstrap_theme/images/ui-bg_glass_75_ffffff_1x400.png) 50% 50% repeat-x; color: #404040; } +/*.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 1em; }*/ +/*.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(../frappe/js/lib/jquery/bootstrap_theme/images/ui-bg_glass_75_ffffff_1x400.png) 50% 50% repeat-x; color: #404040; }*/ +.ui-widget-content { border: 1px solid #aaaaaa; background-color: #ffffff; color: #404040;} .ui-widget-content a { color: #404040; } .ui-widget-header { font-weight:bold; @@ -514,41 +515,41 @@ ----------------------------------*/ /* Corner radius */ -.ui-corner-all, -.ui-corner-top, -.ui-corner-left, -.ui-corner-tl { - -moz-border-radius-topleft: 4px; - -webkit-border-top-left-radius: 4px; - -khtml-border-top-left-radius: 4px; - border-top-left-radius: 4px; +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + -khtml-border-top-left-radius: 4px; + border-top-left-radius: 4px; } -.ui-corner-all, -.ui-corner-top, -.ui-corner-right, -.ui-corner-tr { - -moz-border-radius-topright: 4px; - -webkit-border-top-right-radius: 4px; - -khtml-border-top-right-radius: 4px; - border-top-right-radius: 4px; +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; + -khtml-border-top-right-radius: 4px; + border-top-right-radius: 4px; } -.ui-corner-all, -.ui-corner-bottom, -.ui-corner-left, -.ui-corner-bl { - -moz-border-radius-bottomleft: 4px; - -webkit-border-bottom-left-radius: 4px; - -khtml-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; + -khtml-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; } -.ui-corner-all, -.ui-corner-bottom, -.ui-corner-right, -.ui-corner-br { - -moz-border-radius-bottomright: 4px; - -webkit-border-bottom-right-radius: 4px; - -khtml-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; + -khtml-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; } @@ -562,11 +563,11 @@ * http://jqueryui.com/autocomplete/ */ -.ui-autocomplete { - position: absolute; +.ui-autocomplete { + position: absolute; top: 0; left: 0; - cursor: default; + cursor: default; } /* @@ -685,9 +686,9 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad .ui-menu .ui-menu-divider { margin: 5px -2px 5px -2px; height: 0; font-size: 0; line-height: 0; border-width: 1px 0 0 0; } .ui-menu .ui-menu-item a { text-decoration: none; display: block; padding: 2px .4em; line-height: 1.5; zoom: 1; font-weight: normal; } .ui-menu .ui-menu-item a.ui-state-focus, -.ui-menu .ui-menu-item a.ui-state-active { - font-weight: normal; - margin: 0; +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: 0; color: #ffffff; background: #0064cd; background-color: #0064cd; @@ -695,6 +696,11 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad border-color: #0064cd #0064cd #003f81; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); } +.ui-menu .ui-menu-item a p { margin: 3px 0px; } +.ui-menu .ui-menu-item a .small { color: #555555; } +.ui-menu .ui-menu-item a.ui-state-focus .small, +.ui-menu .ui-menu-item a.ui-state-active .small { color: #ffffff; } + /* Fix problem with border in ui-state-active */ .ui-menu .ui-menu-item a.ui-state-active { padding: 1px .4em; @@ -884,4 +890,4 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad -moz-border-radius: 4px; -khtml-border-radius: 4px; border-radius: 4px; -} \ No newline at end of file +} diff --git a/frappe/sessions.py b/frappe/sessions.py index 48e02f3eec..db9c8100d0 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -32,7 +32,7 @@ def clear_cache(user=None): "time_zone"]) def delete_user_cache(user): - for key in ("bootinfo", "lang", "roles", "restrictions"): + for key in ("bootinfo", "lang", "roles", "restrictions", "home_page"): cache.delete_value(key + ":" + user) def clear_notifications(user=None): @@ -60,6 +60,7 @@ def clear_cache(user=None): delete_user_cache(sess.user) cache.delete_value("session:" + sess.sid) + delete_user_cache("Guest") clear_notifications() frappe.defaults.clear_cache() @@ -70,13 +71,24 @@ def clear_sessions(user=None, keep_current=False): if keep_current and frappe.session.sid==sid[0]: continue else: - frappe.cache().delete_value("session:" + sid[0]) - frappe.db.sql("""delete from tabSessions where sid=%s""", (sid[0],)) + delete_session(sid[0]) def delete_session(sid=None): frappe.cache().delete_value("session:" + sid) frappe.db.sql("""delete from tabSessions where sid=%s""", sid) +def clear_all_sessions(): + """This effectively logs out all users""" + frappe.only_for("Administrator") + for sid in frappe.db.sql_list("select sid from `tabSessions`"): + delete_session(sid) + +def clear_expired_sessions(): + """This function is meant to be called from scheduler""" + for sid in frappe.db.sql_list("""select sid + from tabSessions where TIMEDIFF(NOW(), lastupdate) > TIME(%s)""", get_expiry_period()): + delete_session(sid) + def get(): """get session boot info""" from frappe.core.doctype.notification_count.notification_count import \ @@ -139,7 +151,7 @@ class Session: self.data['data']['session_ip'] = frappe.get_request_header('REMOTE_ADDR') if self.user != "Guest": self.data['data']['last_updated'] = frappe.utils.now() - self.data['data']['session_expiry'] = self.get_expiry_period() + self.data['data']['session_expiry'] = get_expiry_period() self.data['data']['session_country'] = get_geo_ip_country(frappe.get_request_header('REMOTE_ADDR')) # insert session @@ -212,7 +224,7 @@ class Session: rec = frappe.db.sql("""select user, sessiondata from tabSessions where sid=%s and TIMEDIFF(NOW(), lastupdate) < TIME(%s)""", (self.sid, - self.get_expiry_period())) + get_expiry_period())) if rec: data = frappe._dict(eval(rec and rec[0][1] or '{}')) data.user = rec[0][0] @@ -261,17 +273,14 @@ class Session: frappe.utils.now()) frappe.cache().set_value("session:" + self.sid, self.data) - def get_expiry_period(self): - exp_sec = frappe.defaults.get_global_default("session_expiry") or "06:00:00" +def get_expiry_period(): + exp_sec = frappe.defaults.get_global_default("session_expiry") or "06:00:00" - # incase seconds is missing - if exp_sec: - if len(exp_sec.split(':')) == 2: - exp_sec = exp_sec + ':00' - else: - exp_sec = "2:00:00" + # incase seconds is missing + if len(exp_sec.split(':')) == 2: + exp_sec = exp_sec + ':00' - return exp_sec + return exp_sec def get_geo_ip_country(ip_addr): try: diff --git a/frappe/templates/base.html b/frappe/templates/base.html index 1f5f1b6754..343e5111f1 100644 --- a/frappe/templates/base.html +++ b/frappe/templates/base.html @@ -20,10 +20,6 @@ https://frappe.io/apps/frappe {%- endfor -%} {%- endif -%} - {%- for link in web_include_js %} - - {%- endfor -%} - {%- for link in web_include_css %} {%- endfor -%} @@ -38,6 +34,7 @@ https://frappe.io/apps/frappe
    {%- block banner -%} + {% include "templates/includes/banner_extension.html" ignore missing %} {% if banner_html -%}
    {{ banner_html or "" }}
    {%- endif %} @@ -92,6 +89,10 @@ https://frappe.io/apps/frappe {%- block footer -%}{% include "templates/includes/footer.html" %}{%- endblock -%}
    + {%- for link in web_include_js %} + + {%- endfor -%} + {%- block script %} + diff --git a/frappe/templates/pages/about.html b/frappe/templates/pages/about.html index e1242c86e5..77ba712e17 100644 --- a/frappe/templates/pages/about.html +++ b/frappe/templates/pages/about.html @@ -2,27 +2,27 @@ {% block content %}
    - {{ obj.doc.company_introduction or """

    Some Introduction about your company that you would - like your website visitor to know. - More people than you think will read your About page. - People always like to know who the are doing business with. - Be authentic and avoid using jargon like 'value added services' etc. - Be sure to update your company history and + {{ doc.company_introduction or """

    Some Introduction about your company that you would + like your website visitor to know. + More people than you think will read your About page. + People always like to know who the are doing business with. + Be authentic and avoid using jargon like 'value added services' etc. + Be sure to update your company history and list of key team members in Website > About Us Settings

    """ }} - - {% if obj.get({"doctype":"Company History"}) %} -

    {{ obj.doc.company_history_heading or "Company History" }}

    - {% for d in obj.get({"doctype":"Company History"}) %} + + {% if doc.get({"doctype":"Company History"}) %} +

    {{ doc.company_history_heading or "Company History" }}

    + {% for d in doc.get({"doctype":"Company History"}) %}

    {{ d.year }}

    {{ d.highlight }}

    {% endfor %} {% endif %} - - {% if obj.get({"doctype":"About Us Team Member"}) %} -

    {{ obj.doc.team_members_heading or "Team Members" }}

    - {% for d in obj.get({"doctype":"About Us Team Member"}) %} + + {% if doc.get({"doctype":"About Us Team Member"}) %} +

    {{ doc.team_members_heading or "Team Members" }}

    + {% for d in doc.get({"doctype":"About Us Team Member"}) %}
    @@ -35,6 +35,6 @@
    {% endfor %} {% endif %} - {{ obj.doc.footer or "" }} + {{ doc.footer or "" }}
    {% endblock %} diff --git a/frappe/templates/pages/about.py b/frappe/templates/pages/about.py index 13554fe53b..b693a703c4 100644 --- a/frappe/templates/pages/about.py +++ b/frappe/templates/pages/about.py @@ -1,8 +1,8 @@ # 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 def get_context(context): - return { "obj": frappe.get_doc("About Us Settings", "About Us Settings") } + return { "doc": frappe.get_doc("About Us Settings", "About Us Settings") } diff --git a/frappe/templates/pages/contact.html b/frappe/templates/pages/contact.html index 9df1b84337..6b09f0d3f2 100644 --- a/frappe/templates/pages/contact.html +++ b/frappe/templates/pages/contact.html @@ -1,15 +1,17 @@ {% block title %}{{ heading or "Contact Us"}}{% endblock %} {% block content %} +
    -

    +

    -
    @@ -53,9 +55,8 @@
    {% endif %}
    - {{ introduction or ""}}
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/frappe/templates/pages/list.html b/frappe/templates/pages/list.html new file mode 100644 index 0000000000..11a5b15512 --- /dev/null +++ b/frappe/templates/pages/list.html @@ -0,0 +1,84 @@ +{% block title %}{{ type }} {{ _("List") }}{% endblock %} + +{% block header %} +

    {{ type }} {{ _("List") }}

    +{% endblock %} + +{% block content %} + +
    +
    + +
    +
    +
    +{% if txt %} +
    Results filtered by {{ txt }}. ×
    +{% endif %} +
    +{% for item in items %} +
    + {{ item }} +
    +{% endfor %} +
    +
    + +
    +{% endblock %} + +{% block script %} + +{% endblock %} diff --git a/frappe/templates/pages/list.py b/frappe/templates/pages/list.py new file mode 100644 index 0000000000..0e05b2f288 --- /dev/null +++ b/frappe/templates/pages/list.py @@ -0,0 +1,50 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe, os +from frappe.modules import get_doc_path +from jinja2 import Environment, Template, FileSystemLoader + +no_cache = 1 +no_sitemap = 1 + +def get_context(context): + context.type = frappe.local.form_dict.type + context.txt = frappe.local.form_dict.txt + context.update(get_items(context.type, context.txt)) + return context + +@frappe.whitelist(allow_guest=True) +def get_items(type, txt, limit_start=0): + meta = frappe.get_meta(type) + filters, or_filters = [], [] + out = frappe._dict() + + if txt: + if meta.search_fields: + for f in meta.get_search_fields(): + or_filters.append([type, f.strip(), "like", "%" + txt + "%"]) + else: + filters.append([type, "name", "like", "%" + txt + "%"]) + + + out.raw_items = frappe.get_list(type, fields = ["*"], + filters=filters, or_filters = or_filters, limit_start=limit_start, + limit_page_length = 20) + template_path = os.path.join(get_doc_path(meta.module, "DocType", meta.name), "list_item.html") + + if os.path.exists(template_path): + env = Environment(loader = FileSystemLoader("/")) + #template_path = os.path.relpath(template_path) + template = env.get_template(template_path) + else: + template = Template("""""") + + out.items = [template.render(doc=i, doctype=type, + title_field = meta.title_field or "name") for i in out.raw_items] + + out.meta = meta + + return out diff --git a/frappe/templates/pages/message.html b/frappe/templates/pages/message.html index df8f6ac7af..2c70bf8457 100644 --- a/frappe/templates/pages/message.html +++ b/frappe/templates/pages/message.html @@ -7,4 +7,4 @@

    {{ message }}

    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/frappe/templates/pages/rss.py b/frappe/templates/pages/rss.py index a02f1dda11..ac77be2d2d 100644 --- a/frappe/templates/pages/rss.py +++ b/frappe/templates/pages/rss.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 @@ -11,11 +11,11 @@ base_template_path = "templates/pages/rss.xml" def get_context(context): """generate rss feed""" - + host = get_request_site_address() - + blog_list = frappe.db.sql("""\ - select page_name as name, published_on, modified, title, content from `tabBlog Post` + select page_name as name, published_on, modified, title, content from `tabBlog Post` where ifnull(published,0)=1 order by published_on desc limit 20""", as_dict=1) @@ -23,22 +23,21 @@ def get_context(context): blog_page = cstr(urllib.quote(blog.name.encode("utf-8"))) + ".html" blog.link = urllib.basejoin(host, blog_page) blog.content = escape_html(blog.content or "") - + if blog_list: modified = max((blog['modified'] for blog in blog_list)) else: modified = now() - ws = frappe.get_doc('Website Settings', 'Website Settings') + blog_settings = frappe.get_doc('Blog Settings', 'Blog Settings') context = { - 'title': ws.title_prefix, - 'description': ws.description or ((ws.title_prefix or "") + ' Blog'), + 'title': blog_settings.blog_title or "Blog", + 'description': blog_settings.blog_introduction or "", 'modified': modified, 'items': blog_list, 'link': host + '/blog' } - + # print context return context - \ No newline at end of file diff --git a/frappe/templates/pages/view.html b/frappe/templates/pages/view.html new file mode 100644 index 0000000000..3c0acf0a37 --- /dev/null +++ b/frappe/templates/pages/view.html @@ -0,0 +1,78 @@ +{% block title %}{{ doc.doctype }} / {{ doc[meta.title_field or "name"] }}{% endblock %} + +{% block header %} +

    {{ doc[meta.title_field or "name"] }}

    +

    {{ doc.doctype }} {{ _("List") }}

    +{% endblock %} + +{% block content %} +{% if custom_view %} +{{ custom_view }} +{% else %} + +{% macro print_value(df, d, meta) -%} + {% if df.fieldtype=="Check" %} + + {% elif df.fieldtype=="Image" %} + + {% else %} + {{ d[df.fieldname] or "" }} + {% endif %} +{%- endmacro %} + +{% macro get_width(fieldtype) -%} + {%- if fieldtype in ("Int", "Check") -%}{{ 60 }} + {%- else -%}{{ 150 }}{% endif -%} +{%- endmacro %} + +{% for df in meta.fields %} + {% if not df.hidden and not df.permlevel and not df.print_hide %} + {% if df.fieldtype=="Section Break" %} +

    {{ df.label or "" }}

    + {% elif df.fieldtype=="Column Break" %} + {% elif df.fieldtype=="Table" %} + {% set table_meta = frappe.get_meta(df.options) %} +
    + + + + + {% 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 new file mode 100644 index 0000000000..7e72a8895c --- /dev/null +++ b/frappe/templates/pages/view.py @@ -0,0 +1,16 @@ +# 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/pages/website_script.js b/frappe/templates/pages/website_script.js index 63a4634248..3b84bdd084 100644 --- a/frappe/templates/pages/website_script.js +++ b/frappe/templates/pages/website_script.js @@ -10,4 +10,4 @@ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) ga('create', '{{ google_analytics_id }}', 'auto'); ga('send', 'pageview'); -{%- endif %} \ No newline at end of file +{%- endif %} diff --git a/frappe/test_runner.py b/frappe/test_runner.py index 128961600a..78ff15dce9 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -114,8 +114,6 @@ def make_test_records(doctype, verbose=0): frappe.connect() for options in get_dependencies(doctype): - if options.startswith("link:"): - options = options[5:] if options == "[Select]": continue @@ -195,6 +193,8 @@ def make_test_objects(doctype, test_records, verbose=None): doc["doctype"] = doctype d = frappe.copy_doc(doc) + if doc.get('name'): + d.name = doc.get('name') if frappe.local.test_objects.get(d.doctype): # do not create test records, if already exists diff --git a/frappe/tests/test_client_login.py b/frappe/tests/test_client_login.py index 13fa82777c..e5a44d5ced 100644 --- a/frappe/tests/test_client_login.py +++ b/frappe/tests/test_client_login.py @@ -4,20 +4,20 @@ import unittest, frappe from frappe.utils import sel -class TestLogin(unittest.TestCase): - def setUp(self): - sel.login(sel.get_localhost()) - - def test_login(self): - self.assertEquals(sel.driver.current_url, sel.get_localhost() + "/desk") - - def test_to_do(self): - sel.module("ToDo") - sel.find('.appframe-iconbar .icon-plus')[0].click() - sel.wait_for_page("Form/ToDo") - sel.set_field("description", "test description") - sel.primary_action() - self.assertTrue(sel.wait_for_state("clean")) +# class TestLogin(unittest.TestCase): +# def setUp(self): +# sel.login() +# +# def test_login(self): +# self.assertEquals(sel._driver.current_url, sel.get_localhost() + "/desk") +# +# def test_to_do(self): +# sel.go_to_module("ToDo") +# sel.find('.appframe-iconbar .icon-plus')[0].click() +# sel.wait_for_page("Form/ToDo") +# sel.set_field("description", "test description", "textarea") +# sel.primary_action() +# self.assertTrue(sel.wait_for_state("clean")) # def test_material_request(self): # sel.new_doc("Stock", "Material Request") diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py index 8aac135271..abdf219ec8 100644 --- a/frappe/tests/test_db_query.py +++ b/frappe/tests/test_db_query.py @@ -8,7 +8,7 @@ from frappe.model.db_query import DatabaseQuery class TestReportview(unittest.TestCase): def test_basic(self): self.assertTrue({"name":"DocType"} in DatabaseQuery("DocType").execute(limit_page_length=None)) - + def test_fields(self): self.assertTrue({"name":"DocType", "issingle":0} \ in DatabaseQuery("DocType").execute(fields=["name", "issingle"], limit_page_length=None)) @@ -16,7 +16,7 @@ class TestReportview(unittest.TestCase): def test_filters_1(self): self.assertFalse({"name":"DocType"} \ in DatabaseQuery("DocType").execute(filters=[["DocType", "name", "like", "J%"]])) - + def test_filters_2(self): self.assertFalse({"name":"DocType"} \ in DatabaseQuery("DocType").execute(filters=[{"name": ["like", "J%"]}])) @@ -28,4 +28,12 @@ class TestReportview(unittest.TestCase): def test_filters_4(self): self.assertTrue({"name":"DocField"} \ in DatabaseQuery("DocType").execute(filters={"name": "DocField"})) - \ No newline at end of file + + def test_or_filters(self): + data = DatabaseQuery("DocField").execute( + filters={"parent": "DocType"}, fields=["fieldname", "fieldtype"], + or_filters=[{"fieldtype":"Table"}, {"fieldtype":"Select"}]) + + self.assertTrue({"fieldtype":"Table", "fieldname":"fields"} in data) + self.assertTrue({"fieldtype":"Select", "fieldname":"document_type"} in data) + self.assertFalse({"fieldtype":"Check", "fieldname":"issingle"} in data) diff --git a/frappe/tests/test_defaults.py b/frappe/tests/test_defaults.py index 2c0fbb4c61..411e8e6651 100644 --- a/frappe/tests/test_defaults.py +++ b/frappe/tests/test_defaults.py @@ -7,6 +7,7 @@ from frappe.defaults import * class TestDefaults(unittest.TestCase): def test_global(self): + clear_user_default("key1") set_global_default("key1", "value1") self.assertEquals(get_global_default("key1"), "value1") @@ -28,7 +29,7 @@ class TestDefaults(unittest.TestCase): add_user_default("key1", "3value3") self.assertEquals(get_user_default("key1"), "2value2") self.assertEquals(get_user_default_as_list("key1"), ["2value2", "3value3"]) - + def test_global_if_not_user(self): set_global_default("key4", "value4") self.assertEquals(get_user_default("key4"), "value4") @@ -38,7 +39,7 @@ class TestDefaults(unittest.TestCase): self.assertEquals(get_user_default("key5"), "value5") clear_user_default("key5") self.assertEquals(get_user_default("key5"), None) - + def test_clear_global(self): set_global_default("key6", "value6") self.assertEquals(get_user_default("key6"), "value6") diff --git a/frappe/translate.py b/frappe/translate.py index 309f144a46..373d6382cb 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -66,7 +66,7 @@ def get_user_lang(user=None): default_lang = frappe.db.get_default("lang") lang = default_lang or frappe.local.lang - frappe.cache().set_value("lang:" + user, lang) + frappe.cache().set_value("lang:" + user, lang or "en") return lang @@ -133,7 +133,8 @@ def get_lang_js(fortype, name): return "\n\n$.extend(frappe._messages, %s)" % json.dumps(get_dict(fortype, name)) def get_full_dict(lang): - if lang == "en": return {} + if lang == "en": + return {} return frappe.cache().get_value("lang:" + lang, lambda:load_lang(lang)) def load_lang(lang, apps=None): @@ -196,7 +197,6 @@ def get_messages_from_doctype(name): messages.extend([d.label, d.description]) if d.fieldtype=='Select' and d.options \ - and not d.options.startswith("link:") \ and not d.options.startswith("attach_files:"): options = d.options.split('\n') if not "icon" in options[0]: diff --git a/frappe/translations/kn.csv b/frappe/translations/kn.csv new file mode 100644 index 0000000000..d501411dc1 --- /dev/null +++ b/frappe/translations/kn.csv @@ -0,0 +1,1360 @@ + 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},' ListView ರಲ್ಲಿ ' ಅವಕಾಶ ಮಾದರಿ {0} ಸತತವಾಗಿ {1} +'link:' type Select {0} getting replaced,' ಲಿಂಕ್ : ' ಆಯ್ಕೆಮಾಡಿ {0} ಬದಲಿಗೆ ಗೆಟ್ಟಿಂಗ್ +"000 is black, fff is white","000 ಕಪ್ಪು , fff ಬಿಳಿ" +2 days ago,2 ದಿನಗಳ ಹಿಂದೆ +"[?]","ಕವಿದ href=""https://en.wikipedia.org/wiki/Transport_Layer_Security"" target = [ ? ] " +"\ +
  • 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.,ಬಳಕೆದಾರ ಅದೇ ರೀತಿಯ ಅನೇಕ ದಾಖಲೆಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ ಮಾಡಬಹುದು . +About,ಕುರಿತು +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 Attachments,ಲಗತ್ತುಗಳನ್ನು ಸೇರಿಸಿ +Add Bookmark,ಸೇರಿಸಿ ಬುಕ್ಮಾರ್ಕ್ +Add CSS,ಸಿಎಸ್ಎಸ್ ಸೇರಿಸಿ +Add Column,ಅಂಕಣ ಸೇರಿಸಿ +Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.,ಗೂಗಲ್ ಅನಾಲಿಟಿಕ್ಸ್ ID ಸೇರಿಸಿ : ಯುಎ ಉದಾ 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),ಸೈಟ್ ಒಂದು ಬ್ಯಾನರ್ ಸೇರಿಸಿ . ( ಸಣ್ಣ ಬ್ಯಾನರ್ ಸಾಮಾನ್ಯವಾಗಿ ಉತ್ತಮ ) +Add all roles,ಎಲ್ಲಾ ಪಾತ್ರಗಳನ್ನು ಸೇರಿಸಿ +Add attachment,ಲಗತ್ತು ಸೇರಿಸಿ +Add code as <script>,