Merge branch 'develop'

This commit is contained in:
Anand Doshi 2015-10-19 16:28:05 +05:30
commit 0e9b7635cb
120 changed files with 2426 additions and 1804 deletions

View file

@ -310,7 +310,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
as_markdown=False, bulk=False, reference_doctype=None, reference_name=None,
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, content=None, doctype=None, name=None, reply_to=None,
cc=(), message_id=None, as_bulk=False, send_after=None, expose_recipients=False,
cc=(), show_as_cc=(), message_id=None, as_bulk=False, send_after=None, expose_recipients=False,
bulk_priority=1):
"""Send email using user's default **Email Account** or global default **Email Account**.
@ -339,7 +339,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
subject=subject, message=content or message,
reference_doctype = doctype or reference_doctype, reference_name = name or reference_name,
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, send_after=send_after,
attachments=attachments, reply_to=reply_to, cc=cc, show_as_cc=show_as_cc, message_id=message_id, send_after=send_after,
expose_recipients=expose_recipients, bulk_priority=bulk_priority)
else:
import frappe.email
@ -469,11 +469,14 @@ def get_precision(doctype, fieldname, currency=None, doc=None):
from frappe.model.meta import get_field_precision
return get_field_precision(get_meta(doctype).get_field(fieldname), doc, currency)
def generate_hash(txt=None):
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
return hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest()
digest = hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest()
if length:
digest = digest[:length]
return digest
def reset_metadata_version():
"""Reset `metadata_version` (Client (Javascript) build ID) hash."""
@ -735,6 +738,9 @@ def get_file_json(path):
def read_file(path, raise_not_found=False):
"""Open a file and return its content as Unicode."""
from frappe.utils import cstr
if isinstance(path, unicode):
path = path.encode("utf-8")
if os.path.exists(path):
with open(path, "r") as f:
return cstr(f.read())

View file

@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = "6.4.9"
__version__ = "6.5.0"

View file

@ -0,0 +1,2 @@
- **Linked With** will now show links from Dynamic Links
- **Data** field-type size truncated to 140 characters from 255 (by default). Can be changed by setting the **length** property from **Customize Form View**

View file

@ -383,15 +383,28 @@ def reset_perms(context):
@click.command('execute')
@click.argument('method')
@click.option('--args')
@click.option('--kwargs')
@pass_context
def execute(context, method):
def execute(context, method, args=None, kwargs=None):
"execute a function"
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
print frappe.local.site
ret = frappe.get_attr(method)()
if args:
args = eval(args)
else:
args = ()
if kwargs:
kwargs = eval(args)
else:
kwargs = {}
ret = frappe.get_attr(method)(*args, **kwargs)
if frappe.db:
frappe.db.commit()
@ -862,7 +875,9 @@ def drop_site(site, root_login='root', root_password=None):
def get_version(context):
frappe.init(site=context.sites[0])
for m in sorted(frappe.local.app_modules.keys()):
print "{0} {1}".format(m, frappe.get_module(m).__version__)
module = frappe.get_module(m)
if hasattr(module, "__version__"):
print "{0} {1}".format(m, module.__version__)
# commands = [
# new_site,

View file

@ -19,7 +19,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Status",
"label": "Status",
"no_copy": 0,
"options": "\nQueued\nRunning\nSucceeded\nFailed\n",
"permlevel": 0,
@ -195,7 +195,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 08:08:22.193911",
"modified": "2015-10-02 07:38:38.658939",
"modified_by": "Administrator",
"module": "Core",
"name": "Async Task",

View file

@ -39,7 +39,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-03-24 16:03:52.359042",
"modified": "2015-10-02 07:38:39.485971",
"modified_by": "Administrator",
"module": "Core",
"name": "Block Module",

View file

@ -292,7 +292,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-06-08 12:31:15.122312",
"modified": "2015-10-02 07:38:41.308408",
"modified_by": "Administrator",
"module": "Core",
"name": "Comment",

View file

@ -9,6 +9,8 @@ from frappe.model.document import Document
from frappe.model.db_schema import add_column
from frappe.utils import get_fullname
exclude_from_linked_with = True
class Comment(Document):
"""Comments are added to Documents via forms or views like blogs etc."""
no_feed_on_delete = True

View file

@ -21,7 +21,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Series",
"label": "Series",
"no_copy": 0,
"options": "COMM-",
"permlevel": 0,
@ -44,7 +44,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Communication Medium",
"label": "Communication Medium",
"no_copy": 0,
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther",
"permlevel": 0,
@ -153,7 +153,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Status",
"label": "Status",
"no_copy": 0,
"options": "Open\nReplied\nClosed\nLinked",
"permlevel": 0,
@ -176,7 +176,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Sent or Received",
"label": "Sent or Received",
"no_copy": 0,
"options": "Sent\nReceived",
"permlevel": 0,
@ -199,7 +199,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Delivery Status",
"label": "Delivery Status",
"no_copy": 0,
"options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed",
"permlevel": 0,
@ -573,7 +573,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-24 02:27:43.536919",
"modified": "2015-10-02 07:38:41.472917",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",

View file

@ -5,7 +5,7 @@ from __future__ import unicode_literals, absolute_import
import frappe
import json
from email.utils import formataddr, parseaddr
from frappe.utils import get_url, get_formatted_email, cstr, cint, validate_email_add, split_emails
from frappe.utils import get_url, get_formatted_email, cint, validate_email_add, split_emails
from frappe.utils.file_manager import get_file
import frappe.email.smtp
from frappe import _
@ -106,7 +106,7 @@ class Communication(Document):
from frappe.tasks import sendmail
sendmail.delay(frappe.local.site, self.name,
print_html=print_html, print_format=print_format, attachments=attachments,
recipients=recipients, cc=cc)
recipients=recipients, cc=cc, lang=frappe.local.lang)
def _notify(self, print_html=None, print_format=None, attachments=None,
recipients=None, cc=None):
@ -115,6 +115,7 @@ class Communication(Document):
frappe.sendmail(
recipients=(recipients or []) + (cc or []),
show_as_cc=(cc or []),
expose_recipients=True,
sender=self.sender,
reply_to=self.incoming_email_account,
@ -230,8 +231,8 @@ class Communication(Document):
cc = split_emails(self.cc)
if self.reference_doctype and self.reference_name:
if not cc or fetched_from_email_account:
# if CC is not mentioned from the UI or is a fetched email, add follows to CC
if fetched_from_email_account:
# if it is a fetched email, add follows to CC
cc.append(self.get_owner_email())
cc += self.get_assignees()
cc += self.get_starrers()
@ -261,7 +262,7 @@ class Communication(Document):
exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"],
{"reference_doctype": self.reference_doctype, "reference_name": self.reference_name}, as_list=True)]
cc = self.filter_email_list(cc, exclude)
cc = self.filter_email_list(cc, exclude, is_cc=True)
if getattr(self, "send_me_a_copy", False) and self.sender not in cc:
self.all_email_addresses.append(self.sender)
@ -269,7 +270,7 @@ class Communication(Document):
return cc
def filter_email_list(self, email_list, exclude):
def filter_email_list(self, email_list, exclude, is_cc=False):
# temp variables
filtered = []
email_address_list = []
@ -285,6 +286,12 @@ class Communication(Document):
if email_address in exclude:
continue
if is_cc:
is_user_enabled = frappe.db.get_value("User", email_address, "enabled")
if is_user_enabled==0:
# don't send to disabled users
continue
# make sure of case-insensitive uniqueness of email address
if email_address not in email_address_list:
# append the full email i.e. "Human <human@example.com>"

View file

@ -67,7 +67,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-19 01:06:59.622792",
"modified": "2015-10-02 07:38:44.346115",
"modified_by": "Administrator",
"module": "Core",
"name": "DefaultValue",

File diff suppressed because it is too large Load diff

View file

@ -591,7 +591,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-07-22 07:39:40.471092",
"modified": "2015-10-02 07:38:46.740536",
"modified_by": "Administrator",
"module": "Core",
"name": "DocPerm",

View file

@ -178,7 +178,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-07-17 07:02:10.632582",
"modified": "2015-10-02 07:38:47.029636",
"modified_by": "Administrator",
"module": "Core",
"name": "DocShare",

View file

@ -7,6 +7,8 @@ from frappe.model.document import Document
from frappe import _
from frappe.utils import get_fullname
exclude_from_linked_with = True
class DocShare(Document):
no_feed_on_delete = True

View file

@ -134,7 +134,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Document Type",
"label": "Document Type",
"no_copy": 0,
"oldfieldname": "document_type",
"oldfieldtype": "Select",
@ -312,7 +312,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Name Case",
"label": "Name Case",
"no_copy": 0,
"oldfieldname": "name_case",
"oldfieldtype": "Select",
@ -448,7 +448,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sort Order",
"label": "Sort Order",
"no_copy": 0,
"options": "ASC\nDESC",
"permlevel": 0,
@ -851,7 +851,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-07-28 16:18:11.925264",
"modified": "2015-10-02 07:38:47.199387",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('DocType')
class TestDocType(unittest.TestCase):
pass

View file

@ -2,7 +2,7 @@
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "hash",
"autoname": "",
"creation": "2012-12-12 11:19:22",
"custom": 0,
"docstatus": 0,
@ -189,7 +189,7 @@
"collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "file_url",
"fieldtype": "Data",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
@ -205,6 +205,28 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "thumbnail_url",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Thumbnail URL",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -433,7 +455,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-18 06:22:10.902847",
"modified": "2015-10-19 02:13:43.935641",
"modified_by": "Administrator",
"module": "Core",
"name": "File",

View file

@ -13,10 +13,14 @@ from frappe.utils.file_manager import delete_file_data_content
from frappe import _
from frappe.utils.nestedset import NestedSet
from frappe.utils import strip
import json
import urllib
class FolderNotEmpty(frappe.ValidationError): pass
exclude_from_linked_with = True
class File(NestedSet):
nsm_parent_field = 'folder'
no_feed_on_delete = True
@ -40,7 +44,7 @@ class File(NestedSet):
# home
self.name = self.file_name
else:
self.name = self.file_url
self.name = frappe.generate_hash("", 10)
def after_insert(self):
self.update_parent_folder_size()
@ -53,7 +57,8 @@ class File(NestedSet):
return frappe.db.sql_list("select name from tabFile where folder='%s'"%self.name) or []
def validate(self):
self.validate_duplicate_entry()
if self.is_new():
self.validate_duplicate_entry()
self.validate_folder()
self.set_folder_size()
@ -92,6 +97,8 @@ class File(NestedSet):
def validate_duplicate_entry(self):
if not self.flags.ignore_duplicate_entry_error and not self.is_folder:
# check duplicate name
# check duplicate assignement
n_records = frappe.db.sql("""select name from `tabFile`
where content_hash=%s
@ -111,12 +118,55 @@ class File(NestedSet):
super(File, self).on_trash()
self.delete_file()
def make_thumbnail(self):
from PIL import Image, ImageOps
import os
if self.file_url:
if self.file_url.startswith("/files"):
try:
image = Image.open(frappe.get_site_path("public", self.file_url))
filename, extn = self.file_url.rsplit(".", 1)
except IOError:
frappe.msgprint("Unable to read file format for {0}".format(self.file_url))
else:
# downlaod
import requests, StringIO
file_url = frappe.utils.get_url(self.file_url)
r = requests.get(file_url, stream=True)
r.raise_for_status()
image = Image.open(StringIO.StringIO(r.content))
filename, extn = self.file_url.rsplit("/", 1)[1].rsplit(".", 1)
filename = "/files/" + strip(urllib.unquote(filename))
thumbnail = ImageOps.fit(
image,
(300, 300),
Image.ANTIALIAS
)
thumbnail_url = filename + "_small." + extn
path = os.path.abspath(frappe.get_site_path("public", thumbnail_url.lstrip("/")))
try:
thumbnail.save(path)
self.db_set("thumbnail_url", thumbnail_url)
except IOError:
frappe.msgprint("Unable to write file format for {0}".format(path))
return thumbnail_url
def after_delete(self):
self.update_parent_folder_size()
def check_folder_is_empty(self):
"""Throw exception if folder is not empty"""
if self.is_folder and frappe.get_all("File", filters={"folder": self.name}):
files = frappe.get_all("File", filters={"folder": self.name}, fields=("name", "file_name"))
if self.is_folder and files:
frappe.throw(_("Folder {0} is not empty").format(self.name), FolderNotEmpty)
def check_reference_doc_permission(self):
@ -138,6 +188,9 @@ class File(NestedSet):
{"content_hash": self.content_hash, "name": ["!=", self.name]})):
delete_file_data_content(self)
elif self.file_url:
delete_file_data_content(self, only_thumbnail=True)
def on_rollback(self):
self.on_trash()
@ -178,7 +231,10 @@ def create_new_folder(file_name, folder):
@frappe.whitelist()
def move_file(file_list, new_parent, old_parent):
for file_obj in json.loads(file_list):
if isinstance(file_list, basestring):
file_list = json.loads(file_list)
for file_obj in file_list:
setup_folder_path(file_obj.get("name"), new_parent)
# recalculate sizes

