diff --git a/.travis.yml b/.travis.yml index ab46e06c3d..ef03adb693 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,6 +52,6 @@ before_script: script: - set -e - - bench --verbose run-tests + - bench run-tests - sleep 5 - - bench --verbose run-ui-tests --app frappe + - bench run-ui-tests --app frappe diff --git a/frappe/__init__.py b/frappe/__init__.py index 07596bb4e9..906bc0b3fd 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -6,7 +6,7 @@ globals attached to frappe module """ from __future__ import unicode_literals, print_function -from six import iteritems, text_type +from six import iteritems, text_type, string_types from werkzeug.local import Local, release_local import os, sys, importlib, inspect, json @@ -61,7 +61,7 @@ def as_unicode(text, encoding='utf-8'): return text elif text==None: return '' - elif isinstance(text, basestring): + elif isinstance(text, string_types): return text_type(text, encoding) else: return text_type(text) @@ -164,7 +164,7 @@ def connect(site=None, db_name=None): :param site: If site is given, calls `frappe.init`. :param db_name: Optional. Will use from `site_config.json`.""" - from database import Database + from frappe.database import Database if site: init(site) local.db = Database(user=db_name or local.conf.db_name) @@ -235,8 +235,8 @@ def cache(): def get_traceback(): """Returns error traceback.""" - import utils - return utils.get_traceback() + from frappe.utils import get_traceback + return get_traceback() def errprint(msg): """Log error. This is sent back as `exc` in response. @@ -268,7 +268,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, :param raise_exception: [optional] Raise given exception and show message. :param as_table: [optional] If `msg` is a list of lists, render as HTML table. """ - from utils import encode + from frappe.utils import encode out = _dict(message=msg) @@ -421,8 +421,8 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message if not delayed: now = True - import email.queue - email.queue.send(recipients=recipients, sender=sender, + from frappe.email import queue + queue.send(recipients=recipients, sender=sender, subject=subject, message=message, text_content=text_content, reference_doctype = doctype or reference_doctype, reference_name = name or reference_name, unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, @@ -488,7 +488,7 @@ def clear_cache(user=None, doctype=None): elif user: frappe.sessions.clear_cache(user) else: # everything - import translate + from frappe import translate frappe.sessions.clear_cache() translate.clear_cache() reset_metadata_version() @@ -533,7 +533,7 @@ def has_website_permission(doc=None, ptype='read', user=None, verbose=False, doc user = session.user if doc: - if isinstance(doc, basestring): + if isinstance(doc, string_types): doc = get_doc(doctype, doc) doctype = doc.doctype @@ -576,7 +576,7 @@ def generate_hash(txt=None, length=None): """Generates random hash for given text + current timestamp + random string.""" import hashlib, time from .utils import random_string - digest = hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest() + digest = hashlib.sha224(((txt or "") + repr(time.time()) + repr(random_string(8))).encode()).hexdigest() if length: digest = digest[:length] return digest @@ -903,7 +903,7 @@ def get_attr(method_string): def call(fn, *args, **kwargs): """Call a function and match arguments.""" - if isinstance(fn, basestring): + if isinstance(fn, string_types): fn = get_attr(fn) if hasattr(fn, 'fnargs'): @@ -1111,7 +1111,7 @@ def get_list(doctype, *args, **kwargs): :param filters: List of filters (see example). :param order_by: Order By e.g. `modified desc`. :param limit_page_start: Start results at record #. Default 0. - :param limit_poge_length: No of records in the page. Default 20. + :param limit_page_length: No of records in the page. Default 20. Example usage: @@ -1136,7 +1136,7 @@ def get_all(doctype, *args, **kwargs): :param filters: List of filters (see example). :param order_by: Order By e.g. `modified desc`. :param limit_page_start: Start results at record #. Default 0. - :param limit_poge_length: No of records in the page. Default 20. + :param limit_page_length: No of records in the page. Default 20. Example usage: diff --git a/frappe/api.py b/frappe/api.py index 38c4598273..f99afb7a4f 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -8,8 +8,7 @@ import frappe.handler import frappe.client from frappe.utils.response import build_response from frappe import _ -from six.moves.urllib.parse import urlparse -from urllib import urlencode +from six.moves.urllib.parse import urlparse, urlencode def handle(): """ diff --git a/frappe/app.py b/frappe/app.py index 69c90c97ad..6884aa26fa 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import os import MySQLdb from six import iteritems +import logging from werkzeug.wrappers import Request from werkzeug.local import LocalManager @@ -177,7 +178,8 @@ def handle_exception(e): make_error_snapshot(e) if return_as_message: - response = frappe.website.render.render("message", http_status_code=http_status_code) + response = frappe.website.render.render("message", + http_status_code=http_status_code) return response @@ -212,11 +214,11 @@ def serve(port=8000, profile=False, site=None, sites_path='.'): if not os.environ.get('NO_STATICS'): application = SharedDataMiddleware(application, { - b'/assets': os.path.join(sites_path, 'assets').encode("utf-8"), + b'/assets': os.path.join(sites_path, 'assets'), }) application = StaticDataMiddleware(application, { - b'/files': os.path.abspath(sites_path).encode("utf-8") + b'/files': os.path.abspath(sites_path) }) application.debug = True @@ -225,6 +227,10 @@ def serve(port=8000, profile=False, site=None, sites_path='.'): } in_test_env = os.environ.get('CI') + if in_test_env: + log = logging.getLogger('werkzeug') + log.setLevel(logging.ERROR) + run_simple('0.0.0.0', int(port), application, use_reloader=not in_test_env, use_debugger=not in_test_env, diff --git a/frappe/auth.py b/frappe/auth.py index bd510b9fcd..b92e7e604d 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -17,7 +17,7 @@ from frappe.translate import get_lang_code from frappe.utils.password import check_password from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log from frappe.utils.background_jobs import enqueue -from twofactor import (should_run_2fa, authenticate_for_2factor, +from frappe.twofactor import (should_run_2fa, authenticate_for_2factor, confirm_otp_token, get_cached_user_pass) from six.moves.urllib.parse import quote diff --git a/frappe/client.py b/frappe/client.py index fafa535e0e..f257f6abbe 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -8,7 +8,7 @@ import frappe.model import frappe.utils import json, os -from six import iteritems +from six import iteritems, string_types ''' Handle RESTful requests that are mapped to the `/api/resource` route. @@ -92,7 +92,7 @@ def set_value(doctype, name, fieldname, value=None): if not value: values = fieldname - if isinstance(fieldname, basestring): + if isinstance(fieldname, string_types): try: values = json.loads(fieldname) except ValueError: @@ -118,7 +118,7 @@ def insert(doc=None): '''Insert a document :param doc: JSON or dict object to be inserted''' - if isinstance(doc, basestring): + if isinstance(doc, string_types): doc = json.loads(doc) if doc.get("parent") and doc.get("parenttype"): @@ -136,7 +136,7 @@ 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): + if isinstance(docs, string_types): docs = json.loads(docs) out = [] @@ -162,7 +162,7 @@ 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): + if isinstance(doc, string_types): doc = json.loads(doc) doc = frappe.get_doc(doc).save() @@ -183,7 +183,7 @@ def submit(doc): '''Submit a document :param doc: JSON or dict object to be submitted remotely''' - if isinstance(doc, basestring): + if isinstance(doc, string_types): doc = json.loads(doc) doc = frappe.get_doc(doc) @@ -221,7 +221,7 @@ def make_width_property_setter(doc): '''Set width Property Setter :param doc: Property Setter document with `width` property''' - if isinstance(doc, basestring): + if isinstance(doc, string_types): doc = json.loads(doc) if doc["doctype"]=="Property Setter" and doc["property"]=="width": frappe.get_doc(doc).insert(ignore_permissions = True) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index d5944ad023..3e96045d22 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -36,7 +36,7 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N """Install a new Frappe site""" if not db_name: - db_name = hashlib.sha1(site).hexdigest()[:16] + db_name = hashlib.sha1(site.encode()).hexdigest()[:16] from frappe.installer import install_db, make_site_dirs from frappe.installer import install_app as _install_app diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 74da084beb..139529a08f 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -18,6 +18,8 @@ def build(make_copy=False, verbose=False): @click.command('watch') def watch(): "Watch and concatenate JS and CSS files as and when they change" + # if os.environ.get('CI'): + # return import frappe.build frappe.init('') frappe.build.watch(True) diff --git a/frappe/config/setup.py b/frappe/config/setup.py index cd2f58fcc4..55ed2dbd9e 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -124,7 +124,7 @@ def get_data(): { "type": "doctype", "name": "Deleted Document", - "label": _("Deleted Documents"), + "label": _("Deleted Documents"), "description": _("Restore or permanently delete a document.") }, ] @@ -180,6 +180,11 @@ def get_data(): "name": "Print Format", "description": _("Customized HTML Templates for printing transactions.") }, + { + "type": "doctype", + "name": "Print Style", + "description": _("Stylesheets for Print Formats") + }, ] }, { diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index 33a01f3192..18e51f3a61 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -13,7 +13,7 @@ from jinja2 import TemplateSyntaxError from frappe.utils.user import is_website_user from frappe.model.naming import make_autoname from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_links -from six import iteritems +from six import iteritems, string_types class Address(Document): @@ -115,7 +115,7 @@ def get_territory_from_address(address): if not address: return - if isinstance(address, basestring): + if isinstance(address, string_types): address = frappe.get_doc("Address", address) territory = None diff --git a/frappe/contacts/doctype/contact/contact.js b/frappe/contacts/doctype/contact/contact.js index d44904d9d5..109b971430 100644 --- a/frappe/contacts/doctype/contact/contact.js +++ b/frappe/contacts/doctype/contact/contact.js @@ -18,7 +18,7 @@ frappe.ui.form.on("Contact", { if(!frm.doc.user && !frm.is_new() && frm.perm[0].write) { frm.add_custom_button(__("Invite as User"), function() { - frappe.call({ + return frappe.call({ method: "frappe.contacts.doctype.contact.contact.invite_user", args: { contact: frm.doc.name diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 3af3def992..cbc9c5a9bb 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals, absolute_import from six.moves import range +from six import string_types import frappe import json from email.utils import formataddr @@ -71,7 +72,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = # 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): + if isinstance(attachments, string_types): attachments = json.loads(attachments) # if not committed, delayed task doesn't find the communication @@ -250,11 +251,11 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None) print_format=print_format, html=print_html)) if attachments: - if isinstance(attachments, basestring): + if isinstance(attachments, string_types): attachments = json.loads(attachments) for a in attachments: - if isinstance(a, basestring): + if isinstance(a, string_types): # is it a filename? try: file = get_file(a) @@ -342,7 +343,7 @@ def add_attachments(name, attachments): # loop through attachments for a in attachments: - if isinstance(a, basestring): + if isinstance(a, string_types): attach = frappe.db.get_value("File", {"name":a}, ["file_name", "file_url", "is_private"], as_dict=1) diff --git a/frappe/core/doctype/communication/feed.py b/frappe/core/doctype/communication/feed.py index 2d939447cd..40d4418c5f 100644 --- a/frappe/core/doctype/communication/feed.py +++ b/frappe/core/doctype/communication/feed.py @@ -9,6 +9,7 @@ from frappe.utils import get_fullname from frappe import _ from frappe.core.doctype.communication.comment import add_info_comment from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log +from six import string_types def update_feed(doc, method=None): "adds a new communication with comment_type='Updated'" @@ -25,7 +26,7 @@ def update_feed(doc, method=None): feed = doc.get_feed() if feed: - if isinstance(feed, basestring): + if isinstance(feed, string_types): feed = {"subject": feed} feed = frappe._dict(feed) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index f9b65e6556..6dcc94fa5e 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -21,7 +21,7 @@ from frappe import _ from frappe.utils.nestedset import NestedSet from frappe.utils import strip, get_files_path from PIL import Image, ImageOps -from six import StringIO +from six import StringIO, string_types from six.moves.urllib.parse import unquote import zipfile @@ -170,7 +170,7 @@ class File(NestedSet): super(File, self).on_trash() self.delete_file() - def make_thumbnail(self): + def make_thumbnail(self, set_as_thumbnail=True, width=300, height=300, suffix="small"): if self.file_url: if self.file_url.startswith("/files"): try: @@ -184,15 +184,19 @@ class File(NestedSet): except (requests.exceptions.HTTPError, requests.exceptions.SSLError, IOError): return - size = 300, 300 + size = width, height image.thumbnail(size) - thumbnail_url = filename + "_small." + extn + thumbnail_url = filename + "_" + suffix + "." + extn path = os.path.abspath(frappe.get_site_path("public", thumbnail_url.lstrip("/"))) try: image.save(path) + + if set_as_thumbnail: + self.db_set("thumbnail_url", thumbnail_url) + self.db_set("thumbnail_url", thumbnail_url) except IOError: frappe.msgprint(_("Unable to write file format for {0}").format(path)) @@ -305,7 +309,7 @@ def create_new_folder(file_name, folder): @frappe.whitelist() def move_file(file_list, new_parent, old_parent): - if isinstance(file_list, basestring): + if isinstance(file_list, string_types): file_list = json.loads(file_list) for file_obj in file_list: @@ -325,7 +329,12 @@ def setup_folder_path(filename, new_parent): def get_extension(filename, extn, content): mimetype = None + if extn: + # remove '?' char and parameters from extn if present + if '?' in extn: + extn = extn.split('?', 1)[0] + mimetype = mimetypes.guess_type(filename + "." + extn)[0] if mimetype is None or not mimetype.startswith("image/") and content: diff --git a/frappe/core/doctype/sms_settings/sms_settings.py b/frappe/core/doctype/sms_settings/sms_settings.py index a8b59beffa..70f5feebbc 100644 --- a/frappe/core/doctype/sms_settings/sms_settings.py +++ b/frappe/core/doctype/sms_settings/sms_settings.py @@ -9,6 +9,7 @@ from frappe import _, throw, msgprint from frappe.utils import nowdate from frappe.model.document import Document +from six import string_types class SMSSettings(Document): pass @@ -55,7 +56,7 @@ def get_contact_number(contact_name, ref_doctype, ref_name): def send_sms(receiver_list, msg, sender_name = '', success_msg = True): import json - if isinstance(receiver_list, basestring): + if isinstance(receiver_list, string_types): receiver_list = json.loads(receiver_list) if not isinstance(receiver_list, list): receiver_list = [receiver_list] diff --git a/frappe/core/page/data_import_tool/exporter.py b/frappe/core/page/data_import_tool/exporter.py index caa6ae90ef..4f7bf8a067 100644 --- a/frappe/core/page/data_import_tool/exporter.py +++ b/frappe/core/page/data_import_tool/exporter.py @@ -10,6 +10,7 @@ import re, csv, os from frappe.utils.csvutils import UnicodeWriter from frappe.utils import cstr, formatdate, format_datetime from frappe.core.page.data_import_tool.data_import_tool import get_data_keys +from six import string_types reflags = { "I":re.I, @@ -29,7 +30,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data select_columns = json.loads(select_columns); docs_to_export = {} if doctype: - if isinstance(doctype, basestring): + if isinstance(doctype, string_types): doctype = [doctype]; if len(doctype) > 1: docs_to_export = doctype[1] diff --git a/frappe/core/page/data_import_tool/importer.py b/frappe/core/page/data_import_tool/importer.py index 308cef8f55..ae30757c34 100644 --- a/frappe/core/page/data_import_tool/importer.py +++ b/frappe/core/page/data_import_tool/importer.py @@ -17,7 +17,7 @@ from frappe.utils.file_manager import save_url from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url from frappe.core.page.data_import_tool.data_import_tool import get_data_keys -from six import text_type +from six import text_type, string_types @frappe.whitelist() def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, no_email=True, overwrite=None, @@ -119,7 +119,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, elif fieldtype in ("Float", "Currency", "Percent"): d[fieldname] = flt(d[fieldname]) elif fieldtype == "Date": - if d[fieldname] and isinstance(d[fieldname], basestring): + if d[fieldname] and isinstance(d[fieldname], string_types): d[fieldname] = getdate(parse_date(d[fieldname])) elif fieldtype == "Datetime": if d[fieldname]: diff --git a/frappe/database.py b/frappe/database.py index b702a8c53d..77e1109020 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -18,7 +18,7 @@ import redis import frappe.model.meta from frappe.utils import now, get_datetime, cstr from frappe import _ -from six import text_type, binary_type +from six import text_type, binary_type, string_types, integer_types from frappe.utils.global_search import sync_global_search from frappe.model.utils.link_count import flush_local_link_count from six import iteritems, text_type @@ -270,7 +270,7 @@ class Database: """Returns true if the first row in the result has a Date, Datetime, Long Int.""" if result and result[0]: for v in result[0]: - if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, long)): + if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, integer_types)): return True if formatted and isinstance(v, (int, float)): return True @@ -287,7 +287,7 @@ class Database: from frappe.utils import formatdate, fmt_money - if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, long)): + if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, integer_types)): if isinstance(v, datetime.date): v = text_type(v) if formatted: @@ -298,7 +298,7 @@ class Database: v = text_type(v) # long - elif isinstance(v, long): + elif isinstance(v, integer_types): v=int(v) # convert to strings... (if formatted) @@ -386,7 +386,7 @@ class Database: conditions.append(condition) - if isinstance(filters, basestring): + if isinstance(filters, string_types): filters = { "name": filters } for f in filters: @@ -451,7 +451,7 @@ class Database: user = frappe.db.get_values("User", "test@example.com", "*")[0] """ out = None - if cache and isinstance(filters, basestring) and \ + if cache and isinstance(filters, string_types) and \ (doctype, filters, fieldname) in self.value_cache: return self.value_cache[(doctype, filters, fieldname)] @@ -463,7 +463,7 @@ class Database: else: fields = fieldname if fieldname!="*": - if isinstance(fieldname, basestring): + if isinstance(fieldname, string_types): fields = [fieldname] else: fields = fieldname @@ -483,7 +483,7 @@ class Database: else: out = self.get_values_from_single(fields, filters, doctype, as_dict, debug, update) - if cache and isinstance(filters, basestring): + if cache and isinstance(filters, string_types): self.value_cache[(doctype, filters, fieldname)] = out return out @@ -670,7 +670,7 @@ class Database: delete from tabSingles where field in ({0}) and doctype=%s'''.format(', '.join(['%s']*len(keys))), - keys + [dt], debug=debug) + list(keys) + [dt], debug=debug) for key, value in iteritems(to_update): self.sql('''insert into tabSingles(doctype, field, value) values (%s, %s, %s)''', (dt, key, value), debug=debug) @@ -789,7 +789,7 @@ class Database: :param dt: DocType name. :param dn: Document name or filter dict.""" - if isinstance(dt, basestring): + if isinstance(dt, string_types): if dt!="DocType" and dt==dn: return True # single always exists (!) try: @@ -854,7 +854,7 @@ class Database: add index `%s`(%s)""" % (doctype, index_name, ", ".join(fields))) def add_unique(self, doctype, fields, constraint_name=None): - if isinstance(fields, basestring): + if isinstance(fields, string_types): fields = [fields] if not constraint_name: constraint_name = "unique_" + "_".join(fields) diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index d4b4d2c1f5..1319ffba49 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -9,7 +9,7 @@ from frappe import _ import json import random from frappe.model.document import Document -from six import iteritems +from six import iteritems, string_types class DesktopIcon(Document): @@ -171,7 +171,7 @@ def add_user_icon(_doctype, _report=None, label=None, link=None, type='link', st @frappe.whitelist() def set_order(new_order, user=None): '''set new order by duplicating user icons (if user is set) or set global order''' - if isinstance(new_order, basestring): + if isinstance(new_order, string_types): new_order = json.loads(new_order) for i, module_name in enumerate(new_order): if module_name not in ('Explore',): @@ -228,7 +228,7 @@ def set_hidden_list(hidden_list, user=None): '''Sets property `hidden`=1 in **Desktop Icon** for given user. If user is None then it will set global values. It will also set the rest of the icons as shown (`hidden` = 0)''' - if isinstance(hidden_list, basestring): + if isinstance(hidden_list, string_types): hidden_list = json.loads(hidden_list) # set as hidden diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index b77b708ae6..0e97d78fb7 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from six.moves import range +from six import string_types import frappe import json @@ -69,7 +70,7 @@ def send_event_digest(): def get_events(start, end, user=None, for_reminder=False, filters=None): if not user: user = frappe.session.user - if isinstance(filters, basestring): + if isinstance(filters, string_types): filters = json.loads(filters) roles = frappe.get_roles(user) events = frappe.db.sql("""select name, subject, description, color, diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 3a3e1d5a6b..e5192b1edb 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -7,10 +7,11 @@ from frappe.model.meta import is_single from frappe.modules import load_doctype_module import frappe.desk.form.meta import frappe.desk.form.load +from six import string_types @frappe.whitelist() def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): - if isinstance(linkinfo, basestring): + if isinstance(linkinfo, string_types): # additional fields are added in linkinfo linkinfo = json.loads(linkinfo) diff --git a/frappe/desk/form/run_method.py b/frappe/desk/form/run_method.py index 1253cc49b3..0a973c35ed 100644 --- a/frappe/desk/form/run_method.py +++ b/frappe/desk/form/run_method.py @@ -6,7 +6,7 @@ import json, inspect import frappe from frappe import _ from frappe.utils import cint -from six import text_type +from six import text_type, string_types @frappe.whitelist() def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None): @@ -62,7 +62,7 @@ def make_csv_output(res, dt): for r in res: row = [] for v in r: - if isinstance(v, basestring): + if isinstance(v, string_types): v = v.encode("utf-8") row.append(v) writer.writerow(row) diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index cdb138b325..999a83a2fe 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -7,6 +7,7 @@ import frappe.desk.form.meta import frappe.desk.form.load from frappe import _ +from six import string_types @frappe.whitelist() def remove_attach(): @@ -65,7 +66,7 @@ def get_next(doctype, value, prev, filters=None, order_by="modified desc"): sort_field, sort_order = order_by.split(" ") if not filters: filters = [] - if isinstance(filters, basestring): + if isinstance(filters, string_types): filters = json.loads(filters) # condition based on sort order diff --git a/frappe/desk/page/setup_wizard/setup_wizard.css b/frappe/desk/page/setup_wizard/setup_wizard.css index 5313a6b4bc..f61ea87863 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.css +++ b/frappe/desk/page/setup_wizard/setup_wizard.css @@ -8,13 +8,9 @@ } @media (min-width: 768px) { - .setup-wizard-slide.single-column { + .setup-wizard-slide { max-width: 500px; } - - .setup-wizard-slide.two-column { - max-width: 768px; - } } .setup-wizard-slide .lead { @@ -45,7 +41,7 @@ } .setup-wizard-slide.with-form { - margin: 30px auto; + margin: 60px auto; padding: 10px 50px; border: 1px solid #d1d8dd; box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.1); @@ -145,7 +141,6 @@ cursor: pointer; } - .setup-wizard-message-image { margin: 15px auto; } diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index ad3108b67a..e0d8f721b4 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -11,6 +11,7 @@ from frappe.utils.file_manager import save_file from frappe.utils.password import update_password from werkzeug.useragents import UserAgent import install_fixtures +from six import string_types @frappe.whitelist() def setup_complete(args): @@ -127,14 +128,14 @@ def update_user_name(args): def process_args(args): if not args: args = frappe.local.form_dict - if isinstance(args, basestring): + if isinstance(args, string_types): args = json.loads(args) args = frappe._dict(args) # strip the whitespace for key, value in args.items(): - if isinstance(value, basestring): + if isinstance(value, string_types): args[key] = strip(value) return args @@ -204,7 +205,7 @@ def load_user_details(): def prettify_args(args): # remove attachments for key, val in args.items(): - if isinstance(val, basestring) and "data:image" in val: + if isinstance(val, string_types) and "data:image" in val: filename = val.split("data:image", 1)[0].strip(", ") size = round((len(val) * 3 / 4) / 1048576.0, 2) args[key] = "Image Attached: '{0}' of size {1} MB".format(filename, size) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 073576c437..d2b7591574 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -13,6 +13,7 @@ from frappe.model.utils import render_include from frappe.translate import send_translations import frappe.desk.reportview from frappe.permissions import get_role_permissions +from six import string_types def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) @@ -70,7 +71,7 @@ def run(report_name, filters=None, user=None): if not filters: filters = [] - if filters and isinstance(filters, basestring): + if filters and isinstance(filters, string_types): filters = json.loads(filters) if not frappe.has_permission(report.ref_doctype, "report"): @@ -127,13 +128,13 @@ def export_query(): if "csrf_token" in data: del data["csrf_token"] - if isinstance(data.get("filters"), basestring): + if isinstance(data.get("filters"), string_types): filters = json.loads(data["filters"]) - if isinstance(data.get("report_name"), basestring): + if isinstance(data.get("report_name"), string_types): report_name = data["report_name"] - if isinstance(data.get("file_format_type"), basestring): + if isinstance(data.get("file_format_type"), string_types): file_format_type = data["file_format_type"] - if isinstance(data.get("visible_idx"), basestring): + if isinstance(data.get("visible_idx"), string_types): visible_idx = json.loads(data.get("visible_idx")) else: visible_idx = None @@ -181,7 +182,7 @@ def add_total_row(result, columns, meta = None): has_percent = [] for i, col in enumerate(columns): fieldtype, options = None, None - if isinstance(col, basestring): + if isinstance(col, string_types): if meta: # get fieldtype from the meta field = meta.get_field(col) @@ -214,7 +215,7 @@ def add_total_row(result, columns, meta = None): total_row[i] = flt(total_row[i]) / len(result) first_col_fieldtype = None - if isinstance(columns[0], basestring): + if isinstance(columns[0], string_types): first_col = columns[0].split(":") if len(first_col) > 1: first_col_fieldtype = first_col[1].split("/")[0] @@ -319,7 +320,7 @@ def get_linked_doctypes(columns, data): for idx, col in enumerate(columns): df = columns_dict[idx] if df.get("fieldtype")=="Link": - if isinstance(col, basestring): + if isinstance(col, string_types): linked_doctypes[df["options"]] = idx else: # dict @@ -355,7 +356,7 @@ def get_columns_dict(columns): col_dict = frappe._dict() # string - if isinstance(col, basestring): + if isinstance(col, string_types): col = col.split(":") if len(col) > 1: if "/" in col[1]: diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 19ecf3a4e5..eb699aefea 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -10,7 +10,7 @@ import frappe.permissions import MySQLdb from frappe.model.db_query import DatabaseQuery from frappe import _ -from six import text_type +from six import text_type, string_types @frappe.whitelist() def get(): @@ -31,13 +31,13 @@ def get_form_params(): if "csrf_token" in data: del data["csrf_token"] - if isinstance(data.get("filters"), basestring): + if isinstance(data.get("filters"), string_types): data["filters"] = json.loads(data["filters"]) - if isinstance(data.get("fields"), basestring): + if isinstance(data.get("fields"), string_types): data["fields"] = json.loads(data["fields"]) - if isinstance(data.get("docstatus"), basestring): + if isinstance(data.get("docstatus"), string_types): data["docstatus"] = json.loads(data["docstatus"]) - if isinstance(data.get("save_user_settings"), basestring): + if isinstance(data.get("save_user_settings"), string_types): data["save_user_settings"] = json.loads(data["save_user_settings"]) else: data["save_user_settings"] = True @@ -341,7 +341,7 @@ def build_match_conditions(doctype, as_condition=True): return match_conditions def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with_match_conditions=False): - if isinstance(filters, basestring): + if isinstance(filters, string_types): filters = json.loads(filters) if filters: @@ -350,7 +350,7 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with filters = filters.items() flt = [] for f in filters: - if isinstance(f[1], basestring) and f[1][0] == '!': + if isinstance(f[1], string_types) and f[1][0] == '!': flt.append([doctype, f[0], '!=', f[1][1:]]) elif isinstance(f[1], list) and \ f[1][0] in (">", "<", ">=", "<=", "like", "not like", "in", "not in", "between"): diff --git a/frappe/desk/search.py b/frappe/desk/search.py index d9f881e3db..e9c5c62bd5 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe, json from frappe.utils import cstr, unique from frappe import _ +from six import string_types # this is called by the Link Field @frappe.whitelist() @@ -18,7 +19,7 @@ def search_link(doctype, txt, query=None, filters=None, page_length=20, searchfi @frappe.whitelist() def search_widget(doctype, txt, query=None, searchfield=None, start=0, page_length=10, filters=None, filter_fields=None, as_dict=False): - if isinstance(filters, basestring): + if isinstance(filters, string_types): filters = json.loads(filters) meta = frappe.get_meta(doctype) diff --git a/frappe/email/doctype/email_alert/email_alert.json b/frappe/email/doctype/email_alert/email_alert.json index 095f0a9c18..197705b89e 100755 --- a/frappe/email/doctype/email_alert/email_alert.json +++ b/frappe/email/doctype/email_alert/email_alert.json @@ -681,7 +681,7 @@ "collapsible": 0, "columns": 0, "default": "Add your message here", - "depends_on": "eval:!doc.is_standard", + "depends_on": "", "fieldname": "message", "fieldtype": "Code", "hidden": 0, @@ -808,7 +808,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-07-07 16:09:48.804218", + "modified": "2017-08-13 22:43:49.079330", "modified_by": "Administrator", "module": "Email", "name": "Email Alert", diff --git a/frappe/email/doctype/email_alert/email_alert.py b/frappe/email/doctype/email_alert/email_alert.py index f4bce6f5cd..4dfa877856 100755 --- a/frappe/email/doctype/email_alert/email_alert.py +++ b/frappe/email/doctype/email_alert/email_alert.py @@ -7,13 +7,18 @@ import json, os from frappe import _ from frappe.model.document import Document from frappe.core.doctype.role.role import get_emails_from_role -from frappe.utils import validate_email_add, nowdate -from frappe.utils.data import parse_val +from frappe.utils import validate_email_add, nowdate, parse_val, is_html from frappe.utils.jinja import validate_template from frappe.modules.utils import export_module_json, get_doc_module from markdown2 import markdown +from six import string_types class EmailAlert(Document): + def onload(self): + '''load message''' + if self.is_standard: + self.message = self.get_template() + def autoname(self): if not self.name: self.name = self.subject @@ -30,6 +35,7 @@ class EmailAlert(Document): self.validate_forbidden_types() self.validate_condition() + self.validate_standard() def on_update(self): frappe.cache().hdel('email_alerts', self.document_type) @@ -52,6 +58,10 @@ def get_context(context): pass """) + def validate_standard(self): + if self.is_standard and not frappe.conf.developer_mode: + frappe.throw(_('Cannot edit Standard Email Alert. To edit, please disable this and duplicate it')) + def validate_condition(self): temp_doc = frappe.new_doc(self.document_type) if self.condition: @@ -164,26 +174,31 @@ def get_context(context): self.property_value, update_modified = False) doc.set(self.set_property_after_alert, self.property_value) + def get_template(self): + module = get_doc_module(self.module, self.doctype, self.name) + def load_template(extn): + template = '' + template_path = os.path.join(os.path.dirname(module.__file__), + frappe.scrub(self.name) + extn) + if os.path.exists(template_path): + with open(template_path, 'r') as f: + template = f.read() + return template + + return load_template('.html') or load_template('.md') + def load_standard_properties(self, context): + '''load templates and run get_context''' module = get_doc_module(self.module, self.doctype, self.name) if module: if hasattr(module, 'get_context'): out = module.get_context(context) if out: context.update(out) - def load_template(extn): - template_path = os.path.join(os.path.dirname(module.__file__), - frappe.scrub(self.name) + extn) - if os.path.exists(template_path): - with open(template_path, 'r') as f: - self.message = f.read() - return True - - # get template - if not load_template('.html'): - if load_template('.md'): - self.message = markdown(self.message) + self.message = self.get_template() + if not is_html(self.message): + self.message = markdown(self.message) @frappe.whitelist() def get_documents_for_today(email_alert): @@ -210,7 +225,7 @@ def trigger_email_alerts(doc, method=None): def evaluate_alert(doc, alert, event): from jinja2 import TemplateError try: - if isinstance(alert, basestring): + if isinstance(alert, string_types): alert = frappe.get_doc("Email Alert", alert) context = get_context(doc) diff --git a/frappe/email/doctype/email_alert/test_email_alert.js b/frappe/email/doctype/email_alert/test_email_alert.js new file mode 100644 index 0000000000..58b0de5f14 --- /dev/null +++ b/frappe/email/doctype/email_alert/test_email_alert.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Email Alert", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Email Alert + () => frappe.tests.make('Email Alert', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 04790de8b6..1813b01c53 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -72,13 +72,13 @@ class Newsletter(Document): files = frappe.get_all("File", fields = ["name"], filters = {"attached_to_doctype": "Newsletter", "attached_to_name":self.name}, order_by="creation desc") - for a in files: + for file in files: try: # these attachments will be attached on-demand # and won't be stored in the message attachments.append({"fid": file.name}) except IOError: - frappe.throw(_("Unable to find attachment {0}").format(a)) + frappe.throw(_("Unable to find attachment {0}").format(file.name)) send(recipients = self.recipients, sender = sender, subject = self.subject, message = self.message, diff --git a/frappe/email/doctype/standard_reply/standard_reply.py b/frappe/email/doctype/standard_reply/standard_reply.py index 5ca40005a2..17828cb352 100755 --- a/frappe/email/doctype/standard_reply/standard_reply.py +++ b/frappe/email/doctype/standard_reply/standard_reply.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, json from frappe.model.document import Document from frappe.utils.jinja import validate_template +from six import string_types class StandardReply(Document): def validate(self): @@ -13,7 +14,7 @@ class StandardReply(Document): @frappe.whitelist() def get_standard_reply(template_name, doc): '''Returns the processed HTML of a standard reply with the given doc''' - if isinstance(doc, basestring): + if isinstance(doc, string_types): doc = json.loads(doc) standard_reply = frappe.get_doc("Standard Reply", template_name) diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 12deec3427..b589986bc0 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -8,7 +8,7 @@ from frappe.email.smtp import get_outgoing_email_account from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint, split_emails, to_markdown, markdown, encode, random_string, parse_addr) import email.utils -from six import iteritems, text_type +from six import iteritems, text_type, string_types from email.mime.multipart import MIMEMultipart @@ -54,7 +54,7 @@ class EMail: from email import Charset Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') - if isinstance(recipients, basestring): + if isinstance(recipients, string_types): recipients = recipients.replace(';', ',').replace('\n', '') recipients = split_emails(recipients) @@ -432,7 +432,7 @@ def get_header(header=None): if not header: return None - if isinstance(header, basestring): + if isinstance(header, string_types): # header = 'My Title' header = [header, None] if len(header) == 1: diff --git a/frappe/email/queue.py b/frappe/email/queue.py index 5ef75a0df7..0452cba5bc 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -15,7 +15,7 @@ from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split from frappe.utils.file_manager import get_file from rq.timeouts import JobTimeoutException from frappe.utils.scheduler import log -from six import text_type +from six import text_type, string_types class EmailLimitCrossedError(frappe.ValidationError): pass @@ -55,10 +55,10 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= if not recipients and not cc: return - if isinstance(recipients, basestring): + if isinstance(recipients, string_types): recipients = split_emails(recipients) - if isinstance(cc, basestring): + if isinstance(cc, string_types): cc = split_emails(cc) if isinstance(send_after, int): @@ -484,7 +484,7 @@ def prepare_message(email, recipient, recipients_list): pass else: if email.expose_recipients == "footer": - if isinstance(email.show_as_cc, basestring): + if isinstance(email.show_as_cc, string_types): 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]) diff --git a/frappe/frappeclient.py b/frappe/frappeclient.py index 30c71956dd..b92463f474 100644 --- a/frappe/frappeclient.py +++ b/frappe/frappeclient.py @@ -2,7 +2,7 @@ from __future__ import print_function import requests import json import frappe -from six import iteritems +from six import iteritems, string_types ''' FrappeClient is a library that helps you connect with other frappe systems @@ -49,7 +49,7 @@ class FrappeClient(object): def get_list(self, doctype, fields='"*"', filters=None, limit_start=0, limit_page_length=0): """Returns list of records of a particular type""" - if not isinstance(fields, basestring): + if not isinstance(fields, string_types): fields = json.dumps(fields) params = { "fields": fields, diff --git a/frappe/handler.py b/frappe/handler.py index 4845b4d725..98d88297fb 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -11,6 +11,7 @@ import frappe.utils.file_manager import frappe.desk.form.run_method from frappe.utils.response import build_response from werkzeug.wrappers import Response +from six import string_types def handle(): """handle request""" @@ -63,7 +64,7 @@ def is_whitelisted(method): # strictly sanitize form_dict # escapes html characters like <> except for predefined tags like a, b, ul etc. for key, value in frappe.form_dict.items(): - if isinstance(value, basestring): + if isinstance(value, string_types): frappe.form_dict[key] = frappe.utils.sanitize_html(value) else: diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py index b9d143b95f..36eff70fa1 100644 --- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py +++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py @@ -60,9 +60,8 @@ import frappe import json from frappe import _ from frappe.utils import get_url, call_hook_method, cint -from urllib import urlencode +from six.moves.urllib.parse import urlencode from frappe.model.document import Document -import urllib from frappe.integrations.utils import create_request_log, make_post_request, create_payment_gateway class PayPalSettings(Document): @@ -237,9 +236,9 @@ def confirm_payment(token): redirect_url = "/integrations/payment-failed" if redirect_to: - redirect_url += '?' + urllib.urlencode({'redirect_to': redirect_to}) + redirect_url += '?' + urlencode({'redirect_to': redirect_to}) if redirect_message: - redirect_url += '&' + urllib.urlencode({'redirect_message': redirect_message}) + redirect_url += '&' + urlencode({'redirect_message': redirect_message}) # this is done so that functions called via hooks can update flags.redirect_to if redirect: diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index c982424c96..9a79ef9bec 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -55,7 +55,8 @@ For razorpay payment status is Authorized from __future__ import unicode_literals import frappe from frappe import _ -import urllib, json +import json +from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway @@ -82,7 +83,7 @@ class RazorpaySettings(Document): frappe.throw(_("Please select another payment method. Razorpay does not support transactions in currency '{0}'").format(currency)) def get_payment_url(self, **kwargs): - return get_url("./integrations/razorpay_checkout?{0}".format(urllib.urlencode(kwargs))) + return get_url("./integrations/razorpay_checkout?{0}".format(urlencode(kwargs))) def create_request(self, data): self.data = frappe._dict(data) @@ -146,9 +147,9 @@ class RazorpaySettings(Document): redirect_url = 'payment-failed' if redirect_to: - redirect_url += '?' + urllib.urlencode({'redirect_to': redirect_to}) + redirect_url += '?' + urlencode({'redirect_to': redirect_to}) if redirect_message: - redirect_url += '&' + urllib.urlencode({'redirect_message': redirect_message}) + redirect_url += '&' + urlencode({'redirect_message': redirect_message}) return { "redirect_to": redirect_url, diff --git a/frappe/integrations/doctype/stripe_settings/stripe_settings.py b/frappe/integrations/doctype/stripe_settings/stripe_settings.py index 9966ff1049..d72b435667 100644 --- a/frappe/integrations/doctype/stripe_settings/stripe_settings.py +++ b/frappe/integrations/doctype/stripe_settings/stripe_settings.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -import urllib +from six.moves.urllib.parse import urlencode from frappe.utils import get_url, call_hook_method, cint from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway @@ -42,7 +42,7 @@ class StripeSettings(Document): frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency)) def get_payment_url(self, **kwargs): - return get_url("./integrations/stripe_checkout?{0}".format(urllib.urlencode(kwargs))) + return get_url("./integrations/stripe_checkout?{0}".format(urlencode(kwargs))) def create_request(self, data): self.data = frappe._dict(data) @@ -105,9 +105,9 @@ class StripeSettings(Document): redirect_url = 'payment-failed' if redirect_to: - redirect_url += '?' + urllib.urlencode({'redirect_to': redirect_to}) + redirect_url += '?' + urlencode({'redirect_to': redirect_to}) if redirect_message: - redirect_url += '&' + urllib.urlencode({'redirect_message': redirect_message}) + redirect_url += '&' + urlencode({'redirect_message': redirect_message}) return { "redirect_to": redirect_url, diff --git a/frappe/integrations/oauth2.py b/frappe/integrations/oauth2.py index 6889633c9b..7a5b616395 100644 --- a/frappe/integrations/oauth2.py +++ b/frappe/integrations/oauth2.py @@ -2,10 +2,8 @@ from __future__ import unicode_literals import frappe, json from frappe.oauth import OAuthWebRequestValidator, WebApplicationServer from oauthlib.oauth2 import FatalClientError, OAuth2Error -from urllib import urlencode -from six.moves.urllib.parse import quote from werkzeug import url_fix -from six.moves.urllib.parse import urlparse +from six.moves.urllib.parse import quote, urlencode, urlparse from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import get_oauth_settings from frappe import _ diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py index 8215eefd7b..c79a190e94 100644 --- a/frappe/integrations/utils.py +++ b/frappe/integrations/utils.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe import json from six.moves.urllib.parse import parse_qs +from six import string_types from frappe.utils import get_request_session from frappe import _ @@ -49,7 +50,7 @@ def make_post_request(url, auth=None, headers=None, data=None): raise exc def create_request_log(data, integration_type, service_name, name=None): - if isinstance(data, basestring): + if isinstance(data, string_types): data = json.loads(data) integration_request = frappe.get_doc({ diff --git a/frappe/limits.py b/frappe/limits.py index 0f597e445f..0330db2567 100755 --- a/frappe/limits.py +++ b/frappe/limits.py @@ -5,8 +5,9 @@ from frappe.utils import now_datetime, getdate, flt, cint, get_fullname from frappe.installer import update_site_config from frappe.utils.data import formatdate from frappe.utils.user import get_enabled_system_users, disable_users -import os, subprocess, urllib -from six.moves.urllib.parse import parse_qsl, urlsplit, urlunsplit +import os, subprocess +from six.moves.urllib.parse import parse_qsl, urlsplit, urlunsplit, urlencode +from six import string_types class SiteExpiredError(frappe.ValidationError): http_status_code = 417 @@ -131,7 +132,7 @@ def get_upgrade_url(upgrade_url): 'country': frappe.db.get_value("System Settings", "System Settings", 'country') }) - query = urllib.urlencode(params, doseq=True) + query = urlencode(params, doseq=True) url = urlunsplit((parts.scheme, parts.netloc, parts.path, query, parts.fragment)) return url @@ -162,7 +163,7 @@ def update_limits(limits_dict): def clear_limit(key): '''Remove a limit option from site_config''' limits = get_limits() - to_clear = [key] if isinstance(key, basestring) else key + to_clear = [key] if isinstance(key, string_types) else key for key in to_clear: if key in limits: del limits[key] diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 0151feac8e..4a5568b238 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -2,7 +2,7 @@ # MIT License. See license.txt from __future__ import unicode_literals -from six import reraise as raise_, iteritems +from six import iteritems, string_types import frappe, sys from frappe import _ from frappe.utils import (cint, flt, now, cstr, strip_html, getdate, get_datetime, to_timedelta, @@ -296,7 +296,7 @@ class BaseDocument(object): doctype = self.doctype, columns = ", ".join(["`"+c+"`" for c in columns]), values = ", ".join(["%s"] * len(columns)) - ), d.values()) + ), list(d.values())) except Exception as e: if e.args[0]==1062: if "PRIMARY" in cstr(e.args[1]): @@ -307,8 +307,7 @@ class BaseDocument(object): return frappe.msgprint(_("Duplicate name {0} {1}").format(self.doctype, self.name)) - traceback = sys.exc_info()[2] - raise_(frappe.DuplicateEntryError, (self.doctype, self.name, e), traceback) + raise frappe.DuplicateEntryError(self.doctype, self.name, e) elif "Duplicate" in cstr(e.args[1]): # unique constraint @@ -338,7 +337,7 @@ class BaseDocument(object): set {values} where name=%s""".format( doctype = self.doctype, values = ", ".join(["`"+c+"`=%s" for c in columns]) - ), d.values() + [name]) + ), list(d.values()) + [name]) except Exception as e: if e.args[0]==1062 and "Duplicate" in cstr(e.args[1]): self.show_unique_validation_message(e) @@ -361,7 +360,7 @@ class BaseDocument(object): frappe.msgprint(_("{0} must be unique".format(label or fieldname))) # this is used to preserve traceback - raise_(frappe.UniqueValidationError, (self.doctype, self.name, e), traceback) + raise frappe.UniqueValidationError(self.doctype, self.name, e) def db_set(self, fieldname, value=None, update_modified=True): '''Set a value in the document object, update the timestamp and update the database. @@ -610,7 +609,7 @@ class BaseDocument(object): return for fieldname, value in self.get_valid_dict().items(): - if not value or not isinstance(value, basestring): + if not value or not isinstance(value, string_types): continue value = frappe.as_unicode(value) @@ -673,7 +672,7 @@ class BaseDocument(object): :param parentfield: If fieldname is in child table.""" from frappe.model.meta import get_field_precision - if parentfield and not isinstance(parentfield, basestring): + if parentfield and not isinstance(parentfield, string_types): parentfield = parentfield.parentfield cache_key = parentfield or "main" @@ -831,7 +830,7 @@ def _filter(data, filters, limit=None): fval = ("not None", fval) elif fval is False: fval = ("None", fval) - elif isinstance(fval, basestring) and fval.startswith("^"): + elif isinstance(fval, string_types) and fval.startswith("^"): fval = ("^", fval[1:]) else: fval = ("=", fval) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 205fd3a1e7..0cd71684c9 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals -from six import iteritems +from six import iteritems, string_types """build query for doclistview and return results""" @@ -37,7 +37,7 @@ class DatabaseQuery(object): update=None, add_total_row=None, user_settings=None): if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user): frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype)) - raise frappe.PermissionError, self.doctype + raise frappe.PermissionError(self.doctype) # fitlers and fields swappable # its hard to remember what comes first @@ -47,7 +47,7 @@ class DatabaseQuery(object): filters, fields = fields, filters elif fields and isinstance(filters, list) \ - and len(filters) > 1 and isinstance(filters[0], basestring): + and len(filters) > 1 and isinstance(filters[0], string_types): # if `filters` is a list of strings, its probably fields filters, fields = fields, filters @@ -157,7 +157,7 @@ class DatabaseQuery(object): def parse_args(self): """Convert fields and filters from strings to list, dicts""" - if isinstance(self.fields, basestring): + if isinstance(self.fields, string_types): if self.fields == "*": self.fields = ["*"] else: @@ -168,7 +168,7 @@ class DatabaseQuery(object): for filter_name in ["filters", "or_filters"]: filters = getattr(self, filter_name) - if isinstance(filters, basestring): + if isinstance(filters, string_types): filters = json.loads(filters) if isinstance(filters, dict): @@ -203,7 +203,7 @@ class DatabaseQuery(object): doctype = table_name[4:-1] if (not self.flags.ignore_permissions) and (not frappe.has_permission(doctype)): frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(doctype)) - raise frappe.PermissionError, doctype + raise frappe.PermissionError(doctype) def set_field_tables(self): '''If there are more than one table, the fieldname must not be ambigous. @@ -230,7 +230,7 @@ class DatabaseQuery(object): # remove from filters to_remove = [] for each in self.filters: - if isinstance(each, basestring): + if isinstance(each, string_types): each = [each] for element in each: @@ -264,7 +264,7 @@ class DatabaseQuery(object): filters = [filters] for f in filters: - if isinstance(f, basestring): + if isinstance(f, string_types): conditions.append(f) else: conditions.append(self.prepare_filter_condition(f)) @@ -331,12 +331,12 @@ class DatabaseQuery(object): value = get_time(f.value).strftime("%H:%M:%S.%f") fallback = "'00:00:00'" - elif f.operator.lower() in ("like", "not like") or (isinstance(f.value, basestring) and + elif f.operator.lower() in ("like", "not like") or (isinstance(f.value, string_types) and (not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])): value = "" if f.value==None else f.value fallback = '""' - if f.operator.lower() in ("like", "not like") and isinstance(value, basestring): + if f.operator.lower() in ("like", "not like") and isinstance(value, string_types): # because "like" uses backslash (\) for escaping value = value.replace("\\", "\\\\").replace("%", "%%") @@ -345,7 +345,7 @@ class DatabaseQuery(object): fallback = 0 # put it inside double quotes - if isinstance(value, basestring) and not f.operator.lower() == 'between': + if isinstance(value, string_types) and not f.operator.lower() == 'between': value = '"{0}"'.format(frappe.db.escape(value, percent=False)) if (self.ignore_ifnull diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index 37d644883e..e47c678e4c 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -127,7 +127,7 @@ class DbTable: else: raise - if max_length and max_length[0][0] > new_length: + if max_length and max_length[0][0] and max_length[0][0] > new_length: current_type = self.current_columns[col.fieldname]["type"] current_length = re.findall('varchar\(([\d]+)\)', current_type) if not current_length: diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 2994b7c9aa..802a2c665c 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -12,6 +12,7 @@ from frappe.utils.password import delete_all_passwords_for from frappe import _ from frappe.model.naming import revert_series_if_last from frappe.utils.global_search import delete_for_document +from six import string_types def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False, flags=None, ignore_on_trash=False): @@ -26,7 +27,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa name = frappe.form_dict.get('dn') names = name - if isinstance(name, basestring): + if isinstance(name, string_types): names = [name] for name in names or []: diff --git a/frappe/model/document.py b/frappe/model/document.py index 3e8db22802..20627a128e 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -9,7 +9,7 @@ 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 six import iteritems +from six import iteritems, string_types from werkzeug.exceptions import NotFound, Forbidden import hashlib, json from frappe.model import optional_fields @@ -41,7 +41,7 @@ def get_doc(arg1, arg2=None): """ if isinstance(arg1, BaseDocument): return arg1 - elif isinstance(arg1, basestring): + elif isinstance(arg1, string_types): doctype = arg1 else: doctype = arg1.get("doctype") @@ -67,7 +67,7 @@ class Document(BaseDocument): self._default_new_docs = {} self.flags = frappe._dict() - if arg1 and isinstance(arg1, basestring): + if arg1 and isinstance(arg1, string_types): if not arg2: # single self.doctype = self.name = arg1 @@ -662,7 +662,7 @@ class Document(BaseDocument): # hack! to run hooks even if method does not exist fn = lambda self, *args, **kwargs: None - fn.__name__ = method.encode("utf-8") + fn.__name__ = str(method) out = Document.hook(fn)(self, *args, **kwargs) self.run_email_alerts(method) diff --git a/frappe/model/mapper.py b/frappe/model/mapper.py index 72a9a77e3c..8f77668204 100644 --- a/frappe/model/mapper.py +++ b/frappe/model/mapper.py @@ -6,6 +6,7 @@ import frappe, json from frappe import _ from frappe.utils import cstr from frappe.model import default_fields +from six import string_types @frappe.whitelist() def make_mapped_doc(method, source_name, selected_children=None): @@ -43,7 +44,7 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, # main if not target_doc: target_doc = frappe.new_doc(table_maps[from_doctype]["doctype"]) - elif isinstance(target_doc, basestring): + elif isinstance(target_doc, string_types): target_doc = frappe.get_doc(json.loads(target_doc)) if not ignore_permissions and not target_doc.has_permission("create"): diff --git a/frappe/model/naming.py b/frappe/model/naming.py index c47422c3ec..6ccc5a0a19 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -6,6 +6,7 @@ import frappe from frappe import _ from frappe.utils import now_datetime, cint import re +from six import string_types def set_new_name(doc): """ @@ -99,7 +100,7 @@ def make_autoname(key='', doctype='', doc=''): def parse_naming_series(parts, doctype= '', doc = ''): n = '' - if isinstance(parts, basestring): + if isinstance(parts, string_types): parts = parts.split('.') series_set = False @@ -123,7 +124,7 @@ def parse_naming_series(parts, doctype= '', doc = ''): part = doc.get(e) else: part = e - if isinstance(part, basestring): + if isinstance(part, string_types): n+=part return n diff --git a/frappe/model/sync.py b/frappe/model/sync.py index c002136321..9c80a946a1 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -42,7 +42,7 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe if l: for i, doc_path in enumerate(files): import_file_by_path(doc_path, force=force, ignore_version=True, - reset_permissions=reset_permissions) + reset_permissions=reset_permissions, for_sync=True) #print module_name + ' | ' + doctype + ' | ' + name frappe.db.commit() @@ -56,8 +56,9 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=False): """walk and sync all doctypes and pages""" - document_type = ['doctype', 'page', 'report', 'print_format', 'website_theme', 'web_form', 'email_alert'] - for doctype in document_type: + document_types = ['doctype', 'page', 'report', 'print_format', + 'website_theme', 'web_form', 'email_alert', 'print_style'] + for doctype in document_types: doctype_path = os.path.join(start_path, doctype) if os.path.exists(doctype_path): diff --git a/frappe/model/utils/user_settings.py b/frappe/model/utils/user_settings.py index 1034334049..bbbd9298f7 100644 --- a/frappe/model/utils/user_settings.py +++ b/frappe/model/utils/user_settings.py @@ -2,7 +2,7 @@ # such as page_limit, filters, last_view import frappe, json -from six import iteritems +from six import iteritems, string_types def get_user_settings(doctype, for_update=False): @@ -27,7 +27,7 @@ def update_user_settings(doctype, user_settings, for_update=False): else: current = json.loads(get_user_settings(doctype, for_update = True)) - if isinstance(current, basestring): + if isinstance(current, string_types): # corrupt due to old code, remove this in a future release current = {} diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 57c671b407..ade3614c8e 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -33,7 +33,7 @@ def get_file_path(module, dt, dn): return path def import_file_by_path(path, force=False, data_import=False, pre_process=None, ignore_version=None, - reset_permissions=False): + reset_permissions=False, for_sync=False): try: docs = read_doc_from_file(path) except IOError: @@ -86,7 +86,9 @@ def read_doc_from_file(path): ignore_values = { "Report": ["disabled"], - "Print Format": ["disabled"] + "Print Format": ["disabled"], + "Email Alert": ["enabled"], + "Print Style": ["disabled"] } ignore_doctypes = [""] diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 79933a60a5..9e43a28c6f 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -10,8 +10,9 @@ import frappe.utils from frappe import _ lower_case_files_for = ['DocType', 'Page', 'Report', - "Workflow", 'Module Def', 'Desktop Item', 'Workflow State', 'Workflow Action', 'Print Format', - "Website Theme", 'Web Form', 'Email Alert'] + "Workflow", 'Module Def', 'Desktop Item', 'Workflow State', + 'Workflow Action', 'Print Format', "Website Theme", 'Web Form', + 'Email Alert', 'Print Style'] def export_module_json(doc, is_standard, module): """Make a folder for the given doc and add its json file (make it a standard diff --git a/frappe/patches/v5_0/bookmarks_to_stars.py b/frappe/patches/v5_0/bookmarks_to_stars.py index 603697a1b7..048d059701 100644 --- a/frappe/patches/v5_0/bookmarks_to_stars.py +++ b/frappe/patches/v5_0/bookmarks_to_stars.py @@ -3,6 +3,7 @@ import json import frappe import frappe.defaults from frappe.desk.like import _toggle_like +from six import string_types def execute(): for user in frappe.get_all("User"): @@ -12,7 +13,7 @@ def execute(): if not bookmarks: continue - if isinstance(bookmarks, basestring): + if isinstance(bookmarks, string_types): bookmarks = json.loads(bookmarks) for opts in bookmarks: diff --git a/frappe/permissions.py b/frappe/permissions.py index 29f223d08e..2dc57253e9 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals, print_function from six.moves import range +from six import string_types import frappe, copy, json from frappe import _, msgprint from frappe.utils import cint @@ -51,7 +52,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): ["read" if ptype in ("email", "print") else ptype]) if doc: - doc_name = doc if isinstance(doc, basestring) else doc.name + doc_name = doc if isinstance(doc, string_types) else doc.name if doc_name in shared: if verbose: print("Shared") if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): @@ -75,7 +76,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): perm = True if doc: - if isinstance(doc, basestring): + if isinstance(doc, string_types): doc = frappe.get_doc(meta.name, doc) owner_perm = user_perm = controller_perm = None diff --git a/frappe/printing/doctype/print_format/test_print_format.py b/frappe/printing/doctype/print_format/test_print_format.py index a32070e97b..e8375ae5e7 100644 --- a/frappe/printing/doctype/print_format/test_print_format.py +++ b/frappe/printing/doctype/print_format/test_print_format.py @@ -23,8 +23,8 @@ class TestPrintFormat(unittest.TestCase): def test_print_user_modern(self): print_html = self.test_print_user("Modern") - self.assertTrue("/* modern format: don't remove this line */" in print_html) + self.assertTrue("/* modern format: for-test */" in print_html) def test_print_user_classic(self): print_html = self.test_print_user("Classic") - self.assertTrue("font-family: serif;" in print_html) + self.assertTrue("/* classic format: for-test */" in print_html) diff --git a/frappe/printing/doctype/print_settings/print_settings.js b/frappe/printing/doctype/print_settings/print_settings.js index 5840c6930d..ef552e6d30 100644 --- a/frappe/printing/doctype/print_settings/print_settings.js +++ b/frappe/printing/doctype/print_settings/print_settings.js @@ -2,8 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on("Print Settings", "print_style", function (frm) { - frm.get_field("print_style_preview").html(''); + frappe.db.get_value('Print Style', frm.doc.print_style, 'preview').then((r) => { + if(r.message.preview) { + frm.get_field("print_style_preview").$wrapper.html( + ``); + } else { + frm.get_field("print_style_preview").$wrapper.html( + `

${__("No Preview")}

`); + } + }); }); frappe.ui.form.on("Print Settings", "onload", function (frm) { diff --git a/frappe/printing/doctype/print_settings/print_settings.json b/frappe/printing/doctype/print_settings/print_settings.json index b191ac663a..7e1bfa6861 100644 --- a/frappe/printing/doctype/print_settings/print_settings.json +++ b/frappe/printing/doctype/print_settings/print_settings.json @@ -177,6 +177,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Page Settings", "length": 0, "no_copy": 0, "permlevel": 0, @@ -191,188 +192,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "description": "", - "fieldname": "attach_view_link", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Send document web view link in email", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "print_style_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Print Style", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Modern", - "fieldname": "print_style", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Print Style", - "length": 0, - "no_copy": 0, - "options": "Modern\nClassic\nStandard\nMonochrome", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Default", - "fieldname": "font", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Font", - "length": 0, - "no_copy": 0, - "options": "Default\nArial\nHelvetica\nVerdana\nMonospace", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "In points. Default is 9.", - "fieldname": "font_size", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Font Size", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 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, - "set_only_once": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -436,6 +255,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "description": "", + "fieldname": "attach_view_link", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Send document web view link in email", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 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_bulk_edit": 0, "allow_on_submit": 0, @@ -467,6 +347,36 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "allow_page_break_inside_tables", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allow page break inside tables", + "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_bulk_edit": 0, "allow_on_submit": 0, @@ -504,8 +414,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "allow_page_break_inside_tables", - "fieldtype": "Check", + "fieldname": "print_style_section", + "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -513,11 +423,10 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Allow page break inside tables", + "label": "Print Style", "length": 0, "no_copy": 0, "permlevel": 0, - "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -534,17 +443,20 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", + "default": "Modern", + "fieldname": "print_style", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, + "label": "Print Style", "length": 0, "no_copy": 0, + "options": "Print Style", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -584,6 +496,97 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 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_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Fonts", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Default", + "fieldname": "font", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Font", + "length": 0, + "no_copy": 0, + "options": "Default\nArial\nHelvetica\nVerdana\nMonospace", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "In points. Default is 9.", + "fieldname": "font_size", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Font Size", + "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 } ], "has_web_view": 0, @@ -597,7 +600,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-05-03 05:58:55.562540", + "modified": "2017-08-18 01:04:26.692081", "modified_by": "Administrator", "module": "Printing", "name": "Print Settings", diff --git a/frappe/printing/doctype/print_settings/test_print_settings.js b/frappe/printing/doctype/print_settings/test_print_settings.js new file mode 100644 index 0000000000..af61095e97 --- /dev/null +++ b/frappe/printing/doctype/print_settings/test_print_settings.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Print Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Print Settings + () => frappe.tests.make('Print Settings', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/printing/doctype/print_style/__init__.py b/frappe/printing/doctype/print_style/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/printing/doctype/print_style/print_style.js b/frappe/printing/doctype/print_style/print_style.js new file mode 100644 index 0000000000..44c4a528f4 --- /dev/null +++ b/frappe/printing/doctype/print_style/print_style.js @@ -0,0 +1,10 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Print Style', { + refresh: function(frm) { + frm.add_custom_button(__('Print Settings'), () => { + frappe.set_route('Form', 'Print Settings'); + }) + } +}); diff --git a/frappe/printing/doctype/print_style/print_style.json b/frappe/printing/doctype/print_style/print_style.json new file mode 100644 index 0000000000..29e88a460a --- /dev/null +++ b/frappe/printing/doctype/print_style/print_style.json @@ -0,0 +1,214 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 1, + "autoname": "field:print_style_name", + "beta": 0, + "creation": "2017-08-17 01:25:56.910716", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "print_style_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Print Style Name", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "disabled", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Disabled", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "standard", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Standard", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "css", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "CSS", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "preview", + "fieldtype": "Attach Image", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Preview", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_field": "preview", + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-08-17 02:18:08.132853", + "modified_by": "Administrator", + "module": "Printing", + "name": "Print Style", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/printing/doctype/print_style/print_style.py b/frappe/printing/doctype/print_style/print_style.py new file mode 100644 index 0000000000..310babd5df --- /dev/null +++ b/frappe/printing/doctype/print_style/print_style.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, 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 PrintStyle(Document): + def validate(self): + if (self.standard==1 + and not frappe.local.conf.get("developer_mode") + and not (frappe.flags.in_import or frappe.flags.in_test)): + + frappe.throw(frappe._("Standard Print Style cannot be changed. Please duplicate to edit.")) + + def on_update(self): + self.export_doc() + + def export_doc(self): + # export + from frappe.modules.utils import export_module_json + export_module_json(self, self.standard == 1, 'Printing') diff --git a/frappe/printing/doctype/print_style/test_print_style.js b/frappe/printing/doctype/print_style/test_print_style.js new file mode 100644 index 0000000000..d676a0c831 --- /dev/null +++ b/frappe/printing/doctype/print_style/test_print_style.js @@ -0,0 +1,20 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Print Style", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Print Style + () => frappe.tests.make('Print Style', [ + // values to be set + {print_style_name: 'Test Print Style'}, + {css: '/* some css value */'} + ]), + ]); + +}); diff --git a/frappe/printing/doctype/print_style/test_print_style.py b/frappe/printing/doctype/print_style/test_print_style.py new file mode 100644 index 0000000000..cee57f8826 --- /dev/null +++ b/frappe/printing/doctype/print_style/test_print_style.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestPrintStyle(unittest.TestCase): + pass diff --git a/frappe/printing/print_style/__init__.py b/frappe/printing/print_style/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/printing/print_style/classic/__init__.py b/frappe/printing/print_style/classic/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/printing/print_style/classic/classic.json b/frappe/printing/print_style/classic/classic.json new file mode 100644 index 0000000000..1ad609a498 --- /dev/null +++ b/frappe/printing/print_style/classic/classic.json @@ -0,0 +1,15 @@ +{ + "creation": "2017-08-17 02:00:12.502887", + "css": "/*\n\tcommon style for whole page\n\tThis should include:\n\t+ page size related settings\n\t+ font family settings\n\t+ line spacing settings\n*/\n.print-format div,\n.print-format span,\n.print-format td,\n.print-format h1,\n.print-format h2,\n.print-format h3,\n.print-format h4 {\n\tfont-family: Georgia, serif;\n}\n\n/* classic format: for-test */", + "disabled": 0, + "docstatus": 0, + "doctype": "Print Style", + "idx": 1, + "modified": "2017-08-18 00:43:48.675833", + "modified_by": "Administrator", + "name": "Classic", + "owner": "Administrator", + "preview": "/assets/frappe/images/help/print-style-classic.png", + "print_style_name": "Classic", + "standard": 1 +} \ No newline at end of file diff --git a/frappe/printing/print_style/modern/__init__.py b/frappe/printing/print_style/modern/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/printing/print_style/modern/modern.json b/frappe/printing/print_style/modern/modern.json new file mode 100644 index 0000000000..2d79ab6e62 --- /dev/null +++ b/frappe/printing/print_style/modern/modern.json @@ -0,0 +1,15 @@ +{ + "creation": "2017-08-17 02:16:58.060374", + "css": ".print-heading {\n\ttext-align: right;\n\ttext-transform: uppercase;\n\tcolor: #666;\n\tpadding-bottom: 20px;\n\tmargin-bottom: 20px;\n\tborder-bottom: 1px solid #d1d8dd;\n}\n\n.print-heading h2 {\n\tfont-size: 24px;\n}\n\n.print-format th {\n\tbackground-color: #eee !important;\n\tborder-bottom: 0px !important;\n}\n\n/* modern format: for-test */", + "disabled": 0, + "docstatus": 0, + "doctype": "Print Style", + "idx": 0, + "modified": "2017-08-18 00:44:07.438147", + "modified_by": "Administrator", + "name": "Modern", + "owner": "Administrator", + "preview": "/assets/frappe/images/help/print-style-modern.png", + "print_style_name": "Modern", + "standard": 1 +} \ No newline at end of file diff --git a/frappe/printing/print_style/monochrome/__init__.py b/frappe/printing/print_style/monochrome/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/printing/print_style/monochrome/monochrome.json b/frappe/printing/print_style/monochrome/monochrome.json new file mode 100644 index 0000000000..eb75bc7211 --- /dev/null +++ b/frappe/printing/print_style/monochrome/monochrome.json @@ -0,0 +1,15 @@ +{ + "creation": "2017-08-17 02:16:20.992989", + "css": ".print-format * {\n\tcolor: #000 !important;\n}\n\n.print-format .alert {\n\tbackground-color: inherit;\n\tborder: 1px dashed #333;\n}\n\n.print-format .table-bordered,\n.print-format .table-bordered > thead > tr > th,\n.print-format .table-bordered > tbody > tr > th,\n.print-format .table-bordered > tfoot > tr > th,\n.print-format .table-bordered > thead > tr > td,\n.print-format .table-bordered > tbody > tr > td,\n.print-format .table-bordered > tfoot > tr > td {\n\tborder: 1px solid #333;\n}\n\n.print-format hr {\n\tborder-top: 1px solid #333;\n}\n\n.print-heading {\n\tborder-bottom: 2px solid #333;\n}\n", + "disabled": 0, + "docstatus": 0, + "doctype": "Print Style", + "idx": 0, + "modified": "2017-08-18 00:44:25.023898", + "modified_by": "Administrator", + "name": "Monochrome", + "owner": "Administrator", + "preview": "/assets/frappe/images/help/print-style-monochrome.png", + "print_style_name": "Monochrome", + "standard": 1 +} \ No newline at end of file diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css index 2dd5aaa1e2..453266c2e0 100644 --- a/frappe/public/css/form.css +++ b/frappe/public/css/form.css @@ -11,6 +11,12 @@ padding: 10px 0px; border-bottom: 1px solid #d1d8dd; } +.print-toolbar > div { + padding-right: 0px; +} +.print-toolbar > div:last-child { + padding-right: 15px; +} .form-inner-toolbar { padding: 10px 15px 0px; background-color: #fafbfc; @@ -588,6 +594,16 @@ select.form-control { .password-strength-message { margin-top: -10px; } +.control-code, +.control-code.bold { + height: 400px; + font-family: Monaco, "Courier New", monospace; + background-color: black; + color: #fffce7; + font-size: 12px; + line-height: 1.7em; + border: none; +} .delivery-status-indicator { display: inline-block; margin-top: -3px; diff --git a/frappe/public/css/slickgrid.css b/frappe/public/css/slickgrid.css index 30d7133a2d..961da50b55 100644 --- a/frappe/public/css/slickgrid.css +++ b/frappe/public/css/slickgrid.css @@ -57,3 +57,6 @@ .frappe-rtl .slick-wrapper { direction: ltr; } +.slick-cell > span[data-field="_comments"] * { + display: inline-block; +} diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 7c15d260bd..09c068ec35 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -202,7 +202,7 @@ frappe.Application = Class.extend({ moment.tz.add(frappe.boot.timezone_info); } if(frappe.boot.print_css) { - frappe.dom.set_style(frappe.boot.print_css) + frappe.dom.set_style(frappe.boot.print_css, "print-style"); } frappe.user.name = frappe.boot.user.name; } else { @@ -482,14 +482,14 @@ frappe.Application = Class.extend({ }, setup_beforeunload: function() { - if (frappe.defaults.get_default('in_selenium')) { + if (frappe.defaults.get_default('in_selenium') || frappe.boot.developer_mode) { return; } window.onbeforeunload = function () { if (frappe.flags.in_test) return null; var unsaved_docs = []; - for (doctype in locals) { - for (name in locals[doctype]) { + for (const doctype in locals) { + for (const name in locals[doctype]) { var doc = locals[doctype][name]; if(doc.__unsaved) { unsaved_docs.push(doc.name); } } diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index f3727ae16f..0a3890df29 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -93,6 +93,7 @@ frappe.dom = { se.appendChild(document.createTextNode(txt)); } document.getElementsByTagName('head')[0].appendChild(se); + return se; }, add: function(parent, newtag, className, cs, innerHTML, onclick) { if(parent && parent.substr)parent = frappe.dom.by_id(parent); diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index f979bb2cb5..549181618f 100755 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -744,9 +744,24 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ }, set_formatted_input: function(value) { this._super(value); - if(value - && ((this.last_value && this.last_value !== value) - || (!this.datepicker.selectedDates.length))) { + if(!value) return; + + let should_refresh = this.last_value && this.last_value !== value; + + if (!should_refresh) { + if(this.datepicker.selectedDates.length > 0) { + // if date is selected but different from value, refresh + const selected_date = + moment(this.datepicker.selectedDates[0]) + .format(moment.defaultDateFormat); + should_refresh = selected_date !== value; + } else { + // if datepicker has no selected date, refresh + should_refresh = true; + } + } + + if(should_refresh) { this.datepicker.selectDate(frappe.datetime.str_to_obj(value)); } }, @@ -938,8 +953,12 @@ frappe.ui.form.ControlDateRange = frappe.ui.form.ControlData.extend({ this.set_mandatory && this.set_mandatory(value); }, parse: function(value) { - if(value && (value.indexOf(',') !== -1 || value.indexOf('to') !== -1)) { - var vals = value.split(/[( to )(,)]/); + // replace the separator (which can be in user language) with comma + const to = __('{0} to {1}').replace('{0}', '').replace('{1}', ''); + value = value.replace(to, ','); + + if(value && value.includes(',')) { + var vals = value.split(','); var from_date = moment(frappe.datetime.user_to_obj(vals[0])).format('YYYY-MM-DD'); var to_date = moment(frappe.datetime.user_to_obj(vals[vals.length-1])).format('YYYY-MM-DD'); return [from_date, to_date]; @@ -1787,7 +1806,7 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ this._super(); $(this.input_area).find("textarea") .allowTabs() - .css({"height":"400px", "font-family": "Monaco, \"Courier New\", monospace"}); + .addClass('control-code'); } }); diff --git a/frappe/public/js/frappe/form/form_viewers.js b/frappe/public/js/frappe/form/form_viewers.js index b29ceb7584..f6bd8b847a 100644 --- a/frappe/public/js/frappe/form/form_viewers.js +++ b/frappe/public/js/frappe/form/form_viewers.js @@ -4,11 +4,18 @@ frappe.ui.form.Viewers = Class.extend({ init: function(opts) { $.extend(this, opts); }, + get_viewers: function() { + let docinfo = this.frm.get_docinfo(); + if (docinfo) { + return docinfo.viewers || {}; + } else { + return {}; + } + }, refresh: function(data_updated) { - var me = this; this.parent.empty(); - var viewers = this.frm.get_docinfo().viewers || {}; + var viewers = this.get_viewers(); var users = []; var new_users = []; @@ -55,7 +62,8 @@ frappe.ui.form.Viewers = Class.extend({ frappe.ui.form.set_viewers = function(data) { var doctype = data.doctype; var docname = data.docname; - var past_viewers = (frappe.model.get_docinfo(doctype, docname).viewers || {}).past || []; + var docinfo = frappe.model.get_docinfo(doctype, docname); + var past_viewers = ((docinfo && docinfo.viewers) || {}).past || []; var viewers = data.viewers || []; var new_viewers = viewers.filter(viewer => !past_viewers.includes(viewer)); diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index cc99eae370..da19c8bed8 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -35,7 +35,7 @@ frappe.ui.form.PrintPreview = Class.extend({ this.print_sel = this.wrapper .find(".print-preview-select") .on("change", function () { - me.multilingual_preview() + me.multilingual_preview(); }); //On selection of language get code and pass it to preview method @@ -79,7 +79,7 @@ frappe.ui.form.PrintPreview = Class.extend({ }); this.wrapper.find(".btn-print-edit").on("click", function () { - var print_format = me.get_print_format(); + let print_format = me.get_print_format(); if (print_format && print_format.name) { if (print_format.print_format_builder) { frappe.set_route("print-format-builder", print_format.name); @@ -106,7 +106,8 @@ frappe.ui.form.PrintPreview = Class.extend({ this.lang_code = this.frm.doc.language; // Load all languages in the field this.language_sel.empty() - .add_options(frappe.get_languages()) + .add_options([{value:'', label:__("Select Language...")}] + .concat(frappe.get_languages())) .val(this.lang_code); this.preview(); }, @@ -193,6 +194,7 @@ frappe.ui.form.PrintPreview = Class.extend({ this.print_formats = frappe.meta.get_print_formats(this.frm.doctype); return this.print_sel .empty().add_options(this.print_formats); + }, with_old_style: function (opts) { frappe.require("/assets/js/print_format_v3.min.js", function () { diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index ffce4dceff..025f0bba7b 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -1,6 +1,6 @@ frappe.provide('frappe.ui.form'); -frappe.ui.form.make_quick_entry = (doctype, after_insert) => { +frappe.ui.form.make_quick_entry = (doctype, after_insert, init_callback) => { var trimmed_doctype = doctype.replace(/ /g, ''); var controller_name = "QuickEntryForm"; @@ -8,14 +8,15 @@ frappe.ui.form.make_quick_entry = (doctype, after_insert) => { controller_name = trimmed_doctype + "QuickEntryForm"; } - frappe.quick_entry = new frappe.ui.form[controller_name](doctype, after_insert); + frappe.quick_entry = new frappe.ui.form[controller_name](doctype, after_insert, init_callback); return frappe.quick_entry.setup(); }; frappe.ui.form.QuickEntryForm = Class.extend({ - init: function(doctype, after_insert){ + init: function(doctype, after_insert, init_callback){ this.doctype = doctype; this.after_insert = after_insert; + this.init_callback = init_callback; }, setup: function() { @@ -106,6 +107,10 @@ frappe.ui.form.QuickEntryForm = Class.extend({ this.dialog.onhide = () => frappe.quick_entry = null; this.dialog.show(); this.set_defaults(); + + if (this.init_callback) { + this.init_callback(this.dialog); + } }, register_primary_action: function(){ diff --git a/frappe/public/js/frappe/form/sidebar.js b/frappe/public/js/frappe/form/sidebar.js index 6da3c6f8d8..989bdfbca4 100644 --- a/frappe/public/js/frappe/form/sidebar.js +++ b/frappe/public/js/frappe/form/sidebar.js @@ -87,7 +87,7 @@ frappe.ui.form.Sidebar = Class.extend({ parent: this.sidebar.find(".tag-area"), frm: this.frm, on_change: function(user_tags) { - me.frm.doc._user_tags = user_tags; + me.frm.doc._user_tags += ("," + user_tags); } }); }, diff --git a/frappe/public/js/frappe/form/templates/print_layout.html b/frappe/public/js/frappe/form/templates/print_layout.html index eac092f390..bbdf415b17 100644 --- a/frappe/public/js/frappe/form/templates/print_layout.html +++ b/frappe/public/js/frappe/form/templates/print_layout.html @@ -3,7 +3,8 @@
-
+