Browse Source

Merge branch 'develop'

version-14
Anand Doshi 10 years ago
parent
commit
39863477e6
65 changed files with 25134 additions and 13105 deletions
  1. +1
    -1
      frappe/__version__.py
  2. +40
    -0
      frappe/change_log/v5/v5_3_0.md
  3. +1
    -1
      frappe/commands.py
  4. +29
    -1
      frappe/core/doctype/communication/communication.py
  5. +1
    -1
      frappe/core/page/permission_manager/permission_manager.js
  6. +10
    -2
      frappe/data/languages.txt
  7. +3
    -1
      frappe/desk/query_report.py
  8. +18
    -5
      frappe/email/bulk.py
  9. +5
    -3
      frappe/email/doctype/email_account/email_account.py
  10. +7
    -2
      frappe/email/receive.py
  11. +1
    -0
      frappe/exceptions.py
  12. +1
    -1
      frappe/hooks.py
  13. +32
    -19
      frappe/model/base_document.py
  14. +6
    -4
      frappe/permissions.py
  15. +1
    -1
      frappe/public/js/frappe/ui/toolbar/navbar.html
  16. +1
    -1
      frappe/public/js/frappe/ui/toolbar/offcanvas_left_sidebar.html
  17. +88
    -82
      frappe/public/js/lib/slickgrid/jquery.event.drag.js
  18. +9
    -0
      frappe/share.py
  19. +32
    -0
      frappe/tasks.py
  20. +393
    -353
      frappe/translations/ar.csv
  21. +392
    -349
      frappe/translations/bg.csv
  22. +0
    -0
      frappe/translations/bo.csv
  23. +392
    -352
      frappe/translations/bs.csv
  24. +392
    -352
      frappe/translations/ca.csv
  25. +392
    -352
      frappe/translations/cs.csv
  26. +392
    -349
      frappe/translations/da.csv
  27. +397
    -357
      frappe/translations/de.csv
  28. +392
    -349
      frappe/translations/el.csv
  29. +392
    -352
      frappe/translations/es.csv
  30. +393
    -350
      frappe/translations/fa.csv
  31. +1509
    -0
      frappe/translations/fi.csv
  32. +394
    -354
      frappe/translations/fr.csv
  33. +379
    -339
      frappe/translations/he.csv
  34. +392
    -352
      frappe/translations/hi.csv
  35. +392
    -352
      frappe/translations/hr.csv
  36. +392
    -349
      frappe/translations/hu.csv
  37. +400
    -360
      frappe/translations/id.csv
  38. +392
    -352
      frappe/translations/it.csv
  39. +397
    -359
      frappe/translations/ja.csv
  40. +1356
    -0
      frappe/translations/km.csv
  41. +392
    -352
      frappe/translations/kn.csv
  42. +408
    -368
      frappe/translations/ko.csv
  43. +392
    -349
      frappe/translations/lv.csv
  44. +1509
    -0
      frappe/translations/mk.csv
  45. +392
    -349
      frappe/translations/mr.csv
  46. +1509
    -0
      frappe/translations/my.csv
  47. +393
    -353
      frappe/translations/nl.csv
  48. +1509
    -0
      frappe/translations/no.csv
  49. +642
    -602
      frappe/translations/pl.csv
  50. +392
    -352
      frappe/translations/pt-BR.csv
  51. +392
    -352
      frappe/translations/pt.csv
  52. +392
    -352
      frappe/translations/ro.csv
  53. +392
    -352
      frappe/translations/ru.csv
  54. +0
    -0
      frappe/translations/se.csv
  55. +392
    -352
      frappe/translations/sk.csv
  56. +1509
    -0
      frappe/translations/sq.csv
  57. +392
    -352
      frappe/translations/sr.csv
  58. +1509
    -0
      frappe/translations/sv.csv
  59. +392
    -352
      frappe/translations/ta.csv
  60. +392
    -352
      frappe/translations/th.csv
  61. +396
    -356
      frappe/translations/tr.csv
  62. +392
    -352
      frappe/translations/vi.csv
  63. +392
    -352
      frappe/translations/zh-cn.csv
  64. +437
    -398
      frappe/translations/zh-tw.csv
  65. +1
    -1
      setup.py