View file

@ -5,37 +5,26 @@ from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils.file_manager import save_file, get_file, get_files_path
from frappe.utils.file_manager import save_file, get_files_path
from frappe import _
from frappe.core.doctype.file.file import move_file
import json
# test_records = frappe.get_test_records('File')
class TestFile(unittest.TestCase):
def setUp(self):
self.delete_test_data()
self.upload_file()
def delete_test_data(self):
for file_name in ["folder_copy.txt", "file_copy.txt", "Test Folder 2"]:
file_name = frappe.db.get_value("File", {"file_name": file_name}, "name")
if file_name:
file = frappe.get_doc("File", file_name)
ancestors = file.get_ancestors()
file.delete()
self.delete_ancestors(ancestors)
def delete_ancestors(self, ancestors):
for folder in ancestors:
if folder != "Home":
folder = frappe.get_doc("File", folder)
folder.delete()
def delete_test_data(self):
for f in frappe.db.sql('''select name, file_name from tabFile where
is_home_folder = 0 and is_attachments_folder = 0 order by rgt-lft asc'''):
frappe.delete_doc("File", f[0])
def upload_file(self):
self.saved_file = save_file('file_copy.txt', "Testing file copy example.",\
"", "", self.get_folder("Test Folder 1", "Home").name)
self.saved_filename = get_files_path(self.saved_file.file_name)
def get_folder(self, folder_name, parent_folder="Home"):
return frappe.get_doc({
"doctype": "File",
@ -46,61 +35,57 @@ class TestFile(unittest.TestCase):
def tests_after_upload(self):
self.assertEqual(self.saved_file.folder, _("Home/Test Folder 1"))
folder_size = frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size")
saved_file_size = frappe.db.get_value("File", self.saved_file.name, "file_size")
self.assertEqual(folder_size, saved_file_size)
def test_file_copy(self):
folder = self.get_folder("Test Folder 2", "Home")
file = frappe.get_doc("File", "/files/file_copy.txt")
file_dict = [{"name": file.name}]
move_file(json.dumps(file_dict), folder.name, file.folder)
file = frappe.get_doc("File", "/files/file_copy.txt")
file = frappe.get_doc("File", {"file_name":"file_copy.txt"})
move_file([{"name": file.name}], folder.name, file.folder)
file = frappe.get_doc("File", {"file_name":"file_copy.txt"})
self.assertEqual(_("Home/Test Folder 2"), file.folder)
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), file.file_size)
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), None)
def test_folder_copy(self):
folder = self.get_folder("Test Folder 2", "Home")
folder = self.get_folder("Test Folder 3", "Home/Test Folder 2")
self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name)
file_dict = [{"name": folder.name}]
move_file(json.dumps(file_dict), 'Home/Test Folder 1', folder.folder)
file = frappe.get_doc("File", "/files/folder_copy.txt")
move_file([{"name": folder.name}], 'Home/Test Folder 1', folder.folder)
file = frappe.get_doc("File", {"file_name":"folder_copy.txt"})
file_copy_txt = frappe.get_value("File", {"file_name":"file_copy.txt"})
if file_copy_txt:
frappe.get_doc("File", file_copy_txt).delete()
self.assertEqual(_("Home/Test Folder 1/Test Folder 3"), file.folder)
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), file.file_size)
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), None)
def test_non_parent_folder(self):
d = frappe.get_doc({
"doctype": "File",
"file_name": _("Test_Folder"),
"is_folder": 1
})
self.assertRaises(frappe.ValidationError, d.save)
def test_on_delete(self):
file = frappe.get_doc("File", "/files/file_copy.txt")
file = frappe.get_doc("File", {"file_name":"file_copy.txt"})
file.delete()
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), None)
folder = self.get_folder("Test Folder 3", "Home/Test Folder 1")
self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name)
folder = frappe.get_doc("File", "Home/Test Folder 1/Test Folder 3")
self.assertRaises(frappe.ValidationError, folder.delete)

View file

@ -62,7 +62,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-02-05 05:11:41.388856",
"modified": "2015-10-02 07:38:57.452736",
"modified_by": "Administrator",
"module": "Core",
"name": "Module Def",

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Module Def')
class TestModuleDef(unittest.TestCase):
pass

View file

@ -149,7 +149,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Standard",
"label": "Standard",
"no_copy": 0,
"oldfieldname": "standard",
"oldfieldtype": "Select",
@ -217,7 +217,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 12:19:55.121822",
"modified": "2015-10-02 07:38:59.403028",
"modified_by": "Administrator",
"module": "Core",
"name": "Page",

View file

