diff --git a/frappe/__init__.py b/frappe/__init__.py index fd44f49cf6..1b2bad68d7 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -41,16 +41,26 @@ class _dict(dict): def _(msg, lang=None): """Returns translated string in current lang, if exists.""" from frappe.translate import get_full_dict - from frappe.utils import cstr if not lang: lang = local.lang # msg should always be unicode - msg = cstr(msg).strip() + msg = as_unicode(msg).strip() return get_full_dict(local.lang).get(msg) or msg +def as_unicode(text, encoding='utf-8'): + '''Convert to unicode if required''' + if isinstance(text, unicode): + return text + elif text==None: + return '' + elif isinstance(text, basestring): + return unicode(text, encoding) + else: + return unicode(text) + def get_lang_dict(fortype, name=None): """Returns the translated language dict for the given type and name. @@ -225,11 +235,11 @@ def errprint(msg): """Log error. This is sent back as `exc` in response. :param msg: Message.""" - from utils import cstr + msg = as_unicode(msg) if not request or (not "cmd" in local.form_dict): - print cstr(msg) + print msg.encode('utf-8') - error_log.append(cstr(msg)) + error_log.append(msg) def log(msg): """Add to `debug_log`. @@ -239,8 +249,7 @@ def log(msg): if conf.get("logging") or False: print repr(msg) - from utils import cstr - debug_log.append(cstr(msg)) + debug_log.append(as_unicode(msg)) def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False): """Print a message to the user (via HTTP response). @@ -355,12 +364,12 @@ def get_request_header(key, default=None): :param default: Default value.""" return request.headers.get(key, default) -def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", +def sendmail(recipients=[], sender="", subject="No Subject", message="No Message", as_markdown=False, delayed=True, reference_doctype=None, reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, attachments=None, content=None, doctype=None, name=None, reply_to=None, - cc=(), show_as_cc=(), message_id=None, in_reply_to=None, send_after=None, expose_recipients=False, - send_priority=1, communication=None, retry=1): + cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, + send_priority=1, communication=None, retry=1, now=None): """Send email using user's default **Email Account** or global default **Email Account**. @@ -383,25 +392,23 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message :param expose_recipients: Display all recipients in the footer message - "This email was sent to" :param communication: Communication link to be set in Email Queue record """ + message = content or message - if delayed: - import frappe.email.queue - frappe.email.queue.send(recipients=recipients, sender=sender, - subject=subject, message=content or message, - reference_doctype = doctype or reference_doctype, reference_name = name or reference_name, - unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, - attachments=attachments, reply_to=reply_to, cc=cc, show_as_cc=show_as_cc, message_id=message_id, in_reply_to=in_reply_to, - send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, communication=communication) - else: - import frappe.email - if as_markdown: - frappe.email.sendmail_md(recipients, sender=sender, - subject=subject, msg=content or message, attachments=attachments, reply_to=reply_to, - cc=cc, message_id=message_id, in_reply_to=in_reply_to, retry=retry) - else: - frappe.email.sendmail(recipients, sender=sender, - subject=subject, msg=content or message, attachments=attachments, reply_to=reply_to, - cc=cc, message_id=message_id, in_reply_to=in_reply_to, retry=retry) + if as_markdown: + from markdown2 import markdown + message = markdown(message) + + if not delayed: + now = True + + import email.queue + email.queue.send(recipients=recipients, sender=sender, + subject=subject, message=message, + reference_doctype = doctype or reference_doctype, reference_name = name or reference_name, + unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, + attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to, + send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, + communication=communication, now=now) whitelisted = [] guest_methods = [] @@ -847,13 +854,12 @@ def get_file_json(path): def read_file(path, raise_not_found=False): """Open a file and return its content as Unicode.""" - from frappe.utils import cstr if isinstance(path, unicode): path = path.encode("utf-8") if os.path.exists(path): with open(path, "r") as f: - return cstr(f.read()) + return as_unicode(f.read()) elif raise_not_found: raise IOError("{} Not Found".format(path)) else: diff --git a/frappe/api.py b/frappe/api.py index afdc3b8630..0b620bd0b1 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -129,6 +129,7 @@ def handle(): return build_response("json") def validate_oauth(): + from frappe.oauth import get_url_delimiter form_dict = frappe.local.form_dict authorization_header = frappe.get_request_header("Authorization").split(" ") if frappe.get_request_header("Authorization") else None if authorization_header and authorization_header[0].lower() == "bearer": @@ -142,7 +143,7 @@ def validate_oauth(): body = r.get_data() headers = r.headers - required_scopes = frappe.db.get_value("OAuth Bearer Token", token, "scopes").split(";") + required_scopes = frappe.db.get_value("OAuth Bearer Token", token, "scopes").split(get_url_delimiter()) valid, oauthlib_request = get_oauth_server().verify_request(uri, http_method, body, headers, required_scopes) diff --git a/frappe/async.py b/frappe/async.py index 0639d8872c..11d3d1abf6 100644 --- a/frappe/async.py +++ b/frappe/async.py @@ -9,7 +9,6 @@ import frappe import os import time import redis -from functools import wraps from frappe.utils import get_site_path from frappe import conf @@ -93,7 +92,9 @@ def publish_realtime(event=None, message=None, room=None, room = get_site_room() if after_commit: - frappe.local.realtime_log.append([event, message, room]) + params = [event, message, room] + if not params in frappe.local.realtime_log: + frappe.local.realtime_log.append(params) else: emit_via_redis(event, message, room) diff --git a/frappe/change_log/v7/v7_2_0.md b/frappe/change_log/v7/v7_2_0.md new file mode 100644 index 0000000000..1042b5db7e --- /dev/null +++ b/frappe/change_log/v7/v7_2_0.md @@ -0,0 +1,18 @@ +- Filters Dashboard + - Dashboard with pre-defined filters in List/Report View +- Tag Category + - Show/Group tags based on category +- Updated Font Awesome version to 4.x.x +- Checkboxes in grid + - Delete selected rows + - Map selected rows from one document to another. +- Show Totals button in report +- OpenID Connect for Frappe +- Threading based on message id in Email Queue +- New control object daterangepicker for filtering +- Orientation selection in PDF +- Expand/Collapse All buttons in tree view reports +- Add attachment from email and copy attachments to Communication Record +- Bulk Upload from zip file +- Tree view decoration +- Custom menu for report view diff --git a/frappe/client.py b/frappe/client.py index fdcad2a74c..012f76e2ca 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -8,14 +8,33 @@ import frappe.model import frappe.utils import json, os +''' +Handle RESTful requests that are mapped to the `/api/resource` route. + +Requests via FrappeClient are also handled here. +''' + @frappe.whitelist() def get_list(doctype, fields=None, filters=None, order_by=None, limit_start=None, limit_page_length=20): + '''Returns a list of records by filters, fields, ordering and limit + + :param doctype: DocType of the data to be queried + :param fields: fields to be returned. Default is `name` + :param filters: filter list by this dict + :param order_by: Order by this fieldname + :param limit_start: Start at this index + :param limit_page_length: Number of records to be returned (default 20)''' return frappe.get_list(doctype, fields=fields, filters=filters, order_by=order_by, limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=False) @frappe.whitelist() def get(doctype, name=None, filters=None): + '''Returns a document by name or filters + + :param doctype: DocType of the document to be returned + :param name: return document of this `name` + :param filters: If name is not set, filter by these values and return the first match''' if filters and not name: name = frappe.db.get_value(doctype, json.loads(filters)) if not name: @@ -29,6 +48,12 @@ def get(doctype, name=None, filters=None): @frappe.whitelist() def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False): + '''Returns a value form a document + + :param doctype: DocType to be queried + :param fieldname: Field to be returned (default `name`) + :param filters: dict or string for identifying the record''' + if not frappe.has_permission(doctype): frappe.throw(_("Not permitted"), frappe.PermissionError) @@ -83,6 +108,9 @@ def set_value(doctype, name, fieldname, value=None): @frappe.whitelist() def insert(doc=None): + '''Insert a document + + :param doc: JSON or dict object to be inserted''' if isinstance(doc, basestring): doc = json.loads(doc) @@ -98,6 +126,9 @@ def insert(doc=None): @frappe.whitelist() def insert_many(docs=None): + '''Insert multiple documents + + :param docs: JSON or list of dict objects to be inserted in one request''' if isinstance(docs, basestring): docs = json.loads(docs) @@ -121,6 +152,9 @@ def insert_many(docs=None): @frappe.whitelist() def save(doc): + '''Update (save) an existing document + + :param doc: JSON or dict object with the properties of the document to be updated''' if isinstance(doc, basestring): doc = json.loads(doc) @@ -129,11 +163,19 @@ def save(doc): @frappe.whitelist() def rename_doc(doctype, old_name, new_name, merge=False): + '''Rename document + + :param doctype: DocType of the document to be renamed + :param old_name: Current `name` of the document to be renamed + :param new_name: New `name` to be set''' new_name = frappe.rename_doc(doctype, old_name, new_name, merge=merge) return new_name @frappe.whitelist() def submit(doc): + '''Submit a document + + :param doc: JSON or dict object to be submitted remotely''' if isinstance(doc, basestring): doc = json.loads(doc) @@ -144,6 +186,10 @@ def submit(doc): @frappe.whitelist() def cancel(doctype, name): + '''Cancel a document + + :param doctype: DocType of the document to be cancelled + :param name: name of the document to be cancelled''' wrapper = frappe.get_doc(doctype, name) wrapper.cancel() @@ -151,6 +197,10 @@ def cancel(doctype, name): @frappe.whitelist() def delete(doctype, name): + '''Delete a remote document + + :param doctype: DocType of the document to be deleted + :param name: name of the document to be deleted''' frappe.delete_doc(doctype, name) @frappe.whitelist() @@ -161,6 +211,9 @@ def set_default(key, value, parent=None): @frappe.whitelist() def make_width_property_setter(doc): + '''Set width Property Setter + + :param doc: Property Setter document with `width` property''' if isinstance(doc, basestring): doc = json.loads(doc) if doc["doctype"]=="Property Setter" and doc["property"]=="width": @@ -168,6 +221,9 @@ def make_width_property_setter(doc): @frappe.whitelist() def bulk_update(docs): + '''Bulk update documents + + :param docs: JSON list of documents to be updated remotely. Each document must have `docname` property''' docs = json.loads(docs) failed_docs = [] for doc in docs: @@ -187,17 +243,32 @@ def bulk_update(docs): @frappe.whitelist() def has_permission(doctype, docname, perm_type="read"): + '''Returns a JSON with data whether the document has the requested permission + + :param doctype: DocType of the document to be checked + :param docname: `name` of the document to be checked + :param perm_type: one of `read`, `write`, `create`, `submit`, `cancel`, `report`. Default is `read`''' # perm_type can be one of read, write, create, submit, cancel, report return {"has_permission": frappe.has_permission(doctype, perm_type.lower(), docname)} @frappe.whitelist() def get_password(doctype, name, fieldname): + '''Return a password type property. Only applicable for System Managers + + :param doctype: DocType of the document that holds the password + :param name: `name` of the document that holds the password + :param fieldname: `fieldname` of the password property + ''' frappe.only_for("System Manager") return frappe.get_doc(doctype, name).get_password(fieldname) @frappe.whitelist() def get_js(items): + '''Load JS code files. Will also append translations + and extend `frappe._messages` + + :param items: JSON list of paths of the js files to be loaded.''' items = json.loads(items) out = [] for src in items: diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index c7b0b30d38..7c7316dbb9 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -27,7 +27,7 @@ def trigger_scheduler_event(context, event): try: frappe.init(site=site) frappe.connect() - frappe.utils.scheduler.trigger(site, event, now=context.force) + frappe.utils.scheduler.trigger(site, event, now=True) finally: frappe.destroy() diff --git a/frappe/config/docs.py b/frappe/config/docs.py index dd90839762..029149d7c0 100644 --- a/frappe/config/docs.py +++ b/frappe/config/docs.py @@ -1,3 +1,8 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +docs_version = "7.x.x" source_link = "https://github.com/frappe/frappe" docs_base_url = "https://frappe.github.io/frappe" @@ -22,10 +27,13 @@ ERP for managing small and medium sized businesses. [Get started with the Tutorial](https://frappe.github.io/frappe/user/) """ -docs_version = "7.x.x" +google_analytics_id = 'UA-8911157-23' def get_context(context): + context.brand_html = (' Frappé Framework') context.top_bar_items = [ - {"label": "Developer Tutorials", "url": context.docs_base_url + "/user", "right": 1}, - {"label": "API Documentation", "url": context.docs_base_url + "/current", "right": 1} + {"label": "Tutorials", "url": context.docs_base_url + "/user", "right": 1}, + {"label": "API", "url": context.docs_base_url + "/current", "right": 1}, + {"label": "Forum", "url": 'https://discuss.erpnext.com', "right": 1} ] diff --git a/frappe/config/setup.py b/frappe/config/setup.py index 9b75e6b34f..7e39a79675 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -6,7 +6,7 @@ def get_data(): data = [ { "label": _("Users"), - "icon": "icon-group", + "icon": "fa fa-group", "items": [ { "type": "doctype", @@ -22,41 +22,41 @@ def get_data(): }, { "label": _("Permissions"), - "icon": "icon-lock", + "icon": "fa fa-lock", "items": [ { "type": "page", "name": "permission-manager", "label": _("Role Permissions Manager"), - "icon": "icon-lock", + "icon": "fa fa-lock", "description": _("Set Permissions on Document Types and Roles") }, { "type": "page", "name": "user-permissions", "label": _("User Permissions Manager"), - "icon": "icon-shield", + "icon": "fa fa-shield", "description": _("Set Permissions per User") }, { "type": "page", "name": "modules_setup", "label": _("Show / Hide Modules"), - "icon": "icon-upload", + "icon": "fa fa-upload", "description": _("Show or hide modules globally.") }, { "type": "report", "is_query_report": True, "doctype": "User", - "icon": "icon-eye-open", + "icon": "fa fa-eye-open", "name": "Permitted Documents For User", "description": _("Check which Documents are readable by a User") }, { "type": "report", "doctype": "DocShare", - "icon": "icon-share", + "icon": "fa fa-share", "name": "Document Share Report", "description": _("Report of all document shares") } @@ -64,7 +64,7 @@ def get_data(): }, { "label": _("Settings"), - "icon": "icon-wrench", + "icon": "fa fa-wrench", "items": [ { "type": "doctype", @@ -87,13 +87,13 @@ def get_data(): }, { "label": _("Data"), - "icon": "icon-th", + "icon": "fa fa-th", "items": [ { "type": "page", "name": "data-import-tool", "label": _("Import / Export Data"), - "icon": "icon-upload", + "icon": "fa fa-upload", "description": _("Import / Export Data from .csv files.") }, { @@ -121,13 +121,13 @@ def get_data(): "name": "backups", "label": _("Download Backups"), "description": _("List of backups available for download"), - "icon": "icon-download" + "icon": "fa fa-download" }, ] }, { "label": _("Email"), - "icon": "icon-envelope", + "icon": "fa fa-envelope", "items": [ { "type": "doctype", @@ -153,7 +153,7 @@ def get_data(): }, { "label": _("Printing"), - "icon": "icon-print", + "icon": "fa fa-print", "items": [ { "type": "page", @@ -175,7 +175,7 @@ def get_data(): }, { "label": _("Workflow"), - "icon": "icon-random", + "icon": "fa fa-random", "items": [ { "type": "doctype", @@ -196,14 +196,14 @@ def get_data(): }, { "label": _("Integrations"), - "icon": "icon-star", + "icon": "fa fa-star", "items": [ { "type": "page", "name": "applications", "label": _("Application Installer"), "description": _("Install Applications."), - "icon": "icon-download" + "icon": "fa fa-download" }, { "type": "doctype", @@ -229,7 +229,7 @@ def get_data(): }, { "label": _("Customize"), - "icon": "icon-glass", + "icon": "fa fa-glass", "items": [ { "type": "doctype", @@ -257,10 +257,16 @@ def get_data(): "type": "doctype", "name": "DocType", "description": _("Add custom forms.") + }, + { + "type": "doctype", + "label": _("Custom Tags"), + "name": "Tag Category", + "description": _("Add your own Tag Categories") } ] }, ] - add_setup_section(data, "frappe", "website", _("Website"), "icon-globe") + add_setup_section(data, "frappe", "website", _("Website"), "fa fa-globe") return data diff --git a/frappe/config/website.py b/frappe/config/website.py index 9c319e0297..03ab7f454a 100644 --- a/frappe/config/website.py +++ b/frappe/config/website.py @@ -5,7 +5,7 @@ def get_data(): return [ { "label": _("Web Site"), - "icon": "icon-star", + "icon": "fa fa-star", "items": [ { "type": "doctype", @@ -46,7 +46,7 @@ def get_data(): }, { "label": _("Setup"), - "icon": "icon-cog", + "icon": "fa fa-cog", "items": [ { "type": "doctype", @@ -84,5 +84,19 @@ def get_data(): "label": _("Portal Settings"), } ] - } + }, + { + "label": _("Knowledge Base"), + "items": [ + { + "type": "doctype", + "name": "Help Category", + }, + { + "type": "doctype", + "name": "Help Article", + }, + ] + }, + ] diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index a76023bea1..6a1a577ee0 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -1,996 +1,1166 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "", - "creation": "2013-01-29 10:47:14", - "custom": 0, - "description": "Keep a track of all communications", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2013-01-29 10:47:14", + "custom": 0, + "description": "Keep a track of all communications", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "subject", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Subject", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subject", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subject", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "eval:doc.communication_type==='Communication'", - "fieldname": "section_break_10", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": "eval:doc.communication_type==='Communication'", + "columns": 0, + "fieldname": "section_break_10", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "", - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "communication_medium", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Type", - "length": 0, - "no_copy": 0, - "options": "\nEmail\nChat\nPhone\nSMS\nVisit\nOther", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "communication_medium", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Type", + "length": 0, + "no_copy": 0, + "options": "\nEmail\nChat\nPhone\nSMS\nVisit\nOther", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "sender", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "From", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "sender", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "From", + "length": 0, + "no_copy": 0, + "options": "Email", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "", - "fieldname": "recipients", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "To", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "recipients", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "To", + "length": 0, + "no_copy": 0, + "options": "Email", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "cc", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "CC", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "cc", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "CC", + "length": 0, + "no_copy": 0, + "options": "Email", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:in_list([\"Phone\", \"SMS\"], doc.communication_medium)", - "fieldname": "phone_no", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Phone No.", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:in_list([\"Phone\", \"SMS\"], doc.communication_medium)", + "fieldname": "phone_no", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Phone No.", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "Integrations can use this field to set email delivery status", - "fieldname": "delivery_status", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Delivery Status", - "length": 0, - "no_copy": 0, - "options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed\nError\nExpired\nSending", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Integrations can use this field to set email delivery status", + "fieldname": "delivery_status", + "fieldtype": "Select", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Delivery Status", + "length": 0, + "no_copy": 0, + "options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed\nError\nExpired\nSending", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "content", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Message", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "content", + "fieldtype": "Text Editor", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Message", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, "width": "400" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "fieldname": "status_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "status_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "Communication", - "fieldname": "communication_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Communication Type", - "length": 0, - "no_copy": 0, - "options": "Communication\nComment\nChat\nBot\nNotification", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "text_content", + "fieldtype": "Code", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Text Content", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "comment_type", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Comment Type", - "length": 0, - "no_copy": 0, - "options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nUpdated\nSubmitted\nCancelled\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Communication", + "fieldname": "communication_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Communication Type", + "length": 0, + "no_copy": 0, + "options": "Communication\nComment\nChat\nBot\nNotification", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "comment_type", + "fieldtype": "Select", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Comment Type", + "length": 0, + "no_copy": 0, + "options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nUpdated\nSubmitted\nCancelled\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Open\nReplied\nClosed\nLinked", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "sent_or_received", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Sent or Received", - "length": 0, - "no_copy": 0, - "options": "Sent\nReceived", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Open\nReplied\nClosed\nLinked", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "fieldname": "additional_info", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "More Information", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "sent_or_received", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Sent or Received", + "length": 0, + "no_copy": 0, + "options": "Sent\nReceived", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "Today", - "fieldname": "communication_date", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "additional_info", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "More Information", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "communication_date", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "sender_full_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "From Full Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_14", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "fieldname": "reference_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sender_full_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "From Full Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "reference_doctype", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "reference_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Name", - "length": 0, - "no_copy": 0, - "options": "reference_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "reference_owner", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Owner", - "length": 0, - "no_copy": 0, - "options": "reference_name.owner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference Name", + "length": 0, + "no_copy": 0, + "options": "reference_doctype", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "email_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Email Account", - "length": 0, - "no_copy": 0, - "options": "Email Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_owner", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference Owner", + "length": 0, + "no_copy": 0, + "options": "reference_name.owner", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "in_reply_to", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "In Reply To", - "length": 0, - "no_copy": 0, - "options": "Communication", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "email_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Email Account", + "length": 0, + "no_copy": 0, + "options": "Email Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "__user", - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "in_reply_to", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "In Reply To", + "length": 0, + "no_copy": 0, + "options": "Communication", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_27", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "__user", + "fieldname": "user", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "User", + "length": 0, + "no_copy": 0, + "options": "User", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "link_doctype", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Link DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_27", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "link_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Link Name", - "length": 0, - "no_copy": 0, - "options": "link_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "link_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Link DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "timeline_doctype", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Timeline DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "link_name", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Link Name", + "length": 0, + "no_copy": 0, + "options": "link_doctype", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "timeline_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Timeline Name", - "length": 0, - "no_copy": 0, - "options": "timeline_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "timeline_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Timeline DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "0", - "fieldname": "unread_notification_sent", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Unread Notification Sent", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "timeline_name", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Timeline Name", + "length": 0, + "no_copy": 0, + "options": "timeline_doctype", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "seen", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Seen", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "unread_notification_sent", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Unread Notification Sent", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "_user_tags", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "User Tags", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "seen", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Seen", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "message_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_standard_filter": 0, + "in_list_view": 0, + "label": "Message ID", + "length": 995, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "_user_tags", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "User Tags", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-comment", - "idx": 1, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-05-02 11:00:53.679035", - "modified_by": "Administrator", - "module": "Core", - "name": "Communication", - "owner": "Administrator", + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-comment", + "idx": 1, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2016-12-14 16:02:10.145975", + "modified_by": "Administrator", + "module": "Core", + "name": "Communication", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 0, - "export": 0, - "if_owner": 1, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 0, + "export": 0, + "if_owner": 1, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "All", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "subject", - "sort_order": "DESC", - "title_field": "subject", + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "subject", + "sort_order": "DESC", + "title_field": "subject", "track_seen": 0 } \ No newline at end of file diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 2b18c16a0e..71470457a4 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -229,6 +229,7 @@ def on_doctype_update(): frappe.db.add_index("Communication", ["status", "communication_type"]) frappe.db.add_index("Communication", ["creation"]) frappe.db.add_index("Communication", ["modified"]) + frappe.db.add_index("Communication", ["message_id(200)"]) def has_permission(doc, ptype, user): if ptype=="read": diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index d750d18373..7f713284c0 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -10,6 +10,7 @@ from frappe.utils import (get_url, get_formatted_email, cint, from frappe.utils.file_manager import get_file from frappe.email.queue import check_email_limit from frappe.utils.scheduler import log +from frappe.email.email_body import get_message_id import frappe.email.smtp import MySQLdb import time @@ -57,11 +58,22 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, - "reference_name": name + "reference_name": name, + "message_id":get_message_id().strip(" <>") }) comm.insert(ignore_permissions=True) + if not doctype: + # if no reference given, then send it against the communication + comm.db_set(dict(reference_doctype='Communication', reference_name=comm.name)) + + if isinstance(attachments, basestring): + attachments = json.loads(attachments) + # if not committed, delayed task doesn't find the communication + if attachments: + add_attachments(comm.name, attachments) + frappe.db.commit() if cint(send_email): @@ -120,10 +132,15 @@ def _notify(doc, print_html=None, print_format=None, attachments=None, prepare_to_notify(doc, print_html, print_format, attachments) + if doc.outgoing_email_account.send_unsubscribe_message: + unsubscribe_message = _("Leave this conversation") + else: + unsubscribe_message = "" + frappe.sendmail( - recipients=(recipients or []) + (cc or []), - show_as_cc=(cc or []), - expose_recipients=True, + recipients=(recipients or []), + cc=(cc or []), + expose_recipients="header", sender=doc.sender, reply_to=doc.incoming_email_account, subject=doc.subject, @@ -131,8 +148,8 @@ def _notify(doc, print_html=None, print_format=None, attachments=None, reference_doctype=doc.reference_doctype, reference_name=doc.reference_name, attachments=doc.attachments, - message_id=doc.name, - unsubscribe_message=_("Leave this conversation"), + message_id=doc.message_id, + unsubscribe_message=unsubscribe_message, delayed=True, communication=doc.name ) @@ -252,7 +269,7 @@ def set_incoming_outgoing_accounts(doc): if not doc.outgoing_email_account: doc.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, - ["email_id", "always_use_account_email_id_as_sender", "name"], as_dict=True) or frappe._dict() + ["email_id", "always_use_account_email_id_as_sender", "name", "send_unsubscribe_message"], as_dict=True) or frappe._dict() def get_recipients(doc, fetched_from_email_account=False): """Build a list of email addresses for To""" @@ -312,6 +329,20 @@ def get_cc(doc, recipients=None, fetched_from_email_account=False): return cc + +def add_attachments(name, attachments): + '''Add attachments to the given Communiction''' + from frappe.utils.file_manager import save_url + + # loop through attachments + for a in attachments: + attach = frappe.db.get_value("File", {"name":a}, + ["file_name", "file_url", "is_private"], as_dict=1) + + # save attachments to new doc + save_url(attach.file_url, attach.file_name, "Communication", name, + "Home/Attachments", attach.is_private) + def filter_email_list(doc, email_list, exclude, is_cc=False): # temp variables filtered = [] @@ -383,6 +414,8 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments for i in xrange(3): try: communication = frappe.get_doc("Communication", communication_name) + if communication.sent_or_received == "Received": + communication.message_id = None communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, recipients=recipients, cc=cc) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 7a382c7864..3601b986f0 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -23,6 +23,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "", "length": 0, "no_copy": 0, @@ -48,6 +49,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Label", "length": 0, "no_copy": 0, @@ -78,6 +80,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Type", "length": 0, "no_copy": 0, @@ -106,6 +109,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Name", "length": 0, "no_copy": 0, @@ -133,6 +137,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Mandatory", "length": 0, "no_copy": 0, @@ -164,6 +169,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Precision", "length": 0, "no_copy": 0, @@ -191,6 +197,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Length", "length": 0, "no_copy": 0, @@ -217,6 +224,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Index", "length": 0, "no_copy": 0, @@ -246,6 +254,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "In List View", "length": 0, "no_copy": 0, @@ -261,6 +270,33 @@ "unique": 0, "width": "70px" }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "in_standard_filter", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "In Standard Filter", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -273,6 +309,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Bold", "length": 0, "no_copy": 0, @@ -300,6 +337,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Collapsible", "length": 255, "no_copy": 0, @@ -327,6 +365,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Collapsible Depends On", "length": 0, "no_copy": 0, @@ -353,6 +392,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -378,6 +418,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Options", "length": 0, "no_copy": 0, @@ -405,6 +446,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Default", "length": 0, "no_copy": 0, @@ -432,6 +474,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Permissions", "length": 0, "no_copy": 0, @@ -457,6 +500,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Display Depends On", "length": 255, "no_copy": 0, @@ -484,6 +528,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Hidden", "length": 0, "no_copy": 0, @@ -513,6 +558,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Read Only", "length": 0, "no_copy": 0, @@ -540,6 +586,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Unique", "length": 0, "no_copy": 0, @@ -567,6 +614,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Set Only Once", "length": 0, "no_copy": 0, @@ -592,6 +640,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -617,6 +666,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Perm Level", "length": 0, "no_copy": 0, @@ -647,6 +697,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Ignore User Permissions", "length": 0, "no_copy": 0, @@ -672,6 +723,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Allow on Submit", "length": 0, "no_copy": 0, @@ -701,6 +753,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Report Hide", "length": 0, "no_copy": 0, @@ -757,6 +810,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Ignore XSS Filter", "length": 0, "no_copy": 0, @@ -783,6 +837,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Display", "length": 0, "no_copy": 0, @@ -808,6 +863,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "In Filter", "length": 0, "no_copy": 0, @@ -837,6 +893,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "No Copy", "length": 0, "no_copy": 0, @@ -866,6 +923,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Print Hide", "length": 0, "no_copy": 0, @@ -896,6 +954,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Print Hide If No Value", "length": 0, "no_copy": 0, @@ -922,6 +981,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Print Width", "length": 0, "no_copy": 0, @@ -947,6 +1007,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Width", "length": 0, "no_copy": 0, @@ -978,6 +1039,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Columns", "length": 0, "no_copy": 0, @@ -1004,6 +1066,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -1028,6 +1091,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Description", "length": 0, "no_copy": 0, @@ -1057,6 +1121,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "oldfieldname": "oldfieldname", @@ -1083,6 +1148,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "oldfieldname": "oldfieldtype", @@ -1118,5 +1184,5 @@ "read_only": 0, "read_only_onload": 0, "sort_order": "ASC", - "track_seen": 0 -} + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/core/doctype/doctype/boilerplate/controller_list.html b/frappe/core/doctype/doctype/boilerplate/controller_list.html index 0ad19e561b..714bacc8bc 100644 --- a/frappe/core/doctype/doctype/boilerplate/controller_list.html +++ b/frappe/core/doctype/doctype/boilerplate/controller_list.html @@ -1,6 +1,6 @@
-
+
{{%= list.get_avatar_and_id(doc) %}} @@ -13,7 +13,7 @@ - + {{% }} %}} diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index f7eb5b2180..678946bf39 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -25,6 +25,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "", "length": 0, "no_copy": 0, @@ -33,6 +34,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -51,6 +53,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "Module", "length": 0, "no_copy": 0, @@ -61,6 +64,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 1, @@ -80,6 +84,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 1, "label": "Is Child Table", "length": 0, "no_copy": 0, @@ -89,6 +94,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -109,6 +115,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Editable Grid", "length": 0, "no_copy": 0, @@ -117,6 +124,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -136,6 +144,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 1, "label": "Is Single", "length": 0, "no_copy": 0, @@ -145,6 +154,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -163,12 +173,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -180,6 +192,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "description": "", "fieldname": "document_type", "fieldtype": "Select", "hidden": 0, @@ -187,7 +200,8 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Document Type", + "in_standard_filter": 0, + "label": "Show in Module Section", "length": 0, "no_copy": 0, "oldfieldname": "document_type", @@ -197,35 +211,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "InnoDB", - "depends_on": "eval:!doc.issingle", - "fieldname": "engine", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Database Engine", - "length": 0, - "no_copy": 0, - "options": "InnoDB\nMyISAM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -244,6 +230,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Icon", "length": 0, "no_copy": 0, @@ -251,6 +238,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -269,6 +257,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Custom?", "length": 0, "no_copy": 0, @@ -276,6 +265,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -294,6 +284,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Beta", "length": 0, "no_copy": 0, @@ -302,6 +293,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -322,6 +314,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Image View", "length": 0, "no_copy": 0, @@ -330,6 +323,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -348,6 +342,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Plugin", "length": 0, "no_copy": 0, @@ -355,6 +350,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -373,6 +369,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Fields", "length": 0, "no_copy": 0, @@ -381,6 +378,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -399,6 +397,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Fields", "length": 0, "no_copy": 0, @@ -409,6 +408,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -427,6 +427,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Naming", "length": 0, "no_copy": 0, @@ -434,6 +435,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -453,6 +455,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Auto Name", "length": 0, "no_copy": 0, @@ -462,6 +465,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -480,6 +484,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Name Case", "length": 0, "no_copy": 0, @@ -490,6 +495,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -508,6 +514,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Description", "length": 0, "no_copy": 0, @@ -517,6 +524,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -536,12 +544,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -562,6 +572,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Title Field", "length": 0, "no_copy": 0, @@ -569,6 +580,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -588,6 +600,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Search Fields", "length": 0, "no_copy": 0, @@ -597,6 +610,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -616,6 +630,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Image Field", "length": 0, "no_copy": 0, @@ -624,6 +639,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -645,6 +661,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Sort Field", "length": 0, "no_copy": 0, @@ -652,6 +669,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -672,6 +690,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Sort Order", "length": 0, "no_copy": 0, @@ -680,6 +699,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -700,6 +720,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Timeline Field", "length": 0, "no_copy": 0, @@ -708,6 +729,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -727,6 +749,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Permission Rules", "length": 0, "no_copy": 0, @@ -734,6 +757,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -753,6 +777,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Permissions", "length": 0, "no_copy": 0, @@ -763,6 +788,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -782,12 +808,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -806,6 +834,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Permissions Settings", "length": 0, "no_copy": 0, @@ -813,6 +842,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -831,6 +861,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "User Cannot Create", "length": 0, "no_copy": 0, @@ -840,6 +871,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -858,6 +890,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "User Cannot Search", "length": 0, "no_copy": 0, @@ -867,6 +900,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -885,6 +919,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Is Submittable", "length": 0, "no_copy": 0, @@ -892,6 +927,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -911,6 +947,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Allow Import", "length": 0, "no_copy": 0, @@ -918,6 +955,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -936,6 +974,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Allow Rename", "length": 0, "no_copy": 0, @@ -945,6 +984,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -963,6 +1003,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "In Dialog", "length": 0, "no_copy": 0, @@ -972,6 +1013,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -990,6 +1032,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Show Print First", "length": 0, "no_copy": 0, @@ -999,6 +1042,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1017,6 +1061,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Max Attachments", "length": 0, "no_copy": 0, @@ -1026,6 +1071,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1044,6 +1090,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Other Settings", "length": 0, "no_copy": 0, @@ -1051,6 +1098,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1069,6 +1117,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Hide Heading", "length": 0, "no_copy": 0, @@ -1078,6 +1127,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1096,6 +1146,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Hide Toolbar", "length": 0, "no_copy": 0, @@ -1105,6 +1156,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1123,6 +1175,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Hide Copy", "length": 0, "no_copy": 0, @@ -1132,6 +1185,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1150,6 +1204,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Track Seen", "length": 0, "no_copy": 0, @@ -1158,6 +1213,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1177,6 +1233,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Quick Entry", "length": 0, "no_copy": 0, @@ -1185,6 +1242,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1203,6 +1261,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Default Print Format", "length": 0, "no_copy": 0, @@ -1210,6 +1269,66 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "advanced", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Advanced", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "InnoDB", + "depends_on": "eval:!doc.issingle", + "fieldname": "engine", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Database Engine", + "length": 0, + "no_copy": 0, + "options": "InnoDB\nMyISAM", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -1219,7 +1338,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-bolt", + "icon": "fa fa-bolt", "idx": 6, "image_view": 0, "in_create": 0, @@ -1228,7 +1347,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-10-13 01:13:58.133080", + "modified": "2016-11-08 01:17:33.593456", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 9a3a27ed35..66fedc53aa 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -9,7 +9,7 @@ import frappe from frappe import _ from frappe.utils import now, cint -from frappe.model import no_value_fields +from frappe.model import no_value_fields, default_fields from frappe.model.document import Document from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.desk.notifications import delete_notification_count_for @@ -59,15 +59,47 @@ class DocType(Document): self.make_amendable() self.validate_website() + self.update_fields_to_fetch() def check_developer_mode(self): """Throw exception if not developer mode or via patch""" - if frappe.flags.in_patch: + if frappe.flags.in_patch or frappe.flags.in_test: return if not frappe.conf.get("developer_mode") and not self.custom: frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType.")) + def update_fields_to_fetch(self): + '''Update values for newly set fetch values''' + try: + old_meta = frappe.get_meta(frappe.get_doc('DocType', self.name), cached=False) + old_fields_to_fetch = [df.fieldname for df in old_meta.get_fields_to_fetch()] + except frappe.DoesNotExistError: + old_fields_to_fetch = [] + + new_meta = frappe.get_meta(self, cached=False) + + if set(old_fields_to_fetch) != set([df.fieldname for df in new_meta.get_fields_to_fetch()]): + for df in new_meta.get_fields_to_fetch(): + if df.fieldname not in old_fields_to_fetch: + link_fieldname, source_fieldname = df.options.split('.', 1) + link_df = new_meta.get_field(link_fieldname) + + frappe.db.sql('''update + `tab{link_doctype}` source, + `tab{doctype}` target + set + target.`{fieldname}` = source.`{source_fieldname}` + where + target.`{link_fieldname}` = source.name + and ifnull(target.`{fieldname}`, '')="" '''.format( + link_doctype = link_df.options, + source_fieldname = source_fieldname, + doctype = self.name, + fieldname = df.fieldname, + link_fieldname = link_fieldname + )) + def validate_document_type(self): if self.document_type=="Transaction": self.document_type = "Document" @@ -387,7 +419,6 @@ def validate_fields(meta): if not meta.search_fields: return - fieldname_list = [d.fieldname for d in fields] for fieldname in (meta.search_fields or "").split(","): fieldname = fieldname.strip() if fieldname not in fieldname_list: @@ -398,8 +429,6 @@ def validate_fields(meta): if not meta.get("title_field"): return - fieldname_list = [d.fieldname for d in fields] - if meta.title_field not in fieldname_list: frappe.throw(_("Title field must be a valid fieldname"), InvalidFieldNameError) @@ -437,8 +466,6 @@ def validate_fields(meta): if not meta.timeline_field: return - fieldname_list = [d.fieldname for d in fields] - if meta.timeline_field not in fieldname_list: frappe.throw(_("Timeline field must be a valid fieldname"), InvalidFieldNameError) @@ -446,7 +473,22 @@ def validate_fields(meta): if df.fieldtype not in ("Link", "Dynamic Link"): frappe.throw(_("Timeline field must be a Link or Dynamic Link"), InvalidFieldNameError) + def check_sort_field(meta): + '''Validate that sort_field(s) is a valid field''' + if meta.sort_field: + sort_fields = [meta.sort_field] + if ',' in meta.sort_field: + sort_fields = [d.split()[0] for d in meta.sort_field.split(',')] + + for fieldname in sort_fields: + if not fieldname in fieldname_list + list(default_fields): + frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname), + InvalidFieldNameError) + + fields = meta.get("fields") + fieldname_list = [d.fieldname for d in fields] + not_allowed_in_list_view = list(copy.copy(no_value_fields)) if meta.istable: not_allowed_in_list_view.remove('Button') @@ -470,6 +512,7 @@ def validate_fields(meta): check_search_fields(meta) check_title_field(meta) check_timeline_field(meta) + check_sort_field(meta) def validate_permissions_for_doctype(doctype, for_remove=False): """Validates if permissions are set correctly.""" diff --git a/frappe/core/doctype/error_log/error_log.json b/frappe/core/doctype/error_log/error_log.json index b7e9e1808a..7051921eef 100644 --- a/frappe/core/doctype/error_log/error_log.json +++ b/frappe/core/doctype/error_log/error_log.json @@ -93,7 +93,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-warning-sign", + "icon": "fa fa-warning-sign", "idx": 1, "image_view": 0, "in_create": 0, diff --git a/frappe/core/doctype/error_log/error_log.py b/frappe/core/doctype/error_log/error_log.py index 1dc29494c4..c363dbad52 100644 --- a/frappe/core/doctype/error_log/error_log.py +++ b/frappe/core/doctype/error_log/error_log.py @@ -19,3 +19,9 @@ def set_old_logs_as_seen(): # clear old logs frappe.db.sql("""delete from `tabError Log` where datediff(curdate(), creation) > 30""") + +@frappe.whitelist() +def clear_error_logs(): + '''Flush all Error Logs''' + frappe.only_for('System Manager') + frappe.db.sql('''delete from `tabError Log`''') \ No newline at end of file diff --git a/frappe/core/doctype/error_log/error_log_list.js b/frappe/core/doctype/error_log/error_log_list.js index a50bfe31f3..59c4a3011c 100644 --- a/frappe/core/doctype/error_log/error_log_list.js +++ b/frappe/core/doctype/error_log/error_log_list.js @@ -8,4 +8,14 @@ frappe.listview_settings['Error Log'] = { } }, order_by: "seen asc, modified desc", + onload: function(listview) { + listview.page.add_menu_item(__("Clear Error Logs"), function() { + frappe.call({ + method:'frappe.core.doctype.error_log.error_log.clear_error_logs', + callback: function() { + listview.refresh(); + } + }); + }); + } }; diff --git a/frappe/core/doctype/error_snapshot/error_snapshot.html b/frappe/core/doctype/error_snapshot/error_snapshot.html index beda4bb860..6f449e0fe9 100644 --- a/frappe/core/doctype/error_snapshot/error_snapshot.html +++ b/frappe/core/doctype/error_snapshot/error_snapshot.html @@ -62,7 +62,7 @@
- {{ __("Locals") }} + {{ __("Locals") }}
diff --git a/frappe/core/doctype/file/file.js b/frappe/core/doctype/file/file.js index bdcd6cf119..62298fe809 100644 --- a/frappe/core/doctype/file/file.js +++ b/frappe/core/doctype/file/file.js @@ -6,7 +6,7 @@ frappe.ui.form.on("File", "refresh", function(frm) { file_url = file_url.replace(/#/g, '%23'); } window.open(file_url); - }, "icon-download"); + }, "fa fa-download"); } var wrapper = frm.get_field("preview_html").$wrapper; @@ -22,4 +22,18 @@ frappe.ui.form.on("File", "refresh", function(frm) { } else { wrapper.empty(); } + + if(frm.doc.file_name.split('.').splice(-1)[0]==='zip') { + frm.add_custom_button(__('Unzip'), function() { + frappe.call({ + method: "frappe.core.doctype.file.file.unzip_file", + args: { + name: frm.doc.name, + }, + callback: function() { + frappe.set_route('List', 'File'); + } + }); + }); + } }); diff --git a/frappe/core/doctype/file/file.json b/frappe/core/doctype/file/file.json index add5bdb24c..d6b86e046b 100644 --- a/frappe/core/doctype/file/file.json +++ b/frappe/core/doctype/file/file.json @@ -22,6 +22,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "File Name", "length": 0, "no_copy": 0, @@ -31,6 +32,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -50,6 +52,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Is Private", "length": 0, "no_copy": 0, @@ -58,6 +61,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -76,6 +80,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Preview", "length": 0, "no_copy": 0, @@ -84,6 +89,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -102,6 +108,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Preview HTML", "length": 0, "no_copy": 0, @@ -110,6 +117,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -128,6 +136,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -135,6 +144,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -154,6 +164,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Is Home Folder", "length": 0, "no_copy": 0, @@ -162,6 +173,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -180,6 +192,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Is Attachments Folder", "length": 0, "no_copy": 0, @@ -188,6 +201,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -206,6 +220,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "File Size", "length": 0, "no_copy": 0, @@ -213,6 +228,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -231,6 +247,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -238,6 +255,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -257,6 +275,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "File URL", "length": 0, "no_copy": 0, @@ -264,6 +283,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -282,6 +302,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Thumbnail URL", "length": 0, "no_copy": 0, @@ -290,6 +311,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -308,6 +330,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 1, "label": "Folder", "length": 0, "no_copy": 0, @@ -317,6 +340,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -335,6 +359,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Is Folder", "length": 0, "no_copy": 0, @@ -343,6 +368,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -362,6 +388,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -369,6 +396,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -387,6 +415,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 1, "label": "Attached To DocType", "length": 0, "no_copy": 0, @@ -395,6 +424,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 1, @@ -413,6 +443,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -420,6 +451,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -438,6 +470,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Attached To Name", "length": 0, "no_copy": 0, @@ -445,6 +478,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 1, @@ -463,6 +497,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Content Hash", "length": 0, "no_copy": 0, @@ -470,6 +505,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 1, @@ -488,6 +524,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "lft", "length": 0, "no_copy": 0, @@ -496,6 +533,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -514,6 +552,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "rgt", "length": 0, "no_copy": 0, @@ -522,6 +561,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -540,6 +580,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "old_parent", "length": 0, "no_copy": 0, @@ -548,6 +589,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -557,7 +599,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-file", + "icon": "fa fa-file", "idx": 1, "image_view": 0, "in_create": 0, @@ -567,7 +609,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2016-09-21 12:23:34.017457", + "modified": "2016-11-07 05:52:55.387721", "modified_by": "Administrator", "module": "Core", "name": "File", @@ -583,6 +625,7 @@ "export": 1, "if_owner": 0, "import": 1, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -603,6 +646,7 @@ "export": 1, "if_owner": 1, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 5849066be2..dbe929d9b9 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -23,6 +23,7 @@ from frappe import _ from frappe.utils.nestedset import NestedSet from frappe.utils import strip, get_files_path from PIL import Image, ImageOps +import zipfile class FolderNotEmpty(frappe.ValidationError): pass @@ -238,6 +239,35 @@ class File(NestedSet): self.flags.on_rollback = True self.on_trash() + def unzip(self): + '''Unzip current file and replace it by its children''' + if not ".zip" in self.file_name: + frappe.msgprint(_("Not a zip file")) + return + + zip_path = frappe.get_site_path(self.file_url.strip('/')) + base_url = os.path.dirname(self.file_url) + with zipfile.ZipFile(zip_path) as zf: + zf.extractall(os.path.dirname(zip_path)) + for info in zf.infolist(): + if not info.filename.startswith('__MACOSX'): + file_url = file_url = base_url + '/' + info.filename + file_name = frappe.db.get_value('File', dict(file_url=file_url)) + if file_name: + file_doc = frappe.get_doc('File', file_name) + else: + file_doc = frappe.new_doc("File") + file_doc.file_name = info.filename + file_doc.file_size = info.file_size + file_doc.folder = self.folder + file_doc.is_private = self.is_private + file_doc.file_url = file_url + file_doc.attached_to_doctype = self.attached_to_doctype + file_doc.attached_to_name = self.attached_to_name + file_doc.save() + + frappe.delete_doc('File', self.name) + def on_doctype_update(): frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"]) @@ -368,3 +398,9 @@ def check_file_permission(file_url): return True raise frappe.PermissionError + +@frappe.whitelist() +def unzip_file(name): + '''Unzip the given file and make file records for each of the extracted files''' + file_obj = frappe.get_doc('File', name) + file_obj.unzip() diff --git a/frappe/core/doctype/file/file_list.js b/frappe/core/doctype/file/file_list.js index 536a28b9a2..b99c734225 100644 --- a/frappe/core/doctype/file/file_list.js +++ b/frappe/core/doctype/file/file_list.js @@ -20,17 +20,17 @@ frappe.listview_settings['File'] = { var icon = "" if(data.is_folder) { - icon += ' '; + icon += ' '; } else if(frappe.utils.is_image_file(data.file_name)) { - icon += ' '; + icon += ' '; } else { - icon += ' ' + icon += ' ' } data._title = icon + (data.file_name ? data.file_name : data.file_url) if (data.is_private) { - data._title += ' ' + data._title += ' ' } }, onload: function(doclist) { @@ -81,7 +81,32 @@ frappe.listview_settings['File'] = { doclist.page.add_menu_item(__("Edit Folder"), function() { frappe.set_route("Form", "File", doclist.current_folder); }); - }, + + doclist.page.add_menu_item(__("Import .zip"), function() { + // make upload dialog + dialog = frappe.ui.get_upload_dialog({ + args: { + folder: doclist.current_folder, + from_form: 1 + }, + callback: function(attachment, r) { + frappe.call({ + method: "frappe.core.doctype.file.file.unzip_file", + args: { + name: r.message["name"], + }, + callback: function(r) { + if(!r.exc) { + //doclist.refresh(); + } else { + frappe.msgprint(__("Error in uploading files." + r.exc)); + } + } + }); + }, + }); + }); + }, setup_dragdrop: function(doclist) { $(doclist.$page).on('dragenter dragover', false) .on('drop', function (e) { @@ -123,7 +148,7 @@ frappe.listview_settings['File'] = { "new_parent": doclist.current_folder, "old_parent": doclist.old_parent }, - callback:function(r){ + callback:function(r) { doclist.paste = false; frappe.msgprint(__(r.message)); doclist.selected_files = []; @@ -143,7 +168,6 @@ frappe.listview_settings['File'] = { } }, refresh: function(doclist) { - // set folder before querying var name_filter = doclist.filter_list.get_filter("file_name"); var folder_filter = doclist.filter_list.get_filter("folder"); diff --git a/frappe/core/doctype/language/language.json b/frappe/core/doctype/language/language.json index c856f39888..d02d562ce8 100644 --- a/frappe/core/doctype/language/language.json +++ b/frappe/core/doctype/language/language.json @@ -112,7 +112,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-globe", + "icon": "fa fa-globe", "idx": 0, "image_view": 0, "in_create": 0, diff --git a/frappe/core/doctype/language/language.py b/frappe/core/doctype/language/language.py index f108dd5749..83d68acb36 100644 --- a/frappe/core/doctype/language/language.py +++ b/frappe/core/doctype/language/language.py @@ -38,4 +38,4 @@ def update_language_names(): data = json.loads(f.read()) for l in data: - frappe.db.set_value('Language', l['code'], 'language_name', l['name']) + frappe.db.set_value('Language', l['code'], 'language_name', l['name']) \ No newline at end of file diff --git a/frappe/core/doctype/module_def/module_def.json b/frappe/core/doctype/module_def/module_def.json index 67ea9d2cea..256b7370f9 100644 --- a/frappe/core/doctype/module_def/module_def.json +++ b/frappe/core/doctype/module_def/module_def.json @@ -9,11 +9,13 @@ "docstatus": 0, "doctype": "DocType", "editable_grid": 0, + "engine": "InnoDB", "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "module_name", "fieldtype": "Data", "hidden": 0, @@ -21,6 +23,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Module Name", "length": 0, "no_copy": 0, @@ -30,6 +33,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -40,6 +44,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "app_name", "fieldtype": "Data", "hidden": 0, @@ -47,6 +52,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "App Name", "length": 0, "no_copy": 0, @@ -54,6 +60,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -63,7 +70,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-sitemap", + "icon": "fa fa-sitemap", "idx": 1, "image_view": 0, "in_create": 0, @@ -72,7 +79,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-07-25 05:24:25.789580", + "modified": "2016-11-07 05:54:42.688231", "modified_by": "Administrator", "module": "Core", "name": "Module Def", @@ -88,6 +95,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -108,6 +116,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 0, "read": 1, diff --git a/frappe/core/doctype/page/page.json b/frappe/core/doctype/page/page.json index 62bd473a74..7f57f7274d 100644 --- a/frappe/core/doctype/page/page.json +++ b/frappe/core/doctype/page/page.json @@ -3,28 +3,36 @@ "allow_import": 0, "allow_rename": 1, "autoname": "field:page_name", + "beta": 0, "creation": "2012-12-20 17:16:49", "custom": 0, "docstatus": 0, "doctype": "DocType", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "page_html", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Page HTML", "length": 0, "no_copy": 0, "oldfieldtype": "Section Break", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -35,12 +43,15 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "page_name", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Page Name", "length": 0, "no_copy": 0, @@ -48,7 +59,9 @@ "oldfieldtype": "Data", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -59,18 +72,23 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "title", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Title", "length": 0, "no_copy": 1, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -81,18 +99,23 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "icon", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "icon", "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -103,17 +126,22 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "column_break0", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -124,12 +152,15 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "module", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 1, "label": "Module", "length": 0, "no_copy": 0, @@ -138,7 +169,9 @@ "options": "Module Def", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -149,12 +182,15 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "standard", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Standard", "length": 0, "no_copy": 0, @@ -163,7 +199,9 @@ "options": "\nYes\nNo", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 1, @@ -174,17 +212,22 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "section_break0", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -195,12 +238,15 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "roles", "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Roles", "length": 0, "no_copy": 0, @@ -209,7 +255,9 @@ "options": "Page Role", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -219,15 +267,16 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-file", + "icon": "fa fa-file", "idx": 1, + "image_view": 0, "in_create": 0, "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2015-11-16 06:29:51.370746", + "modified": "2016-11-07 05:55:29.162083", "modified_by": "Administrator", "module": "Core", "name": "Page", @@ -243,6 +292,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -263,6 +313,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -274,6 +325,8 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, - "read_only_onload": 0 + "read_only_onload": 0, + "track_seen": 0 } \ No newline at end of file diff --git a/frappe/core/doctype/patch_log/patch_log.json b/frappe/core/doctype/patch_log/patch_log.json index 3270a130ec..1ec345ec31 100644 --- a/frappe/core/doctype/patch_log/patch_log.json +++ b/frappe/core/doctype/patch_log/patch_log.json @@ -39,7 +39,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-cog", + "icon": "fa fa-cog", "idx": 1, "image_view": 0, "in_create": 0, diff --git a/frappe/core/doctype/report/report.js b/frappe/core/doctype/report/report.js index 54888bec88..d0e33db693 100644 --- a/frappe/core/doctype/report/report.js +++ b/frappe/core/doctype/report/report.js @@ -31,7 +31,7 @@ cur_frm.cscript.refresh = function(doc) { frappe.set_route("query-report", doc.name); break; } - }, "icon-table"); + }, "fa fa-table"); if (doc.is_standard === "Yes") { cur_frm.add_custom_button(doc.disabled ? __("Enable Report") : __("Disable Report"), function() { @@ -45,7 +45,7 @@ cur_frm.cscript.refresh = function(doc) { }).always(function() { cur_frm.reload_doc(); }); - }, doc.disabled ? "icon-ok" : "icon-off"); + }, doc.disabled ? "fa fa-check" : "fa fa-off"); } cur_frm.cscript.report_type(doc); diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index 4d40a60899..238be1d2a2 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -3,16 +3,20 @@ "allow_import": 0, "allow_rename": 0, "autoname": "field:report_name", + "beta": 0, "creation": "2013-03-09 15:45:57", "custom": 0, "docstatus": 0, "doctype": "DocType", "document_type": "System", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "report_name", "fieldtype": "Data", "hidden": 0, @@ -20,6 +24,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Report Name", "length": 0, "no_copy": 0, @@ -27,6 +32,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -37,6 +43,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "ref_doctype", "fieldtype": "Link", "hidden": 0, @@ -44,6 +51,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "Ref DocType", "length": 0, "no_copy": 0, @@ -52,6 +60,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -62,6 +71,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "is_standard", "fieldtype": "Select", "hidden": 0, @@ -69,6 +79,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "Is Standard", "length": 0, "no_copy": 0, @@ -77,6 +88,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -87,6 +99,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "module", "fieldtype": "Link", "hidden": 0, @@ -94,6 +107,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Module", "length": 0, "no_copy": 0, @@ -102,6 +116,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -112,6 +127,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "add_total_row", "fieldtype": "Check", "hidden": 0, @@ -119,6 +135,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Add Total Row", "length": 0, "no_copy": 0, @@ -126,6 +143,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -136,6 +154,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "column_break_4", "fieldtype": "Column Break", "hidden": 0, @@ -143,12 +162,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -159,6 +180,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "report_type", "fieldtype": "Select", "hidden": 0, @@ -166,6 +188,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Report Type", "length": 0, "no_copy": 0, @@ -174,6 +197,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -184,6 +208,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "disabled", "fieldtype": "Check", "hidden": 0, @@ -191,6 +216,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Disabled", "length": 0, "no_copy": 0, @@ -198,6 +224,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -208,6 +235,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "1", "depends_on": "eval:[\"Query Report\", \"Script Report\"].indexOf(doc.report_type)!==-1", "fieldname": "apply_user_permissions", @@ -217,6 +245,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Apply User Permissions", "length": 0, "no_copy": 0, @@ -224,6 +253,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -234,6 +264,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", "hidden": 0, @@ -241,12 +272,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -257,6 +290,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "depends_on": "eval:doc.report_type==\"Query Report\"", "fieldname": "query", "fieldtype": "Code", @@ -265,6 +299,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Query", "length": 0, "no_copy": 0, @@ -272,6 +307,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -282,6 +318,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "depends_on": "", "description": "JavaScript Format: frappe.query_reports['REPORTNAME'] = {}", "fieldname": "javascript", @@ -291,6 +328,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Javascript", "length": 0, "no_copy": 0, @@ -298,6 +336,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -308,6 +347,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "depends_on": "eval:doc.report_type==\"Report Builder\"", "fieldname": "json", "fieldtype": "Code", @@ -316,6 +356,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "JSON", "length": 0, "no_copy": 0, @@ -323,6 +364,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -332,15 +374,16 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-table", + "icon": "fa fa-table", "idx": 1, + "image_view": 0, "in_create": 0, "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-02-22 09:14:41.050580", + "modified": "2016-11-07 05:31:31.290828", "modified_by": "Administrator", "module": "Core", "name": "Report", @@ -356,6 +399,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -376,6 +420,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -396,6 +441,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -416,6 +462,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -427,8 +474,10 @@ "write": 0 } ], + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_seen": 0 } \ No newline at end of file diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json index 9e056dfe2a..545e4ad299 100644 --- a/frappe/core/doctype/role/role.json +++ b/frappe/core/doctype/role/role.json @@ -94,7 +94,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-bookmark", + "icon": "fa fa-bookmark", "idx": 1, "image_view": 0, "in_create": 0, diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index dc8926a49a..a7f5419284 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -691,7 +691,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-cog", + "icon": "fa fa-cog", "idx": 0, "image_view": 0, "in_create": 0, diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 8209c28bd1..2805151a25 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -26,6 +26,9 @@ class SystemSettings(Document): if self.language: set_default_language(self.language) + frappe.cache().delete_value('system_settings') + frappe.cache().delete_value('time_zone') + @frappe.whitelist() def load(): if not "System Manager" in frappe.get_roles(): diff --git a/frappe/public/css/font/open-sans/OpenSans-Semibold-webfont.ttf b/frappe/core/doctype/tag/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from frappe/public/css/font/open-sans/OpenSans-Semibold-webfont.ttf rename to frappe/core/doctype/tag/__init__.py diff --git a/frappe/core/doctype/tag/tag.json b/frappe/core/doctype/tag/tag.json new file mode 100644 index 0000000000..2b38a18cb6 --- /dev/null +++ b/frappe/core/doctype/tag/tag.json @@ -0,0 +1,58 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "creation": "2016-05-25 09:43:44.767581", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "tag_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Tags", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2016-05-31 08:29:01.773065", + "modified_by": "Administrator", + "module": "Core", + "name": "Tag", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/frappe/core/doctype/tag/tag.py b/frappe/core/doctype/tag/tag.py new file mode 100644 index 0000000000..cc8e17e8d2 --- /dev/null +++ b/frappe/core/doctype/tag/tag.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class Tag(Document): + def validate(self): + self.tag_name = self.tag_name.title() diff --git a/frappe/core/doctype/tag_category/__init__.py b/frappe/core/doctype/tag_category/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/tag_category/tag_category.js b/frappe/core/doctype/tag_category/tag_category.js new file mode 100644 index 0000000000..e01dad063d --- /dev/null +++ b/frappe/core/doctype/tag_category/tag_category.js @@ -0,0 +1,9 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt +frappe.ui.form.on('Tag', { + tag_name:function(frm){ + for (var i = 0 ;i {{ app_icon }} -
+
diff --git a/frappe/core/page/modules_setup/includes/module_icons.html b/frappe/core/page/modules_setup/includes/module_icons.html index fd57ef32b3..12a23feecc 100644 --- a/frappe/core/page/modules_setup/includes/module_icons.html +++ b/frappe/core/page/modules_setup/includes/module_icons.html @@ -3,7 +3,7 @@
diff --git a/frappe/core/page/modules_setup/modules_setup.html b/frappe/core/page/modules_setup/modules_setup.html index ac59d12a71..48e186d37f 100644 --- a/frappe/core/page/modules_setup/modules_setup.html +++ b/frappe/core/page/modules_setup/modules_setup.html @@ -19,6 +19,14 @@ {% endfor %}
+
+
+ +
+
""".format(unsubscribe_message=unsubscribe_message) + if expose_recipients == "footer": + text = "\n" + else: + text = "" + text += "\n\n{unsubscribe_message}: ".format(unsubscribe_message=unsubscribe_message) return frappe._dict({ "html": html, @@ -263,7 +239,7 @@ def flush(from_test=False): email = cache.lpop('cache_email_queue') if email: - send_one(email, smtpserver, auto_commit) + send_one(email, smtpserver, auto_commit, from_test=from_test) # NOTE: removing commit here because we pass auto_commit # finally: @@ -273,7 +249,7 @@ def make_cache_queue(): cache = frappe.cache() emails = frappe.db.sql('''select name from `tabEmail Queue` - where status='Not Sent' and (send_after is null or send_after < %(now)s) + where (status='Not Sent' or status='Partially Sent') and (send_after is null or send_after < %(now)s) order by priority desc, creation asc limit 500''', { 'now': now_datetime() }) @@ -282,17 +258,25 @@ def make_cache_queue(): for e in emails: cache.rpush('cache_email_queue', e[0]) -def send_one(email, smtpserver=None, auto_commit=True, now=False): +def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=False): '''Send Email Queue with given smtpserver''' email = frappe.db.sql('''select name, status, communication, - message, sender, recipient, reference_doctype + message, sender, reference_doctype, reference_name, unsubscribe_param, unsubscribe_method, expose_recipients, show_as_cc from `tabEmail Queue` where name=%s for update''', email, as_dict=True)[0] - if email.status != 'Not Sent': + + recipients_list = frappe.db.sql('''select name, recipient, status from `tabEmail Queue Recipient` where parent=%s''',email.name,as_dict=1) + + if frappe.are_emails_muted(): + frappe.msgprint(_("Emails are muted")) + return + + if email.status not in ('Not Sent','Partially Sent') : # rollback to release lock and return frappe.db.rollback() return + frappe.db.sql("""update `tabEmail Queue` set status='Sending', modified=%s where name=%s""", (now_datetime(), email.name), auto_commit=auto_commit) @@ -300,14 +284,32 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False): frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit) try: - if auto_commit: + if not frappe.flags.in_test: if not smtpserver: smtpserver = SMTPServer() smtpserver.setup_email_account(email.reference_doctype) - smtpserver.sess.sendmail(email.sender, email.recipient, encode(email.message)) - frappe.db.sql("""update `tabEmail Queue` set status='Sent', modified=%s where name=%s""", - (now_datetime(), email.name), auto_commit=auto_commit) + for recipient in recipients_list: + if recipient.status != "Not Sent": + continue + message = prepare_message(email, recipient.recipient, recipients_list) + if not frappe.flags.in_test: + smtpserver.sess.sendmail(email.sender, recipient.recipient, encode(message)) + + recipient.status = "Sent" + frappe.db.sql("""update `tabEmail Queue Recipient` set status='Sent', modified=%s where name=%s""", + (now_datetime(), recipient.name), auto_commit=auto_commit) + + #if all are sent set status + if any("Sent" == s.status for s in recipients_list): + frappe.db.sql("""update `tabEmail Queue` set status='Sent', modified=%s where name=%s""", + (now_datetime(), email.name), auto_commit=auto_commit) + else: + frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s + where name=%s""", ("No recipients to send to", email.name), auto_commit=auto_commit) + if frappe.flags.in_test: + frappe.flags.sent_mail = message + return if email.communication: frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit) @@ -318,8 +320,13 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False): JobTimeoutException): # bad connection/timeout, retry later - frappe.db.sql("""update `tabEmail Queue` set status='Not Sent', modified=%s where name=%s""", - (now_datetime(), email.name), auto_commit=auto_commit) + + if any("Sent" == s.status for s in recipients_list): + frappe.db.sql("""update `tabEmail Queue` set status='Partially Sent', modified=%s where name=%s""", + (now_datetime(), email.name), auto_commit=auto_commit) + else: + frappe.db.sql("""update `tabEmail Queue` set status='Not Sent', modified=%s where name=%s""", + (now_datetime(), email.name), auto_commit=auto_commit) if email.communication: frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit) @@ -330,8 +337,12 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False): except Exception, e: frappe.db.rollback() - frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s - where name=%s""", (unicode(e), email.name), auto_commit=auto_commit) + if any("Sent" == s.status for s in recipients_list): + frappe.db.sql("""update `tabEmail Queue` set status='Partially Errored', error=%s where name=%s""", + (unicode(e), email.name), auto_commit=auto_commit) + else: + frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s +where name=%s""", (unicode(e), email.name), auto_commit=auto_commit) if email.communication: frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit) @@ -343,10 +354,38 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False): # log to Error Log log('frappe.email.queue.flush', unicode(e)) -def clear_outbox(): - """Remove mails older than 31 days in Outbox. Called daily via scheduler.""" - frappe.db.sql("""delete from `tabEmail Queue` where - datediff(now(), creation) > 31""") +def prepare_message(email, recipient, recipients_list): + message = email.message + if email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url + unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient, + email.unsubscribe_method, email.unsubscribe_params) + message = message.replace("", unsubscribe_url) - frappe.db.sql("""update `tabEmail Queue` set status='Expired' - where datediff(curdate(), creation) > 7 and status='Not Sent'""") + if email.expose_recipients == "header": + pass + else: + if email.expose_recipients == "footer": + if isinstance(email.show_as_cc, basestring): + email.show_as_cc = email.show_as_cc.split(",") + email_sent_to = [r.recipient for r in recipients_list] + email_sent_cc = ", ".join([e for e in email_sent_to if e in email.show_as_cc]) + email_sent_to = ", ".join([e for e in email_sent_to if e not in email.show_as_cc]) + + if email_sent_cc: + email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to,email_sent_cc) + else: + email_sent_message = _("This email was sent to {0}").format(email_sent_to) + message = message.replace("", email_sent_message) + + message = message.replace("", recipient) + return message + +def clear_outbox(): + """Remove low priority older than 31 days in Outbox and expire mails not sent for 7 days. + + Called daily via scheduler.""" + frappe.db.sql("""delete q, r from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r where q.name = r.parent and q.priority=0 and + datediff(now(), q.modified) > 31""") + + frappe.db.sql("""update `tabEmail Queue` as q, `tabEmail Queue Recipient` as r set q.status='Expired', r.status='Expired' + where q.name = r.parent and datediff(curdate(), q.modified) > 7 and q.status='Not Sent' and r.status='Not Sent'""") diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 39b970497f..7bceaab1be 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -262,6 +262,7 @@ class Email: self.set_content_and_type() self.set_subject() self.set_from() + self.message_id = (self.mail.get('Message-ID') or "").strip(" <>") if self.mail["Date"]: utc = email.utils.mktime_tz(email.utils.parsedate_tz(self.mail["Date"])) diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index 31b0acc856..3de1830b69 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -11,15 +11,7 @@ from frappe.utils import cint from frappe import _ def send(email, append_to=None, retry=1): - """send the message or add it to Outbox Email""" - if frappe.flags.in_test: - frappe.flags.sent_mail = email.as_string() - return - - if frappe.are_emails_muted(): - frappe.msgprint(_("Emails are muted")) - return - + """Deprecated: Send the message or add it to Outbox Email""" def _send(retry): try: smtpserver = SMTPServer(append_to=append_to) @@ -79,7 +71,7 @@ def get_default_outgoing_email_account(raise_exception_not_set=True): { "mail_server": "smtp.example.com", "mail_port": 587, - "use_ssl": 1, + "use_tls": 1, "mail_login": "emails@example.com", "mail_password": "Super.Secret.Password", "auto_email_id": "emails@example.com", @@ -97,7 +89,9 @@ def get_default_outgoing_email_account(raise_exception_not_set=True): email_account.update({ "smtp_server": frappe.conf.get("mail_server"), "smtp_port": frappe.conf.get("mail_port"), - "use_tls": cint(frappe.conf.get("use_ssl") or 0), + + # legacy: use_ssl was used in site_config instead of use_tls, but meant the same thing + "use_tls": cint(frappe.conf.get("use_tls") or 0) or cint(frappe.conf.get("use_ssl") or 0), "login_id": frappe.conf.get("mail_login"), "email_id": frappe.conf.get("auto_email_id") or frappe.conf.get("mail_login") or 'notifications@example.com', "password": frappe.conf.get("mail_password"), @@ -123,7 +117,7 @@ def _get_email_account(filters): return frappe.get_doc("Email Account", name) if name else None class SMTPServer: - def __init__(self, login=None, password=None, server=None, port=None, use_ssl=None, append_to=None): + def __init__(self, login=None, password=None, server=None, port=None, use_tls=None, append_to=None): # get defaults from mail settings self._sess = None @@ -132,7 +126,7 @@ class SMTPServer: if server: self.server = server self.port = port - self.use_ssl = cint(use_ssl) + self.use_tls = cint(use_tls) self.login = login self.password = password @@ -146,7 +140,7 @@ class SMTPServer: self.login = getattr(self.email_account, "login_id", None) or self.email_account.email_id self.password = self.email_account.password self.port = self.email_account.smtp_port - self.use_ssl = self.email_account.use_tls + self.use_tls = self.email_account.use_tls self.sender = self.email_account.email_id self.always_use_account_email_id_as_sender = cint(self.email_account.get("always_use_account_email_id_as_sender")) @@ -163,7 +157,7 @@ class SMTPServer: raise frappe.OutgoingEmailError, err_msg try: - if self.use_ssl and not self.port: + if self.use_tls and not self.port: self.port = 587 self._sess = smtplib.SMTP((self.server or "").encode('utf-8'), @@ -174,7 +168,7 @@ class SMTPServer: frappe.msgprint(err_msg) raise frappe.OutgoingEmailError, err_msg - if self.use_ssl: + if self.use_tls: self._sess.ehlo() self._sess.starttls() self._sess.ehlo() diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 2bd5e94851..c09ebd26e0 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -62,3 +62,4 @@ class AppNotInstalledError(ValidationError): pass class IncorrectSitePath(NotFound): pass class ImplicitCommitError(ValidationError): pass class RetryBackgroundJobError(Exception): pass +class DocumentLockedError(ValidationError): pass diff --git a/frappe/frappeclient.py b/frappe/frappeclient.py index 855564e8fc..10c5f505ba 100644 --- a/frappe/frappeclient.py +++ b/frappe/frappeclient.py @@ -2,6 +2,13 @@ import requests import json import frappe +''' +FrappeClient is a library that helps you connect with other frappe systems + + + +''' + class AuthError(Exception): pass @@ -13,7 +20,7 @@ class FrappeClient(object): self.verify = verify self.session = requests.session() self.url = url - self.login(username, password) + self._login(username, password) def __enter__(self): return self @@ -21,7 +28,8 @@ class FrappeClient(object): def __exit__(self, *args, **kwargs): self.logout() - def login(self, username, password): + def _login(self, username, password): + '''Login/start a sesion. Called internally on init''' r = self.session.post(self.url, data={ 'cmd': 'login', 'usr': username, @@ -34,6 +42,7 @@ class FrappeClient(object): raise AuthError def logout(self): + '''Logout session''' self.session.get(self.url, params={ 'cmd': 'logout', }, verify=self.verify) @@ -54,41 +63,65 @@ class FrappeClient(object): return self.post_process(res) def insert(self, doc): + '''Insert a document to the remote server + + :param doc: A dict or Document object to be inserted remotely''' res = self.session.post(self.url + "/api/resource/" + doc.get("doctype"), data={"data":frappe.as_json(doc)}, verify=self.verify) return self.post_process(res) def insert_many(self, docs): + '''Insert multiple documents to the remote server + + :param docs: List of dict or Document objects to be inserted in one request''' return self.post_request({ "cmd": "frappe.client.insert_many", "docs": frappe.as_json(docs) }) def update(self, doc): + '''Update a remote document + + :param doc: dict or Document object to be updated remotely. `name` is mandatory for this''' url = self.url + "/api/resource/" + doc.get("doctype") + "/" + doc.get("name") res = self.session.put(url, data={"data":frappe.as_json(doc)}, verify=self.verify) return self.post_process(res) def bulk_update(self, docs): + '''Bulk update documents remotely + + :param docs: List of dict or Document objects to be updated remotely (by `name`)''' return self.post_request({ "cmd": "frappe.client.bulk_update", "docs": frappe.as_json(docs) }) def delete(self, doctype, name): + '''Delete remote document by name + + :param doctype: `doctype` to be deleted + :param name: `name` of document to be deleted''' return self.post_request({ "cmd": "frappe.model.delete_doc", "doctype": doctype, "name": name }) - def submit(self, doclist): + def submit(self, doc): + '''Submit remote document + + :param doc: dict or Document object to be submitted remotely''' return self.post_request({ "cmd": "frappe.client.submit", - "doclist": frappe.as_json(doclist) + "doc": frappe.as_json(doc) }) def get_value(self, doctype, fieldname=None, filters=None): + '''Returns a value form a document + + :param doctype: DocType to be queried + :param fieldname: Field to be returned (default `name`) + :param filters: dict or string for identifying the record''' return self.get_request({ "cmd": "frappe.client.get_value", "doctype": doctype, @@ -97,6 +130,12 @@ class FrappeClient(object): }) def set_value(self, doctype, docname, fieldname, value): + '''Set a value in a remote document + + :param doctype: DocType of the document to be updated + :param docname: name of the document to be updated + :param fieldname: fieldname of the document to be updated + :param value: value to be updated''' return self.post_request({ "cmd": "frappe.client.set_value", "doctype": doctype, @@ -106,6 +145,10 @@ class FrappeClient(object): }) def cancel(self, doctype, name): + '''Cancel a remote document + + :param doctype: DocType of the document to be cancelled + :param name: name of the document to be cancelled''' return self.post_request({ "cmd": "frappe.client.cancel", "doctype": doctype, @@ -113,6 +156,12 @@ class FrappeClient(object): }) def get_doc(self, doctype, name="", filters=None, fields=None): + '''Returns a single remote document + + :param doctype: DocType of the document to be returned + :param name: (optional) `name` of the document to be returned + :param filters: (optional) Filter by this dict if name is not set + :param fields: (optional) Fields to be returned, will return everythign if not set''' params = {} if filters: params["filters"] = json.dumps(filters) @@ -125,6 +174,11 @@ class FrappeClient(object): return self.post_process(res) def rename_doc(self, doctype, old_name, new_name): + '''Rename remote document + + :param doctype: DocType of the document to be renamed + :param old_name: Current `name` of the document to be renamed + :param new_name: New `name` to be set''' params = { "cmd": "frappe.client.rename_doc", "doctype": doctype, diff --git a/frappe/geo/doctype/country/country.json b/frappe/geo/doctype/country/country.json index 118361bbd2..16ae4ef4fd 100644 --- a/frappe/geo/doctype/country/country.json +++ b/frappe/geo/doctype/country/country.json @@ -112,7 +112,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-globe", + "icon": "fa fa-globe", "idx": 1, "image_view": 0, "in_create": 0, diff --git a/frappe/geo/doctype/currency/currency.json b/frappe/geo/doctype/currency/currency.json index 48dbf6f717..e604a5da04 100644 --- a/frappe/geo/doctype/currency/currency.json +++ b/frappe/geo/doctype/currency/currency.json @@ -183,7 +183,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-bitcoin", + "icon": "fa fa-bitcoin", "idx": 1, "in_create": 0, "in_dialog": 0, diff --git a/frappe/hooks.py b/frappe/hooks.py index 1eecca0b4b..a79e377763 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -48,7 +48,8 @@ web_include_css = [ ] website_route_rules = [ {"from_route": "/blog", "to_route": "Blog Post"}, - {"from_route": "/blog/", "to_route": "Blog Post"} + {"from_route": "/blog/", "to_route": "Blog Post"}, + {"from_route": "/kb/", "to_route": "Help Article"} ] write_file_keys = ["file_url", "file_name"] @@ -57,7 +58,8 @@ notification_config = "frappe.core.notifications.get_notification_config" before_tests = "frappe.utils.install.before_tests" -website_generators = ["Web Page", "Blog Post", "Blog Category", "Web Form"] +website_generators = ["Web Page", "Blog Post", "Blog Category", "Web Form", + "Help Article"] email_append_to = ["Event", "ToDo", "Communication"] @@ -116,6 +118,7 @@ scheduler_events = { "frappe.email.queue.flush", "frappe.email.doctype.email_account.email_account.pull", "frappe.email.doctype.email_account.email_account.notify_unreplied", + "frappe.oauth.delete_oauth2_data" ], "hourly": [ "frappe.model.utils.link_count.update_link_count", diff --git a/frappe/integration_broker/doctype/integration_request/integration_request.json b/frappe/integration_broker/doctype/integration_request/integration_request.json index e32450d9bb..dbe8d19ad6 100644 --- a/frappe/integration_broker/doctype/integration_request/integration_request.json +++ b/frappe/integration_broker/doctype/integration_request/integration_request.json @@ -23,6 +23,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Integration Type", "length": 0, "no_copy": 0, @@ -32,6 +33,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -50,6 +52,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Integration Request Service", "length": 0, "no_copy": 0, @@ -59,6 +62,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -78,6 +82,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "Status", "length": 0, "no_copy": 0, @@ -87,6 +92,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -105,6 +111,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Data", "length": 0, "no_copy": 0, @@ -113,6 +120,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -131,6 +139,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Output", "length": 0, "no_copy": 0, @@ -139,6 +148,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -157,6 +167,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Error", "length": 0, "no_copy": 0, @@ -165,6 +176,65 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference Doctype", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_docname", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference Docname", + "length": 0, + "no_copy": 0, + "options": "reference_doctype", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -182,7 +252,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-10-13 05:01:14.913553", + "modified": "2016-11-10 18:04:27.500673", "modified_by": "Administrator", "module": "Integration Broker", "name": "Integration Request", diff --git a/frappe/integration_broker/doctype/integration_service/integration_service.py b/frappe/integration_broker/doctype/integration_service/integration_service.py index c8f5c96f8b..81ed105543 100644 --- a/frappe/integration_broker/doctype/integration_service/integration_service.py +++ b/frappe/integration_broker/doctype/integration_service/integration_service.py @@ -64,14 +64,16 @@ class IntegrationService(Document): pass def create_request(self, data, integration_type, service_name, name=None): - if not isinstance(data, basestring): - data = json.dumps(data) + if isinstance(data, basestring): + data = json.loads(data) integration_request = frappe.get_doc({ "doctype": "Integration Request", "integration_type": integration_type, "integration_request_service": service_name, - "data": data + "reference_doctype": data.get("reference_doctype"), + "reference_docname": data.get("reference_docname"), + "data": json.dumps(data) }) if name: diff --git a/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.json b/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.json index 35ee594063..48aadf501e 100644 --- a/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.json +++ b/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.json @@ -24,6 +24,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "Client", "length": 0, "no_copy": 0, @@ -32,6 +33,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -50,6 +52,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "User", "length": 0, "no_copy": 0, @@ -58,6 +61,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -76,6 +80,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Scopes", "length": 0, "no_copy": 0, @@ -83,6 +88,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -101,6 +107,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Authorization Code", "length": 0, "no_copy": 0, @@ -108,6 +115,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -126,6 +134,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Expiration time", "length": 0, "no_copy": 0, @@ -133,6 +142,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -151,6 +161,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Redirect URI Bound To Auth Code", "length": 0, "no_copy": 0, @@ -159,6 +170,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -177,6 +189,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Validity", "length": 0, "no_copy": 0, @@ -186,6 +199,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -203,7 +217,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-10-20 00:13:53.224437", + "modified": "2016-11-07 18:31:54.470173", "modified_by": "Administrator", "module": "Integration Broker", "name": "OAuth Authorization Code", @@ -214,7 +228,7 @@ "amend": 0, "apply_user_permissions": 0, "cancel": 0, - "create": 1, + "create": 0, "delete": 1, "email": 1, "export": 1, @@ -229,7 +243,7 @@ "set_user_permissions": 0, "share": 1, "submit": 0, - "write": 1 + "write": 0 } ], "quick_entry": 0, diff --git a/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.json b/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.json index 029b8754fe..cc0cea719b 100644 --- a/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.json +++ b/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.json @@ -24,6 +24,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "Client", "length": 0, "no_copy": 0, @@ -32,6 +33,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -50,6 +52,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "User", "length": 0, "no_copy": 0, @@ -58,6 +61,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -76,6 +80,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Scopes", "length": 0, "no_copy": 0, @@ -83,6 +88,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -101,6 +107,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Access Token", "length": 0, "no_copy": 0, @@ -108,6 +115,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -126,6 +134,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Refresh Token", "length": 0, "no_copy": 0, @@ -133,6 +142,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -151,6 +161,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Expiration time", "length": 0, "no_copy": 0, @@ -158,6 +169,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -176,6 +188,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Expires In", "length": 0, "no_copy": 0, @@ -184,6 +197,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -202,6 +216,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 1, "label": "Status", "length": 0, "no_copy": 0, @@ -211,6 +226,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -228,7 +244,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-10-20 00:13:41.747141", + "modified": "2016-11-07 18:31:32.243853", "modified_by": "Administrator", "module": "Integration Broker", "name": "OAuth Bearer Token", @@ -239,7 +255,7 @@ "amend": 0, "apply_user_permissions": 0, "cancel": 0, - "create": 1, + "create": 0, "delete": 1, "email": 1, "export": 1, @@ -254,7 +270,7 @@ "set_user_permissions": 0, "share": 1, "submit": 0, - "write": 1 + "write": 0 } ], "quick_entry": 0, diff --git a/frappe/integration_broker/doctype/oauth_client/oauth_client.json b/frappe/integration_broker/doctype/oauth_client/oauth_client.json index 83ec6bebdd..2edeb8e746 100644 --- a/frappe/integration_broker/doctype/oauth_client/oauth_client.json +++ b/frappe/integration_broker/doctype/oauth_client/oauth_client.json @@ -12,6 +12,34 @@ "editable_grid": 1, "engine": "InnoDB", "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "client_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "App Client ID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -24,6 +52,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "App Name", "length": 0, "no_copy": 0, @@ -32,6 +61,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -50,6 +80,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "User", "length": 0, "no_copy": 0, @@ -59,6 +90,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -77,6 +109,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -84,6 +117,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -95,21 +129,23 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "", - "fieldname": "client_id", + "fieldname": "client_secret", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "App Client ID", + "in_standard_filter": 0, + "label": "App Client Secret", "length": 0, "no_copy": 0, "permlevel": 0, + "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -129,6 +165,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Skip Authorization", "length": 0, "no_copy": 0, @@ -137,6 +174,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -156,6 +194,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "", "length": 0, "no_copy": 0, @@ -164,6 +203,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -175,6 +215,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "all openid", "description": "A list of resources which the Client App will have access to after the user allows it.
e.g. project", "fieldname": "scopes", "fieldtype": "Text", @@ -183,6 +224,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Scopes", "length": 0, "no_copy": 0, @@ -190,6 +232,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -208,6 +251,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -215,6 +259,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -234,6 +279,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Redirect URIs", "length": 0, "no_copy": 0, @@ -241,6 +287,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -259,6 +306,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Default Redirect URI", "length": 0, "no_copy": 0, @@ -267,6 +315,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -286,6 +335,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": " Advanced Settings", "length": 0, "no_copy": 0, @@ -294,6 +344,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -312,6 +363,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "Grant Type", "length": 0, "no_copy": 0, @@ -320,6 +372,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -338,6 +391,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -345,6 +399,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -364,6 +419,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, + "in_standard_filter": 1, "label": "Response Type", "length": 0, "no_copy": 0, @@ -372,6 +428,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -389,7 +446,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-10-20 00:32:11.993940", + "modified": "2016-11-07 18:53:59.549740", "modified_by": "Administrator", "module": "Integration Broker", "name": "OAuth Client", diff --git a/frappe/integration_broker/doctype/oauth_client/oauth_client.py b/frappe/integration_broker/doctype/oauth_client/oauth_client.py index 3493832064..ebf2f64547 100644 --- a/frappe/integration_broker/doctype/oauth_client/oauth_client.py +++ b/frappe/integration_broker/doctype/oauth_client/oauth_client.py @@ -9,3 +9,5 @@ from frappe.model.document import Document class OAuthClient(Document): def validate(self): self.client_id = self.name + if not self.client_secret: + self.client_secret = frappe.generate_hash(length=10) diff --git a/frappe/integration_broker/oauth2.py b/frappe/integration_broker/oauth2.py index ed48b83a4c..3b79c850af 100644 --- a/frappe/integration_broker/oauth2.py +++ b/frappe/integration_broker/oauth2.py @@ -3,8 +3,10 @@ import frappe, json from frappe.oauth import OAuthWebRequestValidator, WebApplicationServer from oauthlib.oauth2 import FatalClientError, OAuth2Error from urllib import quote, urlencode +from werkzeug import url_fix from urlparse import urlparse from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import get_oauth_settings +from frappe import _ def get_oauth_server(): if not getattr(frappe.local, 'oauth_server', None): @@ -25,7 +27,7 @@ def get_urlparams_from_kwargs(param_kwargs): @frappe.whitelist() def approve(*args, **kwargs): r = frappe.request - uri = r.url + uri = url_fix(r.url.replace("+"," ")) http_method = r.method body = r.get_data() headers = r.headers @@ -62,7 +64,7 @@ def authorize(*args, **kwargs): elif frappe.session['user']!='Guest': try: r = frappe.request - uri = r.url + uri = url_fix(r.url) http_method = r.method body = r.get_data() headers = r.headers @@ -96,14 +98,42 @@ def authorize(*args, **kwargs): def get_token(*args, **kwargs): r = frappe.request - uri = r.url + uri = url_fix(r.url) http_method = r.method body = r.form headers = r.headers + + #Check whether frappe server URL is set + frappe_server_url = frappe.db.get_value("Social Login Keys", None, "frappe_server_url") or None + if not frappe_server_url: + frappe.throw(_("Define Frappe Server URL in Social Login Keys")) try: headers, body, status = get_oauth_server().create_token_response(uri, http_method, body, headers, frappe.flags.oauth_credentials) - frappe.local.response = frappe._dict(json.loads(body)) + out = frappe._dict(json.loads(body)) + if not out.error and "openid" in out.scope: + token_user = frappe.db.get_value("OAuth Bearer Token", out.access_token, "user") + token_client = frappe.db.get_value("OAuth Bearer Token", out.access_token, "client") + client_secret = frappe.db.get_value("OAuth Client", token_client, "client_secret") + if token_user in ["Guest", "Administrator"]: + frappe.throw(_("Logged in as Guest or Administrator")) + import hashlib + id_token_header = { + "typ":"jwt", + "alg":"HS256" + } + id_token = { + "aud": token_client, + "exp": int((frappe.db.get_value("OAuth Bearer Token", out.access_token, "expiration_time") - frappe.utils.datetime.datetime(1970, 1, 1)).total_seconds()), + "sub": frappe.db.get_value("User", token_user, "frappe_userid"), + "iss": frappe_server_url, + "at_hash": frappe.oauth.calculate_at_hash(out.access_token, hashlib.sha256) + } + import jwt + id_token_encoded = jwt.encode(id_token, client_secret, algorithm='HS256', headers=id_token_header) + out.update({"id_token":id_token_encoded}) + frappe.local.response = out + except FatalClientError as e: return e @@ -111,7 +141,7 @@ def get_token(*args, **kwargs): @frappe.whitelist(allow_guest=True) def revoke_token(*args, **kwargs): r = frappe.request - uri = r.url + uri = url_fix(r.url) http_method = r.method body = r.form headers = r.headers @@ -122,4 +152,38 @@ def revoke_token(*args, **kwargs): if status == 200: return "success" else: - return "bad request" \ No newline at end of file + return "bad request" + +@frappe.whitelist() +def openid_profile(*args, **kwargs): + picture = None + first_name, last_name, avatar, name, frappe_userid = frappe.db.get_value("User", frappe.session.user, ["first_name", "last_name", "user_image", "name", "frappe_userid"]) + request_url = urlparse(frappe.request.url) + + if avatar: + if validate_url(avatar): + picture = avatar + else: + picture = request_url.scheme + "://" + request_url.netloc + avatar + + user_profile = frappe._dict({ + "sub": frappe_userid, + "name": " ".join(filter(None, [first_name, last_name])), + "given_name": first_name, + "family_name": last_name, + "email": name, + "picture": picture + }) + + frappe.local.response = user_profile + +def validate_url(url_string): + from urlparse import urlparse + try: + result = urlparse(url_string) + if result.scheme and result.scheme in ["http", "https", "ftp", "ftps"]: + return True + else: + return False + except: + return False \ No newline at end of file diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py index 2702cb7f2f..33f14843b1 100644 --- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py +++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py @@ -2,15 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -import json -from frappe import _ -from frappe.utils import get_url, call_hook_method -from urllib import urlencode -from frappe.integration_broker.doctype.integration_service.integration_service import IntegrationService -import urllib - """ # Integrating PayPal @@ -63,28 +54,44 @@ More Details: """ +from __future__ import unicode_literals +import frappe +import json +from frappe import _ +from frappe.utils import get_url, call_hook_method, cint +from urllib import urlencode +from frappe.integration_broker.doctype.integration_service.integration_service import IntegrationService +import urllib + class PayPalSettings(IntegrationService): service_name = "PayPal" - + supported_currencies = ["AUD", "BRL", "CAD", "CZK", "DKK", "EUR", "HKD", "HUF", "ILS", "JPY", "MYR", "MXN", "TWD", "NZD", "NOK", "PHP", "PLN", "GBP", "RUB", "SGD", "SEK", "CHF", "THB", "TRY", "USD"] - + + def __setup__(self): + setattr(self, "use_sandbox", 0) + + def setup_sandbox_env(self, token): + data = json.loads(frappe.db.get_value("Integration Request", token, "data")) + setattr(self, "use_sandbox", cint(frappe._dict(data).use_sandbox) or 0) + def validate(self): if not self.flags.ignore_mandatory: self.validate_paypal_credentails() - + def on_update(self): pass - + def enable(self): call_hook_method('payment_gateway_enabled', gateway=self.service_name) if not self.flags.ignore_mandatory: self.validate_paypal_credentails() - + def validate_transaction_currency(self, currency): if currency not in self.supported_currencies: frappe.throw(_("Please select another payment method. {0} does not support transactions in currency '{1}'").format(self.service_name, currency)) - + def get_paypal_params_and_url(self): params = { "USER": self.api_username, @@ -94,7 +101,14 @@ class PayPalSettings(IntegrationService): "METHOD": "GetPalDetails" } - api_url = "https://api-3t.sandbox.paypal.com/nvp" if self.paypal_sandbox else "https://api-3t.paypal.com/nvp" + if hasattr(self, "use_sandbox") and self.use_sandbox: + params.update({ + "USER": frappe.conf.sandbox_api_username, + "PWD": frappe.conf.sandbox_api_password, + "SIGNATURE": frappe.conf.sandbox_signature + }) + + api_url = "https://api-3t.sandbox.paypal.com/nvp" if (self.paypal_sandbox or self.use_sandbox) else "https://api-3t.paypal.com/nvp" return params, api_url @@ -110,11 +124,13 @@ class PayPalSettings(IntegrationService): except Exception: frappe.throw(_("Invalid payment gateway credentials")) - + def get_payment_url(self, **kwargs): + setattr(self, "use_sandbox", cint(kwargs.get("use_sandbox", 0))) + response = self.execute_set_express_checkout(kwargs["amount"], kwargs["currency"]) - if self.paypal_sandbox: + if self.paypal_sandbox or self.use_sandbox: return_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" else: return_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" @@ -153,13 +169,13 @@ def get_service_details():

Steps to configure Service

    -
  1. Get PayPal api credentials from link: +
  2. Get PayPal api credentials from link: https://developer.paypal.com/docs/classic/api/apiCredentials/

  3. -
  4. Setup credentials on PayPal settings doctype. +
  5. Setup credentials on PayPal settings doctype. Click on top right corner @@ -186,78 +202,92 @@ def get_service_details(): @frappe.whitelist(allow_guest=True, xss_safe=True) def get_express_checkout_details(token): - doc = frappe.get_doc("PayPal Settings") - params, url = doc.get_paypal_params_and_url() - params.update({ - "METHOD": "GetExpressCheckoutDetails", - "TOKEN": token - }) + try: + doc = frappe.get_doc("PayPal Settings") + doc.setup_sandbox_env(token) - response = doc.post_request(url, data=params) + params, url = doc.get_paypal_params_and_url() + params.update({ + "METHOD": "GetExpressCheckoutDetails", + "TOKEN": token + }) - if response.get("ACK")[0] != "Success": - frappe.respond_as_web_page(_("Something went wrong"), - _("Looks like something went wrong during the transaction. Since we haven't confirmed the payment, Paypal will automatically refund you this amount. If it doesn't, please send us an email and mention the Correlation ID: {0}.").format(response.get("CORRELATIONID", [None])[0]), - success=False, - http_status_code=frappe.ValidationError.http_status_code) + response = doc.post_request(url, data=params) - return + if response.get("ACK")[0] != "Success": + frappe.respond_as_web_page(_("Something went wrong"), + _("Looks like something went wrong during the transaction. Since we haven't confirmed the payment, Paypal will automatically refund you this amount. If it doesn't, please send us an email and mention the Correlation ID: {0}.").format(response.get("CORRELATIONID", [None])[0]), + success=False, + http_status_code=frappe.ValidationError.http_status_code) - update_integration_request_status(token, { - "payerid": response.get("PAYERID")[0], - "payer_email": response.get("EMAIL")[0] - }, "Authorized") + return - frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = get_url( \ - "/api/method/frappe.integrations.doctype.paypal_settings.paypal_settings.confirm_payment?token={0}".format(token)) + update_integration_request_status(token, { + "payerid": response.get("PAYERID")[0], + "payer_email": response.get("EMAIL")[0] + }, "Authorized") + + frappe.local.response["type"] = "redirect" + frappe.local.response["location"] = get_url( \ + "/api/method/frappe.integrations.doctype.paypal_settings.paypal_settings.confirm_payment?token={0}".format(token)) + + except Exception: + frappe.log_error(frappe.get_traceback()) @frappe.whitelist(allow_guest=True, xss_safe=True) def confirm_payment(token): - redirect = True - status_changed_to, redirect_to = None, None - doc = frappe.get_doc("PayPal Settings") - integration_request = frappe.get_doc("Integration Request", token) - data = json.loads(integration_request.data) - - redirect_to = data.get('redirect_to') or None - redirect_message = data.get('redirect_message') or None - - params, url = doc.get_paypal_params_and_url() - params.update({ - "METHOD": "DoExpressCheckoutPayment", - "PAYERID": data.get("payerid"), - "TOKEN": token, - "PAYMENTREQUEST_0_PAYMENTACTION": "SALE", - "PAYMENTREQUEST_0_AMT": data.get("amount"), - "PAYMENTREQUEST_0_CURRENCYCODE": data.get("currency").upper() - }) + try: + redirect = True + status_changed_to, redirect_to = None, None - response = doc.post_request(url, data=params) + doc = frappe.get_doc("PayPal Settings") + doc.setup_sandbox_env(token) - if response.get("ACK")[0] == "Success": - update_integration_request_status(token, { - "transaction_id": response.get("PAYMENTINFO_0_TRANSACTIONID")[0], - "correlation_id": response.get("CORRELATIONID")[0] - }, "Completed") + integration_request = frappe.get_doc("Integration Request", token) + data = json.loads(integration_request.data) - if data.get("reference_doctype") and data.get("reference_docname"): - redirect_url = frappe.get_doc(data.get("reference_doctype"), data.get("reference_docname")).run_method("on_payment_authorized", "Completed") + redirect_to = data.get('redirect_to') or None + redirect_message = data.get('redirect_message') or None - if not redirect_url: - redirect_url = '/integrations/payment-success' - else: - redirect_url = "/integrations/payment-failed" + params, url = doc.get_paypal_params_and_url() + params.update({ + "METHOD": "DoExpressCheckoutPayment", + "PAYERID": data.get("payerid"), + "TOKEN": token, + "PAYMENTREQUEST_0_PAYMENTACTION": "SALE", + "PAYMENTREQUEST_0_AMT": data.get("amount"), + "PAYMENTREQUEST_0_CURRENCYCODE": data.get("currency").upper() + }) - if redirect_to: - redirect_url += '?' + urllib.urlencode({'redirect_to': redirect_to}) - if redirect_message: - redirect_url += '&' + urllib.urlencode({'redirect_message': redirect_message}) + response = doc.post_request(url, data=params) - # this is done so that functions called via hooks can update flags.redirect_to - if redirect: - frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = get_url(redirect_to) + if response.get("ACK")[0] == "Success": + update_integration_request_status(token, { + "transaction_id": response.get("PAYMENTINFO_0_TRANSACTIONID")[0], + "correlation_id": response.get("CORRELATIONID")[0] + }, "Completed") + + if data.get("reference_doctype") and data.get("reference_docname"): + redirect_url = frappe.get_doc(data.get("reference_doctype"), data.get("reference_docname")).run_method("on_payment_authorized", "Completed") + frappe.db.commit() + + if not redirect_url: + redirect_url = '/integrations/payment-success' + else: + redirect_url = "/integrations/payment-failed" + + if redirect_to: + redirect_url += '?' + urllib.urlencode({'redirect_to': redirect_to}) + if redirect_message: + redirect_url += '&' + urllib.urlencode({'redirect_message': redirect_message}) + + # this is done so that functions called via hooks can update flags.redirect_to + if redirect: + frappe.local.response["type"] = "redirect" + frappe.local.response["location"] = get_url(redirect_url) + + except Exception: + frappe.log_error(frappe.get_traceback()) def update_integration_request_status(token, data, status, error=False): frappe.get_doc("Integration Request", token).update_status(data, status) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 5437bcfd17..e9877636ae 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -2,13 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals -import frappe -from frappe.utils import get_url, call_hook_method -from frappe import _ -import urllib, json -from frappe.integration_broker.doctype.integration_service.integration_service import IntegrationService - """ # Integrating RazorPay @@ -58,6 +51,13 @@ For razorpay payment status is Authorized """ +from __future__ import unicode_literals +import frappe +from frappe.utils import get_url, call_hook_method, cint +from frappe import _ +import urllib, json +from frappe.integration_broker.doctype.integration_service.integration_service import IntegrationService + class RazorpaySettings(IntegrationService): service_name = "Razorpay" supported_currencies = ["INR"] @@ -117,8 +117,9 @@ class RazorpaySettings(IntegrationService): The money is deducted from the customer’s account, but will not be transferred to the merchant’s account until it is explicitly captured by merchant. """ - settings = self.get_settings() data = json.loads(self.integration_request.data) + + settings = self.get_settings(data) redirect_to = data.get('notes', {}).get('redirect_to') or None redirect_message = data.get('notes', {}).get('redirect_message') or None @@ -167,12 +168,20 @@ class RazorpaySettings(IntegrationService): "status": status } - def get_settings(self): - return frappe._dict({ + def get_settings(self, data): + settings = frappe._dict({ "api_key": self.api_key, "api_secret": self.get_password(fieldname="api_secret", raise_exception=False) }) + if cint(data.get('notes', {}).get('use_sandbox')): + settings.update({ + "api_key": frappe.conf.sandbox_api_key, + "api_secret": frappe.conf.sandbox_api_secret, + }) + + return settings + def capture_payment(is_sandbox=False, sanbox_response=None): """ Verifies the purchase as complete by the merchant. @@ -190,8 +199,10 @@ def capture_payment(is_sandbox=False, sanbox_response=None): resp = sanbox_response else: data = json.loads(doc.data) + settings = controller.get_settings(data) + resp = controller.post_request("https://api.razorpay.com/v1/payments/{0}/capture".format(data.get("razorpay_payment_id")), - auth=(controller.api_key, controller.get_password("api_secret")), data={"amount": data.get("amount")}) + auth=(settings.api_key, settings.api_secret), data={"amount": data.get("amount")}) if resp.get("status") == "captured": frappe.db.set_value("Integration Request", doc.name, "status", "Completed") @@ -212,7 +223,6 @@ def get_checkout_url(**kwargs): success=False, http_status_code=frappe.ValidationError.http_status_code) - @frappe.whitelist() def get_service_details(): return """ diff --git a/frappe/integrations/doctype/social_login_keys/social_login_keys.json b/frappe/integrations/doctype/social_login_keys/social_login_keys.json index fdb469c65c..d35cdac6bd 100644 --- a/frappe/integrations/doctype/social_login_keys/social_login_keys.json +++ b/frappe/integrations/doctype/social_login_keys/social_login_keys.json @@ -2,26 +2,33 @@ "allow_copy": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2014-03-04 08:29:52", "custom": 0, "docstatus": 0, "doctype": "DocType", "document_type": "System", + "editable_grid": 0, "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "facebook", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Facebook", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -33,16 +40,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "facebook_client_id", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Facebook Client ID", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -54,16 +66,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "facebook_client_secret", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Facebook Client Secret", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -75,16 +92,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "google", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Google", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -96,16 +118,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "google_client_id", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Google Client ID", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -117,16 +144,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "google_client_secret", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Google Client Secret", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -138,16 +170,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "github", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "GitHub", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -159,16 +196,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "github_client_id", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "GitHub Client ID", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -180,16 +222,129 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "github_client_secret", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "GitHub Client Secret", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frappe", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Frappe", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frappe_client_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Frappe Client ID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frappe_client_secret", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Frappe Client Secret", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frappe_server_url", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Frappe Server URL", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -202,12 +357,14 @@ "hide_toolbar": 0, "icon": "icon-signin", "idx": 1, + "image_view": 0, "in_create": 0, "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-08-05 08:14:52.667728", + "max_attachments": 0, + "modified": "2016-10-29 13:36:35.121599", "modified_by": "Administrator", "module": "Integrations", "name": "Social Login Keys", @@ -223,6 +380,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 0, "read": 1, @@ -234,6 +392,9 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, - "read_only_onload": 0 + "read_only_onload": 0, + "sort_order": "ASC", + "track_seen": 0 } \ No newline at end of file diff --git a/frappe/integrations/doctype/social_login_keys/social_login_keys.py b/frappe/integrations/doctype/social_login_keys/social_login_keys.py index f447a4421d..5a4f662a99 100644 --- a/frappe/integrations/doctype/social_login_keys/social_login_keys.py +++ b/frappe/integrations/doctype/social_login_keys/social_login_keys.py @@ -7,6 +7,20 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe import _ class SocialLoginKeys(Document): - pass \ No newline at end of file + def validate(self): + self.validate_frappe_server_url() + + def validate_frappe_server_url(self): + if self.frappe_server_url: + if self.frappe_server_url.endswith('/'): + self.frappe_server_url = self.frappe_server_url[:-1] + import requests + try: + r = requests.get(self.frappe_server_url + "/api/method/frappe.handler.version", timeout=5) + except: + frappe.throw(_("Unable to make request to the Frappe Server URL")) + if r.status_code != 200: + frappe.throw(_("Check Frappe Server URL")) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 6db4ca70d9..ae01396150 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -190,12 +190,11 @@ class BaseDocument(object): df = self.meta.get_field(fieldname) if df: if df.fieldtype=="Check": - if (not isinstance(d[fieldname], int) or d[fieldname] > 1): - d[fieldname] = 1 if cint(d[fieldname]) else 0 + if d[fieldname]==None: + d[fieldname] = 0 - # get the default value if none, for insert / update - elif d[fieldname]==None: - d[fieldname] = df.get('default') + elif (not isinstance(d[fieldname], int) or d[fieldname] > 1): + d[fieldname] = 1 if cint(d[fieldname]) else 0 elif df.fieldtype=="Int" and not isinstance(d[fieldname], int): d[fieldname] = cint(d[fieldname]) @@ -286,6 +285,7 @@ class BaseDocument(object): self.created_by = self.modifield_by = frappe.session.user d = self.get_valid_dict() + columns = d.keys() try: frappe.db.sql("""insert into `tab{doctype}` @@ -360,8 +360,21 @@ class BaseDocument(object): # this is used to preserve traceback raise frappe.UniqueValidationError, (self.doctype, self.name, e), traceback - def db_set(self, fieldname, value, update_modified=True): - self.set(fieldname, value) + def db_set(self, fieldname, value=None, update_modified=True): + '''Set a value in the document object, update the timestamp and update the database. + + WARNING: This method does not trigger controller validations and should + be used very carefully. + + :param fieldname: fieldname of the property to be updated, or a {"field":"value"} dictionary + :param value: value of the property to be updated + :param update_modified: default True. updates the `modified` and `modified_by` properties + ''' + if isinstance(fieldname, dict): + self.update(fieldname) + else: + self.set(fieldname, value) + if update_modified and (self.doctype, self.name) not in frappe.flags.currently_saving: # don't update modified timestamp if called from post save methods # like on_update or on_submit @@ -426,6 +439,7 @@ class BaseDocument(object): return missing def get_invalid_links(self, is_submittable=False): + '''Returns list of invalid links and also updates fetch values if not set''' def get_msg(df, docname): if self.parentfield: return "{} #{}: {}: {}".format(_("Row"), self.idx, _(df.label), docname) @@ -434,6 +448,7 @@ class BaseDocument(object): invalid_links = [] cancelled_links = [] + for df in (self.meta.get_link_fields() + self.meta.get("fields", {"fieldtype":"Dynamic Link"})): docname = self.get(df.fieldname) @@ -449,15 +464,38 @@ class BaseDocument(object): frappe.throw(_("{0} must be set first").format(self.meta.get_label(df.options))) # MySQL is case insensitive. Preserve case of the original docname in the Link Field. - value = frappe.db.get_value(doctype, docname, "name", cache=True) - if frappe.get_meta(doctype).issingle: - value = doctype - setattr(self, df.fieldname, value) + # get a map of values ot fetch along with this link query + # that are mapped as link_fieldname.source_fieldname in Options of + # Readonly or Data or Text type fields + fields_to_fetch = [ + _df for _df in self.meta.get_fields_to_fetch(df.fieldname) + if not self.get(_df.fieldname) + ] + + if not fields_to_fetch: + # cache a single value type + values = frappe._dict(name=frappe.db.get_value(doctype, docname, + 'name', cache=True)) + else: + values_to_fetch = ['name'] + [_df.options.split('.')[-1] + for _df in fields_to_fetch] + + # don't cache if fetching other values too + values = frappe.db.get_value(doctype, docname, + values_to_fetch, as_dict=True) + + if frappe.get_meta(doctype).issingle: + values.name = doctype + + setattr(self, df.fieldname, values.name) + + for _df in fields_to_fetch: + setattr(self, _df.fieldname, values[_df.options.split('.')[-1]]) notify_link_count(doctype, docname) - if not value: + if not values.name: invalid_links.append((df.fieldname, docname, get_msg(df, docname))) elif (df.fieldname != "amended_from" diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 3c38be9170..d64eef4976 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -8,7 +8,7 @@ import frappe, json, copy import frappe.defaults import frappe.share import frappe.permissions -from frappe.utils import flt, cint, getdate, get_datetime, get_time, make_filter_tuple, get_filter +from frappe.utils import flt, cint, getdate, get_datetime, get_time, make_filter_tuple, get_filter, add_to_date from frappe import _ from frappe.model import optional_fields from frappe.model.utils.list_settings import get_list_settings, update_list_settings @@ -291,7 +291,13 @@ class DatabaseQuery(object): if df and df.fieldtype in ("Check", "Float", "Int", "Currency", "Percent"): can_be_null = False - if df and df.fieldtype=="Date": + if f.operator=='Between' and \ + (f.fieldname in ('creation', 'modified') or (df and (df.fieldtype=="Date" or df.fieldtype=="Datetime"))): + value = "'%s' AND '%s'" % ( + get_datetime(f.value[0]).strftime("%Y-%m-%d %H:%M:%S.%f"), + add_to_date(get_datetime(f.value[1]),days=1).strftime("%Y-%m-%d %H:%M:%S.%f")) + fallback = "'0000-00-00 00:00:00'" + elif df and df.fieldtype=="Date": value = getdate(f.value).strftime("%Y-%m-%d") fallback = "'0000-00-00'" @@ -317,7 +323,7 @@ class DatabaseQuery(object): fallback = 0 # put it inside double quotes - if isinstance(value, basestring): + if isinstance(value, basestring) and not f.operator=='Between': value = '"{0}"'.format(frappe.db.escape(value, percent=False)) if (self.ignore_ifnull diff --git a/frappe/model/document.py b/frappe/model/document.py index 72050cad5c..af6310895e 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -3,14 +3,17 @@ from __future__ import unicode_literals import frappe +import time from frappe import _, msgprint -from frappe.utils import flt, cstr, now, get_datetime_str +from frappe.utils import flt, cstr, now, get_datetime_str, file_lock from frappe.utils.background_jobs import enqueue from frappe.model.base_document import BaseDocument, get_controller from frappe.model.naming import set_new_name from werkzeug.exceptions import NotFound, Forbidden import hashlib, json from frappe.model import optional_fields +from frappe.utils.file_manager import save_url + # once_only validation # methods @@ -156,23 +159,6 @@ class Document(BaseDocument): frappe.msgprint(msg) raise frappe.PermissionError(msg) - def lock(self): - '''Will set docstatus to 3 + the current docstatus and mark it as queued - - 3 = queued for saving - 4 = queued for submission - 5 = queued for cancellation - ''' - self.db_set('docstatus', 3 + self.docstatus, update_modified = False) - - def unlock(self): - '''set the original docstatus at the time it was locked in the controller''' - current_docstatus = self.db_get('docstatus') - 4 - if current_docstatus < 0: - current_docstatus = 0 - - self.db_set('docstatus', current_docstatus, update_modified = False) - def insert(self, ignore_permissions=None): """Insert the document in the database (as a new document). This will check for user permissions and execute `before_insert`, @@ -219,6 +205,10 @@ class Document(BaseDocument): self.run_method("after_insert") self.flags.in_insert = True + + if self.get("amended_from"): + self.copy_attachments_from_amended_from() + self.run_post_save_methods() self.flags.in_insert = False @@ -280,6 +270,16 @@ class Document(BaseDocument): return self + def copy_attachments_from_amended_from(self): + '''Copy attachments from `amended_from`''' + from frappe.desk.form.load import get_attachments + + #loop through attachments + for attach_item in get_attachments(self.doctype, self.amended_from): + + #save attachments to new doc + save_url(attach_item.file_url, attach_item.file_name, self.doctype, self.name, "Home/Attachments") + def update_children(self): '''update child tables''' for df in self.meta.get_table_fields(): @@ -498,8 +498,10 @@ class Document(BaseDocument): self._action = "save" if not self.get('__islocal'): if self.meta.issingle: - modified = frappe.db.get_value(self.doctype, self.name, "modified") - if cstr(modified) and cstr(modified) != cstr(self._original_modified): + modified = frappe.db.sql('''select value from tabSingles + where doctype=%s and field='modified' for update''', self.doctype) + modified = modified and modified[0][0] + if modified and modified != cstr(self._original_modified): conflict = True else: tmp = frappe.db.sql("""select modified, docstatus from `tab{0}` @@ -534,13 +536,7 @@ class Document(BaseDocument): - Submit (1) > Submit (1) - Submit (1) > Cancel (2) - If docstatus is > 2, it will throw exception as document is deemed queued """ - - if self.docstatus > 2: - frappe.throw(_('This document is currently queued for execution. Please try again'), - title=_('Document Queued'), indicator='red') - if not self.docstatus: self.docstatus = 0 if docstatus==0: @@ -796,27 +792,6 @@ class Document(BaseDocument): def clear_cache(self): frappe.cache().hdel("last_modified", self.doctype) - self.clear_linked_with_cache() - - def clear_linked_with_cache(self): - cache = frappe.cache() - def _clear_cache(d): - for df in (d.meta.get_link_fields() + d.meta.get_dynamic_link_fields()): - if d.get(df.fieldname): - doctype = df.options if df.fieldtype=="Link" else d.get(df.options) - name = d.get(df.fieldname) - - if df.fieldtype=="Dynamic Link": - # clear linked doctypes list - cache.hdel("linked_doctypes", doctype) - - # for all users, delete linked with cache and per doctype linked with cache - cache.delete_value("user:*:linked_with:{doctype}:{name}".format(doctype=doctype, name=name)) - cache.delete_value("user:*:linked_with:{doctype}:{name}:*".format(doctype=doctype, name=name)) - - _clear_cache(self) - for d in self.get_all_children(): - _clear_cache(d) def reset_seen(self): '''Clear _seen property and set current user as seen''' @@ -1018,28 +993,46 @@ class Document(BaseDocument): def queue_action(self, action, **kwargs): '''Run an action in background. If the action has an inner function, like _submit for submit, it will call that instead''' - if action in ('save', 'submit', 'cancel'): - # set docstatus explicitly again due to inconsistent action - self.docstatus = {'save':0, 'submit':1, 'cancel': 2}[action] - else: - raise 'Action must be one of save, submit, cancel' - # call _submit instead of submit, so you can override submit to call # run_delayed based on some action # See: Stock Reconciliation if hasattr(self, '_' + action): action = '_' + action + if file_lock.lock_exists(self.get_signature()): + frappe.throw(_('This document is currently queued for execution. Please try again'), + title=_('Document Queued'), indicator='red') + self.lock() - frappe.db.commit() enqueue('frappe.model.document.execute_action', doctype=self.doctype, name=self.name, action=action, **kwargs) + def lock(self, timeout=None): + '''Creates a lock file for the given document. If timeout is set, + it will retry every 1 second for acquiring the lock again + + :param timeout: Timeout in seconds, default 0''' + signature = self.get_signature() + if file_lock.lock_exists(signature): + lock_exists = True + if timeout: + for i in range(timeout): + time.sleep(1) + if not file_lock.lock_exists(signature): + lock_exists = False + break + if lock_exists: + raise frappe.DocumentLockedError + file_lock.create_lock(signature) + + def unlock(self): + '''Delete the lock file for this document''' + file_lock.delete_lock(self.get_signature()) + def execute_action(doctype, name, action, **kwargs): '''Execute an action on a document (called by background worker)''' doc = frappe.get_doc(doctype, name) doc.unlock() - frappe.db.commit() try: getattr(doc, action)(**kwargs) except Exception: diff --git a/frappe/model/mapper.py b/frappe/model/mapper.py index ff227ab174..d8384eee09 100644 --- a/frappe/model/mapper.py +++ b/frappe/model/mapper.py @@ -7,6 +7,25 @@ from frappe import _ from frappe.utils import cstr from frappe.model import default_fields +@frappe.whitelist() +def make_mapped_doc(method, source_name, selected_children=None): + '''Returns the mapped document calling the given mapper method. + Sets selected_children as flags for the `get_mapped_doc` method. + + Called from `open_mapped_doc` from create_new.js''' + method = frappe.get_attr(method) + + if method not in frappe.whitelisted: + raise frappe.PermissionError + + if selected_children: + selected_children = json.loads(selected_children) + + frappe.flags.selected_children = selected_children or None + + return method(source_name) + + def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, postprocess=None, ignore_permissions=False, ignore_child_tables=False): @@ -51,6 +70,13 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, if not table_map["condition"](source_d): continue + # if children are selected (checked from UI) for this table type, + # and this record is not in the selected children, then continue + if (frappe.flags.selected_children + and (df.fieldname in frappe.flags.selected_children) + and source_d.name not in frappe.flags.selected_children[df.fieldname]): + continue + target_child_doctype = table_map["doctype"] target_parentfield = target_doc.get_parentfield_of_doctype(target_child_doctype) diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 3435126147..5b7fa083ed 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -59,7 +59,10 @@ class Meta(Document): def __init__(self, doctype): self._fields = {} - super(Meta, self).__init__("DocType", doctype) + if isinstance(doctype, Document): + super(Meta, self).__init__(doctype.as_dict()) + else: + super(Meta, self).__init__("DocType", doctype) self.process() def load_from_db(self): @@ -155,6 +158,32 @@ class Meta(Document): return search_fields + def get_fields_to_fetch(self, link_fieldname=None): + '''Returns a list of docfield objects for fields whose values + are to be fetched and updated for a particular link field + + These fields are of type Data, Link, Text, Readonly and their + options property is set as `link_fieldname`.`source_fieldname`''' + + out = [] + + if not link_fieldname: + link_fields = [df.fieldname for df in self.get_link_fields()] + + for df in self.fields: + if df.fieldtype in ('Data', 'Read Only', 'Text', 'Small Text', + 'Text Editor', 'Code') and df.options: + if link_fieldname: + if df.options.startswith(link_fieldname + '.'): + out.append(df) + else: + if '.' in df.options: + fieldname = df.options.split('.', 1)[0] + if fieldname in link_fields: + out.append(df) + + return out + 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)] @@ -424,9 +453,6 @@ def clear_cache(doctype=None): for name in groups: cache.hdel(name, dt) - # also clear linked_with list cache - cache.delete_keys("user:*:linked_with:{doctype}:".format(doctype=doctype)) - if doctype: clear_single(doctype) diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 35ff2b4691..fb1f369a04 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -170,8 +170,7 @@ def load_doctype_module(doctype, module=None, prefix="", suffix=""): if key not in doctype_python_modules: doctype_python_modules[key] = frappe.get_module(module_name) except ImportError: - print 'Module import failed for {0} ({1})'.format(doctype, module_name) - raise + raise ImportError, 'Module import failed for {0} ({1})'.format(doctype, module_name) return doctype_python_modules[key] diff --git a/frappe/oauth.py b/frappe/oauth.py index 22e1f17562..89f00c41a6 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -1,8 +1,9 @@ import frappe, urllib +from frappe import _ from urlparse import parse_qs, urlparse from oauthlib.oauth2.rfc6749.tokens import BearerToken -from oauthlib.oauth2.rfc6749.grant_types import AuthorizationCodeGrant, ImplicitGrant, ResourceOwnerPasswordCredentialsGrant, ClientCredentialsGrant, RefreshTokenGrant +from oauthlib.oauth2.rfc6749.grant_types import AuthorizationCodeGrant, ImplicitGrant, ResourceOwnerPasswordCredentialsGrant, ClientCredentialsGrant, RefreshTokenGrant, OpenIDConnectAuthCode from oauthlib.oauth2 import RequestValidator from oauthlib.oauth2.rfc6749.endpoints.authorization import AuthorizationEndpoint from oauthlib.oauth2.rfc6749.endpoints.token import TokenEndpoint @@ -10,6 +11,9 @@ from oauthlib.oauth2.rfc6749.endpoints.resource import ResourceEndpoint from oauthlib.oauth2.rfc6749.endpoints.revocation import RevocationEndpoint from oauthlib.common import Request +def get_url_delimiter(separator_character=" "): + return separator_character + class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint, RevocationEndpoint): @@ -32,10 +36,19 @@ class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoin """ auth_grant = AuthorizationCodeGrant(request_validator) refresh_grant = RefreshTokenGrant(request_validator) + openid_connect_auth = OpenIDConnectAuthCode(request_validator) bearer = BearerToken(request_validator, token_generator, token_expires_in, refresh_token_generator) AuthorizationEndpoint.__init__(self, default_response_type='code', - response_types={'code': auth_grant}, + response_types={ + 'code': auth_grant, + 'code+token': openid_connect_auth, + 'code+id_token': openid_connect_auth, + 'code+token+id_token': openid_connect_auth, + 'code token': openid_connect_auth, + 'code id_token': openid_connect_auth, + 'code token id_token': openid_connect_auth, + }, default_token_type=bearer) TokenEndpoint.__init__(self, default_grant_type='authorization_code', grant_types={ @@ -64,7 +77,7 @@ class OAuthWebRequestValidator(RequestValidator): # Is the client allowed to use the supplied redirect_uri? i.e. has # the client previously registered this EXACT redirect uri. - redirect_uris = frappe.db.get_value("OAuth Client", client_id, 'redirect_uris').split(';') + redirect_uris = frappe.db.get_value("OAuth Client", client_id, 'redirect_uris').split(get_url_delimiter()) if redirect_uri in redirect_uris: return True @@ -80,7 +93,7 @@ class OAuthWebRequestValidator(RequestValidator): def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): # Is the client allowed to access the requested scopes? - client_scopes = frappe.db.get_value("OAuth Client", client_id, 'scopes').split(';') + client_scopes = frappe.db.get_value("OAuth Client", client_id, 'scopes').split(get_url_delimiter()) are_scopes_valid = True @@ -92,7 +105,7 @@ class OAuthWebRequestValidator(RequestValidator): def get_default_scopes(self, client_id, request, *args, **kwargs): # Scopes a client will authorize for if none are supplied in the # authorization request. - scopes = frappe.db.get_value("OAuth Client", client_id, 'scopes').split(';') + scopes = frappe.db.get_value("OAuth Client", client_id, 'scopes').split(get_url_delimiter()) request.scopes = scopes #Apparently this is possible. return scopes @@ -100,8 +113,11 @@ class OAuthWebRequestValidator(RequestValidator): # Clients should only be allowed to use one type of response type, the # one associated with their one allowed grant type. # In this case it must be "code". + allowed_response_types = [client.response_type.lower(), + "code token", "code id_token", "code token id_token", + "code+token", "code+id_token", "code+token id_token"] - return (client.response_type.lower() == response_type) + return (response_type in allowed_response_types) # Post-authorization @@ -111,7 +127,7 @@ class OAuthWebRequestValidator(RequestValidator): cookie_dict = get_cookie_dict_from_headers(request) oac = frappe.new_doc('OAuth Authorization Code') - oac.scopes = ';'.join(request.scopes) + oac.scopes = get_url_delimiter().join(request.scopes) oac.redirect_uri_bound_to_authorization_code = request.redirect_uri oac.client = client_id oac.user = urllib.unquote(cookie_dict['user_id']) @@ -130,8 +146,10 @@ class OAuthWebRequestValidator(RequestValidator): #Extract token, instantiate OAuth Bearer Token and use clientid from there. if frappe.form_dict.has_key("refresh_token"): oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", {"refresh_token": frappe.form_dict["refresh_token"]}, 'client')) - else: + elif frappe.form_dict.has_key("token"): oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", frappe.form_dict["token"], 'client')) + else: + oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", frappe.get_request_header("Authorization").split(" ")[1], 'client')) try: request.client = request.client or oc.as_dict() except Exception, e: @@ -159,7 +177,7 @@ class OAuthWebRequestValidator(RequestValidator): checkcodes.append(vcode["name"]) if code in checkcodes: - request.scopes = frappe.db.get_value("OAuth Authorization Code", code, 'scopes').split(';') + request.scopes = frappe.db.get_value("OAuth Authorization Code", code, 'scopes').split(get_url_delimiter()) request.user = frappe.db.get_value("OAuth Authorization Code", code, 'user') return True else: @@ -185,7 +203,7 @@ class OAuthWebRequestValidator(RequestValidator): otoken = frappe.new_doc("OAuth Bearer Token") otoken.client = request.client['name'] otoken.user = request.user - otoken.scopes = ";".join(request.scopes) + otoken.scopes = get_url_delimiter().join(request.scopes) otoken.access_token = token['access_token'] otoken.refresh_token = token['refresh_token'] otoken.expires_in = token['expires_in'] @@ -209,7 +227,7 @@ class OAuthWebRequestValidator(RequestValidator): otoken = frappe.get_doc("OAuth Bearer Token", token) #{"access_token": str(token)}) is_token_valid = (frappe.utils.datetime.datetime.now() < otoken.expiration_time) \ and otoken.status != "Revoked" - client_scopes = frappe.db.get_value("OAuth Client", otoken.client, 'scopes').split(';') + client_scopes = frappe.db.get_value("OAuth Client", otoken.client, 'scopes').split(get_url_delimiter()) are_scopes_valid = True for scp in scopes: are_scopes_valid = are_scopes_valid and True if scp in client_scopes else False @@ -261,7 +279,6 @@ class OAuthWebRequestValidator(RequestValidator): # - Resource Owner Password Credentials Grant (also indirectly) # - Refresh Token Grant # """ - # raise NotImplementedError('Subclasses must implement this method.') otoken = frappe.get_doc("OAuth Bearer Token", {"refresh_token": refresh_token, "status": "Active"}) @@ -270,7 +287,101 @@ class OAuthWebRequestValidator(RequestValidator): else: return True - #TODO: Validate scopes. + # OpenID Connect + def get_id_token(self, token, token_handler, request): + """ + In the OpenID Connect workflows when an ID Token is requested this method is called. + Subclasses should implement the construction, signing and optional encryption of the + ID Token as described in the OpenID Connect spec. + + In addition to the standard OAuth2 request properties, the request may also contain + these OIDC specific properties which are useful to this method: + + - nonce, if workflow is implicit or hybrid and it was provided + - claims, if provided to the original Authorization Code request + + The token parameter is a dict which may contain an ``access_token`` entry, in which + case the resulting ID Token *should* include a calculated ``at_hash`` claim. + + Similarly, when the request parameter has a ``code`` property defined, the ID Token + *should* include a calculated ``c_hash`` claim. + + http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_) + + .. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken + .. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken + .. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken + + :param token: A Bearer token dict + :param token_handler: the token handler (BearerToken class) + :param request: the HTTP Request (oauthlib.common.Request) + :return: The ID Token (a JWS signed JWT) + """ + # the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token + + def validate_silent_authorization(self, request): + """Ensure the logged in user has authorized silent OpenID authorization. + + Silent OpenID authorization allows access tokens and id tokens to be + granted to clients without any user prompt or interaction. + + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - OpenIDConnectAuthCode + - OpenIDConnectImplicit + - OpenIDConnectHybrid + """ + if request.prompt == "login": + False + else: + True + + def validate_silent_login(self, request): + """Ensure session user has authorized silent OpenID login. + + If no user is logged in or has not authorized silent login, this + method should return False. + + If the user is logged in but associated with multiple accounts and + not selected which one to link to the token then this method should + raise an oauthlib.oauth2.AccountSelectionRequired error. + + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - OpenIDConnectAuthCode + - OpenIDConnectImplicit + - OpenIDConnectHybrid + """ + if frappe.session.user == "Guest" or request.prompt.lower() == "login": + return False + else: + return True + + def validate_user_match(self, id_token_hint, scopes, claims, request): + """Ensure client supplied user id hint matches session user. + + If the sub claim or id_token_hint is supplied then the session + user must match the given ID. + + :param id_token_hint: User identifier string. + :param scopes: List of OAuth 2 scopes and OpenID claims (strings). + :param claims: OpenID Connect claims dict. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - OpenIDConnectAuthCode + - OpenIDConnectImplicit + - OpenIDConnectHybrid + """ + if id_token_hint and id_token_hint == frappe.get_value("User", frappe.session.user, "frappe_userid"): + return True + else: + return False def get_cookie_dict_from_headers(r): if r.headers.get('Cookie'): @@ -280,3 +391,39 @@ def get_cookie_dict_from_headers(r): return cookie_dict else: return {} + +def calculate_at_hash(access_token, hash_alg): + """Helper method for calculating an access token + hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken + Its value is the base64url encoding of the left-most half of the hash of the octets + of the ASCII representation of the access_token value, where the hash algorithm + used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE + Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, + then take the left-most 128 bits and base64url encode them. The at_hash value is a + case sensitive string. + Args: + access_token (str): An access token string. + hash_alg (callable): A callable returning a hash object, e.g. hashlib.sha256 + """ + hash_digest = hash_alg(access_token.encode('utf-8')).digest() + cut_at = int(len(hash_digest) / 2) + truncated = hash_digest[:cut_at] + from jwt.utils import base64url_encode + at_hash = base64url_encode(truncated) + return at_hash.decode('utf-8') + +def delete_oauth2_data(): + # Delete Invalid Authorization Code and Revoked Token + commit_code, commit_token = False, False + code_list = frappe.get_all("OAuth Authorization Code", filters={"validity":"Invalid"}) + token_list = frappe.get_all("OAuth Bearer Token", filters={"status":"Revoked"}) + if len(code_list) > 0: + commit_code = True + if len(token_list) > 0: + commit_token = True + for code in code_list: + frappe.delete_doc("OAuth Authorization Code", code["name"]) + for token in token_list: + frappe.delete_doc("OAuth Bearer Token", token["name"]) + if commit_code or commit_token: + frappe.db.commit() diff --git a/frappe/patches.txt b/frappe/patches.txt index 6c26f4aa4c..fd2e2a2cec 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -146,4 +146,6 @@ execute:frappe.db.set_default('language', '') frappe.patches.v7_1.refactor_integration_broker frappe.patches.v7_1.set_backup_limit frappe.patches.v7_1.disabled_print_settings_for_custom_print_format +frappe.patches.v7_2.set_doctype_engine +frappe.patches.v7_2.merge_knowledge_base frappe.patches.v7_0.update_report_builder_json diff --git a/frappe/patches/v7_2/__init__.py b/frappe/patches/v7_2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/patches/v7_2/merge_knowledge_base.py b/frappe/patches/v7_2/merge_knowledge_base.py new file mode 100644 index 0000000000..2eb52acfd6 --- /dev/null +++ b/frappe/patches/v7_2/merge_knowledge_base.py @@ -0,0 +1,29 @@ +import frappe + +from frappe.patches.v7_0.re_route import update_routes +from frappe.installer import remove_from_installed_apps + +def execute(): + if 'knowledge_base' in frappe.get_installed_apps(): + frappe.reload_doc('website', 'doctype', 'help_category') + frappe.reload_doc('website', 'doctype', 'help_article') + update_routes(['Help Category', 'Help Article']) + remove_from_installed_apps('knowledge_base') + + # remove desktop icon + desktop_icon_name = frappe.db.get_value('Desktop Icon', + dict(module_name='Knowledge Base', type='module')) + if desktop_icon_name: + frappe.delete_doc('Desktop Icon', desktop_icon_name) + + # remove module def + if frappe.db.exists('Module Def', 'Knowledge Base'): + frappe.delete_doc('Module Def', 'Knowledge Base') + + # set missing routes + for doctype in ('Help Category', 'Help Article'): + for d in frappe.get_all(doctype, fields=['name', 'route']): + if not d.route: + doc = frappe.get_doc(doctype, d.name) + doc.set_route() + doc.db_update() \ No newline at end of file diff --git a/frappe/patches/v7_2/set_doctype_engine.py b/frappe/patches/v7_2/set_doctype_engine.py new file mode 100644 index 0000000000..fa914003f3 --- /dev/null +++ b/frappe/patches/v7_2/set_doctype_engine.py @@ -0,0 +1,6 @@ +import frappe + +def execute(): + for t in frappe.db.sql('show table status'): + if t[0].startswith('tab'): + frappe.db.sql('update tabDocType set engine=%s where name=%s', (t[1], t[0][3:])) \ No newline at end of file diff --git a/frappe/print/doctype/letter_head/letter_head.json b/frappe/print/doctype/letter_head/letter_head.json index 432cc20067..706c7c0729 100644 --- a/frappe/print/doctype/letter_head/letter_head.json +++ b/frappe/print/doctype/letter_head/letter_head.json @@ -146,7 +146,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-font", + "icon": "fa fa-font", "idx": 1, "in_create": 0, "in_dialog": 0, diff --git a/frappe/print/doctype/print_format/print_format.json b/frappe/print/doctype/print_format/print_format.json index a8e202f71e..efe9c93556 100644 --- a/frappe/print/doctype/print_format/print_format.json +++ b/frappe/print/doctype/print_format/print_format.json @@ -24,6 +24,7 @@ "ignore_xss_filter": 0, "in_filter": 1, "in_list_view": 1, + "in_standard_filter": 1, "label": "DocType", "length": 0, "no_copy": 0, @@ -32,6 +33,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -50,6 +52,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Module", "length": 0, "no_copy": 0, @@ -59,6 +62,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -77,6 +81,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Disabled", "length": 0, "no_copy": 0, @@ -84,6 +89,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -102,12 +108,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -127,6 +135,7 @@ "ignore_xss_filter": 0, "in_filter": 1, "in_list_view": 0, + "in_standard_filter": 0, "label": "Standard", "length": 0, "no_copy": 1, @@ -137,6 +146,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 1, @@ -155,6 +165,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Custom Format", "length": 0, "no_copy": 0, @@ -163,6 +174,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -182,12 +194,14 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -209,6 +223,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Print Format Type", "length": 0, "no_copy": 0, @@ -217,6 +232,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -236,6 +252,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "HTML", "length": 0, "no_copy": 0, @@ -246,6 +263,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -265,6 +283,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Style Settings", "length": 0, "no_copy": 0, @@ -273,6 +292,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -292,6 +312,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Align Labels to the Left", "length": 0, "no_copy": 0, @@ -300,6 +321,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -319,6 +341,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Show Section Headings", "length": 0, "no_copy": 0, @@ -327,6 +350,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -346,6 +370,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Show Line Breaks after Sections", "length": 0, "no_copy": 0, @@ -354,6 +379,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -372,6 +398,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -379,6 +406,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -399,6 +427,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Font", "length": 0, "no_copy": 0, @@ -408,6 +437,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -426,6 +456,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -433,6 +464,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -451,6 +483,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Custom CSS", "length": 0, "no_copy": 0, @@ -459,6 +492,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -477,6 +511,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Custom HTML Help", "length": 0, "no_copy": 0, @@ -486,6 +521,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -505,6 +541,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -512,6 +549,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -531,6 +569,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Print Format Help", "length": 0, "no_copy": 0, @@ -539,6 +578,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -557,6 +597,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Format Data", "length": 0, "no_copy": 0, @@ -565,6 +606,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -583,6 +625,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Print Format Builder", "length": 0, "no_copy": 0, @@ -591,6 +634,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -600,7 +644,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-print", + "icon": "fa fa-print", "idx": 1, "image_view": 0, "in_create": 0, @@ -609,7 +653,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-10-10 06:06:34.530193", + "modified": "2016-11-07 05:23:46.983533", "modified_by": "Administrator", "module": "Print", "name": "Print Format", diff --git a/frappe/print/doctype/print_settings/print_settings.json b/frappe/print/doctype/print_settings/print_settings.json index d6d31a62bd..569dfd5386 100644 --- a/frappe/print/doctype/print_settings/print_settings.json +++ b/frappe/print/doctype/print_settings/print_settings.json @@ -485,7 +485,7 @@ ], "hide_heading": 0, "hide_toolbar": 0, - "icon": "icon-cog", + "icon": "fa fa-cog", "idx": 0, "image_view": 0, "in_create": 0, diff --git a/frappe/print/page/print_format_builder/print_format_builder_sidebar.html b/frappe/print/page/print_format_builder/print_format_builder_sidebar.html index 38152191db..5c288319dc 100644 --- a/frappe/print/page/print_format_builder/print_format_builder_sidebar.html +++ b/frappe/print/page/print_format_builder/print_format_builder_sidebar.html @@ -5,7 +5,7 @@
  6. ', info)) .insertBefore(this.parent.find('.add-assignment')); if(d[i].owner===user) { me.primary_action = this.frm.page.add_menu_item(__("Assignment Complete"), function() { me.remove(user); - }, "icon-ok", "btn-success") + }, "fa fa-check", "btn-success") } if(!(d[i].owner === user || me.frm.perm[0].write)) { @@ -85,7 +86,7 @@ frappe.ui.form.AssignTo = Class.extend({ } if(!me.dialog) { - me.dialog = frappe.ui.to_do_dialog({ + me.dialog = new frappe.ui.form.AssignToDialog({ obj: me, method: 'frappe.desk.form.assign_to.add', doctype: me.frm.doctype, @@ -103,28 +104,7 @@ frappe.ui.form.AssignTo = Class.extend({ } me.dialog.show(); - - var myself = me.dialog.get_input("myself").on("click", function() { - me.toggle_myself(this); - }); - me.toggle_myself(myself); }, - - toggle_myself: function(myself) { - var me = this; - if($(myself).prop("checked")) { - me.dialog.set_value("assign_to", user); - me.dialog.set_value("notify", 0); - me.dialog.get_field("notify").$wrapper.toggle(false); - me.dialog.get_field("assign_to").$wrapper.toggle(false); - } else { - me.dialog.set_value("assign_to", ""); - me.dialog.set_value("notify", 1); - me.dialog.get_field("notify").$wrapper.toggle(true); - me.dialog.get_field("assign_to").$wrapper.toggle(true); - } - }, - remove: function(owner) { var me = this; @@ -149,35 +129,56 @@ frappe.ui.form.AssignTo = Class.extend({ }); -frappe.ui.to_do_dialog = function(opts){ - var dialog = new frappe.ui.Dialog({ - title: __('Add to To Do'), - fields: [ - {fieldtype: 'Link', fieldname:'assign_to', options:'User', - label:__("Assign To"), reqd:true, filters: {'user_type': 'System User'}}, - {fieldtype:'Check', fieldname:'myself', label:__("Assign to me"), "default":0}, - {fieldtype:'Small Text', fieldname:'description', label:__("Comment"), reqd:true}, - {fieldtype: 'Section Break'}, - {fieldtype: 'Column Break'}, - {fieldtype:'Date', fieldname:'date', label: __("Complete By")}, - {fieldtype:'Check', fieldname:'notify', - label:__("Notify by Email"), "default":1}, - {fieldtype: 'Column Break'}, - {fieldtype:'Select', fieldname:'priority', label: __("Priority"), - options:[ - {value: 'Low', label: __('Low')}, - {value:'Medium', label: __('Medium')}, - {value: 'High', label: __('High')}], - 'default':'Medium'}, - ], - primary_action: function() { frappe.ui.add_assignment(opts, dialog); }, - primary_action_label: __("Add") - }); +frappe.ui.form.AssignToDialog = Class.extend({ + init: function(opts){ + var me = this + $.extend(me,new frappe.ui.Dialog({ + title: __('Add to To Do'), + fields: [ + {fieldtype: 'Link', fieldname:'assign_to', options:'User', + label:__("Assign To"), reqd:true, filters: {'user_type': 'System User'}}, + {fieldtype:'Check', fieldname:'myself', label:__("Assign to me"), "default":0}, + {fieldtype:'Small Text', fieldname:'description', label:__("Comment"), reqd:true}, + {fieldtype: 'Section Break'}, + {fieldtype: 'Column Break'}, + {fieldtype:'Date', fieldname:'date', label: __("Complete By")}, + {fieldtype:'Check', fieldname:'notify', + label:__("Notify by Email"), "default":1}, + {fieldtype: 'Column Break'}, + {fieldtype:'Select', fieldname:'priority', label: __("Priority"), + options:[ + {value:'Low', label:__('Low')}, + {value:'Medium', label:__('Medium')}, + {value:'High', label:__('High')}], + 'default':'Medium'}, + ], + primary_action: function() { frappe.ui.add_assignment(opts, me); }, + primary_action_label: __("Add") + })); - dialog.fields_dict.assign_to.get_query = "frappe.core.doctype.user.user.user_query"; + me.fields_dict.assign_to.get_query = "frappe.core.doctype.user.user.user_query"; - return dialog -} + var myself = me.get_input("myself").on("click", function() { + me.toggle_myself(this); + }); + me.toggle_myself(myself); + }, + toggle_myself: function(myself) { + var me = this; + if($(myself).prop("checked")) { + me.set_value("assign_to", user); + me.set_value("notify", 0); + me.get_field("notify").$wrapper.toggle(false); + me.get_field("assign_to").$wrapper.toggle(false); + } else { + me.set_value("assign_to", ""); + me.set_value("notify", 1); + me.get_field("notify").$wrapper.toggle(true); + me.get_field("assign_to").$wrapper.toggle(true); + } + }, + +}); frappe.ui.add_assignment = function(opts, dialog) { var assign_to = opts.obj.dialog.fields_dict.assign_to.get_value(); diff --git a/frappe/public/js/frappe/form/footer/attachments.js b/frappe/public/js/frappe/form/footer/attachments.js index 49b2137349..13645e7083 100644 --- a/frappe/public/js/frappe/form/footer/attachments.js +++ b/frappe/public/js/frappe/form/footer/attachments.js @@ -63,13 +63,13 @@ frappe.ui.form.Attachments = Class.extend({ var me = this; var $attach = $(repl('
  7. \ - ×\ + ×\ %(lock_icon)s\ \ + class="ellipsis" style="max-width: calc(100% - 43px);">\ %(file_name)s\
  8. ', { - lock_icon: attachment.is_private ? ' ': "", + lock_icon: attachment.is_private ? ' ': "", file_name: file_name, file_url: frappe.urllib.get_full_url(file_url) })) @@ -228,13 +228,14 @@ frappe.ui.get_upload_dialog = function(opts){ 'method': 'frappe.client.get_value', 'args': { 'doctype': 'File', - 'fieldname': ['file_url','file_name'], + 'fieldname': ['file_url','file_name','is_private'], 'filters': { 'name': dialog.get_value("file") } }, callback: function(r){ dialog.$wrapper.find('[name="file_url"]').val(r.message.file_url); + dialog.$wrapper.find('.private-file input').prop('checked', r.message.is_private); opts.args.filename = r.message.file_name } }); diff --git a/frappe/public/js/frappe/form/footer/footer.js b/frappe/public/js/frappe/form/footer/footer.js index 1b6fa5e632..cedd64fb72 100644 --- a/frappe/public/js/frappe/form/footer/footer.js +++ b/frappe/public/js/frappe/form/footer/footer.js @@ -25,7 +25,7 @@ frappe.ui.form.Footer = Class.extend({ this.frm.timeline = new frappe.ui.form.Timeline({ parent: this.wrapper.find(".form-comments"), frm: this.frm - }) + }); }, refresh: function() { if(this.frm.doc.__islocal) { diff --git a/frappe/public/js/frappe/form/footer/form_footer.html b/frappe/public/js/frappe/form/footer/form_footer.html index f7d8a46432..00ab0468ae 100644 --- a/frappe/public/js/frappe/form/footer/form_footer.html +++ b/frappe/public/js/frappe/form/footer/form_footer.html @@ -3,6 +3,6 @@
- +
diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index d9b78e0406..3c53037fdf 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -115,6 +115,7 @@ frappe.ui.form.Timeline = Class.extend({ this.frm.sidebar.refresh_comments(); + this.frm.trigger('timeline_refresh'); }, render_timeline_item: function(c) { diff --git a/frappe/public/js/frappe/form/footer/timeline_item.html b/frappe/public/js/frappe/form/footer/timeline_item.html index eeb56e6ef8..65189a8b56 100755 --- a/frappe/public/js/frappe/form/footer/timeline_item.html +++ b/frappe/public/js/frappe/form/footer/timeline_item.html @@ -19,7 +19,7 @@
{% if(data.communication_type==="Communication" || (data.communication_type==="Comment" && data.comment_type==="Comment")) { %}
- + {%= data.fullname %} {% if (data.timeline_doctype===data.frm.doc.doctype && data.timeline_name===data.frm.doc.name) { %} @@ -77,14 +77,14 @@
-
-
+
+
{%= data.content_html %}
{% } else if(in_list(["Assignment Completed", "Assigned", "Shared", "Unshared"], data.comment_type)) { %}
- + {% if (data.timeline_doctype===data.frm.doc.doctype && data.timeline_name===data.frm.doc.name) { %} {{ __(data.reference_doctype) }} {{ data.reference_name }} @@ -103,7 +103,7 @@
{% } else { %}
- + {% if (data.comment_type == "Like") { %} {% if (data.timeline_doctype===data.frm.doc.doctype && data.timeline_name===data.frm.doc.name) { %} @@ -130,12 +130,12 @@ {% if(data.attachments && data.attachments.length) { %}
{% $.each(data.attachments, function(i, a) { %} -
+ diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 44c3c3f74e..2ab935c9a6 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -59,7 +59,7 @@ frappe.form.formatters = { if(value) { return ''; } else { - return ''; + return ''; } }, Link: function(value, docfield, options, doc) { @@ -80,6 +80,9 @@ frappe.form.formatters = { if(!value) { return ""; } + if(value[0] == "'" && value[value.length -1] == "'") { + return value.substring(1, value.length - 1); + } if(docfield && docfield.link_onclick) { return repl('%(value)s', {onclick: docfield.link_onclick.replace(/"/g, '"'), value:value}); @@ -110,7 +113,7 @@ frappe.form.formatters = { if(frappe.boot.sysdefaults.time_zone) { m = m.tz(frappe.boot.sysdefaults.time_zone); } - return m.format('MMMM Do YYYY, h:mm a z'); + return m.format(frappe.boot.sysdefaults.date_format.toUpperCase() + ', h:mm a z'); } else { return ""; } @@ -183,7 +186,7 @@ frappe.form.formatters = { return repl("\ - %(value)s", { + %(value)s", { value: value, style: workflow_state.style.toLowerCase(), icon: workflow_state.icon diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index cecba0264d..58e122bf62 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -58,10 +58,44 @@ frappe.ui.form.Grid = Class.extend({ this.custom_buttons = {}; this.grid_buttons = this.wrapper.find('.grid-buttons'); + this.remove_rows_button = this.grid_buttons.find('.grid-remove-rows') this.setup_allow_bulk_edit(); + this.setup_check(); }, + setup_check: function() { + var me = this; + this.wrapper.on('click', '.grid-row-check', function(e) { + $check = $(this); + if($check.parents('.grid-heading-row:first').length!==0) { + // select all? + $check.parents('.form-grid:first').find('.grid-row-check').prop('checked', $check.prop('checked')); + } else { + var docname = $check.parents('.grid-row:first').attr('data-name'); + me.grid_rows_by_docname[docname].select($check.prop('checked')); + } + me.refresh_remove_rows_button(); + }); + + this.remove_rows_button.on('click', function() { + me.get_selected().forEach(function(docname) { + me.grid_rows_by_docname[docname].remove(); + }); + setTimeout(function() { me.refresh_remove_rows_button(); }, 100); + }); + }, + select_row: function(name) { + me.grid_rows_by_docname[name].select(); + }, + refresh_remove_rows_button: function() { + this.remove_rows_button.toggleClass('hide', + this.wrapper.find('.grid-body .grid-row-check:checked:first').length ? false : true); + }, + get_selected: function() { + return (this.grid_rows || []).map(function(row) { return row.doc.__checked ? row.doc.name : null; }) + .filter(function(d) { return d; }); + }, make_head: function() { // labels if(!this.header_row) { @@ -143,6 +177,8 @@ frappe.ui.form.Grid = Class.extend({ this.last_docname = this.frm.docname; frappe.utils.scroll_to(_scroll_y); } + + this.refresh_remove_rows_button(); }, setup_toolbar: function() { if(this.is_editable()) { @@ -216,7 +252,6 @@ frappe.ui.form.Grid = Class.extend({ return; } - new Sortable($rows.get(0), { group: {name: 'row'}, handle: ".sortable-handle", @@ -320,7 +355,7 @@ frappe.ui.form.Grid = Class.extend({ } setTimeout(function() { me.grid_rows[idx].row - .find('input,textarea,select').filter(':visible:first').focus(); + .find('input[type="Text"],textarea,select').filter(':visible:first').focus(); }, 100); }, @@ -545,6 +580,7 @@ frappe.ui.form.GridRow = Class.extend({ init: function(opts) { this.on_grid_fields_dict = {}; this.on_grid_fields = []; + this.row_check_html = ''; this.columns = {}; this.columns_list = []; $.extend(this, opts); @@ -552,9 +588,13 @@ frappe.ui.form.GridRow = Class.extend({ }, make: function() { var me = this; + this.wrapper = $('
').appendTo(this.parent).data("grid_row", this); this.row = $('
').appendTo(this.wrapper) - .on("click", function() { + .on("click", function(e) { + if($(e.target).hasClass('grid-row-check') || $(e.target).hasClass('row-index') || $(e.target).parent().hasClass('row-index')) { + return; + } if(me.grid.allow_on_grid_editing() && me.grid.is_editable()) { // pass } else { @@ -563,6 +603,11 @@ frappe.ui.form.GridRow = Class.extend({ } }); + // no checkboxes if too small + if(this.is_too_small()) { + this.row_check_html = ''; + } + if(this.grid.template && !this.grid.meta.editable_grid) { this.render_template(); } else { @@ -582,10 +627,17 @@ frappe.ui.form.GridRow = Class.extend({ this.wrapper .attr('data-name', this.doc.name) .attr("data-idx", this.doc.idx) - .find(".row-index, .grid-form-row-index").html(this.doc.idx) + .find(".row-index span, .grid-form-row-index").html(this.doc.idx) } }, + select: function(checked) { + this.doc.__checked = checked ? 1 : 0; + }, + refresh_check: function() { + this.wrapper.find('.grid-row-check').prop('checked', this.doc ? !!this.doc.__checked : false); + this.grid.refresh_remove_rows_button(); + }, remove: function() { if(this.grid.is_editable()) { if(this.get_open_form()) { @@ -629,6 +681,8 @@ frappe.ui.form.GridRow = Class.extend({ } }, render_template: function() { + this.set_row_index(); + if(this.row_display) { this.row_display.remove(); } @@ -638,9 +692,9 @@ frappe.ui.form.GridRow = Class.extend({ if(this.doc) { if(!this.row_index) { this.row_index = $('
').appendTo(this.row); + margin-right: -20px;">'+this.row_check_html+'
').appendTo(this.row); } - this.row_index.html(this.doc.idx); + this.row_index.find('span').html(this.doc.idx); } this.row_display = $('
'+ @@ -657,15 +711,23 @@ frappe.ui.form.GridRow = Class.extend({ // index (1, 2, 3 etc) if(!this.row_index) { - this.row_index = $('
' + (this.doc ? this.doc.idx : " ")+ '
') + var txt = (this.doc ? this.doc.idx : " "); + this.row_index = $('
' + + this.row_check_html + + ' ' + txt + '
') .appendTo(this.row) - .on('click', function() { me.toggle_view(); }); + .on('click', function(e) { + if(!$(e.target).hasClass('grid-row-check')) { + me.toggle_view(); + } + }); } else { - this.row_index.html(this.doc ? this.doc.idx : " "); + this.row_index.find('span').html(txt); } this.setup_columns(); this.add_open_form_button(); + this.refresh_check(); if(this.doc) { $(this.frm.wrapper).trigger("grid-row-render", [this]); @@ -676,6 +738,10 @@ frappe.ui.form.GridRow = Class.extend({ this.row.toggleClass('editable-row', this.grid.is_editable()); }, + is_too_small: function() { + return this.row.width() < 400; + }, + add_open_form_button: function() { var me = this; if(this.doc) { @@ -686,7 +752,7 @@ frappe.ui.form.GridRow = Class.extend({ .appendTo($('
').appendTo(this.row)) .on('click', function() { me.toggle_view(); return false; }); - if(this.row.width() < 400) { + if(this.is_too_small()) { // narrow this.open_form_button.css({'margin-right': '-2px'}); } @@ -742,13 +808,13 @@ frappe.ui.form.GridRow = Class.extend({ out = me.toggle_editable_row(); var col = this; setTimeout(function() { - $(col).find(':input:first').focus(); + $(col).find('input[type="Text"]:first').focus(); }, 500); return out; }); $col.field_area = $('
').appendTo($col).toggle(false); - $col.static_area = $('
').appendTo($col).html(txt); + $col.static_area = $('
').appendTo($col).html(txt); $col.df = df; $col.column_index = ci; @@ -836,7 +902,13 @@ frappe.ui.form.GridRow = Class.extend({ field.$input.on('keydown', function(e) { var values = me.frm.doc[me.grid.df.fieldname]; var fieldname = $(this).attr('data-fieldname'); + var fieldtype = $(this).attr('data-fieldtype'); + // TAB + if(in_list(['Text', 'Small Text'], fieldtype)) { + return; + } + if(e.which==TAB) { // last column if(me.grid.wrapper.find('input:enabled:last').get(0)===this) { @@ -1104,7 +1176,7 @@ frappe.ui.form.GridRowForm = Class.extend({ var me = this; setTimeout(function() { if(me.row.frm.doc.docstatus===0) { - var first = me.form_area.find(":input:first"); + var first = me.form_area.find("input:first"); if(first.length && !in_list(["Date", "Datetime", "Time"], first.attr("data-fieldtype"))) { try { first.get(0).focus(); diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 5fc8d8d2fe..f81ba69e36 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -461,7 +461,7 @@ frappe.ui.form.Section = Class.extend({ this.make_head(); } if(this.df.description) { - $('
' + __(this.df.description) + '
') + $('
' + __(this.df.description) + '
') .appendTo(this.wrapper); } } diff --git a/frappe/public/js/frappe/form/link_selector.js b/frappe/public/js/frappe/form/link_selector.js index f17a125b73..58f9ee2f74 100644 --- a/frappe/public/js/frappe/form/link_selector.js +++ b/frappe/public/js/frappe/form/link_selector.js @@ -99,11 +99,11 @@ frappe.ui.form.LinkSelector = Class.extend({ }) }) } else { - $('
' + __("No Results") - + (frappe.model.can_read(me.doctype) ? - ('. ' - + __("Make a new") + " " + __(me.doctype) + "") : '') - + '
').appendTo(parent).find(".new-doc").click(function() { + $('


' + __("No Results") + '' + + (frappe.model.can_create(me.doctype) ? + ('

' + + __("Make a new {0}", [__(me.doctype)]) + "") : '') + + '

').appendTo(parent).find(".new-doc").click(function() { me.target.new_doc(); }); } diff --git a/frappe/public/js/frappe/form/linked_with.js b/frappe/public/js/frappe/form/linked_with.js index 44679c8b4d..449ec26edd 100644 --- a/frappe/public/js/frappe/form/linked_with.js +++ b/frappe/public/js/frappe/form/linked_with.js @@ -12,7 +12,7 @@ frappe.ui.form.LinkedWith = Class.extend({ if(!this.dialog) this.make_dialog(); - this.dialog.fields_dict.list.$wrapper.html('
' + this.dialog.fields_dict.list.$wrapper.html('
' + __("Loading") + '...
'); this.dialog.show(); diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index cbdda43402..65e708067a 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -103,12 +103,6 @@ frappe.ui.form.save = function(frm, action, callback, btn) { if(frm.doc.docstatus==2) return true; // don't check for cancel $.each(frappe.model.get_all_docs(frm.doc), function(i, doc) { - - if(doc.parent && doc.__unedited) { - frappe.model.remove_from_locals(doc.doctype, doc.name); - return; - } - var error_fields = []; var folded = false; @@ -141,7 +135,7 @@ frappe.ui.form.save = function(frm, action, callback, btn) { if(error_fields.length) msgprint(__('Mandatory fields required in {0}', [(doc.parenttype ? (__(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label) + ' ('+ __("Table") + ')') - : __(doc.doctype))]) + '\n' + error_fields.join('\n')); + : __(doc.doctype))]) + '
  • ' + error_fields.join('
  • ') + "
"); }); return !has_errors; diff --git a/frappe/public/js/frappe/form/script_manager.js b/frappe/public/js/frappe/form/script_manager.js index 98bac6d9fa..128880bd17 100644 --- a/frappe/public/js/frappe/form/script_manager.js +++ b/frappe/public/js/frappe/form/script_manager.js @@ -102,7 +102,8 @@ frappe.ui.form.ScriptManager = Class.extend({ } function setup_add_fetch(df) { - if((df.fieldtype==="Read Only" || df.read_only==1) + if((in_list(['Data', 'Read Only', 'Text', 'Small Text', + 'Text Editor', 'Code'], df.fieldtype) || df.read_only==1) && df.options && df.options.indexOf(".")!=-1) { var parts = df.options.split("."); me.frm.add_fetch(parts[0], parts[1], df.fieldname); diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 7233397ffe..f3ed783e89 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -70,3 +70,7 @@ {% if(frappe.get_form_sidebar_extension) { %} {{ frappe.get_form_sidebar_extension() }} {% } %} +
    +
  • +
  • Close
  • +
\ No newline at end of file diff --git a/frappe/public/js/frappe/form/templates/grid_body.html b/frappe/public/js/frappe/form/templates/grid_body.html index 7a2adac38f..d74667d4df 100644 --- a/frappe/public/js/frappe/form/templates/grid_body.html +++ b/frappe/public/js/frappe/form/templates/grid_body.html @@ -7,6 +7,8 @@