+ 1
- 1
frappe/__version__.py View File

@@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = "5.2.2"
__version__ = "5.3.0"

+ 40
- 0
frappe/change_log/v5/v5_3_0.md View File

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

+ 1
- 1
frappe/commands.py View File

@@ -103,7 +103,7 @@ def _is_scheduler_enabled():
enable_scheduler = False
try:
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:
pass
finally:


+ 29
- 1
frappe/core/doctype/communication/communication.py View File

@@ -72,6 +72,26 @@ class Communication(Document):
["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):
"""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)
if not recipients:
recipients = self.get_recipients(except_recipient=except_recipient)
@@ -143,6 +163,7 @@ class Communication(Document):
sender = parseaddr(self.sender)[1]

filtered = []
email_addresses = []
for e in list(set(recipients)):
if (e=="Administrator") or ((e==self.sender) and (e not in original_recipients)) or \
(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
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)
email_addresses.append(email_id.lower())

if getattr(self, "send_me_a_copy", False):
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)

# needed for communication.notify which uses celery delay
# if not committed, delayed task doesn't find the communication
frappe.db.commit()

recipients = None
if send_email:
comm.send_me_a_copy = send_me_a_copy


+ 1
- 1
frappe/core/page/permission_manager/permission_manager.js View File

@@ -277,7 +277,7 @@ frappe.PermissionEngine = Class.extend({
},

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) {
cell.html("<a class='grey' href='#'>"+__(role)+"</a>")


+ 10
- 2
frappe/data/languages.txt View File

@@ -1,5 +1,6 @@
ar العربية
bg bǎlgarski
bo ལྷ་སའི་སྐད་
bs bosanski
ca català
cs česky
@@ -9,6 +10,7 @@ el ελληνικά
es español
en english
fa پارسی
fi suomalainen
fr français
he עברית
hi हिंदी
@@ -17,21 +19,27 @@ hu magyar
id Indonesia
it italiano
ja 日本語
km ភាសាខ្មែរ
kn ಕನ್ನಡ
ko 한국의
lv latviešu valoda
mr मराठी
mk македонски
my Melayu
nl nederlands
no norsk
pl polski
pt português
pt-BR português brasileiro
ro român
ru русский
sv svenska
sk slovenčina
sq shqiptar
sr српски
ta தமிழ்
th ไทย
tr Türk
vi việt
zh-cn 中国(简体)
zh-tw 中國(繁體)
zh-cn 簡體中文
zh-tw 正體中文

+ 3
- 1
frappe/desk/query_report.py View File

@@ -258,11 +258,13 @@ def get_columns_dict(columns):
else:
col_dict["fieldtype"] = col[1]

col_dict["fieldname"] = col[0].lower()
col_dict["fieldname"] = frappe.scrub(col[0])

# dict
else:
col_dict.update(col)
if "fieldname" not in col_dict:
col_dict["fieldname"] = frappe.scrub(col_dict["label"])

columns_dict[idx] = col_dict
columns_dict[col_dict["fieldname"]] = col_dict


+ 18
- 5
frappe/email/bulk.py View File

@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
import HTMLParser
import smtplib
from frappe import msgprint, throw, _
from frappe.email.smtp import SMTPServer, get_outgoing_email_account
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)

def check_bulk_limit(recipients):
# get count of mails sent this month
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
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

if (this_month + len(recipients)) > monthly_bulk_mail_limit:
@@ -174,6 +179,9 @@ def flush(from_test=False):

auto_commit = not from_test

# additional check
check_bulk_limit([])

if frappe.flags.mute_emails or frappe.conf.get("mute_emails") or False:
msgprint(_("Emails are muted"))
from_test = True
@@ -200,11 +208,16 @@ def flush(from_test=False):
frappe.db.sql("""update `tabBulk Email` set status='Sent' where name=%s""",
(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:
frappe.db.sql("""update `tabBulk Email` set status='Error', error=%s
where name=%s""", (unicode(e), email["name"]), auto_commit=auto_commit)

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
datediff(now(), creation) > 30""")
datediff(now(), creation) > 31""")

+ 5
- 3
frappe/email/doctype/email_account/email_account.py View File

@@ -124,7 +124,7 @@ class EmailAccount(Document):
exceptions = []
for raw in incoming_mails:
try:
self.insert_communication(raw)
communication = self.insert_communication(raw)

except Exception:
frappe.db.rollback()
@@ -132,6 +132,7 @@ class EmailAccount(Document):

else:
frappe.db.commit()
communication.notify(attachments=communication._attachments, except_recipient=True)

if exceptions:
raise Exception, frappe.as_json(exceptions)
@@ -156,7 +157,7 @@ class EmailAccount(Document):
communication.insert(ignore_permissions = 1)

# 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):
self.send_auto_reply(communication, email)
@@ -164,7 +165,8 @@ class EmailAccount(Document):
# notify all participants of this thread
# convert content to HTML - by default text parts of replies are used.
communication.content = markdown2.markdown(communication.content)
communication.notify(attachments=email.attachments, except_recipient = True)

return communication

def set_thread(self, communication, email):
"""Appends communication to parent based on thread ID. Will extract