@ -41,7 +41,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-19 01:07:00.897854",
"modified": "2015-10-02 07:38:59.530013",
"modified_by": "Administrator",
"module": "Core",
"name": "Page Role",

View file

@ -15,7 +15,7 @@
"bold": 0,
"collapsible": 0,
"fieldname": "patch",
"fieldtype": "Data",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
@ -41,7 +41,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2013-12-20 19:24:15",
"modified": "2015-10-02 07:38:59.666628",
"modified_by": "Administrator",
"module": "Core",
"name": "Patch Log",

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Patch Log')
class TestPatchLog(unittest.TestCase):
pass

View file

@ -62,7 +62,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Is Standard",
"label": "Is Standard",
"no_copy": 0,
"options": "No\nYes",
"permlevel": 0,
@ -147,7 +147,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Report Type",
"label": "Report Type",
"no_copy": 0,
"options": "Report Builder\nQuery Report\nScript Report",
"permlevel": 0,
@ -300,7 +300,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 15:51:26",
"modified": "2015-10-02 07:39:07.933259",
"modified_by": "Administrator",
"module": "Core",
"name": "Report",

View file

@ -41,7 +41,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 15:51:26",
"modified": "2015-10-02 07:39:08.074305",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",

View file

@ -10,4 +10,6 @@ class Role(Document):
def after_insert(self):
# Add role to Administrator
if frappe.flags.in_install != "frappe":
frappe.get_doc("User", "Administrator").add_roles(self.name)
user = frappe.get_doc("User", "Administrator")
user.flags.ignore_permissions = True
user.add_roles(self.name)

View file

@ -85,7 +85,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-05-28 02:49:12.819934",
"modified": "2015-10-02 07:39:12.348067",
"modified_by": "Administrator",
"module": "Core",
"name": "Scheduler Log",

View file

@ -1263,7 +1263,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 5,
"modified": "2015-08-18 11:58:00.000691",
"modified": "2015-10-19 03:04:48.829054",
"modified_by": "Administrator",
"module": "Core",
"name": "User",

View file

@ -43,7 +43,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-19 01:07:02.561834",
"modified": "2015-10-02 07:39:18.179539",
"modified_by": "Administrator",
"module": "Core",
"name": "UserRole",

View file

@ -83,7 +83,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-07-28 16:18:12.706419",
"modified": "2015-10-02 07:39:18.235343",
"modified_by": "Administrator",
"module": "Core",
"name": "Version",

File diff suppressed because it is too large Load diff

View file

@ -44,7 +44,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Script Type",
"label": "Script Type",
"no_copy": 0,
"oldfieldname": "script_type",
"oldfieldtype": "Select",
@ -114,7 +114,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-08-27 06:36:20.439949",
"modified": "2015-10-02 07:38:43.345211",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Script",

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Custom Script')
class TestCustomScript(unittest.TestCase):
pass

View file

@ -234,7 +234,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sort Order",
"label": "Sort Order",
"no_copy": 0,
"options": "ASC\nDESC",
"permlevel": 0,
@ -301,7 +301,7 @@
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"modified": "2015-07-27 01:00:32.901851",
"modified": "2015-10-02 07:17:18.939161",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form",

View file

@ -47,7 +47,8 @@ class CustomizeForm(Document):
'description': 'Text',
'default': 'Text',
'precision': 'Select',
'read_only': 'Check'
'read_only': 'Check',
'length': 'Int'
}
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),

View file

@ -64,11 +64,11 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Type",
"label": "Type",
"no_copy": 0,
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"options": "Attach\nAttach Image\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
@ -202,7 +202,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Precision",
"label": "Precision",
"no_copy": 0,
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9",
"permlevel": 0,
@ -215,6 +215,29 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Length",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -709,7 +732,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-10-01 07:59:15.490247",
"modified": "2015-10-03 07:38:44.026280",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",

View file

@ -61,7 +61,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "DocType or Field",
"label": "DocType or Field",
"no_copy": 0,
"options": "\nDocField\nDocType",
"permlevel": 0,
@ -233,7 +233,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-02-05 05:11:43.216164",
"modified": "2015-10-02 07:39:02.618929",
"modified_by": "Administrator",
"module": "Custom",
"name": "Property Setter",

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Property Setter')
class TestPropertySetter(unittest.TestCase):
pass

View file

@ -36,8 +36,8 @@ CREATE TABLE `tabDocField` (
`no_copy` int(1) NOT NULL DEFAULT 0,
`allow_on_submit` int(1) NOT NULL DEFAULT 0,
`trigger` varchar(255) DEFAULT NULL,
`collapsible_depends_on` varchar(255) DEFAULT NULL,
`depends_on` varchar(255) DEFAULT NULL,
`collapsible_depends_on` text,
`depends_on` text,
`permlevel` int(11) DEFAULT '0',
`ignore_user_permissions` int(1) NOT NULL DEFAULT 0,
`width` varchar(255) DEFAULT NULL,
@ -48,6 +48,7 @@ CREATE TABLE `tabDocField` (
`in_list_view` int(1) NOT NULL DEFAULT 0,
`read_only` int(1) NOT NULL DEFAULT 0,
`precision` varchar(255) DEFAULT NULL,
`length` int(11) DEFAULT NULL,
PRIMARY KEY (`name`),
KEY `parent` (`parent`),
KEY `label` (`label`),

View file

@ -2,7 +2,7 @@ ar العربية
bg bǎlgarski
bo ལྷ་སའི་སྐད་
bs bosanski
bn বাঙালি
bn বাংলা
ca català
cs česky
da dansk
@ -36,9 +36,8 @@ pt português
pt-BR português brasileiro
ro român
ru русский
si slovenščina
sk slovenčina
sv svenska
sk slovenčina
sq shqiptar
sr српски
ta தமிழ்

View file

@ -61,7 +61,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Event Type",
"label": "Event Type",
"no_copy": 0,
"oldfieldname": "event_type",
"oldfieldtype": "Select",
@ -253,7 +253,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Repeat On",
"label": "Repeat On",
"no_copy": 0,
"options": "\nEvery Day\nEvery Week\nEvery Month\nEvery Year",
"permlevel": 0,
@ -635,7 +635,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 15:51:26",
"modified": "2015-10-02 07:38:49.897665",
"modified_by": "Administrator",
"module": "Desk",
"name": "Event",

View file

@ -43,7 +43,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-19 01:07:00.166770",
"modified": "2015-10-02 07:38:50.115057",
"modified_by": "Administrator",
"module": "Desk",
"name": "Event Role",

View file

@ -18,7 +18,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Feed Type",
"label": "Feed Type",
"no_copy": 0,
"options": "\nComment\nLogin\nLabel\nInfo",
"permlevel": 0,
@ -77,7 +77,7 @@
"bold": 0,
"collapsible": 0,
"fieldname": "subject",
"fieldtype": "Data",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
@ -145,7 +145,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 15:51:26",
"modified": "2015-10-02 07:38:50.611929",
"modified_by": "Administrator",
"module": "Desk",
"name": "Feed",

View file

@ -9,6 +9,8 @@ from frappe.model.document import Document
from frappe.utils import get_fullname
from frappe import _
exclude_from_linked_with = True
class Feed(Document):
pass

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Feed')
class TestFeed(unittest.TestCase):
pass

View file

@ -84,7 +84,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 12:20:04.912891",
"modified": "2015-10-02 07:38:57.968895",
"modified_by": "Administrator",
"module": "Desk",
"name": "Note",

View file

@ -32,6 +32,9 @@ frappe.ui.form.on("ToDo", {
frm.save();
}, null, "btn-default");
}
frm.add_custom_button(__("New"), function() {
newdoc("ToDo")
}, null, "btn-default");
}
}
});

View file

@ -85,7 +85,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Status",
"label": "Status",
"no_copy": 0,
"options": "Open\nClosed",
"permlevel": 0,
@ -108,7 +108,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Priority",
"label": "Priority",
"no_copy": 0,
"oldfieldname": "priority",
"oldfieldtype": "Data",
@ -335,7 +335,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-09-07 15:51:26",
"modified": "2015-10-02 07:39:17.248993",
"modified_by": "Administrator",
"module": "Desk",
"name": "ToDo",

View file

@ -10,6 +10,7 @@ from frappe.utils import get_fullname
subject_field = "description"
sender_field = "sender"
exclude_from_linked_with = True
class ToDo(Document):
def validate(self):

View file

@ -103,8 +103,8 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
user_info = get_fullnames()
# Search for email address in description -- i.e. assignee
from frappe.utils import get_url_to_form
assignment = get_url_to_form(doc_type, doc_name, label="%s: %s" % (doc_type, doc_name))
from frappe.utils import get_link_to_form
assignment = get_link_to_form(doc_type, doc_name, label="%s: %s" % (doc_type, doc_name))
owner_name = user_info.get(owner, {}).get('fullname')
user_name = user_info.get(frappe.session.get('user'), {}).get('fullname')
if action=='CLOSE':

