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


+ 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() ["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


+ 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", 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>")


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

@@ -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 正體中文

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

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


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


# dict # dict
else: else:
col_dict.update(col) 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[idx] = col_dict
columns_dict[col_dict["fieldname"]] = 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 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""")

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

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


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

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


+ 1
- 0
frappe/exceptions.py View File

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

+ 1
- 1
frappe/hooks.py View File

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


app_icon = "octicon octicon-circuit-board" app_icon = "octicon octicon-circuit-board"
app_version = "5.2.2"
app_version = "5.3.0"
app_color = "orange" app_color = "orange"
github_link = "https://github.com/frappe/frappe" 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.parent = self.name
value.parenttype = self.doctype value.parenttype = self.doctype
value.parentfield = key value.parentfield = key
value.docstatus = 0

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


if not getattr(value, "idx", None): if not getattr(value, "idx", None):
value.idx = len(self.get(key) or []) + 1 value.idx = len(self.get(key) or []) + 1
@@ -182,10 +184,9 @@ 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
@@ -262,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


@@ -290,13 +299,17 @@ 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)


+ 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(): 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




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

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


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

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


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

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


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


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

// check for related events // check for related events
if ( $.data( this, drag.datakey ) )
if ( $.data( this, drag.datakey ) )
return; return;
// initialize the drag data with copied defaults // initialize the drag data with copied defaults
var data = $.extend({ related:0 }, drag.defaults ); var data = $.extend({ related:0 }, drag.defaults );
// store the interaction data // store the interaction data
$.data( this, drag.datakey, data ); $.data( this, drag.datakey, data );
// bind the mousedown event, which starts drag interactions // 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 ); $event.add( this, "touchstart mousedown", drag.init, data );
// prevent image dragging in IE... // prevent image dragging in IE...
if ( this.attachEvent )
this.attachEvent("ondragstart", drag.dontstart );
if ( this.attachEvent )
this.attachEvent("ondragstart", drag.dontstart );
}, },
// destroy configured interaction // destroy configured interaction
teardown: function(){ teardown: function(){
var data = $.data( this, drag.datakey ) || {}; var data = $.data( this, drag.datakey ) || {};
// check for related events // check for related events
if ( data.related )
if ( data.related )
return; return;
// remove the stored data // remove the stored data
$.removeData( this, drag.datakey ); $.removeData( this, drag.datakey );
// remove the mousedown event // remove the mousedown event
$event.remove( this, "touchstart mousedown", drag.init ); $event.remove( this, "touchstart mousedown", drag.init );
// enable text selection // enable text selection
drag.textselect( true );
drag.textselect( true );
// un-prevent image dragging in IE... // 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 // initialize the interaction
init: function( event ){
init: function( event ){
// sorry, only one touch at a time // sorry, only one touch at a time
if ( drag.touched )
if ( drag.touched )
return; return;
// the drag/drop interaction data // the drag/drop interaction data
var dd = event.data, results; var dd = event.data, results;
// check the which directive // 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 // check for suppressed selector
if ( $( event.target ).is( dd.not ) )
if ( $( event.target ).is( dd.not ) )
return; return;
// check for handle selector // 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; return;


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


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


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

+ 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.celery_app import get_celery, celery_task, task_logger, LONGJOBS_PREFIX
from frappe.utils import get_sites from frappe.utils import get_sites
from frappe.utils.file_lock import create_lock, delete_lock from frappe.utils.file_lock import create_lock, delete_lock
import time
import MySQLdb


@celery_task() @celery_task()
def sync_queues(): def sync_queues():
@@ -122,3 +124,33 @@ def pull_from_email_account(site, email_account):
frappe.db.commit() frappe.db.commit()
finally: finally:
frappe.destroy() 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 from setuptools import setup, find_packages


version = "5.2.2"
version = "5.3.0"


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


Loading…
Cancel
Save