@@ -0,0 +1,40 @@ | |||||
- Added Language Support for Following languages | |||||
<table class="table table-bordered"> | |||||
<tr> | |||||
<td style="width: 30%">bo*</td> | |||||
<td>ལྷ་སའི་སྐད་</td> | |||||
</tr> | |||||
<tr> | |||||
<td>fi</td> | |||||
<td>suomalainen</td> | |||||
</tr> | |||||
<tr> | |||||
<td>km</td> | |||||
<td>ភាសាខ្មែរ</td> | |||||
</tr> | |||||
<tr> | |||||
<td>mk</td> | |||||
<td>македонски</td> | |||||
</tr> | |||||
<tr> | |||||
<td>my</td> | |||||
<td>Melayu</td> | |||||
</tr> | |||||
<tr> | |||||
<td>no</td> | |||||
<td>norsk</td> | |||||
</tr> | |||||
<tr> | |||||
<td>sv</td> | |||||
<td>Svenska</td> | |||||
</tr> | |||||
<tr> | |||||
<td>sq</td> | |||||
<td>shqiptar</td> | |||||
</tr> | |||||
</table> | |||||
* Unable to find translations for Tibetian via Google | |||||
- To contribute to translations, please login to [https://translate.erpnext.com](https://translate.erpnext.com) |
@@ -103,7 +103,7 @@ def _is_scheduler_enabled(): | |||||
enable_scheduler = False | enable_scheduler = False | ||||
try: | try: | ||||
frappe.connect() | frappe.connect() | ||||
enable_scheduler = cint(frappe.db.get_default("enable_scheduler")) | |||||
enable_scheduler = cint(frappe.db.get_single_value("System Settings", "enable_scheduler")) and True or False | |||||
except: | except: | ||||
pass | pass | ||||
finally: | finally: | ||||
@@ -72,6 +72,26 @@ class Communication(Document): | |||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict() | ["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict() | ||||
def notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | def notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | ||||
"""Calls a delayed celery task 'sendmail' that enqueus email in Bulk Email queue | |||||
:param print_html: Send given value as HTML attachment | |||||
:param print_format: Attach print format of parent document | |||||
:param attachments: A list of filenames that should be attached when sending this email | |||||
:param recipients: Email recipients | |||||
:param except_recipient: True when pulling email, the notification shouldn't go to the main recipient | |||||
""" | |||||
if frappe.flags.in_test: | |||||
# for test cases, run synchronously | |||||
self._notify(print_html=print_html, print_format=print_format, attachments=attachments, | |||||
recipients=recipients, except_recipient=except_recipient) | |||||
else: | |||||
from frappe.tasks import sendmail | |||||
sendmail.delay(frappe.local.site, self.name, | |||||
print_html=print_html, print_format=print_format, attachments=attachments, | |||||
recipients=recipients, except_recipient=except_recipient) | |||||
def _notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | |||||
self.prepare_to_notify(print_html, print_format, attachments) | self.prepare_to_notify(print_html, print_format, attachments) | ||||
if not recipients: | if not recipients: | ||||
recipients = self.get_recipients(except_recipient=except_recipient) | recipients = self.get_recipients(except_recipient=except_recipient) | ||||
@@ -143,6 +163,7 @@ class Communication(Document): | |||||
sender = parseaddr(self.sender)[1] | sender = parseaddr(self.sender)[1] | ||||
filtered = [] | filtered = [] | ||||
email_addresses = [] | |||||
for e in list(set(recipients)): | for e in list(set(recipients)): | ||||
if (e=="Administrator") or ((e==self.sender) and (e not in original_recipients)) or \ | if (e=="Administrator") or ((e==self.sender) and (e not in original_recipients)) or \ | ||||
(e in unsubscribed) or (e in email_accounts): | (e in unsubscribed) or (e in email_accounts): | ||||
@@ -160,8 +181,11 @@ class Communication(Document): | |||||
# while pulling email, don't send email to current recipient | # while pulling email, don't send email to current recipient | ||||
continue | continue | ||||
if e not in filtered and email_id not in filtered: | |||||
# make sure of case-insensitive uniqueness of email address | |||||
if email_id.lower() not in email_addresses: | |||||
# append the full email i.e. "Human <human@example.com>" | |||||
filtered.append(e) | filtered.append(e) | ||||
email_addresses.append(email_id.lower()) | |||||
if getattr(self, "send_me_a_copy", False): | if getattr(self, "send_me_a_copy", False): | ||||
filtered.append(self.sender) | filtered.append(self.sender) | ||||
@@ -252,6 +276,10 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = | |||||
}) | }) | ||||
comm.insert(ignore_permissions=True) | comm.insert(ignore_permissions=True) | ||||
# needed for communication.notify which uses celery delay | |||||
# if not committed, delayed task doesn't find the communication | |||||
frappe.db.commit() | |||||
recipients = None | recipients = None | ||||
if send_email: | if send_email: | ||||
comm.send_me_a_copy = send_me_a_copy | comm.send_me_a_copy = send_me_a_copy | ||||
@@ -262,8 +262,8 @@ def validate_fields(meta): | |||||
def check_dynamic_link_options(d): | def check_dynamic_link_options(d): | ||||
if d.fieldtype=="Dynamic Link": | if d.fieldtype=="Dynamic Link": | ||||
doctype_pointer = filter(lambda df: df.fieldname==d.options, fields) | doctype_pointer = filter(lambda df: df.fieldname==d.options, fields) | ||||
if not doctype_pointer or (doctype_pointer[0].fieldtype!="Link") \ | |||||
or (doctype_pointer[0].options!="DocType"): | |||||
if not doctype_pointer or (doctype_pointer[0].fieldtype not in ("Link", "Select")) \ | |||||
or (doctype_pointer[0].fieldtype=="Link" and doctype_pointer[0].options!="DocType"): | |||||
frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'")) | frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'")) | ||||
def check_illegal_default(d): | def check_illegal_default(d): | ||||
@@ -277,7 +277,7 @@ frappe.PermissionEngine = Class.extend({ | |||||
}, | }, | ||||
rights: ["read", "write", "create", "delete", "submit", "cancel", "amend", | rights: ["read", "write", "create", "delete", "submit", "cancel", "amend", | ||||
"print", "email", "report", "import", "export", "set_user_permissions"], | |||||
"print", "email", "report", "import", "export", "set_user_permissions", "share"], | |||||
set_show_users: function(cell, role) { | set_show_users: function(cell, role) { | ||||
cell.html("<a class='grey' href='#'>"+__(role)+"</a>") | cell.html("<a class='grey' href='#'>"+__(role)+"</a>") | ||||
@@ -1,5 +1,6 @@ | |||||
ar العربية | ar العربية | ||||
bg bǎlgarski | bg bǎlgarski | ||||
bo ལྷ་སའི་སྐད་ | |||||
bs bosanski | bs bosanski | ||||
ca català | ca català | ||||
cs česky | cs česky | ||||
@@ -9,6 +10,7 @@ el ελληνικά | |||||
es español | es español | ||||
en english | en english | ||||
fa پارسی | fa پارسی | ||||
fi suomalainen | |||||
fr français | fr français | ||||
he עברית | he עברית | ||||
hi हिंदी | hi हिंदी | ||||
@@ -17,21 +19,27 @@ hu magyar | |||||
id Indonesia | id Indonesia | ||||
it italiano | it italiano | ||||
ja 日本語 | ja 日本語 | ||||
km ភាសាខ្មែរ | |||||
kn ಕನ್ನಡ | kn ಕನ್ನಡ | ||||
ko 한국의 | ko 한국의 | ||||
lv latviešu valoda | lv latviešu valoda | ||||
mr मराठी | mr मराठी | ||||
mk македонски | |||||
my Melayu | |||||
nl nederlands | nl nederlands | ||||
no norsk | |||||
pl polski | pl polski | ||||
pt português | pt português | ||||
pt-BR português brasileiro | pt-BR português brasileiro | ||||
ro român | ro român | ||||
ru русский | ru русский | ||||
sv svenska | |||||
sk slovenčina | sk slovenčina | ||||
sq shqiptar | |||||
sr српски | sr српски | ||||
ta தமிழ் | ta தமிழ் | ||||
th ไทย | th ไทย | ||||
tr Türk | tr Türk | ||||
vi việt | vi việt | ||||
zh-cn 中国(简体) | |||||
zh-tw 中國(繁體) | |||||
zh-cn 簡體中文 | |||||
zh-tw 正體中文 |
@@ -32,10 +32,12 @@ def savedocs(): | |||||
raise | raise | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def cancel(doctype=None, name=None): | |||||
def cancel(doctype=None, name=None, workflow_state_fieldname=None, workflow_state=None): | |||||
"""cancel a doclist""" | """cancel a doclist""" | ||||
try: | try: | ||||
doc = frappe.get_doc(doctype, name) | doc = frappe.get_doc(doctype, name) | ||||
if workflow_state_fieldname and workflow_state: | |||||
doc.set(workflow_state_fieldname, workflow_state) | |||||
doc.cancel() | doc.cancel() | ||||
send_updated_docs(doc) | send_updated_docs(doc) | ||||
@@ -144,7 +144,9 @@ def get_linked_docs(doctype, name, metadata_loaded=None, no_metadata=False): | |||||
filters=[[dt, link.get("fieldname"), '=', name]]) | filters=[[dt, link.get("fieldname"), '=', name]]) | ||||
except frappe.PermissionError: | except frappe.PermissionError: | ||||
frappe.local.message_log.pop() | |||||
if frappe.local.message_log: | |||||
frappe.local.message_log.pop() | |||||
continue | continue | ||||
if ret: | if ret: | ||||
@@ -4,6 +4,7 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
import HTMLParser | import HTMLParser | ||||
import smtplib | |||||
from frappe import msgprint, throw, _ | from frappe import msgprint, throw, _ | ||||
from frappe.email.smtp import SMTPServer, get_outgoing_email_account | from frappe.email.smtp import SMTPServer, get_outgoing_email_account | ||||
from frappe.email.email_body import get_email, get_formatted_html | from frappe.email.email_body import get_email, get_formatted_html | ||||
@@ -107,14 +108,18 @@ def add(email, sender, subject, formatted, text_content=None, | |||||
e.insert(ignore_permissions=True) | e.insert(ignore_permissions=True) | ||||
def check_bulk_limit(recipients): | def check_bulk_limit(recipients): | ||||
# get count of mails sent this month | |||||
this_month = frappe.db.sql("""select count(*) from `tabBulk Email` where | this_month = frappe.db.sql("""select count(*) from `tabBulk Email` where | ||||
MONTH(creation)=MONTH(CURDATE())""")[0][0] | |||||
status='Sent' and MONTH(creation)=MONTH(CURDATE())""")[0][0] | |||||
# if using settings from site_config.json, check bulk limit | |||||
# No limit for own email settings | # No limit for own email settings | ||||
smtp_server = SMTPServer() | smtp_server = SMTPServer() | ||||
if smtp_server.email_account and getattr(smtp_server.email_account, | |||||
"from_site_config", False) or frappe.flags.in_test: | |||||
if (smtp_server.email_account | |||||
and getattr(smtp_server.email_account, "from_site_config", False) | |||||
or frappe.flags.in_test): | |||||
monthly_bulk_mail_limit = frappe.conf.get('monthly_bulk_mail_limit') or 500 | monthly_bulk_mail_limit = frappe.conf.get('monthly_bulk_mail_limit') or 500 | ||||
if (this_month + len(recipients)) > monthly_bulk_mail_limit: | if (this_month + len(recipients)) > monthly_bulk_mail_limit: | ||||
@@ -174,6 +179,9 @@ def flush(from_test=False): | |||||
auto_commit = not from_test | auto_commit = not from_test | ||||
# additional check | |||||
check_bulk_limit([]) | |||||
if frappe.flags.mute_emails or frappe.conf.get("mute_emails") or False: | if frappe.flags.mute_emails or frappe.conf.get("mute_emails") or False: | ||||
msgprint(_("Emails are muted")) | msgprint(_("Emails are muted")) | ||||
from_test = True | from_test = True | ||||
@@ -200,11 +208,16 @@ def flush(from_test=False): | |||||
frappe.db.sql("""update `tabBulk Email` set status='Sent' where name=%s""", | frappe.db.sql("""update `tabBulk Email` set status='Sent' where name=%s""", | ||||
(email["name"],), auto_commit=auto_commit) | (email["name"],), auto_commit=auto_commit) | ||||
except smtplib.SMTPException: | |||||
# bad connection, retry later | |||||
frappe.db.sql("""update `tabBulk Email` set status='Not Sent' where name=%s""", | |||||
(email["name"],), auto_commit=auto_commit) | |||||
except Exception, e: | except Exception, e: | ||||
frappe.db.sql("""update `tabBulk Email` set status='Error', error=%s | frappe.db.sql("""update `tabBulk Email` set status='Error', error=%s | ||||
where name=%s""", (unicode(e), email["name"]), auto_commit=auto_commit) | where name=%s""", (unicode(e), email["name"]), auto_commit=auto_commit) | ||||
def clear_outbox(): | def clear_outbox(): | ||||
"""remove mails older than 30 days in Outbox""" | |||||
"""Remove mails older than 31 days in Outbox. Called daily via scheduler.""" | |||||
frappe.db.sql("""delete from `tabBulk Email` where | frappe.db.sql("""delete from `tabBulk Email` where | ||||
datediff(now(), creation) > 30""") | |||||
datediff(now(), creation) > 31""") |
@@ -124,7 +124,7 @@ class EmailAccount(Document): | |||||
exceptions = [] | exceptions = [] | ||||
for raw in incoming_mails: | for raw in incoming_mails: | ||||
try: | try: | ||||
self.insert_communication(raw) | |||||
communication = self.insert_communication(raw) | |||||
except Exception: | except Exception: | ||||
frappe.db.rollback() | frappe.db.rollback() | ||||
@@ -132,6 +132,7 @@ class EmailAccount(Document): | |||||
else: | else: | ||||
frappe.db.commit() | frappe.db.commit() | ||||
communication.notify(attachments=communication._attachments, except_recipient=True) | |||||
if exceptions: | if exceptions: | ||||
raise Exception, frappe.as_json(exceptions) | raise Exception, frappe.as_json(exceptions) | ||||
@@ -156,7 +157,7 @@ class EmailAccount(Document): | |||||
communication.insert(ignore_permissions = 1) | communication.insert(ignore_permissions = 1) | ||||
# save attachments | # save attachments | ||||
email.save_attachments_in_doc(communication) | |||||
communication._attachments = email.save_attachments_in_doc(communication) | |||||
if self.enable_auto_reply and getattr(communication, "is_first", False): | if self.enable_auto_reply and getattr(communication, "is_first", False): | ||||
self.send_auto_reply(communication, email) | self.send_auto_reply(communication, email) | ||||
@@ -164,7 +165,8 @@ class EmailAccount(Document): | |||||
# notify all participants of this thread | # notify all participants of this thread | ||||
# convert content to HTML - by default text parts of replies are used. | # convert content to HTML - by default text parts of replies are used. | ||||
communication.content = markdown2.markdown(communication.content) | communication.content = markdown2.markdown(communication.content) | ||||
communication.notify(attachments=email.attachments, except_recipient = True) | |||||
return communication | |||||
def set_thread(self, communication, email): | def set_thread(self, communication, email): | ||||
"""Appends communication to parent based on thread ID. Will extract | """Appends communication to parent based on thread ID. Will extract | ||||
@@ -153,7 +153,7 @@ class POP3Server: | |||||
"Connection timed out", | "Connection timed out", | ||||
) | ) | ||||
for message in messages: | for message in messages: | ||||
if message in strip(cstr(e.message)): | |||||
if message in strip(cstr(e.message)) or message in strip(cstr(getattr(e, 'strerror', ''))): | |||||
return True | return True | ||||
return False | return False | ||||
@@ -296,10 +296,13 @@ class Email: | |||||
def save_attachments_in_doc(self, doc): | def save_attachments_in_doc(self, doc): | ||||
"""Save email attachments in given document.""" | """Save email attachments in given document.""" | ||||
from frappe.utils.file_manager import save_file, MaxFileSizeReachedError | from frappe.utils.file_manager import save_file, MaxFileSizeReachedError | ||||
saved_attachments = [] | |||||
for attachment in self.attachments: | for attachment in self.attachments: | ||||
try: | try: | ||||
save_file(attachment['fname'], attachment['fcontent'], | |||||
file_data = save_file(attachment['fname'], attachment['fcontent'], | |||||
doc.doctype, doc.name) | doc.doctype, doc.name) | ||||
saved_attachments.append(file_data.file_name) | |||||
except MaxFileSizeReachedError: | except MaxFileSizeReachedError: | ||||
# WARNING: bypass max file size exception | # WARNING: bypass max file size exception | ||||
pass | pass | ||||
@@ -307,6 +310,8 @@ class Email: | |||||
# same file attached twice?? | # same file attached twice?? | ||||
pass | pass | ||||
return saved_attachments | |||||
def get_thread_id(self): | def get_thread_id(self): | ||||
"""Extract thread ID from `[]`""" | """Extract thread ID from `[]`""" | ||||
import re | import re | ||||
@@ -53,3 +53,4 @@ class EmptyTableError(ValidationError): pass | |||||
class LinkExistsError(ValidationError): pass | class LinkExistsError(ValidationError): pass | ||||
class InvalidEmailAddressError(ValidationError): pass | class InvalidEmailAddressError(ValidationError): pass | ||||
class TemplateNotFoundError(ValidationError): pass | class TemplateNotFoundError(ValidationError): pass | ||||
class UniqueValidationError(ValidationError): pass |
@@ -161,7 +161,7 @@ class BaseDocument(object): | |||||
value.parenttype = self.doctype | value.parenttype = self.doctype | ||||
value.parentfield = key | value.parentfield = key | ||||
if value.docstatus==None: | |||||
if value.docstatus is None: | |||||
value.docstatus = 0 | value.docstatus = 0 | ||||
if not getattr(value, "idx", None): | if not getattr(value, "idx", None): | ||||
@@ -185,9 +185,8 @@ class BaseDocument(object): | |||||
elif df.fieldtype in ("Datetime", "Date") and d[fieldname]=="": | elif df.fieldtype in ("Datetime", "Date") and d[fieldname]=="": | ||||
d[fieldname] = None | d[fieldname] = None | ||||
if d[fieldname]=="": | |||||
df = self.meta.get_field(fieldname) | |||||
if df and df.fieldtype in ("Datetime", "Date"): | |||||
elif df.get("unique") and cstr(d[fieldname]).strip()=="": | |||||
# unique empty field should be set to None | |||||
d[fieldname] = None | d[fieldname] = None | ||||
return d | return d | ||||
@@ -264,15 +263,23 @@ class BaseDocument(object): | |||||
values = ", ".join(["%s"] * len(columns)) | values = ", ".join(["%s"] * len(columns)) | ||||
), d.values()) | ), d.values()) | ||||
except Exception, e: | except Exception, e: | ||||
if e.args[0]==1062 and "PRIMARY" in cstr(e.args[1]): | |||||
if self.meta.autoname=="hash": | |||||
# hash collision? try again | |||||
self.name = None | |||||
self.db_insert() | |||||
return | |||||
type, value, traceback = sys.exc_info() | |||||
frappe.msgprint(_("Duplicate name {0} {1}").format(self.doctype, self.name)) | |||||
raise frappe.DuplicateEntryError, (self.doctype, self.name, e), traceback | |||||
if e.args[0]==1062: | |||||
if "PRIMARY" in cstr(e.args[1]): | |||||
if self.meta.autoname=="hash": | |||||
# hash collision? try again | |||||
self.name = None | |||||
self.db_insert() | |||||
return | |||||
type, value, traceback = sys.exc_info() | |||||
frappe.msgprint(_("Duplicate name {0} {1}").format(self.doctype, self.name)) | |||||
raise frappe.DuplicateEntryError, (self.doctype, self.name, e), traceback | |||||
elif "Duplicate" in cstr(e.args[1]): | |||||
# unique constraint | |||||
self.show_unique_validation_message(e) | |||||
else: | |||||
raise | |||||
else: | else: | ||||
raise | raise | ||||
@@ -292,14 +299,18 @@ class BaseDocument(object): | |||||
values = ", ".join(["`"+c+"`=%s" for c in columns]) | values = ", ".join(["`"+c+"`=%s" for c in columns]) | ||||
), d.values() + [d.get("name")]) | ), d.values() + [d.get("name")]) | ||||
except Exception, e: | except Exception, e: | ||||
if e.args[0]==1062: | |||||
type, value, traceback = sys.exc_info() | |||||
fieldname = str(e).split("'")[-2] | |||||
frappe.msgprint(_("{0} must be unique".format(self.meta.get_label(fieldname)))) | |||||
raise frappe.ValidationError, (self.doctype, self.name, e), traceback | |||||
if e.args[0]==1062 and "Duplicate" in cstr(e.args[1]): | |||||
self.show_unique_validation_message(e) | |||||
else: | else: | ||||
raise | raise | ||||
def show_unique_validation_message(self, e): | |||||
type, value, traceback = sys.exc_info() | |||||
fieldname = str(e).split("'")[-2] | |||||
label = fieldname if fieldname.startswith("unique_") else self.meta.get_label(fieldname) | |||||
frappe.msgprint(_("{0} must be unique".format(label))) | |||||
raise frappe.UniqueValidationError, (self.doctype, self.name, e), traceback | |||||
def db_set(self, fieldname, value, update_modified=True): | def db_set(self, fieldname, value, update_modified=True): | ||||
self.set(fieldname, value) | self.set(fieldname, value) | ||||
self.set("modified", now()) | self.set("modified", now()) | ||||
@@ -84,6 +84,7 @@ frappe.patches.v5_0.expire_old_scheduler_logs | |||||
execute:frappe.permissions.reset_perms("DocType") | execute:frappe.permissions.reset_perms("DocType") | ||||
execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'") | execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'") | ||||
frappe.patches.v5_2.change_checks_to_not_null | frappe.patches.v5_2.change_checks_to_not_null | ||||
frappe.patches.v5_3.rename_chinese_languages | |||||
frappe.patches.v6_0.make_task_log_folder | frappe.patches.v6_0.make_task_log_folder | ||||
frappe.patches.v6_0.document_type_rename | frappe.patches.v6_0.document_type_rename |
@@ -0,0 +1,18 @@ | |||||
# -*- coding: utf-8 -*- | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
def execute(): | |||||
language_map = { | |||||
"中国(简体)": "簡體中文", | |||||
"中國(繁體)": "正體中文" | |||||
} | |||||
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 }) |
@@ -44,17 +44,19 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): | |||||
def false_if_not_shared(): | def false_if_not_shared(): | ||||
if ptype in ("read", "write", "share", "email", "print"): | if ptype in ("read", "write", "share", "email", "print"): | ||||
shared = frappe.share.get_shared(doctype, user, | |||||
["read" if ptype in ("email", "print") else ptype]) | |||||
if doc: | if doc: | ||||
doc_name = doc if isinstance(doc, basestring) else doc.name | doc_name = doc if isinstance(doc, basestring) else doc.name | ||||
shared = frappe.share.get_shared(doctype, user, | |||||
["read" if ptype in ("email", "print") else ptype]) | |||||
if doc_name in shared: | if doc_name in shared: | ||||
if verbose: print "Shared" | if verbose: print "Shared" | ||||
if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): | if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): | ||||
return True | return True | ||||
else: | |||||
elif shared: | |||||
# if atleast one shared doc of that type, then return True | |||||
# this is used in db_query to check if permission on DocType | |||||
if verbose: print "Has a shared document" | if verbose: print "Has a shared document" | ||||
return True | return True | ||||
@@ -92,7 +92,7 @@ frappe.form.formatters = { | |||||
}, | }, | ||||
Text: function(value) { | Text: function(value) { | ||||
if(value) { | if(value) { | ||||
var tags = ["<p", "<div", "<br"]; | |||||
var tags = ["<p", "<div", "<br", "<table"]; | |||||
var match = false; | var match = false; | ||||
for(var i=0; i<tags.length; i++) { | for(var i=0; i<tags.length; i++) { | ||||
@@ -38,9 +38,24 @@ frappe.ui.form.save = function(frm, action, callback, btn) { | |||||
}; | }; | ||||
var cancel = function() { | var cancel = function() { | ||||
var args = { | |||||
doctype: frm.doc.doctype, | |||||
name: frm.doc.name | |||||
}; | |||||
// update workflow state value if workflow exists | |||||
var workflow_state_fieldname = frappe.workflow.get_state_fieldname(frm.doctype); | |||||
if(workflow_state_fieldname) { | |||||
$.extend(args, { | |||||
workflow_state_fieldname: workflow_state_fieldname, | |||||
workflow_state: frm.doc[workflow_state_fieldname] | |||||
}); | |||||
} | |||||
_call({ | _call({ | ||||
method: "frappe.desk.form.save.cancel", | method: "frappe.desk.form.save.cancel", | ||||
args: { doctype: frm.doc.doctype, name: frm.doc.name }, | |||||
args: args, | |||||
callback: function(r) { | callback: function(r) { | ||||
$(document).trigger("save", [frm.doc]); | $(document).trigger("save", [frm.doc]); | ||||
callback(r); | callback(r); | ||||
@@ -13,7 +13,7 @@ frappe.ui.misc.about = function() { | |||||
<h4>Installed Apps</h4>\ | <h4>Installed Apps</h4>\ | ||||
<div id='about-app-versions'>Loading versions...</div>\ | <div id='about-app-versions'>Loading versions...</div>\ | ||||
<hr>\ | <hr>\ | ||||
<p class='text-muted'>© 2014 Frappe Technologies Pvt. Ltd and contributors </p> \ | |||||
<p class='text-muted'>© 2015 Frappe Technologies Pvt. Ltd and contributors </p> \ | |||||
</div>", frappe.app)); | </div>", frappe.app)); | ||||
frappe.ui.misc.about_dialog = d; | frappe.ui.misc.about_dialog = d; | ||||
@@ -35,7 +35,7 @@ | |||||
{%= __("About") %}</a></li> | {%= __("About") %}</a></li> | ||||
<li><a href="https://frappe.io" target="_blank" data-link="docs"> | <li><a href="https://frappe.io" target="_blank" data-link="docs"> | ||||
{%= __("Documentation") %}</a></li> | {%= __("Documentation") %}</a></li> | ||||
<li><a href="https://discuss.frappe.io" target="_blank"> | |||||
<li><a href="https://discuss.erpnext.com" target="_blank"> | |||||
{%= __("Forums") %}</a></li> | {%= __("Forums") %}</a></li> | ||||
<li><a href="https://github.com/frappe/erpnext/issues" target="_blank"> | <li><a href="https://github.com/frappe/erpnext/issues" target="_blank"> | ||||
{%= __("Report an Issue") %}</a></li> | {%= __("Report an Issue") %}</a></li> | ||||
@@ -25,7 +25,7 @@ | |||||
{%= __("About") %}</a></li> | {%= __("About") %}</a></li> | ||||
<li><a href="https://frappe.io" target="_blank" data-link="docs"> | <li><a href="https://frappe.io" target="_blank" data-link="docs"> | ||||
{%= __("Documentation") %}</a></li> | {%= __("Documentation") %}</a></li> | ||||
<li><a href="https://discuss.frappe.io" target="_blank"> | |||||
<li><a href="https://discuss.erpnext.com" target="_blank"> | |||||
{%= __("Forums") %}</a></li> | {%= __("Forums") %}</a></li> | ||||
<li><a href="https://github.com/frappe/erpnext/issues" target="_blank"> | <li><a href="https://github.com/frappe/erpnext/issues" target="_blank"> | ||||
{%= __("Report an Issue") %}</a></li> | {%= __("Report an Issue") %}</a></li> | ||||
@@ -3,6 +3,7 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
from frappe import _ | |||||
from frappe.utils import cint | from frappe.utils import cint | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
@@ -11,6 +12,8 @@ def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=No | |||||
if not user: | if not user: | ||||
user = frappe.session.user | user = frappe.session.user | ||||
check_share_permission(doctype, name) | |||||
share_name = get_share_name(doctype, name, user, everyone) | share_name = get_share_name(doctype, name, user, everyone) | ||||
if share_name: | if share_name: | ||||
@@ -48,6 +51,8 @@ def remove(doctype, name, user, flags=None): | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def set_permission(doctype, name, user, permission_to, value=1, everyone=0): | def set_permission(doctype, name, user, permission_to, value=1, everyone=0): | ||||
"""Set share permission.""" | """Set share permission.""" | ||||
check_share_permission(doctype, name) | |||||
share_name = get_share_name(doctype, name, user, everyone) | share_name = get_share_name(doctype, name, user, everyone) | ||||
value = int(value) | value = int(value) | ||||
@@ -124,3 +129,7 @@ def get_share_name(doctype, name, user, everyone): | |||||
return share_name | return share_name | ||||
def check_share_permission(doctype, name): | |||||
"""Check if the user can share with other users""" | |||||
if not frappe.has_permission(doctype, ptype="share", doc=name): | |||||
frappe.throw(_("No permission to {0} {1} {2}".format("share", doctype, name)), frappe.PermissionError) |
@@ -11,6 +11,8 @@ from frappe.handler import execute_cmd | |||||
from frappe.async import set_task_status, END_LINE, get_std_streams | from frappe.async import set_task_status, END_LINE, get_std_streams | ||||
import frappe.utils.response | import frappe.utils.response | ||||
import sys | import sys | ||||
import time | |||||
import MySQLdb | |||||
@celery_task() | @celery_task() | ||||
def sync_queues(): | def sync_queues(): | ||||
@@ -134,7 +136,6 @@ def pull_from_email_account(site, email_account): | |||||
finally: | finally: | ||||
frappe.destroy() | frappe.destroy() | ||||
@celery_task(bind=True) | @celery_task(bind=True) | ||||
def run_async_task(self, site, user, cmd, form_dict): | def run_async_task(self, site, user, cmd, form_dict): | ||||
ret = {} | ret = {} | ||||
@@ -177,3 +178,34 @@ def run_async_task(self, site, user, cmd, form_dict): | |||||
sys.stderr.close() | sys.stderr.close() | ||||
sys.stdout, sys.stderr = 1, 0 | sys.stdout, sys.stderr = 1, 0 | ||||
return ret | return ret | ||||
@celery_task() | |||||
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | |||||
try: | |||||
frappe.connect(site=site) | |||||
# upto 3 retries | |||||
for i in xrange(3): | |||||
try: | |||||
communication = frappe.get_doc("Communication", communication_name) | |||||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, recipients=recipients, except_recipient=except_recipient) | |||||
except MySQLdb.OperationalError, e: | |||||
# deadlock, try again | |||||
if e.args[0]==1213: | |||||
frappe.db.rollback() | |||||
time.sleep(1) | |||||
continue | |||||
else: | |||||
raise | |||||
else: | |||||
break | |||||
except: | |||||
frappe.db.rollback() | |||||
else: | |||||
frappe.db.commit() | |||||
finally: | |||||
frappe.destroy() |