View file

@ -0,0 +1,177 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe, json
from frappe.model.meta import is_single
from frappe.modules import load_doctype_module
import frappe.desk.form.meta
import frappe.desk.form.load
@frappe.whitelist()
def get_linked_docs(doctype, name, linkinfo=None):
results = frappe.cache().get_value("linked_with:{doctype}:{name}".format(doctype=doctype, name=name))
if results:
return results
meta = frappe.desk.form.meta.get_meta(doctype)
results = {}
if isinstance(linkinfo, basestring):
# additional fields are added in linkinfo
linkinfo = json.loads(linkinfo)
if not linkinfo:
return results
me = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
for dt, link in linkinfo.items():
link["doctype"] = dt
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
linkmeta = link_meta_bundle[0]
if not linkmeta.get("issingle"):
fields = [d.fieldname for d in linkmeta.get("fields", {"in_list_view":1,
"fieldtype": ["not in", ["Image", "HTML", "Button", "Table"]]})] \
+ ["name", "modified", "docstatus"]
if link.get("add_fields"):
fields += link["add_fields"]
fields = ["`tab{dt}`.`{fn}`".format(dt=dt, fn=sf.strip()) for sf in fields if sf
and "`tab" not in sf]
try:
if link.get("get_parent"):
if me and me.parent and me.parenttype == dt:
ret = frappe.get_list(doctype=dt, fields=fields,
filters=[[dt, "name", '=', me.parent]])
else:
ret = None
elif link.get("child_doctype"):
filters = [[link.get('child_doctype'), link.get("fieldname"), '=', name]]
# dynamic link
if link.get("doctype_fieldname"):
filters.append([link.get('child_doctype'), link.get("doctype_fieldname"), "=", doctype])
ret = frappe.get_list(doctype=dt, fields=fields, filters=filters)
else:
filters = [[dt, link.get("fieldname"), '=', name]]
# dynamic link
if link.get("doctype_fieldname"):
filters.append([dt, link.get("doctype_fieldname"), "=", doctype])
ret = frappe.get_list(doctype=dt, fields=fields, filters=filters)
except frappe.PermissionError:
if frappe.local.message_log:
frappe.local.message_log.pop()
continue
if ret:
results[dt] = ret
frappe.cache().set_value("linked_with:{doctype}:{name}".format(doctype=doctype, name=name), results, user=True)
return results
@frappe.whitelist()
def get_linked_doctypes(doctype):
"""add list of doctypes this doctype is 'linked' with.
Example, for Customer:
{"Address": {"fieldname": "customer"}..}
"""
return frappe.cache().hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype))
def _get_linked_doctypes(doctype):
ret = {}
# find fields where this doctype is linked
ret.update(get_linked_fields(doctype))
ret.update(get_dynamic_linked_fields(doctype))
# find links of parents
links = frappe.db.sql("""select dt from `tabCustom Field`
where (fieldtype="Table" and options=%s)""", (doctype))
links += frappe.db.sql("""select parent from tabDocField
where (fieldtype="Table" and options=%s)""", (doctype))
for dt, in links:
if not dt in ret:
ret[dt] = {"get_parent": True}
for dt in ret.keys():
doctype_module = load_doctype_module(dt)
if getattr(doctype_module, "exclude_from_linked_with", False):
del ret[dt]
return ret
def get_linked_fields(doctype):
links = frappe.db.sql("""select parent, fieldname from tabDocField
where (fieldtype="Link" and options=%s)
or (fieldtype="Select" and options=%s)""", (doctype, "link:"+ doctype))
links += frappe.db.sql("""select dt as parent, fieldname from `tabCustom Field`
where (fieldtype="Link" and options=%s)
or (fieldtype="Select" and options=%s)""", (doctype, "link:"+ doctype))
links = dict(links)
ret = {}
if links:
for dt in links:
ret[dt] = { "fieldname": links[dt] }
# find out if linked in a child table
for parent, options in frappe.db.sql("""select parent, options from tabDocField
where fieldtype="Table"
and options in (select name from tabDocType
where istable=1 and name in (%s))""" % ", ".join(["%s"] * len(links)) ,tuple(links)):
ret[parent] = {"child_doctype": options, "fieldname": links[options] }
if options in ret:
del ret[options]
return ret
def get_dynamic_linked_fields(doctype):
ret = {}
links = frappe.db.sql("""select parent as doctype, fieldname, options as doctype_fieldname
from `tabDocField` where fieldtype='Dynamic Link'""", as_dict=True)
links += frappe.db.sql("""select dt as doctype, fieldname, options as doctype_fieldname
from `tabCustom Field` where fieldtype='Dynamic Link'""", as_dict=True)
for df in links:
if is_single(df.doctype):
continue
# optimized to get both link exists and parenttype
possible_link = frappe.db.sql("""select distinct `{doctype_fieldname}`, parenttype
from `tab{doctype}` where `{doctype_fieldname}`=%s""".format(**df), doctype, as_dict=True)
if possible_link:
for d in possible_link:
# is child
if d.parenttype:
ret[d.parenttype] = {
"child_doctype": df.doctype,
"fieldname": df.fieldname,
"doctype_fieldname": df.doctype_fieldname
}
else:
ret[df.doctype] = {
"fieldname": df.fieldname,
"doctype_fieldname": df.doctype_fieldname
}
return ret

View file

