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 @@