Merge branch 'develop' into staging

This commit is contained in:
mbauskar 2017-08-23 13:18:55 +05:30
commit 2c95983e4d
125 changed files with 1156 additions and 546 deletions

View file

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

View file

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

View file

@ -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():
"""

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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")
},
]
},
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"):

View file

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

View file

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

View file

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

View file

@ -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()
]);
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 []:

View file

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

View file

@ -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"):

View file

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

View file

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

View file

@ -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 = {}

View file

@ -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 = [""]

View file

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

View file

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

View file

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

View file

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

View file

@ -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('<img src="/assets/frappe/images/help/print-style-' +
frm.doc.print_style.toLowerCase() + '.png" class="img-responsive">');
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(
`<img src="${r.message.preview}" class="img-responsive">`);
} else {
frm.get_field("print_style_preview").$wrapper.html(
`<p style="margin: 60px 0px" class="text-center text-muted">${__("No Preview")}</p>`);
}
});
});
frappe.ui.form.on("Print Settings", "onload", function (frm) {

View file

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

View file

@ -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()
]);
});

View file

@ -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');
})
}
});

View file

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

View file

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

View file

@ -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 */'}
]),
]);
});

View file

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

View file

View file

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

View file

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

View file

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

View file

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

View file

@ -57,3 +57,6 @@
.frappe-rtl .slick-wrapper {
direction: ltr;
}
.slick-cell > span[data-field="_comments"] * {
display: inline-block;
}

View file

@ -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); }
}

View file

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

View file

@ -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');
}
});

View file

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

View file

@ -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 () {

View file

@ -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(){

View file

@ -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);
}
});
},

View file

@ -3,7 +3,8 @@
<div class="col-xs-2">
<select class="print-preview-select input-sm form-control"></select></div>
<div class="col-xs-2">
<select class="languages input-sm form-control"></select></div>
<select class="languages input-sm form-control"
placeholder="{{ __("Language") }}"></select></div>
<div class="col-xs-2">
<div class="checkbox small" style="margin-top: 7px; margin-bottom: 0px;">
<label>
@ -16,8 +17,10 @@
<div class="btn-group">
<a class="btn-print-print btn-sm btn btn-default">
<strong>{%= __("Print") %}</strong></a>
<a class="btn-sm btn btn-default" href="#Form/Print Settings">
{%= __("Settings...") %}</a>
<a class="btn-print-edit btn-sm btn btn-default">
{%= __("Customize") %}</a>
{%= __("Customize...") %}</a>
<a class="btn-print-preview btn-sm btn btn-default">
{%= __("Full Page") %}</a>
<a class="btn-download-pdf btn-sm btn btn-default">

View file

@ -108,6 +108,10 @@ frappe.views.ListRenderer = Class.extend({
add_field(this.meta.title_field);
}
if (this.meta.image_field) {
add_field(this.meta.image_field);
}
// enabled / disabled
if (frappe.meta.has_field(this.doctype, 'enabled')) { add_field('enabled'); }
if (frappe.meta.has_field(this.doctype, 'disabled')) { add_field('disabled'); }

View file

@ -314,7 +314,7 @@ $.extend(frappe.model, {
});
frappe.create_routes = {};
frappe.new_doc = function (doctype, opts) {
frappe.new_doc = function (doctype, opts, init_callback) {
return new Promise(resolve => {
if(opts && $.isPlainObject(opts)) {
frappe.route_options = opts;
@ -324,7 +324,7 @@ frappe.new_doc = function (doctype, opts) {
frappe.set_route(frappe.create_routes[doctype])
.then(() => resolve());
} else {
frappe.ui.form.make_quick_entry(doctype)
frappe.ui.form.make_quick_entry(doctype, null, init_callback)
.then(() => resolve());
}
});

View file

@ -68,7 +68,7 @@ frappe.ui.keys.on('esc', function(e) {
close_grid_and_dialog();
});
frappe.ui.keys.on('Enter', function(e) {
frappe.ui.keys.on('enter', function(e) {
if(cur_dialog && cur_dialog.confirm_dialog) {
cur_dialog.get_primary_btn().trigger('click');
}

View file

@ -25,7 +25,7 @@ frappe.ui.TagEditor = Class.extend({
method: 'frappe.desk.tags.add_tag',
args: me.get_args(tag),
callback: function(r) {
var user_tags = me.user_tags.split(",");
var user_tags = me.user_tags ? me.user_tags.split(",") : [];
user_tags.push(tag)
me.user_tags = user_tags.join(",");
me.on_change && me.on_change(me.user_tags);

View file

@ -560,6 +560,10 @@ frappe.views.CommunicationComposer = Class.extend({
if(last_email) {
var last_email_content = last_email.original_comment || last_email.content;
last_email_content = last_email_content
.replace(/&lt;meta[\s\S]*meta&gt;/g, '') // remove <meta> tags
.replace(/&lt;style[\s\S]*&lt;\/style&gt;/g, ''); // // remove <style> tags
content = '<div><br></div>'
+ reply
+ "<br><!-- original-reply --><br>"

View file

@ -534,7 +534,11 @@ frappe.views.QueryReport = Class.extend({
col.field = df.fieldname || df.label;
df.label = __(df.label);
col.name = col.id = col.label = df.label;
if(df.width < 0) {
col.hidden = true;
}
return col
}));
},
@ -604,6 +608,7 @@ frappe.views.QueryReport = Class.extend({
var me = this;
this.dataView.onRowCountChanged.subscribe(function (e, args) {
me.update_totals_row();
me.grid.updateRowCount();
me.grid.render();
});
@ -613,8 +618,38 @@ frappe.views.QueryReport = Class.extend({
me.grid.render();
});
},
update_totals_row: function() {
if(!this.report_doc.add_total_row) return;
const data_length = this.dataView.getLength();
const last_index = data_length - 1;
const number_fields = ['Currency', 'Float', 'Int'];
const fields = this.columns
.filter(col => number_fields.includes(col.fieldtype))
.map(col => col.field);
// reset numeric fields
let updated_totals = Object.assign({}, this.dataView.getItem(last_index));
fields.map(field => {
updated_totals[field] = 0.0;
});
// loop all the rows except the last Total row
for (let i = 0; i < data_length - 1; i++) {
const item = this.dataView.getItem(i);
fields.map(field => {
updated_totals[field] += item[field];
});
}
this.dataView.updateItem(updated_totals.id, updated_totals);
},
inline_filter: function (item) {
var me = frappe.container.page.query_report;
if(me.report_doc.add_total_row) {
// always show totals row
if(Object.values(item).includes("'Total'")) return true;
}
for (var columnId in me.columnFilters) {
if (columnId !== undefined && me.columnFilters[columnId] !== "") {
var c = me.grid.getColumns()[me.grid.getColumnIndex(columnId)];
@ -784,6 +819,16 @@ frappe.views.QueryReport = Class.extend({
var cols = args.sortCols;
me.data.sort(function (dataRow1, dataRow2) {
// Totals row should always be last
if(me.report_doc.add_total_row) {
if(Object.values(dataRow1).includes("'Total'")) {
return 1;
}
if(Object.values(dataRow2).includes("'Total'")) {
return -1;
}
}
for (var i = 0, l = cols.length; i < l; i++) {
var field = cols[i].sortCol.field;
var sign = cols[i].sortAsc ? 1 : -1;

View file

@ -14,6 +14,13 @@
margin: 0px;
padding: 10px 0px;
border-bottom: 1px solid @border-color;
> div {
padding-right: 0px;
}
> div:last-child {
padding-right: 15px;
}
}
.form-inner-toolbar {
@ -735,6 +742,16 @@ select.form-control {
margin-top: -10px;
}
.control-code, .control-code.bold {
height: 400px;
font-family: Monaco, "Courier New", monospace;
background-color: black;
color: @light-yellow;
font-size: 12px;
line-height: 1.7em;
border: none;
}
.delivery-status-indicator {
display: inline-block;
margin-top: -3px;

View file

@ -63,4 +63,8 @@
.frappe-rtl .slick-wrapper {
direction: ltr;
}
.slick-cell > span[data-field="_comments"] * {
display: inline-block;
}

View file

@ -5,6 +5,7 @@ import frappe
from frappe import _
from frappe.utils import flt, cint
import json
from six import string_types
no_cache = 1
no_sitemap = 1
@ -39,7 +40,7 @@ def get_api_key():
def make_payment(razorpay_payment_id, options, reference_doctype, reference_docname):
data = {}
if isinstance(options, basestring):
if isinstance(options, string_types):
data = json.loads(options)
data.update({

View file

@ -61,8 +61,7 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
{%- macro render_field_with_label(df, doc) -%}
<div class="row {% if df.bold %}important{% endif %} data-field" {{ fieldmeta(df) }}>
<div class="col-xs-{{ "9" if df.fieldtype=="Check" else "5" }}
{%- if not doc._align_labels_left %} text-right{%- endif -%}">
<div class="col-xs-{{ "9" if df.fieldtype=="Check" else "5" }}">
{% if df.fieldtype not in ("Image","HTML","Check") and
doc.get(df.fieldname) != None %}
<label>{{ _(df.label) }}</label>

View file

@ -1,16 +0,0 @@
/*
common style for whole page
This should include:
+ page size related settings
+ font family settings
+ line spacing settings
*/
.print-format div,
.print-format span,
.print-format td,
.print-format h1,
.print-format h2,
.print-format h3,
.print-format h4 {
font-family: serif;
}

View file

@ -1,19 +0,0 @@
.print-heading {
text-align: right;
text-transform: uppercase;
color: #666;
padding-bottom: 20px;
margin-bottom: 20px;
border-bottom: 1px solid #d1d8dd;
}
.print-heading h2 {
font-size: 24px;
}
.print-format th {
background-color: #eee !important;
border-bottom: 0px !important;
}
/* modern format: don't remove this line */

View file

@ -1,26 +0,0 @@
.print-format * {
color: #000 !important;
}
.print-format .alert {
background-color: inherit;
border: 1px dashed #333;
}
.print-format .table-bordered,
.print-format .table-bordered > thead > tr > th,
.print-format .table-bordered > tbody > tr > th,
.print-format .table-bordered > tfoot > tr > th,
.print-format .table-bordered > thead > tr > td,
.print-format .table-bordered > tbody > tr > td,
.print-format .table-bordered > tfoot > tr > td {
border: 1px solid #333;
}
.print-format hr {
border-top: 1px solid #333;
}
.print-heading {
border-bottom: 2px solid #333;
}

View file

@ -66,14 +66,22 @@ class TestFmtMoney(unittest.TestCase):
def test_currency_precision(self):
frappe.db.set_default("currency_precision", "4")
frappe.db.set_default("number_format", "#,###.##")
self.assertEquals(fmt_money(100), "100.0000")
self.assertEquals(fmt_money(1000), "1,000.0000")
self.assertEquals(fmt_money(10000), "10,000.0000")
self.assertEquals(fmt_money(100000), "100,000.0000")
self.assertEquals(fmt_money(1000000), "1,000,000.0000")
self.assertEquals(fmt_money(10000000), "10,000,000.0000")
self.assertEquals(fmt_money(100000000), "100,000,000.0000")
self.assertEquals(fmt_money(1000000000), "1,000,000,000.0000")
self.assertEquals(fmt_money(100), "100.00")
self.assertEquals(fmt_money(1000), "1,000.00")
self.assertEquals(fmt_money(10000), "10,000.00")
self.assertEquals(fmt_money(100000), "100,000.00")
self.assertEquals(fmt_money(1000000), "1,000,000.00")
self.assertEquals(fmt_money(10000000), "10,000,000.00")
self.assertEquals(fmt_money(100000000), "100,000,000.00")
self.assertEquals(fmt_money(1000000000), "1,000,000,000.00")
self.assertEquals(fmt_money(100.23), "100.23")
self.assertEquals(fmt_money(1000.456), "1,000.456")
self.assertEquals(fmt_money(10000.7890), "10,000.789")
self.assertEquals(fmt_money(100000.1234), "100,000.1234")
self.assertEquals(fmt_money(1000000.3456), "1,000,000.3456")
self.assertEquals(fmt_money(10000000.3344567), "10,000,000.3345")
self.assertEquals(fmt_money(100000000.37827268), "100,000,000.378")
self.assertEquals(fmt_money(1000000000.2718272637), "1,000,000,000.27")
frappe.db.set_default("currency_precision", "")
if __name__=="__main__":

View file

@ -14,6 +14,7 @@ from frappe.permissions import (add_user_permission, remove_user_permission,
get_valid_perms)
from frappe.core.page.permission_manager.permission_manager import update, reset
from frappe.test_runner import make_test_records_for_doctype
from six import string_types
test_records = frappe.get_test_records('Blog Post')
@ -361,7 +362,7 @@ def set_user_permission_doctypes(doctypes, role, apply_user_permissions,
user_permission_doctypes):
user_permission_doctypes = None if not user_permission_doctypes else json.dumps(user_permission_doctypes)
if isinstance(doctypes, basestring):
if isinstance(doctypes, string_types):
doctypes = [doctypes]
for doctype in doctypes:

View file

@ -4,7 +4,7 @@ from __future__ import unicode_literals
import unittest
from frappe.utils import evaluate_filters, money_in_words
from frappe.utils import evaluate_filters, money_in_words, scrub_urls, get_url
class TestFilters(unittest.TestCase):
def test_simple_dict(self):
@ -57,4 +57,30 @@ class TestMoney(unittest.TestCase):
self.assertEqual(
money_in_words(num[0], "NGN"), num[1], "{0} is not the same as {1}".
format(money_in_words(num[0], "NGN"), num[1])
)
)
class TestDataManipulation(unittest.TestCase):
def test_scrub_urls(self):
html = '''
<p>You have a new message from: <b>John</b></p>
<p>Hey, wassup!</p>
<div class="more-info">
<a href="http://test.com">Test link 1</a>
<a href="/about">Test link 2</a>
<a href="login">Test link 3</a>
<img src="/assets/frappe/test.jpg">
</div>
<div style="background-image: url('/assets/frappe/bg.jpg')">
Please mail us at <a href="mailto:test@example.com">email</a>
</div>
'''
html = scrub_urls(html)
url = get_url()
self.assertTrue('<a href="http://test.com">Test link 1</a>' in html)
self.assertTrue('<a href="{0}/about">Test link 2</a>'.format(url) in html)
self.assertTrue('<a href="{0}/login">Test link 3</a>'.format(url) in html)
self.assertTrue('<img src="{0}/assets/frappe/test.jpg">'.format(url) in html)
self.assertTrue('style="background-image: url(\'{0}/assets/frappe/bg.jpg\') !important"'.format(url) in html)
self.assertTrue('<a href="mailto:test@example.com">email</a>' in html)

View file

@ -140,6 +140,12 @@ frappe.tests = {
// Method to check the visibility of an element
return $(`${tag}:contains("${text}")`).is(`:visible`);
},
/**
* Clicks a button on a form.
* @param {String} text - The button's text
* @return {frappe.timeout}
* @throws will throw an exception if a matching visible button is not found
*/
click_button: function(text) {
let element = $(`.btn:contains("${text}"):visible`);
if(!element.length) {
@ -148,6 +154,12 @@ frappe.tests = {
element.click();
return frappe.timeout(0.5);
},
/**
* Clicks a link on a form.
* @param {String} text - The text of the link to be clicked
* @return {frappe.timeout}
* @throws will throw an exception if a link with the given text is not found
*/
click_link: function(text) {
let element = $(`a:contains("${text}"):visible`);
if(!element.length) {
@ -156,6 +168,13 @@ frappe.tests = {
element.get(0).click();
return frappe.timeout(0.5);
},
/**
* Sets the given control to the value given.
* @param {String} fieldname - The Doctype's field name
* @param {String} value - The value the control should be changed to
* @return {frappe.timeout}
* @throws will throw an exception if the field is not found or is not visible
*/
set_control: function(fieldname, value) {
let control = $(`.form-control[data-fieldname="${fieldname}"]:visible`);
if(!control.length) {
@ -164,5 +183,18 @@ frappe.tests = {
control.val(value).trigger('change');
return frappe.timeout(0.5);
},
/**
* Checks if given field is disabled.
* @param {String} fieldname - The Doctype field name
* @return {Boolean} true if condition is met
* @throws will throw an exception if the field is not found or is not a form control
*/
is_disabled_field: function(fieldname){
let control = $(`.form-control[data-fieldname="${fieldname}"]:disabled`);
if(!control.length) {
throw `did not find any control with fieldname ${fieldname}`;
} else {
return true;
}
}
};

Some files were not shown because too many files have changed in this diff Show more