@ -36,7 +36,6 @@ class FormMeta(Meta):
self.add_linked_document_type()
if not self.istable:
self.add_linked_with()
self.add_code()
self.load_print_formats()
self.load_workflows()
@ -130,52 +129,6 @@ class FormMeta(Meta):
# edge case where options="[Select]"
pass
def add_linked_with(self):
"""add list of doctypes this doctype is 'linked' with.
Example, for Customer:
{"Address": {"fieldname": "customer"}..}
"""
# find fields where this doctype is linked
links = frappe.db.sql("""select parent, fieldname from tabDocField
where (fieldtype="Link" and options=%s)
or (fieldtype="Select" and options=%s)""", (self.name, "link:"+ self.name))
links += frappe.db.sql("""select dt as parent, fieldname from `tabCustom Field`
where (fieldtype="Link" and options=%s)
or (fieldtype="Select" and options=%s)""", (self.name, "link:"+ self.name))
links = dict(links)
ret = {}
for dt in links:
ret[dt] = { "fieldname": links[dt] }
if links:
# find out if linked in a child table
for parent, options in frappe.db.sql("""select parent, options from tabDocField
where fieldtype="Table"
and options in (select name from tabDocType
where istable=1 and name in (%s))""" % ", ".join(["%s"] * len(links)) ,tuple(links)):
ret[parent] = {"child_doctype": options, "fieldname": links[options] }
if options in ret:
del ret[options]
# find links of parents
links = frappe.db.sql("""select dt from `tabCustom Field`
where (fieldtype="Table" and options=%s)""", (self.name))
links += frappe.db.sql("""select parent from tabDocField
where (fieldtype="Table" and options=%s)""", (self.name))
for dt, in links:
if not dt in ret:
ret[dt] = {"get_parent": True}
self.set("__linked_with", ret, as_value=True)
def load_print_formats(self):
print_formats = frappe.db.sql("""select * FROM `tabPrint Format`
WHERE doc_type=%s AND docstatus<2 and ifnull(disabled, 0)=0""", (self.name,), as_dict=1,

View file

@ -1,17 +1,17 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe, unittest
from frappe.desk.form.utils import get_linked_docs
from frappe.desk.form.linked_with import get_linked_docs, get_linked_doctypes
class TestForm(unittest.TestCase):
def test_linked_with(self):
results = get_linked_docs("Role", "System Manager")
results = get_linked_docs("Role", "System Manager", linkinfo=get_linked_doctypes("Role"))
self.assertTrue("User" in results)
self.assertTrue("DocType" in results)
if __name__=="__main__":
frappe.connect()
unittest.main()

View file

@ -105,54 +105,3 @@ def get_next(doctype, value, prev, filters=None, order_by="modified desc"):
else:
return res[0][0]
@frappe.whitelist()
def get_linked_docs(doctype, name, metadata_loaded=None, no_metadata=False):
if not metadata_loaded: metadata_loaded = []
meta = frappe.desk.form.meta.get_meta(doctype)
linkinfo = meta.get("__linked_with")
results = {}
if not linkinfo:
return results
me = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
for dt, link in linkinfo.items():
link["doctype"] = dt
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
linkmeta = link_meta_bundle[0]
if not linkmeta.get("issingle"):
fields = [d.fieldname for d in linkmeta.get("fields", {"in_list_view":1,
"fieldtype": ["not in", ["Image", "HTML", "Button", "Table"]]})] \
+ ["name", "modified", "docstatus"]
fields = ["`tab{dt}`.`{fn}`".format(dt=dt, fn=sf.strip()) for sf in fields if sf]
try:
if link.get("get_parent"):
if me and me.parent and me.parenttype == dt:
ret = frappe.get_list(doctype=dt, fields=fields,
filters=[[dt, "name", '=', me.parent]])
else:
ret = None
elif link.get("child_doctype"):
ret = frappe.get_list(doctype=dt, fields=fields,
filters=[[link.get('child_doctype'), link.get("fieldname"), '=', name]])
else:
ret = frappe.get_list(doctype=dt, fields=fields,
filters=[[dt, link.get("fieldname"), '=', name]])
except frappe.PermissionError:
if frappe.local.message_log:
frappe.local.message_log.pop()
continue
if ret:
results[dt] = ret
if not no_metadata and not dt in metadata_loaded:
frappe.local.response.docs.extend(link_meta_bundle)
return results

View file

@ -16,7 +16,7 @@ class BulkLimitCrossedError(frappe.ValidationError): pass
def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None,
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, reply_to=None, cc=(), message_id=None, send_after=None,
attachments=None, reply_to=None, cc=(), show_as_cc=(), message_id=None, send_after=None,
expose_recipients=False, bulk_priority=1):
"""Add email to sending queue (Bulk Email)
@ -89,6 +89,14 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
email_content = email_content.replace("<!--unsubscribe link here-->", unsubscribe_link.html)
email_text_context += unsubscribe_link.text
# show as cc
cc_message = ""
if email in show_as_cc:
cc_message = _("This email was sent to you as CC")
email_content = email_content.replace("<!-- cc message -->", cc_message)
email_text_context = cc_message + "\n" + email_text_context
# add to queue
add(email, sender, subject, email_content, email_text_context, reference_doctype,
reference_name, attachments, reply_to, cc, message_id, send_after, bulk_priority)
@ -230,7 +238,7 @@ def flush(from_test=False):
frappe.db.sql("""update `tabBulk Email` set status='Expired'
where datediff(curdate(), creation) > 3""", auto_commit=auto_commit)
for i in xrange(100):
for i in xrange(500):
email = frappe.db.sql("""select * from `tabBulk Email` where
status='Not Sent' and ifnull(send_after, "2000-01-01 00:00:00") < %s
order by priority desc, creation asc limit 1 for update""", now_datetime(), as_dict=1)

View file

@ -84,7 +84,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Status",
"label": "Status",
"no_copy": 0,
"options": "\nNot Sent\nSending\nSent\nError",
"permlevel": 0,
@ -215,7 +215,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-29 05:16:31.857121",
"modified": "2015-10-02 07:38:40.795371",
"modified_by": "Administrator",
"module": "Email",
"name": "Bulk Email",

View file

@ -41,7 +41,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Service",
"label": "Service",
"no_copy": 0,
"options": "\nGMail\nYahoo Mail\nOutlook.com",
"permlevel": 0,
@ -780,7 +780,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-18 01:34:31.784444",
"modified": "2015-10-02 07:38:47.651995",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",

View file

@ -56,6 +56,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "To add dynamic subject, use jinja tags like\n\n<div><pre><code>{{ doc.name }} Delivered</code></pre></div>",
"fieldname": "subject",
"fieldtype": "Data",
"hidden": 0,
@ -411,7 +412,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-07-09 00:27:00.169741",
"modified": "2015-10-16 01:35:51.254775",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Alert",

View file

@ -94,7 +94,11 @@ def evaluate_alert(doc, alert, event):
if not recipients:
return
frappe.sendmail(recipients=recipients, subject=alert.subject,
subject = alert.subject
if "{" in subject:
subject = frappe.render_template(alert.subject, {"doc": doc, "alert": alert})
frappe.sendmail(recipients=recipients, subject=subject,
message= frappe.render_template(alert.message, {"doc": doc, "alert":alert}),
bulk=True, reference_doctype = doc.doctype, reference_name = doc.name,
attachments = [frappe.attach_print(doc.doctype, doc.name)] if alert.attach_print else None)

View file

@ -82,7 +82,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2014-07-11 17:54:53.298536",
"modified": "2015-10-02 07:38:48.185785",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Alert Recipient",

View file

@ -106,7 +106,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-08-05 06:02:12.805282",
"modified": "2015-10-02 07:38:48.744583",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Unsubscribe",

View file

@ -83,7 +83,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 15:51:26",
"modified": "2015-10-02 07:39:13.407883",
"modified_by": "Administrator",
"module": "Email",
"name": "Standard Reply",

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Standard Reply')
class TestStandardReply(unittest.TestCase):
pass

View file

@ -77,6 +77,7 @@ def get_default_outgoing_email_account(raise_exception_not_set=True):
"sender": frappe.conf.get("auto_email_id", "notifications@example.com")
})
email_account.from_site_config = True
email_account.name = frappe.conf.get("email_sender_name") or "Frappe"
if not email_account and not raise_exception_not_set:
return None

View file

@ -47,6 +47,7 @@ class MandatoryError(ValidationError): pass
class InvalidSignatureError(ValidationError): pass
class RateLimitExceededError(ValidationError): pass
class CannotChangeConstantError(ValidationError): pass
class CharacterLengthExceededError(ValidationError): pass
class UpdateAfterSubmitError(ValidationError): pass
class LinkValidationError(ValidationError): pass
class CancelledLinkError(LinkValidationError): pass

View file

@ -105,7 +105,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-07 15:51:26",
"modified": "2015-10-02 07:38:42.770807",
"modified_by": "Administrator",
"module": "Geo",
"name": "Country",

View file

@ -131,7 +131,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Number Format",
"label": "Number Format",
"no_copy": 0,
"options": "\n#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###",
"permlevel": 0,
@ -153,7 +153,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-14 03:17:04.837607",
"modified": "2015-10-02 07:38:42.863135",
"modified_by": "Administrator",
"module": "Geo",
"name": "Currency",

View file

@ -26,7 +26,7 @@ to ERPNext.
"""
app_icon = "octicon octicon-circuit-board"
app_version = "6.4.9"
app_version = "6.5.0"
app_color = "orange"
github_link = "https://github.com/frappe/frappe"

View file

@ -9,6 +9,7 @@ from frappe.model import default_fields
from frappe.model.naming import set_new_name
from frappe.modules import load_doctype_module
from frappe.model import display_fieldtypes
from frappe.model.db_schema import type_map, varchar_len
_classes = {}
@ -444,6 +445,25 @@ class BaseDocument(object):
frappe.throw(_("Value cannot be changed for {0}").format(self.meta.get_label(fieldname)),
frappe.CannotChangeConstantError)
def _validate_length(self):
if frappe.flags.in_install:
return
for fieldname, value in self.get_valid_dict().iteritems():
df = self.meta.get_field(fieldname)
if df and df.fieldtype in type_map and type_map[df.fieldtype][0]=="varchar":
max_length = cint(df.get("length")) or cint(varchar_len)
if len(cstr(value)) > max_length:
if self.parentfield and self.idx:
reference = _("{0}, Row {1}").format(_(self.doctype), self.idx)
else:
reference = "{0} {1}".format(_(self.doctype), self.name)
frappe.throw(_("{0}: '{1}' will get truncated, as max characters allowed is {2}")\
.format(reference, _(df.label), max_length), frappe.CharacterLengthExceededError)
def _validate_update_after_submit(self):
# get the full doc with children
db_values = frappe.get_doc(self.doctype, self.name).as_dict()

View file