+ 7
- 2
frappe/email/receive.py View File

@@ -153,7 +153,7 @@ class POP3Server:
"Connection timed out",
)
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 False

@@ -296,10 +296,13 @@ class Email:
def save_attachments_in_doc(self, doc):
"""Save email attachments in given document."""
from frappe.utils.file_manager import save_file, MaxFileSizeReachedError
saved_attachments = []

for attachment in self.attachments:
try:
save_file(attachment['fname'], attachment['fcontent'],
file_data = save_file(attachment['fname'], attachment['fcontent'],
doc.doctype, doc.name)
saved_attachments.append(file_data.file_name)
except MaxFileSizeReachedError:
# WARNING: bypass max file size exception
pass
@@ -307,6 +310,8 @@ class Email:
# same file attached twice??
pass

return saved_attachments

def get_thread_id(self):
"""Extract thread ID from `[]`"""
import re


+ 1
- 0
frappe/exceptions.py View File

@@ -53,3 +53,4 @@ class EmptyTableError(ValidationError): pass
class LinkExistsError(ValidationError): pass
class InvalidEmailAddressError(ValidationError): pass
class TemplateNotFoundError(ValidationError): pass
class UniqueValidationError(ValidationError): pass

+ 1
- 1
frappe/hooks.py View File

@@ -26,7 +26,7 @@ to ERPNext.
"""

app_icon = "octicon octicon-circuit-board"
app_version = "5.2.2"
app_version = "5.3.0"
app_color = "orange"
github_link = "https://github.com/frappe/frappe"



+ 32
- 19
frappe/model/base_document.py View File

@@ -160,7 +160,9 @@ class BaseDocument(object):
value.parent = self.name
value.parenttype = self.doctype
value.parentfield = key
value.docstatus = 0

if value.docstatus is None:
value.docstatus = 0

if not getattr(value, "idx", None):
value.idx = len(self.get(key) or []) + 1
@@ -182,10 +184,9 @@ class BaseDocument(object):

elif df.fieldtype in ("Datetime", "Date") and d[fieldname]=="":
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

return d
@@ -262,15 +263,23 @@ class BaseDocument(object):
values = ", ".join(["%s"] * len(columns))
), d.values())
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:
raise

@@ -290,13 +299,17 @@ class BaseDocument(object):
values = ", ".join(["`"+c+"`=%s" for c in columns])
), d.values() + [d.get("name")])
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:
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):
self.set(fieldname, value)


+ 6
- 4
frappe/permissions.py View File

@@ -44,17 +44,19 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):

def false_if_not_shared():
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:
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 verbose: print "Shared"
if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype):
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"
return True



+ 1
- 1
frappe/public/js/frappe/ui/toolbar/navbar.html View File

@@ -35,7 +35,7 @@
{%= __("About") %}</a></li>
<li><a href="https://frappe.io" target="_blank" data-link="docs">
{%= __("Documentation") %}</a></li>
<li><a href="https://discuss.frappe.io" target="_blank">
<li><a href="https://discuss.erpnext.com" target="_blank">
{%= __("Forums") %}</a></li>
<li><a href="https://github.com/frappe/erpnext/issues" target="_blank">
{%= __("Report an Issue") %}</a></li>


+ 1
- 1
frappe/public/js/frappe/ui/toolbar/offcanvas_left_sidebar.html View File

@@ -25,7 +25,7 @@
{%= __("About") %}</a></li>
<li><a href="https://frappe.io" target="_blank" data-link="docs">
{%= __("Documentation") %}</a></li>
<li><a href="https://discuss.frappe.io" target="_blank">
<li><a href="https://discuss.erpnext.com" target="_blank">
{%= __("Forums") %}</a></li>
<li><a href="https://github.com/frappe/erpnext/issues" target="_blank">
{%= __("Report an Issue") %}</a></li>


+ 88
- 82
frappe/public/js/lib/slickgrid/jquery.event.drag.js View File

@@ -1,9 +1,9 @@
/*!
/*!
* jquery.event.drag - v 2.2
* Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
* Open Source MIT License - http://threedubmedia.com/code/license
*/
// Created: 2008-06-04
// Created: 2008-06-04
// Updated: 2012-05-21
// REQUIRES: jquery 1.7.x

@@ -16,7 +16,7 @@ $.fn.drag = function( str, arg, opts ){
// figure out the event handler...
fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
// fix the event type
if ( type.indexOf("drag") !== 0 )
if ( type.indexOf("drag") !== 0 )
type = "drag"+ type;
// were options passed
opts = ( str == fn ? arg : opts ) || {};
@@ -25,11 +25,11 @@ $.fn.drag = function( str, arg, opts ){
};

// local refs (increase compression)
var $event = $.event,
var $event = $.event,
$special = $event.special,
// configure the drag special event
// configure the drag special event
drag = $special.drag = {
// these are the default settings
defaults: {
which: 1, // mouse button pressed to start drag sequence
@@ -40,82 +40,88 @@ drag = $special.drag = {
drop: true, // false to suppress drop events, true or selector to allow
click: false // false to suppress click events after dragend (no proxy)
},
// the key name for stored drag data
datakey: "dragdata",
// prevent bubbling for better performance
noBubble: true,
// count bound related events
add: function( obj ){
add: function( obj ){
// read the interaction data
var data = $.data( this, drag.datakey ),
// read any passed options
// read any passed options
opts = obj.data || {};
// count another realted event
data.related += 1;
// extend data options bound with this event
// don't iterate "opts" in case it is a node
// don't iterate "opts" in case it is a node
$.each( drag.defaults, function( key, def ){
if ( opts[ key ] !== undefined )
data[ key ] = opts[ key ];
});
},
// forget unbound related events
remove: function(){
$.data( this, drag.datakey ).related -= 1;
},
// configure interaction, capture settings
setup: function(){

// check for related events
if ( $.data( this, drag.datakey ) )
if ( $.data( this, drag.datakey ) )
return;
// initialize the drag data with copied defaults
var data = $.extend({ related:0 }, drag.defaults );
// store the interaction data
$.data( this, drag.datakey, data );
// bind the mousedown event, which starts drag interactions

// don't attached drag event via special for fullcalendar
// return false to attach the normal way
if(this===document) return false;

$event.add( this, "touchstart mousedown", drag.init, data );
// prevent image dragging in IE...
if ( this.attachEvent )
this.attachEvent("ondragstart", drag.dontstart );
if ( this.attachEvent )
this.attachEvent("ondragstart", drag.dontstart );
},
// destroy configured interaction
teardown: function(){
var data = $.data( this, drag.datakey ) || {};
// check for related events
if ( data.related )
if ( data.related )
return;
// remove the stored data
$.removeData( this, drag.datakey );
// remove the mousedown event
$event.remove( this, "touchstart mousedown", drag.init );
// enable text selection
drag.textselect( true );
drag.textselect( true );
// un-prevent image dragging in IE...
if ( this.detachEvent )
this.detachEvent("ondragstart", drag.dontstart );
if ( this.detachEvent )
this.detachEvent("ondragstart", drag.dontstart );
},
// initialize the interaction
init: function( event ){
init: function( event ){
// sorry, only one touch at a time
if ( drag.touched )
if ( drag.touched )
return;
// the drag/drop interaction data
var dd = event.data, results;
// check the which directive
if ( event.which != 0 && dd.which > 0 && event.which != dd.which )
return;
if ( event.which != 0 && dd.which > 0 && event.which != dd.which )
return;
// check for suppressed selector
if ( $( event.target ).is( dd.not ) )
if ( $( event.target ).is( dd.not ) )
return;
// check for handle selector
if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length )
if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length )
return;

drag.touched = event.type == 'touchstart' ? this : null;
@@ -126,7 +132,7 @@ drag = $special.drag = {
dd.pageX = event.pageX;
dd.pageY = event.pageY;
dd.dragging = null;
// handle draginit event...
// handle draginit event...
results = drag.hijack( event, "draginit", dd );
// early cancel
if ( !dd.propagates )
@@ -143,43 +149,43 @@ drag = $special.drag = {
// remember how many interactions are propagating
dd.propagates = dd.interactions.length;
// locate and init the drop targets
if ( dd.drop !== false && $special.drop )
if ( dd.drop !== false && $special.drop )
$special.drop.handler( event, dd );
// disable text selection
drag.textselect( false );
drag.textselect( false );
// bind additional events...
if ( drag.touched )
$event.add( drag.touched, "touchmove touchend", drag.handler, dd );
else
else
$event.add( document, "mousemove mouseup", drag.handler, dd );
// helps prevent text selection or scrolling
if ( !drag.touched || dd.live )
return false;
},
},
// returns an interaction object
interaction: function( elem, dd ){
var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 };
return {
drag: elem,
callback: new drag.callback(),
drag: elem,
callback: new drag.callback(),
droppable: [],
offset: offset
};
},
// handle drag-releatd DOM events
handler: function( event ){
handler: function( event ){
// read the data before hijacking anything
var dd = event.data;
var dd = event.data;
// handle various events
switch ( event.type ){
// mousemove, check distance, start dragging
case !dd.dragging && 'touchmove':
case !dd.dragging && 'touchmove':
event.preventDefault();
case !dd.dragging && 'mousemove':
// drag tolerance, x≤ + y≤ = distance≤
if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) )
// drag tolerance, x² + y² = distance²
if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) )
break; // distance tolerance not reached
event.target = dd.target; // force target from "mousedown" event (fix distance issue)
drag.hijack( event, "dragstart", dd ); // trigger "dragstart"
@@ -190,42 +196,42 @@ drag = $special.drag = {
event.preventDefault();
case 'mousemove':
if ( dd.dragging ){
// trigger "drag"
// trigger "drag"
drag.hijack( event, "drag", dd );
if ( dd.propagates ){
// manage drop events
if ( dd.drop !== false && $special.drop )
$special.drop.handler( event, dd ); // "dropstart", "dropend"
break; // "drag" not rejected, stop
$special.drop.handler( event, dd ); // "dropstart", "dropend"
break; // "drag" not rejected, stop
}
event.type = "mouseup"; // helps "drop" handler behave
}
// mouseup, stop dragging
case 'touchend':
case 'mouseup':
case 'touchend':
case 'mouseup':
default:
if ( drag.touched )
$event.remove( drag.touched, "touchmove touchend", drag.handler ); // remove touch events
else
$event.remove( document, "mousemove mouseup", drag.handler ); // remove page events
else
$event.remove( document, "mousemove mouseup", drag.handler ); // remove page events
if ( dd.dragging ){
if ( dd.drop !== false && $special.drop )
$special.drop.handler( event, dd ); // "drop"
drag.hijack( event, "dragend", dd ); // trigger "dragend"
drag.hijack( event, "dragend", dd ); // trigger "dragend"
}
drag.textselect( true ); // enable text selection
// if suppressing click events...
if ( dd.click === false && dd.dragging )
$.data( dd.mousedown, "suppress.click", new Date().getTime() + 5 );
dd.dragging = drag.touched = false; // deactivate element
dd.dragging = drag.touched = false; // deactivate element
break;
}
},
// re-use event object for custom events
hijack: function( event, type, dd, x, elem ){
// not configured
if ( !dd )
if ( !dd )
return;
// remember the original event and type
var orig = { event:event.originalEvent, type:event.type },
@@ -255,7 +261,7 @@ drag = $special.drag = {
callback.target = subject;
// force propagtion of the custom event
event.isPropagationStopped = function(){ return false; };
// handle the event
// handle the event
result = subject ? $event.dispatch.call( subject, event, callback ) : null;
// stop the drag interaction for this element
if ( result === false ){
@@ -270,25 +276,25 @@ drag = $special.drag = {
// assign any dropinit elements
else if ( type == "dropinit" )
ia.droppable.push( drag.element( result ) || subject );
// accept a returned proxy element
// accept a returned proxy element
if ( type == "dragstart" )
ia.proxy = $( drag.element( result ) || ia.drag )[0];
// remember this result
// remember this result
ia.results.push( result );
// forget the event result, for recycling
delete event.result;
// break on cancelled handler
if ( type !== "dropinit" )
return result;
});
// flatten the results
dd.results[ i ] = drag.flatten( ia.results );
});
// flatten the results
dd.results[ i ] = drag.flatten( ia.results );
// accept a set of valid drop targets
if ( type == "dropinit" )
ia.droppable = drag.flatten( ia.droppable );
// locate drop targets
if ( type == "dragstart" && !ia.cancelled )
callback.update();
callback.update();
}
while ( ++i < len )
// restore the original event & type
@@ -297,9 +303,9 @@ drag = $special.drag = {
// return all handler results
return drag.flatten( dd.results );
},
// extend the callback object with drag/drop properties...
properties: function( event, dd, ia ){
properties: function( event, dd, ia ){
var obj = ia.callback;
// elements
obj.drag = ia.drag;
@@ -314,44 +320,44 @@ drag = $special.drag = {
obj.originalX = ia.offset.left;
obj.originalY = ia.offset.top;
// adjusted element position
obj.offsetX = obj.originalX + obj.deltaX;
obj.offsetX = obj.originalX + obj.deltaX;
obj.offsetY = obj.originalY + obj.deltaY;
// assign the drop targets information
obj.drop = drag.flatten( ( ia.drop || [] ).slice() );
obj.available = drag.flatten( ( ia.droppable || [] ).slice() );
return obj;
return obj;
},
// determine is the argument is an element or jquery instance
element: function( arg ){
if ( arg && ( arg.jquery || arg.nodeType == 1 ) )
return arg;
},
// flatten nested jquery objects and arrays into a single dimension array
flatten: function( arr ){
return $.map( arr, function( member ){
return member && member.jquery ? $.makeArray( member ) :
return member && member.jquery ? $.makeArray( member ) :
member && member.length ? drag.flatten( member ) : member;
});
},
// toggles text selection attributes ON (true) or OFF (false)
textselect: function( bool ){
textselect: function( bool ){
$( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart )
.css("MozUserSelect", bool ? "" : "none" );
// .attr("unselectable", bool ? "off" : "on" )
document.unselectable = bool ? "off" : "on";
document.unselectable = bool ? "off" : "on";
},
// suppress "selectstart" and "ondragstart" events
dontstart: function(){
return false;
dontstart: function(){
return false;
},
// a callback instance contructor
callback: function(){}
};

// callback methods
@@ -375,9 +381,9 @@ $event.dispatch = function( event ){
};

// event fix hooks for touch events...
var touchHooks =
$event.fixHooks.touchstart =
$event.fixHooks.touchmove =
var touchHooks =
$event.fixHooks.touchstart =
$event.fixHooks.touchmove =
$event.fixHooks.touchend =
$event.fixHooks.touchcancel = {
props: "clientX clientY pageX pageY screenX screenY".split( " " ),
@@ -385,9 +391,9 @@ $event.fixHooks.touchcancel = {
if ( orig ){
var touched = ( orig.touches && orig.touches[0] )
|| ( orig.changedTouches && orig.changedTouches[0] )
|| null;
|| null;
// iOS webkit: touchstart, touchmove, touchend
if ( touched )
if ( touched )
$.each( touchHooks.props, function( i, prop ){
event[ prop ] = touched[ prop ];
});
@@ -399,4 +405,4 @@ $event.fixHooks.touchcancel = {
// share the same special event configuration with related events...
$special.draginit = $special.dragstart = $special.dragend = drag;

})( jQuery );
})( jQuery );

+ 9
- 0
frappe/share.py View File

@@ -3,6 +3,7 @@

from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint

@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:
user = frappe.session.user

check_share_permission(doctype, name)

share_name = get_share_name(doctype, name, user, everyone)

if share_name:
@@ -48,6 +51,8 @@ def remove(doctype, name, user, flags=None):
@frappe.whitelist()
def set_permission(doctype, name, user, permission_to, value=1, everyone=0):
"""Set share permission."""
check_share_permission(doctype, name)

share_name = get_share_name(doctype, name, user, everyone)
value = int(value)

@@ -124,3 +129,7 @@ def get_share_name(doctype, name, user, everyone):

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)

+ 32
- 0
frappe/tasks.py View File

@@ -7,6 +7,8 @@ from frappe.utils.scheduler import enqueue_events
from frappe.celery_app import get_celery, celery_task, task_logger, LONGJOBS_PREFIX
from frappe.utils import get_sites
from frappe.utils.file_lock import create_lock, delete_lock
import time
import MySQLdb

@celery_task()
def sync_queues():
@@ -122,3 +124,33 @@ def pull_from_email_account(site, email_account):
frappe.db.commit()
finally:
frappe.destroy()

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

+ 393
- 353
frappe/translations/ar.csv
File diff suppressed because it is too large
View File


+ 392
- 349
frappe/translations/bg.csv
File diff suppressed because it is too large
View File


frappe/translations/en.csv → frappe/translations/bo.csv View File


+ 392
- 352
frappe/translations/bs.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/ca.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/cs.csv
File diff suppressed because it is too large
View File


+ 392
- 349
frappe/translations/da.csv
File diff suppressed because it is too large
View File


+ 397
- 357
frappe/translations/de.csv
File diff suppressed because it is too large
View File


+ 392
- 349
frappe/translations/el.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/es.csv
File diff suppressed because it is too large
View File


+ 393
- 350
frappe/translations/fa.csv
File diff suppressed because it is too large
View File


+ 1509
- 0
frappe/translations/fi.csv
File diff suppressed because it is too large
View File


+ 394
- 354
frappe/translations/fr.csv
File diff suppressed because it is too large
View File


+ 379
- 339
frappe/translations/he.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/hi.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/hr.csv
File diff suppressed because it is too large
View File


+ 392
- 349
frappe/translations/hu.csv
File diff suppressed because it is too large
View File


+ 400
- 360
frappe/translations/id.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/it.csv
File diff suppressed because it is too large
View File


+ 397
- 359
frappe/translations/ja.csv
File diff suppressed because it is too large
View File


+ 1356
- 0
frappe/translations/km.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/kn.csv
File diff suppressed because it is too large
View File


+ 408
- 368
frappe/translations/ko.csv
File diff suppressed because it is too large
View File


+ 392
- 349
frappe/translations/lv.csv
File diff suppressed because it is too large
View File


+ 1509
- 0
frappe/translations/mk.csv
File diff suppressed because it is too large
View File


+ 392
- 349
frappe/translations/mr.csv
File diff suppressed because it is too large
View File


+ 1509
- 0
frappe/translations/my.csv
File diff suppressed because it is too large
View File


+ 393
- 353
frappe/translations/nl.csv
File diff suppressed because it is too large
View File


+ 1509
- 0
frappe/translations/no.csv
File diff suppressed because it is too large
View File


+ 642
- 602
frappe/translations/pl.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/pt-BR.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/pt.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/ro.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/ru.csv
File diff suppressed because it is too large
View File


+ 0
- 0
frappe/translations/se.csv View File


+ 392
- 352
frappe/translations/sk.csv
File diff suppressed because it is too large
View File


+ 1509
- 0
frappe/translations/sq.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/sr.csv
File diff suppressed because it is too large
View File


+ 1509
- 0
frappe/translations/sv.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/ta.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/th.csv
File diff suppressed because it is too large
View File


+ 396
- 356
frappe/translations/tr.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/vi.csv
File diff suppressed because it is too large
View File


+ 392
- 352
frappe/translations/zh-cn.csv
File diff suppressed because it is too large
View File


+ 437
- 398
frappe/translations/zh-tw.csv
File diff suppressed because it is too large
View File


+ 1
- 1
setup.py View File

@@ -1,6 +1,6 @@
from setuptools import setup, find_packages

version = "5.2.2"
version = "5.3.0"

with open("requirements.txt", "r") as f:
install_requires = f.readlines()


Loading…
Cancel
Save