@ -13,9 +13,13 @@ import os
import frappe
from frappe import _
from frappe.utils import cstr, cint
import MySQLdb
class InvalidColumnName(frappe.ValidationError): pass
varchar_len = '140'
standard_varchar_columns = ('name', 'owner', 'modified_by', 'parent', 'parentfield', 'parenttype')
type_map = {
'Currency': ('decimal', '18,6')
,'Int': ('int', '11')
@ -30,14 +34,14 @@ type_map = {
,'Datetime': ('datetime', '6')
,'Time': ('time', '6')
,'Text': ('text', '')
,'Data': ('varchar', '255')
,'Link': ('varchar', '255')
,'Dynamic Link':('varchar', '255')
,'Password': ('varchar', '255')
,'Select': ('varchar', '255')
,'Read Only': ('varchar', '255')
,'Attach': ('varchar', '255')
,'Attach Image':('varchar', '255')
,'Data': ('varchar', varchar_len)
,'Link': ('varchar', varchar_len)
,'Dynamic Link':('varchar', varchar_len)
,'Password': ('varchar', varchar_len)
,'Select': ('varchar', varchar_len)
,'Read Only': ('varchar', varchar_len)
,'Attach': ('text', '')
,'Attach Image':('text', '')
}
default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner',
@ -57,8 +61,10 @@ def updatedb(dt):
raise Exception, 'Wrong doctype "%s" in updatedb' % dt
if not res[0][0]:
frappe.db.commit()
tab = DbTable(dt, 'tab')
tab.validate()
frappe.db.commit()
tab.sync()
frappe.db.begin()
@ -80,12 +86,57 @@ class DbTable:
# load
self.get_columns_from_docfields()
def validate(self):
"""Check if change in varchar length isn't truncating the columns"""
if self.is_new():
return
self.get_columns_from_db()
columns = [frappe._dict({"fieldname": f, "fieldtype": "Data"}) for f in standard_varchar_columns]
columns += self.columns.values()
for col in columns:
if col.fieldtype in type_map and type_map[col.fieldtype][0]=="varchar":
# validate length range
new_length = cint(col.length) or cint(varchar_len)
if not (1 <= new_length <= 255):
frappe.throw(_("Length of {0} should be between 1 and 255").format(col.fieldname))
try:
# check for truncation
max_length = frappe.db.sql("""select max(length(`{fieldname}`)) from `tab{doctype}`"""\
.format(fieldname=col.fieldname, doctype=self.doctype))
except MySQLdb.OperationalError, e:
if e.args[0]==1054:
# Unknown column 'column_name' in 'field list'
continue
else:
raise
if max_length and max_length[0][0] > new_length:
current_type = self.current_columns[col.fieldname]["type"]
current_length = re.findall('varchar\(([\d]+)\)', current_type)[0]
if col.fieldname in self.columns:
self.columns[col.fieldname].length = current_length
frappe.msgprint(_("Reverting length to {0} for '{1}' in '{2}'; Setting the length as {3} will cause truncation of data.")\
.format(current_length, col.fieldname, self.doctype, new_length))
def sync(self):
if not self.name in DbManager(frappe.db).get_tables_list(frappe.db.cur_db_name):
if self.is_new():
self.create()
else:
self.alter()
def is_new(self):
return self.name not in DbManager(frappe.db).get_tables_list(frappe.db.cur_db_name)
def create(self):
add_text = ''
@ -99,21 +150,21 @@ class DbTable:
# create table
frappe.db.sql("""create table `%s` (
name varchar(255) not null primary key,
name varchar({varchar_len}) not null primary key,
creation datetime(6),
modified datetime(6),
modified_by varchar(255),
owner varchar(255),
modified_by varchar({varchar_len}),
owner varchar({varchar_len}),
docstatus int(1) default '0',
parent varchar(255),
parentfield varchar(255),
parenttype varchar(255),
parent varchar({varchar_len}),
parentfield varchar({varchar_len}),
parenttype varchar({varchar_len}),
idx int(8),
%sindex parent(parent))
ENGINE=InnoDB
ROW_FORMAT=COMPRESSED
CHARACTER SET=utf8mb4
COLLATE=utf8mb4_unicode_ci""" % (self.name, add_text))
COLLATE=utf8mb4_unicode_ci""".format(varchar_len=varchar_len) % (self.name, add_text))
def get_column_definitions(self):
column_list = [] + default_columns
@ -139,6 +190,7 @@ class DbTable:
get columns from docfields and custom fields
"""
fl = frappe.db.sql("SELECT * FROM tabDocField WHERE parent = %s", self.doctype, as_dict = 1)
lengths = {}
precisions = {}
uniques = {}
@ -148,19 +200,26 @@ class DbTable:
WHERE dt = %s AND docstatus < 2""", (self.doctype,), as_dict=1)
if custom_fl: fl += custom_fl
# get precision from property setters
for ps in frappe.get_all("Property Setter", fields=["field_name", "value"],
filters={"doc_type": self.doctype, "doctype_or_field": "DocField", "property": "precision"}):
precisions[ps.field_name] = ps.value
# apply length, precision and unique from property setters
for ps in frappe.get_all("Property Setter", fields=["field_name", "property", "value"],
filters={
"doc_type": self.doctype,
"doctype_or_field": "DocField",
"property": ["in", ["precision", "length", "unique"]]
}):
# apply unique from property setters
for ps in frappe.get_all("Property Setter", fields=["field_name", "value"],
filters={"doc_type": self.doctype, "doctype_or_field": "DocField", "property": "unique"}):
if ps.property=="length":
lengths[ps.field_name] = cint(ps.value)
elif ps.property=="precision":
precisions[ps.field_name] = cint(ps.value)
elif ps.property=="unique":
uniques[ps.field_name] = cint(ps.value)
for f in fl:
self.columns[f['fieldname']] = DbColumn(self, f['fieldname'],
f['fieldtype'], f.get('length'), f.get('default'), f.get('search_index'),
f['fieldtype'], lengths.get(f["fieldname"]) or f.get('length'), f.get('default'), f.get('search_index'),
f.get('options'), uniques.get(f["fieldname"], f.get('unique')), precisions.get(f['fieldname']) or f.get('precision'))
def get_columns_from_db(self):
@ -201,7 +260,6 @@ class DbTable:
frappe.db.sql("set foreign_key_checks=1")
def alter(self):
self.get_columns_from_db()
for col in self.columns.values():
col.build_for_alter_table(self.current_columns.get(col.fieldname, None))
@ -268,7 +326,7 @@ class DbColumn:
self.precision = precision
def get_definition(self, with_default=1):
column_def = get_definition(self.fieldtype, self.precision)
column_def = get_definition(self.fieldtype, precision=self.precision, length=self.length)
if not column_def:
return column_def
@ -287,7 +345,7 @@ class DbColumn:
return column_def
def build_for_alter_table(self, current_def):
column_def = get_definition(self.fieldtype)
column_def = get_definition(self.fieldtype, self.precision, self.length)
# no columns
if not column_def:
@ -471,20 +529,28 @@ def remove_all_foreign_keys():
for f in fklist:
frappe.db.sql("alter table `tab%s` drop foreign key `%s`" % (t[0], f[1]))
def get_definition(fieldtype, precision=None):
def get_definition(fieldtype, precision=None, length=None):
d = type_map.get(fieldtype)
if not d:
return
ret = d[0]
coltype = d[0]
size = None
if d[1]:
length = d[1]
if fieldtype in ["Float", "Currency", "Percent"] and cint(precision) > 6:
length = '18,9'
ret += '(' + length + ')'
size = d[1]
return ret
if size:
if fieldtype in ["Float", "Currency", "Percent"] and cint(precision) > 6:
size = '18,9'
if coltype == "varchar" and length:
size = length
if size is not None:
coltype = "{coltype}({size})".format(coltype=coltype, size=size)
return coltype
def add_column(doctype, column_name, fieldtype, precision=None):
frappe.db.commit()

View file

@ -337,11 +337,13 @@ class Document(BaseDocument):
self._validate_links()
self._validate_selects()
self._validate_constants()
self._validate_length()
children = self.get_all_children()
for d in children:
d._validate_selects()
d._validate_constants()
d._validate_length()
# extract images after validations to save processing if some validation error is raised
self._extract_images_from_text_editor()
@ -616,11 +618,34 @@ class Document(BaseDocument):
elif self._action=="update_after_submit":
self.run_method("on_update_after_submit")
frappe.cache().hdel("last_modified", self.doctype)
self.clear_cache()
self.notify_update()
self.latest = None
def clear_cache(self):
frappe.cache().hdel("last_modified", self.doctype)
self.clear_linked_with_cache()
def clear_linked_with_cache(self):
cache = frappe.cache()
def _clear_cache(d):
for df in (d.meta.get_link_fields() + d.meta.get_dynamic_link_fields()):
if d.get(df.fieldname):
doctype = df.options if df.fieldtype=="Link" else d.get(df.options)
name = d.get(df.fieldname)
if df.fieldtype=="Dynamic Link":
# clear linked doctypes list
cache.hdel("linked_doctypes", doctype)
# delete linked with cache for all users
cache.delete_value("user:*:linked_with:{doctype}:{name}".format(doctype=doctype, name=name))
_clear_cache(self)
for d in self.get_all_children():
_clear_cache(d)
def notify_update(self):
"""Publish realtime that the current document is modified"""
frappe.publish_realtime("doc_update", {"modified": self.modified, "doctype": self.doctype, "name": self.name},

View file

@ -60,6 +60,9 @@ class Meta(Document):
def get_link_fields(self):
return self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]})
def get_dynamic_link_fields(self):
return self.get("fields", {"fieldtype": "Dynamic Link"})
def get_select_fields(self):
return self.get("fields", {"fieldtype": "Select", "options":["not in",
["[Select]", "Loading..."]]})
@ -329,14 +332,19 @@ def trim_tables():
frappe.db.sql_ddl(query)
def clear_cache(doctype=None):
frappe.cache().delete_value("is_table")
frappe.cache().delete_value("doctype_modules")
cache = frappe.cache()
groups = ["meta", "form_meta", "table_columns", "last_modified"]
cache.delete_value("is_table")
cache.delete_value("doctype_modules")
groups = ["meta", "form_meta", "table_columns", "last_modified", "linked_doctypes"]
def clear_single(dt):
for name in groups:
frappe.cache().hdel(name, dt)
cache.hdel(name, dt)
# also clear linked_with list cache
cache.delete_keys("user:*:linked_with:{doctype}:".format(doctype=doctype))
if doctype:
clear_single(doctype)
@ -353,5 +361,5 @@ def clear_cache(doctype=None):
else:
# clear all
for name in groups:
frappe.cache().delete_value(name)
cache.delete_value(name)

View file

@ -82,7 +82,7 @@ def make_autoname(key, doctype=''):
DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series
"""
if key=="hash":
return frappe.generate_hash(doctype)[:10]
return frappe.generate_hash(doctype, 10)
if not "#" in key:
key = key + ".#####"

View file

@ -2,8 +2,9 @@ execute:frappe.db.sql("""update `tabPatch Log` set patch=replace(patch, '.4_0.',
frappe.patches.v5_0.convert_to_barracuda_and_utf8mb4
frappe.patches.v6_1.rename_file_data
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2014-01-24
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2015-08-20
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2015-10-16
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2014-06-24
execute:frappe.reload_doc('custom', 'doctype', 'custom_field') #2015-10-19
execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26
execute:frappe.reload_doc('core', 'doctype', 'report') #2014-06-03
execute:frappe.reload_doc('core', 'doctype', 'version') #2014-02-21
@ -94,3 +95,5 @@ frappe.patches.v6_2.ignore_user_permissions_if_missing
execute:frappe.db.sql("delete from tabSessions where user is null")
frappe.patches.v6_2.rename_backup_manager
execute:frappe.delete_doc("DocType", "Backup Manager")
frappe.patches.v6_4.reduce_varchar_length
frappe.patches.v6_4.rename_bengali_language

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import frappe
from frappe.translate import rename_language
def execute():
language_map = {
@ -8,11 +9,5 @@ def execute():
"中國(繁體)": "正體中文"
}
language_in_system_settings = frappe.db.get_single_value("System Settings", "language")
if language_in_system_settings in language_map:
new_language_name = language_map[language_in_system_settings]
frappe.db.set_value("System Settings", "System Settings", "language", new_language_name)
for old_name, new_name in language_map.items():
frappe.db.sql("""update `tabUser` set language=%(new_name)s where language=%(old_name)s""",
{ "old_name": old_name, "new_name": new_name })
rename_language(old_name, new_name)

View file

View file

@ -0,0 +1,33 @@
from __future__ import unicode_literals
import frappe
def execute():
for doctype in frappe.get_all("DocType", filters={"issingle": 0}):
doctype = doctype.name
for column in frappe.db.sql("desc `tab{doctype}`".format(doctype=doctype), as_dict=True):
fieldname = column["Field"]
column_type = column["Type"]
if not column_type.startswith("varchar"):
continue
max_length = frappe.db.sql("""select max(length(`{fieldname}`)) from `tab{doctype}`"""\
.format(fieldname=fieldname, doctype=doctype))
max_length = max_length[0][0] if max_length else None
if max_length and max_length > 140:
print "setting length of '{fieldname}' in '{doctype}' as {length}".format(
fieldname=fieldname, doctype=doctype, length=max_length)
# create property setter for length
frappe.make_property_setter({
"doctype": doctype,
"fieldname": fieldname,
"property": "length",
"value": max_length,
"property_type": "Int"
})
frappe.clear_cache(doctype=doctype)

View file

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import frappe
from frappe.translate import rename_language
def execute():
rename_language("বাঙালি", "বাংলা")

View file

@ -116,7 +116,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 3,
"modified": "2015-09-07 15:51:26",
"modified": "2015-10-02 07:38:56.001216",
"modified_by": "Administrator",
"module": "Print",
"name": "Letter Head",

View file

@ -83,7 +83,7 @@
"ignore_user_permissions": 0,
"in_filter": 1,
"in_list_view": 0,
"label": "Standard",
"label": "Standard",
"no_copy": 1,
"oldfieldname": "standard",
"oldfieldtype": "Select",
@ -153,7 +153,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Print Format Type",
"label": "Print Format Type",
"no_copy": 0,
"options": "Server\nClient",
"permlevel": 0,
@ -268,7 +268,7 @@
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Font",
"label": "Font",
"no_copy": 0,
"options": "Default\nArial\nHelvetica\nVerdana\nMonospace",
"permlevel": 0,
@ -447,7 +447,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-09-09 05:46:11.025962",
"modified": "2015-10-02 07:39:00.918464",
"modified_by": "Administrator",
"module": "Print",
"name": "Print Format",

View file

@ -353,6 +353,11 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({
.addClass("input-with-feedback form-control")
.prependTo(this.input_area)
if (in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'],
this.df.fieldtype)) {
this.$input.attr("maxlength", this.df.length || 140);
}
this.set_input_attributes();
this.input = this.$input.get(0);
this.has_input = true;

View file

@ -12,20 +12,13 @@ frappe.ui.form.LinkedWith = Class.extend({
if(!this.dialog)
this.make_dialog();
this.dialog.fields_dict.list.$wrapper.html('<div class="text-muted text-center">'
+ __("Loading") + '...</div>');
this.dialog.show();
},
make_dialog: function() {
var me = this;
this.linked_with = this.frm.meta.__linked_with;
var links = [];
$.each(this.linked_with, function(doctype, tmp) {
if(frappe.model.can_get_report(doctype)) {
links.push({label: __(doctype), value: doctype});
}
});
links = frappe.utils.sort(links, "label");
this.dialog = new frappe.ui.Dialog({
hide_on_page_refresh: true,
@ -37,49 +30,116 @@ frappe.ui.form.LinkedWith = Class.extend({
this.dialog.$wrapper.find(".modal-dialog").addClass("linked-with-dialog");
if(!links) {
this.dialog.fields_dict.list.$wrapper.html("<div class='alert alert-warning'>"
+ this.frm.doctype + ": "
+ (this.linked_with ? __("Not Linked to any record.") : __("Not enough permission to see links."))
+ "</div>")
return;
}
this.dialog.on_page_show = function() {
me.dialog.fields_dict.list.$wrapper.html('<div class="text-muted text-center">'
+ __("Loading") + '...</div>');
frappe.call({
method:"frappe.desk.form.utils.get_linked_docs",
args: {
doctype: me.frm.doctype,
name: me.frm.docname,
metadata_loaded: keys(locals.DocType)
},
callback: function(r) {
var parent = me.dialog.fields_dict.list.$wrapper.empty();
if(keys(r.message || {}).length) {
$.each(keys(r.message).sort(), function(i, doctype) {
var listview = frappe.views.get_listview(doctype, me);
listview.no_delete = true;
var wrapper = $('<div class="panel panel-default"><div>').appendTo(parent);
$('<div class="panel-heading">').html(__(doctype).bold()).appendTo(wrapper);
var body = $('<div class="panel-body">').appendTo(wrapper);
$.each(r.message[doctype], function(i, d) {
d.doctype = doctype;
listview.render($('<div class="list-row"></div>')
.appendTo(body), d, me);
})
})
} else {
parent.html(__("Not Linked to any record."));
// execute ajax calls sequentially
// 1. get linked doctypes
// 2. load all doctypes
// 3. load linked docs
$.when(me.get_linked_doctypes())
.then(function() { return me.load_doctypes() })
.then(function() {
if (me.links_not_permitted_or_missing()) {
return;
}
}
})
return me.get_linked_docs();
});
}
},
load_doctypes: function() {
var me = this;
var already_loaded = Object.keys(locals.DocType);
var doctypes_to_load = [];
$.each(Object.keys(me.frm.__linked_doctypes), function(i, v) {
if (already_loaded.indexOf(v)===-1) {
doctypes_to_load.push(v);
}
});
// load all doctypes sequentially using with_doctype
return $.when.apply($, $.map(doctypes_to_load, function(dt) {
return frappe.model.with_doctype(dt, function() {
if (frappe.listview_settings[dt]) {
// add additional fields to __linked_doctypes
me.frm.__linked_doctypes[dt].add_fields = frappe.listview_settings[dt].add_fields;
}
});
}));
},
links_not_permitted_or_missing: function() {
var me = this;
var links = [];
$.each(me.frm.__linked_doctypes, function(doctype, tmp) {
if(frappe.model.can_get_report(doctype)) {
links.push({label: __(doctype), value: doctype});
}
});
links = frappe.utils.sort(links, "label");
if(!links) {
me.dialog.fields_dict.list.$wrapper.html("<div class='alert alert-warning'>"
+ me.frm.doctype + ": "
+ (me.frm.__linked_doctypes ? __("Not Linked to any record.") : __("Not enough permission to see links."))
+ "</div>")
return true;
}
return false;
},
get_linked_doctypes: function() {
var me = this;
if (this.frm.__linked_doctypes) {
return;
}
return frappe.call({
method: "frappe.desk.form.linked_with.get_linked_doctypes",
args: {
doctype: this.frm.doctype
},
callback: function(r) {
me.frm.__linked_doctypes = r.message;
}
});
},
get_linked_docs: function() {
var me = this;
return frappe.call({
method:"frappe.desk.form.linked_with.get_linked_docs",
args: {
doctype: me.frm.doctype,
name: me.frm.docname,
linkinfo: me.frm.__linked_doctypes
},
callback: function(r) {
var parent = me.dialog.fields_dict.list.$wrapper.empty();
if(keys(r.message || {}).length) {
$.each(keys(r.message).sort(), function(i, doctype) {
var listview = frappe.views.get_listview(doctype, me);
listview.no_delete = true;
var wrapper = $('<div class="panel panel-default"><div>').appendTo(parent);
$('<div class="panel-heading">').html(__(doctype).bold()).appendTo(wrapper);
var body = $('<div class="panel-body">').appendTo(wrapper);
$.each(r.message[doctype], function(i, d) {
d.doctype = doctype;
listview.render($('<div class="list-row"></div>')
.appendTo(body), d, me);
})
})
} else {
parent.html(__("Not Linked to any record."));
}
}
});
}
});

View file

@ -83,7 +83,7 @@ $.extend(frappe.model, {
with_doctype: function(doctype, callback) {
if(locals.DocType[doctype]) {
callback();
callback && callback();
} else {
var cached_timestamp = null;
if(localStorage["_doctype:" + doctype]) {
@ -112,7 +112,7 @@ $.extend(frappe.model, {
}
frappe.model.init_doctype(doctype);
frappe.defaults.set_user_permissions(r.user_permissions);
callback(r);
callback && callback(r);
}
});
}

View file

@ -57,7 +57,7 @@ frappe.ui.FilterList = Class.extend({
var filter = new frappe.ui.Filter({
flist: this,
doctype: doctype,
_doctype: doctype,
fieldname: fieldname,
condition: condition,
value: value,
@ -168,7 +168,7 @@ frappe.ui.Filter = Class.extend({
// set the field
if(me.fieldname) {
// pre-sets given (could be via tags!)
this.set_values(me.doctype, me.fieldname, me.condition, me.value);
this.set_values(me._doctype, me.fieldname, me.condition, me.value);
} else {
me.set_field(me.doctype, 'name');
}

View file

@ -190,6 +190,11 @@ frappe.search.verbs = [
// doctype list
function(txt) {
if (txt.toLowerCase().indexOf(" list")) {
// remove list keyword
txt = txt.replace(/ list/ig, "").trim();
}
frappe.search.find(frappe.boot.user.can_read, txt, function(match) {
if(in_list(frappe.boot.single_types, match)) {
return {

View file

@ -435,7 +435,7 @@ frappe.views.CommunicationComposer = Class.extend({
callback: function(r) {
if(!r.exc) {
if(form_values.send_email && r.message["emails_not_sent_to"]) {
msgprint( __("Email not sent to {0}",
msgprint( __("Email not sent to {0} (unsubscribed / disabled)",
[ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) );
}

View file

@ -82,7 +82,7 @@ frappe.views.Gantt = frappe.views.CalendarBase.extend({
gantt_area.gantt({
source: me.get_source(r),
navigate: "scroll",
scale: "days",
scale: me.gantt_scale || "days",
minScale: "hours",
maxScale: "months",
itemsPerPage: 20,

View file

@ -32,7 +32,7 @@ def clear_cache(user=None):
cache = frappe.cache()
groups = ("bootinfo", "user_recent", "user_roles", "user_doc", "lang",
"defaults", "user_permissions", "roles", "home_page")
"defaults", "user_permissions", "roles", "home_page", "linked_with")
if user:
for name in groups:

View file

@ -186,10 +186,13 @@ def run_async_task(self, site=None, user=None, cmd=None, form_dict=None, hijack_
@celery_task()
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None,
recipients=None, cc=None):
recipients=None, cc=None, lang=None):
try:
frappe.connect(site=site)
if lang:
frappe.local.lang = lang
# upto 3 retries
for i in xrange(3):
try:

View file

@ -8,6 +8,7 @@
<body style="line-height: 1.5; color: #36414C;">
<!-- body -->
<div style="font-family: Helvetica, Arial, sans-serif; font-size: 14px; padding: 10px;">
<em><!-- cc message --></em>
{{ content }}
{{ signature }}
</div>

View file

@ -150,3 +150,9 @@ class TestDocument(unittest.TestCase):
d.load_from_db()
d.starts_on = "2014-01-01"
d.validate_update_after_submit()
def test_varchar_length(self):
d = self.test_insert()
d.subject = "abcde"*100
self.assertRaises(frappe.CharacterLengthExceededError, d.save)

View file

@ -529,3 +529,11 @@ def deduplicate_messages(messages):
def get_bench_dir():
return os.path.join(frappe.__file__, '..', '..', '..', '..')
def rename_language(old_name, new_name):
language_in_system_settings = frappe.db.get_single_value("System Settings", "language")
if language_in_system_settings == old_name:
frappe.db.set_value("System Settings", "System Settings", "language", new_name)
frappe.db.sql("""update `tabUser` set language=%(new_name)s where language=%(old_name)s""",
{ "old_name": old_name, "new_name": new_name })

View file

@ -513,6 +513,9 @@ def get_url(uri=None, full_address=False):
"""get app url from request"""
host_name = frappe.local.conf.host_name
if uri and (uri.startswith("http://") or uri.startswith("https://")):
return uri
if not host_name:
if hasattr(frappe.local, "request") and frappe.local.request and frappe.local.request.host:
protocol = 'https' == frappe.get_request_header('X-Forwarded-Proto', "") and 'https://' or 'http://'
@ -538,10 +541,16 @@ def get_url(uri=None, full_address=False):
def get_host_name():
return get_url().rsplit("//", 1)[-1]
def get_url_to_form(doctype, name, label=None):
def get_link_to_form(doctype, name, label=None):
if not label: label = name
return """<a href="/desk#!Form/%(doctype)s/%(name)s">%(label)s</a>""" % locals()
return """<a href="{0}">{1}</a>""".format(get_url_to_form(doctype, name), label)
def get_url_to_form(doctype, name):
return get_url(uri = "desk/#Form/{0}/{1}".format(doctype, name))
def get_url_to_list(doctype):
return get_url(uri = "desk/#List/{0}".format(doctype))
operator_map = {
# startswith

View file

@ -10,6 +10,7 @@ from frappe.utils import get_site_path, get_hook_method, get_files_path, random_
from frappe import _
from frappe import conf
from copy import copy
import urllib
class MaxFileSizeReachedError(frappe.ValidationError): pass
@ -59,6 +60,8 @@ def save_url(file_url, dt, dn, folder):
# frappe.msgprint("URL must start with 'http://' or 'https://'")
# return None, None
file_url = urllib.unquote(file_url)
f = frappe.get_doc({
"doctype": "File",
"file_url": file_url,
@ -241,22 +244,30 @@ def remove_file(fid, attached_to_doctype=None, attached_to_name=None):
return comment
def delete_file_data_content(doc):
def delete_file_data_content(doc, only_thumbnail=False):
method = get_hook_method('delete_file_data_content', fallback=delete_file_from_filesystem)
method(doc)
method(doc, only_thumbnail=only_thumbnail)
def delete_file_from_filesystem(doc):
path = doc.file_name
if path.startswith("files/"):
path = frappe.utils.get_site_path("public", doc.file_name)
def delete_file_from_filesystem(doc, only_thumbnail=False):
"""Delete file, thumbnail from File document"""
if only_thumbnail:
delete_file(doc.thumbnail_url)
else:
path = frappe.utils.get_site_path("public", "files", doc.file_name)
delete_file(doc.file_url)
delete_file(doc.thumbnail_url)
path = encode(path)
def delete_file(path):
"""Delete file from `public folder`"""
if path and path.startswith("/files/"):
parts = os.path.split(path)
path = frappe.utils.get_site_path("public", "files", parts[-1])
if os.path.exists(path):
os.remove(path)
if "/../" in path:
frappe.msgprint(_("It is risky to delete this file: {0}. Please contact your System Manager.").format(path))
path = encode(path)
if os.path.exists(path):
os.remove(path)
def get_file(fname):
f = frappe.db.sql("""select file_name from `tabFile`

View file

@ -147,4 +147,3 @@ class RedisWrapper(redis.Redis):
return super(redis.Redis, self).hkeys(self.make_key(name))
except redis.exceptions.ConnectionError:
return []

View file

@ -83,7 +83,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-19 09:29:46.804175",
"modified": "2015-10-19 03:04:52.537466",
"modified_by": "Administrator",
"module": "Website",
"name": "About Us Team Member",

View file

@ -126,7 +126,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-09-11 12:20:05.555186",
"modified": "2015-10-02 07:38:39.540340",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Category",

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