diff --git a/frappe/boot.py b/frappe/boot.py
index 5d042f82ce..9f0c05a6ce 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -30,6 +30,7 @@ def get_bootinfo():
get_user(bootinfo)
# system info
+ bootinfo.sitename = frappe.local.site
bootinfo.sysdefaults = frappe.defaults.get_defaults()
bootinfo.user_permissions = get_user_permissions()
bootinfo.server_date = frappe.utils.nowdate()
diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py
index aa9a7c3a4e..a1ff57907a 100644
--- a/frappe/core/doctype/communication/communication.py
+++ b/frappe/core/doctype/communication/communication.py
@@ -7,7 +7,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.utils import validate_email_add, get_fullname, strip_html, cstr
from frappe.core.doctype.communication.comment import (notify_mentions,
- update_comment_in_doc)
+ update_comment_in_doc, on_trash)
from frappe.core.doctype.communication.email import (validate_email,
notify, _notify, update_parent_status)
from frappe.utils.bot import BotReply
@@ -111,6 +111,8 @@ class Communication(Document):
frappe.publish_realtime('delete_communication', self.as_dict(),
doctype= self.reference_doctype, docname = self.reference_name,
after_commit=True)
+ # delete the comments from _comment
+ on_trash(self)
def set_status(self):
if not self.is_new():
diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py
index 4341d557aa..1976f4c862 100755
--- a/frappe/core/doctype/file/file.py
+++ b/frappe/core/doctype/file/file.py
@@ -76,6 +76,7 @@ class File(NestedSet):
if frappe.db.exists('File', {'name': self.name, 'is_folder': 0}):
if not self.is_folder and (self.is_private != self.db_get('is_private')):
+ old_file_url = self.file_url
private_files = frappe.get_site_path('private', 'files')
public_files = frappe.get_site_path('public', 'files')
@@ -91,6 +92,11 @@ class File(NestedSet):
self.file_url = "/private/files/{0}".format(self.file_name)
+ # update documents image url with new file url
+ if self.attached_to_doctype and self.attached_to_name and \
+ frappe.db.get_value(self.attached_to_doctype, self.attached_to_name, "image") == old_file_url:
+ frappe.db.set_value(self.attached_to_doctype, self.attached_to_name, "image", self.file_url)
+
def set_folder_size(self):
"""Set folder size if folder"""
if self.is_folder and not self.is_new():
diff --git a/frappe/core/page/data_import_tool/importer.py b/frappe/core/page/data_import_tool/importer.py
index aa16802c5d..d26dcfd2b1 100644
--- a/frappe/core/page/data_import_tool/importer.py
+++ b/frappe/core/page/data_import_tool/importer.py
@@ -217,8 +217,8 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
# header
if not rows:
- from frappe.utils.file_manager import save_uploaded
- file_doc = save_uploaded(dt=None, dn="Data Import", folder='Home', is_private=1)
+ from frappe.utils.file_manager import get_file_doc
+ file_doc = get_file_doc(dt='', dn="Data Import", folder='Home', is_private=1)
filename, file_extension = os.path.splitext(file_doc.file_name)
if file_extension == '.xlsx' and from_data_import == 'Yes':
diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json
index 4fd21e8b5e..dce8a9e44f 100644
--- a/frappe/email/doctype/newsletter/newsletter.json
+++ b/frappe/email/doctype/newsletter/newsletter.json
@@ -15,6 +15,7 @@
"engine": "InnoDB",
"fields": [
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -44,6 +45,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -74,6 +76,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -103,6 +106,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -131,6 +135,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -159,6 +164,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -170,7 +176,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
- "in_list_view": 0,
+ "in_list_view": 1,
"in_standard_filter": 0,
"label": "Subject",
"length": 0,
@@ -187,6 +193,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -198,7 +205,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
- "in_list_view": 0,
+ "in_list_view": 1,
"in_standard_filter": 0,
"label": "Message",
"length": 0,
@@ -215,6 +222,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -245,6 +253,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -275,6 +284,69 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "description": "",
+ "fieldname": "published",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Published",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Route",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -304,6 +376,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -333,6 +406,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -362,6 +436,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -391,19 +466,20 @@
"unique": 0
}
],
- "has_web_view": 0,
+ "has_web_view": 1,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-envelope",
"idx": 1,
"image_view": 0,
"in_create": 0,
+ "is_published_field": "published",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 3,
"menu_index": 0,
- "modified": "2017-03-07 12:59:18.173824",
+ "modified": "2017-09-14 15:38:01.891251",
"modified_by": "Administrator",
"module": "Email",
"name": "Newsletter",
@@ -433,6 +509,7 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
+ "route": "newsletters",
"show_name_in_global_search": 0,
"sort_order": "ASC",
"title_field": "subject",
diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py
index 1813b01c53..d69fae1f1d 100755
--- a/frappe/email/doctype/newsletter/newsletter.py
+++ b/frappe/email/doctype/newsletter/newsletter.py
@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe
import frappe.utils
from frappe import throw, _
-from frappe.model.document import Document
+from frappe.website.website_generator import WebsiteGenerator
from frappe.email.queue import check_email_limit
from frappe.utils.verified_command import get_signed_params, verify_request
from frappe.utils.background_jobs import enqueue
@@ -17,7 +17,7 @@ from frappe.utils import parse_addr
from frappe.utils import validate_email_add
-class Newsletter(Document):
+class Newsletter(WebsiteGenerator):
def onload(self):
if self.email_sent:
self.get("__onload").status_count = dict(frappe.db.sql("""select status, count(name)
@@ -25,6 +25,7 @@ class Newsletter(Document):
group by status""", (self.doctype, self.name))) or None
def validate(self):
+ self.route = "newsletters/" + self.name
if self.send_from:
validate_email_add(self.send_from, True)
@@ -105,6 +106,26 @@ class Newsletter(Document):
throw(_("Please save the Newsletter before sending"))
check_email_limit(self.recipients)
+ def get_context(self, context):
+ newsletters = get_newsletter_list("Newsletter", None, None, 0)
+ if newsletters:
+ newsletter_list = [d.name for d in newsletters]
+ if self.name not in newsletter_list:
+ frappe.redirect_to_message(_('Permission Error'),
+ _("You are not permitted to view the newsletter."))
+ frappe.local.flags.redirect_location = frappe.local.response.location
+ raise frappe.Redirect
+ else:
+ context.attachments = get_attachments(self.name)
+ context.no_cache = 1
+ context.show_sidebar = True
+
+
+def get_attachments(name):
+ return frappe.get_all("File",
+ fields=["name", "file_name", "file_url", "is_private"],
+ filters = {"attached_to_name": name, "attached_to_doctype": "Newsletter", "is_private":0})
+
def get_email_groups(name):
return frappe.db.get_all("Newsletter Email Group", ["email_group"],{"parent":name, "parenttype":"Newsletter"})
@@ -219,4 +240,24 @@ def send_newsletter(newsletter):
frappe.db.commit()
+def get_list_context(context=None):
+ context.update({
+ "show_sidebar": True,
+ "show_search": True,
+ 'no_breadcrumbs': True,
+ "title": _("Newsletter"),
+ "get_list": get_newsletter_list,
+ "row_template": "email/doctype/newsletter/templates/newsletter_row.html",
+ })
+
+
+def get_newsletter_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"):
+ email_group_list = frappe.db.sql('''select eg.name from `tabEmail Group` eg, `tabEmail Group Member` egm
+ where egm.unsubscribed=0 and eg.name=egm.email_group and egm.email = %s''', frappe.session.user)
+ if email_group_list:
+ return frappe.db.sql('''select n.name, n.subject, n.message, n.modified
+ from `tabNewsletter` n, `tabNewsletter Email Group` neg
+ where n.name = neg.parent and n.email_sent=1 and n.published=1 and neg.email_group in %s
+ order by n.modified desc limit {0}, {1}
+ '''.format(limit_start, limit_page_length), [email_group_list], as_dict=1)
diff --git a/frappe/email/doctype/newsletter/templates/newsletter.html b/frappe/email/doctype/newsletter/templates/newsletter.html
new file mode 100644
index 0000000000..733c7df6af
--- /dev/null
+++ b/frappe/email/doctype/newsletter/templates/newsletter.html
@@ -0,0 +1,65 @@
+{% extends "templates/web.html" %}
+
+{% block title %} {{ _("Newsletter") }} {% endblock %}
+
+{% block page_content %}
+
+
+
+
+
+
+
+ {{ frappe.format_date(doc.modified) }}
+
+
+
+ {{ doc.message }}
+
+
+
+ {% if attachments %}
+
+
+
+ {{ _("Attachments") }}
+
+
+
+
+ {% endif %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/frappe/email/doctype/newsletter/templates/newsletter_row.html b/frappe/email/doctype/newsletter/templates/newsletter_row.html
new file mode 100644
index 0000000000..503fadb1ea
--- /dev/null
+++ b/frappe/email/doctype/newsletter/templates/newsletter_row.html
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/frappe/email/doctype/newsletter/test_newsletter.js b/frappe/email/doctype/newsletter/test_newsletter.js
new file mode 100644
index 0000000000..40664a439a
--- /dev/null
+++ b/frappe/email/doctype/newsletter/test_newsletter.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Newsletter", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Newsletter
+ () => frappe.tests.make('Newsletter', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py
index 2b13799775..9b2f275260 100644
--- a/frappe/email/doctype/newsletter/test_newsletter.py
+++ b/frappe/email/doctype/newsletter/test_newsletter.py
@@ -7,24 +7,26 @@ import frappe, unittest
from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe
from six.moves.urllib.parse import unquote
+
emails = ["test_subscriber1@example.com", "test_subscriber2@example.com",
- "test_subscriber3@example.com"]
+ "test_subscriber3@example.com", "test1@example.com"]
class TestNewsletter(unittest.TestCase):
def setUp(self):
+ frappe.set_user("Administrator")
frappe.db.sql('delete from `tabEmail Group Member`')
for email in emails:
frappe.get_doc({
"doctype": "Email Group Member",
"email": email,
"email_group": "_Test Email Group"
- }).insert()
+ }).insert()
def test_send(self):
name = self.send_newsletter()
email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")]
- self.assertEquals(len(email_queue_list), 3)
+ self.assertEquals(len(email_queue_list), 4)
recipients = [e.recipients[0].recipient for e in email_queue_list]
for email in emails:
self.assertTrue(email in recipients)
@@ -41,13 +43,14 @@ class TestNewsletter(unittest.TestCase):
name = self.send_newsletter()
email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")]
- self.assertEquals(len(email_queue_list), 2)
+ self.assertEquals(len(email_queue_list), 3)
recipients = [e.recipients[0].recipient for e in email_queue_list]
for email in emails:
if email != to_unsubscribe:
self.assertTrue(email in recipients)
- def send_newsletter(self):
+ @staticmethod
+ def send_newsletter(published=0):
frappe.db.sql("delete from `tabEmail Queue`")
frappe.db.sql("delete from `tabEmail Queue Recipient`")
frappe.db.sql("delete from `tabNewsletter`")
@@ -55,7 +58,8 @@ class TestNewsletter(unittest.TestCase):
"doctype": "Newsletter",
"subject": "_Test Newsletter",
"send_from": "Test Sender ",
- "message": "Testing my news."
+ "message": "Testing my news.",
+ "published": published
}).insert(ignore_permissions=True)
newsletter.append("email_group", {"email_group": "_Test Email Group"})
@@ -63,4 +67,21 @@ class TestNewsletter(unittest.TestCase):
newsletter.send_emails()
return newsletter.name
+ def test_portal(self):
+ self.send_newsletter(1)
+ frappe.set_user("test1@example.com")
+ from frappe.email.doctype.newsletter.newsletter import get_newsletter_list
+ newsletters = get_newsletter_list("Newsletter", None, None, 0)
+ self.assertEquals(len(newsletters), 1)
+
+ def test_newsletter_context(self):
+ context = frappe._dict()
+ newsletter_name = self.send_newsletter(1)
+ frappe.set_user("test2@example.com")
+ doc = frappe.get_doc("Newsletter", newsletter_name)
+ doc.get_context(context)
+ self.assertEquals(context.no_cache, 1)
+ self.assertTrue("attachments" not in context.keys())
+
+
test_dependencies = ["Email Group"]
diff --git a/frappe/geo/country_info.json b/frappe/geo/country_info.json
index 376a9f3c10..53a14ffc5c 100644
--- a/frappe/geo/country_info.json
+++ b/frappe/geo/country_info.json
@@ -269,9 +269,11 @@
},
"Benin": {
"code": "bj",
+ "currency": "XOF",
+ "currency_name": "West African CFA Franc",
+ "currency_symbol": "CFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Porto-Novo"
@@ -386,6 +388,8 @@
},
"Bulgaria": {
"code": "bg",
+ "currency": "BGN",
+ "currency_name": "Bulgarian Lev",
"currency_fraction": "Stotinka",
"currency_fraction_units": 100,
"currency_symbol": "\u043b\u0432",
@@ -396,9 +400,11 @@
},
"Burkina Faso": {
"code": "bf",
+ "currency": "XOF",
+ "currency_name": "West African CFA Franc",
+ "currency_symbol": "CFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Ouagadougou"
@@ -430,9 +436,11 @@
},
"Cameroon": {
"code": "cm",
+ "currency": "XAF",
+ "currency_name": "Central African CFA Franc",
+ "currency_symbol": "FCFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Douala"
@@ -504,9 +512,11 @@
},
"Central African Republic": {
"code": "cf",
+ "currency": "XAF",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
+ "currency_name": "Central African CFA Franc",
+ "currency_symbol": "FCFA",
"number_format": "#,###.##",
"timezones": [
"Africa/Bangui"
@@ -514,9 +524,11 @@
},
"Chad": {
"code": "td",
+ "currency": "XAF",
+ "currency_name": "Central African CFA Franc",
+ "currency_symbol": "FCFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Ndjamena"
@@ -592,7 +604,12 @@
},
"Congo": {
"code": "cg",
- "number_format": "#,###.##"
+ "number_format": "#,###.##",
+ "currency": "XAF",
+ "currency_name": "Central African CFA Franc",
+ "currency_symbol": "FCFA",
+ "currency_fraction": "Centime",
+ "currency_fraction_units": 100
},
"Congo, The Democratic Republic of the": {
"code": "cd",
@@ -758,9 +775,11 @@
},
"Equatorial Guinea": {
"code": "gq",
+ "currency": "XAF",
+ "currency_name": "Central African CFA Franc",
+ "currency_symbol": "FCFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Malabo"
@@ -877,9 +896,11 @@
},
"Gabon": {
"code": "ga",
+ "currency": "XAF",
+ "currency_name": "Central African CFA Franc",
+ "currency_symbol": "FCFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Libreville"
@@ -1019,9 +1040,11 @@
},
"Guinea-Bissau": {
"code": "gw",
+ "currency": "XOF",
+ "currency_name": "West African CFA Franc",
+ "currency_symbol": "CFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Bissau"
@@ -1205,10 +1228,15 @@
},
"Ivory Coast": {
"code": "ci",
+ "currency": "XOF",
+ "currency_name": "West African CFA Franc",
+ "currency_symbol": "CFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
- "number_format": "#,###.##"
+ "number_format": "#,###.##",
+ "timeszones": [
+ "Africa/Abidjan"
+ ]
},
"Jamaica": {
"code": "jm",
@@ -1496,9 +1524,11 @@
},
"Mali": {
"code": "ml",
+ "currency": "XOF",
+ "currency_name": "West African CFA Franc",
+ "currency_symbol": "CFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Bamako"
@@ -1758,9 +1788,11 @@
},
"Niger": {
"code": "ne",
+ "currency": "XOF",
+ "currency_name": "West African CFA Franc",
+ "currency_symbol": "CFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Niamey"
@@ -2087,9 +2119,11 @@
},
"Senegal": {
"code": "sn",
+ "currency": "XOF",
+ "currency_name": "West African CFA Franc",
+ "currency_symbol": "CFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Dakar"
@@ -2355,9 +2389,11 @@
},
"Togo": {
"code": "tg",
+ "currency": "XOF",
+ "currency_name": "West African CFA Franc",
+ "currency_symbol": "CFA",
"currency_fraction": "Centime",
"currency_fraction_units": 100,
- "currency_symbol": "Fr",
"number_format": "#,###.##",
"timezones": [
"Africa/Lome"
diff --git a/frappe/hooks.py b/frappe/hooks.py
index f5d98f0421..9bdd8352a6 100755
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from . import __version__ as app_version
+
app_name = "frappe"
app_title = "Frappe Framework"
app_publisher = "Frappe Technologies"
@@ -48,9 +49,11 @@ bootstrap = "assets/frappe/css/bootstrap.css"
web_include_css = [
"assets/css/frappe-web.css"
]
+
website_route_rules = [
{"from_route": "/blog/", "to_route": "Blog Post"},
- {"from_route": "/kb/", "to_route": "Help Article"}
+ {"from_route": "/kb/", "to_route": "Help Article"},
+ {"from_route": "/newsletters", "to_route": "Newsletter"}
]
write_file_keys = ["file_url", "file_name"]
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 28bc5cb574..86015c001c 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -193,3 +193,4 @@ frappe.patches.v8_x.update_user_permission
frappe.patches.v8_5.patch_event_colors
frappe.patches.v8_7.update_email_queue_status
frappe.patches.v8_10.delete_static_web_page_from_global_search
+frappe.patches.v8_x.add_bgn_xaf_xof_currencies
diff --git a/frappe/patches/v8_x/add_bgn_xaf_xof_currencies.py b/frappe/patches/v8_x/add_bgn_xaf_xof_currencies.py
new file mode 100644
index 0000000000..298f385f40
--- /dev/null
+++ b/frappe/patches/v8_x/add_bgn_xaf_xof_currencies.py
@@ -0,0 +1,12 @@
+"""
+This will add the following currencies:
+1. BGN (Bulgarian Lev) to Bulgaria.
+2. XAF (Central African CFA Franc) to Cameroon, Republic of Congo, Chad, Gabon, Equitorial Guinea and
+ Central African Republic.
+3. XOF (West African CFA Franc) to Benin, Niger, Burkina Faso, Mali, Senegal, Togo, Ivory Coast and Guinea Bissau.
+"""
+from frappe.utils.install import import_country_and_currency
+
+
+def execute():
+ import_country_and_currency()
diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css
index f03f8d987d..f2f758aac5 100644
--- a/frappe/public/css/form.css
+++ b/frappe/public/css/form.css
@@ -601,11 +601,9 @@ select.form-control {
.control-code.bold {
height: 400px;
font-family: Monaco, "Courier New", monospace;
- background-color: black;
- color: #fffce7;
+ color: #36414C;
font-size: 12px;
line-height: 1.7em;
- border: none;
}
.delivery-status-indicator {
display: inline-block;
diff --git a/frappe/public/css/sidebar.css b/frappe/public/css/sidebar.css
index 1bea165047..d5b07cc422 100644
--- a/frappe/public/css/sidebar.css
+++ b/frappe/public/css/sidebar.css
@@ -94,9 +94,6 @@ body[data-route^="Module"] .main-menu .form-sidebar {
position: absolute;
right: 5px;
}
-.form-sidebar .attachment-row a.close {
- margin-top: -5px;
-}
.form-sidebar .form-shared .share-doc-btn,
.form-sidebar .form-viewers .share-doc-btn {
cursor: pointer;
diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js
index 68c63dae10..0610e6b551 100644
--- a/frappe/public/js/frappe/desk.js
+++ b/frappe/public/js/frappe/desk.js
@@ -29,7 +29,7 @@ frappe.Application = Class.extend({
this.startup();
},
startup: function() {
- frappe.socket.init();
+ frappe.socketio.init();
frappe.model.init();
if(frappe.boot.status==='failed') {
diff --git a/frappe/public/js/frappe/form/controls/attach.js b/frappe/public/js/frappe/form/controls/attach.js
index 0efd76dd13..99bef4a113 100644
--- a/frappe/public/js/frappe/form/controls/attach.js
+++ b/frappe/public/js/frappe/form/controls/attach.js
@@ -9,10 +9,10 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
});
this.$value = $('')
+ ×')
.prependTo(me.input_area)
.toggle(false);
this.input = this.$input.get(0);
diff --git a/frappe/public/js/frappe/form/controls/html.js b/frappe/public/js/frappe/form/controls/html.js
index 5c4f736071..53f1f04c08 100644
--- a/frappe/public/js/frappe/form/controls/html.js
+++ b/frappe/public/js/frappe/form/controls/html.js
@@ -2,13 +2,21 @@ frappe.ui.form.ControlHTML = frappe.ui.form.Control.extend({
make: function() {
this._super();
this.disp_area = this.wrapper;
+ $(document).on('change', () => {
+ setTimeout(() => this.refresh_input(), 500);
+ });
},
refresh_input: function() {
var content = this.get_content();
if(content) this.$wrapper.html(content);
},
get_content: function() {
- return this.df.options || "";
+ var content = this.df.options || "";
+ try {
+ return frappe.render(content, this);
+ } catch (e) {
+ return content;
+ }
},
html: function(html) {
this.$wrapper.html(html || this.get_content());
diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js
index 1110fee7fc..00af468754 100644
--- a/frappe/public/js/frappe/form/controls/text_editor.js
+++ b/frappe/public/js/frappe/form/controls/text_editor.js
@@ -137,17 +137,17 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
});
},
get_image: function (fileobj, callback) {
- var freader = new FileReader();
+ var reader = new FileReader();
- freader.onload = function() {
- var dataurl = freader.result;
+ reader.onload = function() {
+ var dataurl = reader.result;
// add filename to dataurl
var parts = dataurl.split(",");
parts[0] += ";filename=" + fileobj.name;
dataurl = parts[0] + ',' + parts[1];
callback(dataurl);
};
- freader.readAsDataURL(fileobj);
+ reader.readAsDataURL(fileobj);
},
hide_elements_on_mobile: function() {
this.note_editor.find('.note-btn-underline,\
diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js
index 025f0bba7b..015cc27ded 100644
--- a/frappe/public/js/frappe/form/quick_entry.js
+++ b/frappe/public/js/frappe/form/quick_entry.js
@@ -48,7 +48,8 @@ frappe.ui.form.QuickEntryForm = Class.extend({
return false;
}
- if (this.too_many_mandatory_fields() || this.has_child_table()) {
+ if (this.too_many_mandatory_fields() || this.has_child_table()
+ || !this.mandatory.length) {
return false;
}
diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js
index 19b622e8ee..bde19db1f3 100644
--- a/frappe/public/js/frappe/request.js
+++ b/frappe/public/js/frappe/request.js
@@ -34,7 +34,7 @@ frappe.call = function(opts) {
var callback = function(data, response_text) {
if(data.task_id) {
// async call, subscribe
- frappe.socket.subscribe(data.task_id, opts);
+ frappe.socketio.subscribe(data.task_id, opts);
if(opts.queued) {
opts.queued(data);
diff --git a/frappe/public/js/frappe/socketio_client.js b/frappe/public/js/frappe/socketio_client.js
index c8af253bed..d058b8a3f4 100644
--- a/frappe/public/js/frappe/socketio_client.js
+++ b/frappe/public/js/frappe/socketio_client.js
@@ -1,4 +1,4 @@
-frappe.socket = {
+frappe.socketio = {
open_tasks: {},
open_docs: [],
emit_queue: [],
@@ -7,40 +7,40 @@ frappe.socket = {
return;
}
- if (frappe.socket.socket) {
+ if (frappe.socketio.socket) {
return;
}
if (frappe.boot.developer_mode) {
// File watchers for development
- frappe.socket.setup_file_watchers();
+ frappe.socketio.setup_file_watchers();
}
//Enable secure option when using HTTPS
if (window.location.protocol == "https:") {
- frappe.socket.socket = io.connect(frappe.socket.get_host(), {secure: true});
+ frappe.socketio.socket = io.connect(frappe.socketio.get_host(), {secure: true});
}
else if (window.location.protocol == "http:") {
- frappe.socket.socket = io.connect(frappe.socket.get_host());
+ frappe.socketio.socket = io.connect(frappe.socketio.get_host());
}
else if (window.location.protocol == "file:") {
- frappe.socket.socket = io.connect(window.localStorage.server);
+ frappe.socketio.socket = io.connect(window.localStorage.server);
}
- if (!frappe.socket.socket) {
- console.log("Unable to connect to " + frappe.socket.get_host());
+ if (!frappe.socketio.socket) {
+ console.log("Unable to connect to " + frappe.socketio.get_host());
return;
}
- frappe.socket.socket.on('msgprint', function(message) {
+ frappe.socketio.socket.on('msgprint', function(message) {
frappe.msgprint(message);
});
- frappe.socket.socket.on('eval_js', function(message) {
+ frappe.socketio.socket.on('eval_js', function(message) {
eval(message);
});
- frappe.socket.socket.on('progress', function(data) {
+ frappe.socketio.socket.on('progress', function(data) {
if(data.progress) {
data.percent = flt(data.progress[0]) / data.progress[1] * 100;
}
@@ -53,23 +53,24 @@ frappe.socket = {
}
});
- frappe.socket.setup_listeners();
- frappe.socket.setup_reconnect();
+ frappe.socketio.setup_listeners();
+ frappe.socketio.setup_reconnect();
+ frappe.socketio.uploader = new frappe.socketio.SocketIOUploader();
$(document).on('form-load form-rename', function(e, frm) {
if (frm.is_new()) {
return;
}
- for (var i=0, l=frappe.socket.open_docs.length; i${filename} changed Click to Reload
@@ -239,7 +240,7 @@ frappe.socket = {
}
// success
- var opts = frappe.socket.open_tasks[data.task_id];
+ var opts = frappe.socketio.open_tasks[data.task_id];
if(opts[method]) {
opts[method](data);
}
@@ -264,15 +265,108 @@ frappe.socket = {
frappe.provide("frappe.realtime");
frappe.realtime.on = function(event, callback) {
- frappe.socket.socket && frappe.socket.socket.on(event, callback);
+ frappe.socketio.socket && frappe.socketio.socket.on(event, callback);
};
frappe.realtime.off = function(event, callback) {
- frappe.socket.socket && frappe.socket.socket.off(event, callback);
+ frappe.socketio.socket && frappe.socketio.socket.off(event, callback);
}
frappe.realtime.publish = function(event, message) {
- if(frappe.socket.socket) {
- frappe.socket.socket.emit(event, message);
+ if(frappe.socketio.socket) {
+ frappe.socketio.socket.emit(event, message);
}
}
+
+frappe.socketio.SocketIOUploader = class SocketIOUploader {
+ constructor() {
+ frappe.socketio.socket.on('upload-request-slice', (data) => {
+ var place = data.currentSlice * this.chunk_size,
+ slice = this.file.slice(place,
+ place + Math.min(this.chunk_size, this.file.size - place));
+
+ if (this.on_progress) {
+ // update progress
+ this.on_progress(place / this.file.size * 100);
+ }
+
+ this.reader.readAsArrayBuffer(slice);
+ this.keep_alive();
+ });
+
+ frappe.socketio.socket.on('upload-end', (data) => {
+ if (data.file_url.substr(0, 7)==='/public') {
+ data.file_url = data.file_url.substr(7);
+ }
+ this.callback(data);
+ this.reader = null;
+ this.file = null;
+ });
+
+ frappe.socketio.socket.on('upload-error', (data) => {
+ this.disconnect(false);
+ frappe.msgprint({
+ title: __('Upload Failed'),
+ message: data.error,
+ indicator: 'red'
+ });
+ });
+
+ frappe.socketio.socket.on('disconnect', () => {
+ this.disconnect();
+ });
+ }
+
+ start({file=null, is_private=0, filename='', callback=null, on_progress=null,
+ chunk_size=100000} = {}) {
+
+ if (this.reader) {
+ frappe.throw(__('File Upload in Progress. Please try again in a few moments.'));
+ }
+
+ this.reader = new FileReader();
+ this.file = file;
+ this.chunk_size = chunk_size;
+ this.callback = callback;
+ this.on_progress = on_progress;
+
+ this.reader.onload = () => {
+ frappe.socketio.socket.emit('upload-accept-slice', {
+ is_private: is_private,
+ name: filename,
+ type: this.file.type,
+ size: this.file.size,
+ data: this.reader.result
+ });
+ this.keep_alive();
+ };
+
+ var slice = file.slice(0, this.chunk_size);
+ this.reader.readAsArrayBuffer(slice);
+ }
+
+ keep_alive() {
+ if (this.next_check) {
+ clearTimeout (this.next_check);
+ }
+ this.next_check = setTimeout (() => {
+ this.disconnect();
+ }, 3000);
+ }
+
+ disconnect(with_message = true) {
+ if (this.reader) {
+ this.reader = null;
+ this.file = null;
+ frappe.hide_progress();
+ if (with_message) {
+ frappe.msgprint({
+ title: __('File Upload'),
+ message: __('File Upload Disconnected. Please try again.'),
+ indicator: 'red'
+ });
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js
index 79c1345cfa..b4d4244bbf 100644
--- a/frappe/public/js/frappe/ui/messages.js
+++ b/frappe/public/js/frappe/ui/messages.js
@@ -234,7 +234,7 @@ frappe.verify_password = function(callback) {
}, __("Verify Password"), __("Verify"))
}
-frappe.show_progress = function(title, count, total) {
+frappe.show_progress = function(title, count, total=100, description) {
if(frappe.cur_progress && frappe.cur_progress.title === title
&& frappe.cur_progress.$wrapper.is(":visible")) {
var dialog = frappe.cur_progress;
@@ -242,7 +242,10 @@ frappe.show_progress = function(title, count, total) {
var dialog = new frappe.ui.Dialog({
title: title,
});
- dialog.progress = $('')
+ dialog.progress = $(``)
.appendTo(dialog.body);
dialog.progress_bar = dialog.progress.css({"margin-top": "10px"})
.find(".progress-bar");
@@ -250,7 +253,12 @@ frappe.show_progress = function(title, count, total) {
dialog.show();
frappe.cur_progress = dialog;
}
- dialog.progress_bar.css({"width": cint(flt(count) * 100 / total) + "%" });
+ if (description) {
+ dialog.progress.find('.description').text(description);
+ }
+ dialog.percent = cint(flt(count) * 100 / total);
+ dialog.progress_bar.css({"width": dialog.percent + "%" });
+ return dialog;
}
frappe.hide_progress = function() {
diff --git a/frappe/public/js/frappe/upload.js b/frappe/public/js/frappe/upload.js
index 319fc61971..5914cb9c79 100644
--- a/frappe/public/js/frappe/upload.js
+++ b/frappe/public/js/frappe/upload.js
@@ -187,7 +187,7 @@ frappe.upload = {
},
upload_multiple_files: function(files /*FileData array*/, args, opts) {
var i = -1;
-
+ frappe.upload.total_files = files ? files.length : 0;
// upload the first file
upload_next();
// subsequent files will be uploaded after
@@ -200,7 +200,7 @@ frappe.upload = {
var file = files[i];
args.is_private = file.is_private;
if(!opts.progress) {
- frappe.show_progress(__('Uploading'), i+1, files.length);
+ frappe.show_progress(__('Uploading'), i, files.length);
}
}
frappe.upload.upload_file(file, args, opts);
@@ -225,20 +225,21 @@ frappe.upload = {
return;
}
- if(args.file_url) {
- frappe.upload._upload_file(fileobj, args, opts);
- } else {
+ if(fileobj) {
frappe.upload.read_file(fileobj, args, opts);
+ } else {
+ // with file_url
+ frappe.upload._upload_file(fileobj, args, opts);
}
},
- _upload_file: function(fileobj, args, opts, dataurl) {
+ _upload_file: function(fileobj, args, opts) {
if (args.file_size) {
frappe.upload.validate_max_file_size(args.file_size);
}
if(opts.on_attach) {
- opts.on_attach(args, dataurl)
+ opts.on_attach(args)
} else {
if (opts.confirm_is_private) {
frappe.prompt({
@@ -248,55 +249,53 @@ frappe.upload = {
"default": 1
}, function(values) {
args["is_private"] = values.is_private;
- frappe.upload.upload_to_server(fileobj, args, opts, dataurl);
+ frappe.upload.upload_to_server(fileobj, args, opts);
}, __("Private or Public?"));
} else {
if ("is_private" in opts) {
args["is_private"] = opts.is_private;
}
- frappe.upload.upload_to_server(fileobj, args, opts, dataurl);
+ frappe.upload.upload_to_server(fileobj, args, opts);
}
}
},
read_file: function(fileobj, args, opts) {
- var freader = new FileReader();
+ args.filename = fileobj.name.split(' ').join('_');
+ args.file_url = null;
- freader.onload = function() {
- args.filename = fileobj.name.split(' ').join('_');
- if(opts.options && opts.options.toLowerCase()=="image") {
- if(!frappe.utils.is_image_file(args.filename)) {
- frappe.msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed"));
- return;
- }
+ if(opts.options && opts.options.toLowerCase()=="image") {
+ if(!frappe.utils.is_image_file(args.filename)) {
+ frappe.msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed"));
+ return;
}
+ }
- if((opts.max_width || opts.max_height) && frappe.utils.is_image_file(args.filename)) {
- frappe.utils.resize_image(freader, function(_dataurl) {
- var dataurl = _dataurl;
- args.filedata = _dataurl.split(",")[1];
- args.file_size = Math.round(args.filedata.length * 3 / 4);
- console.log("resized!")
- frappe.upload._upload_file(fileobj, args, opts, dataurl);
- })
- } else {
- var dataurl = freader.result;
- args.filedata = freader.result.split(",")[1];
- args.file_size = fileobj.size;
- frappe.upload._upload_file(fileobj, args, opts, dataurl);
+ let start_complete = frappe.cur_progress ? frappe.cur_progress.percent : 0;
+
+ frappe.socketio.uploader.start({
+ file: fileobj,
+ filename: args.filename,
+ is_private: args.is_private,
+ callback: (data) => {
+ args.file_url = data.file_url;
+ frappe.upload._upload_file(fileobj, args, opts);
+ },
+ on_progress: (percent_complete) => {
+ let increment = (flt(percent_complete) / frappe.upload.total_files);
+ frappe.show_progress(__('Uploading'),
+ start_complete + increment);
}
- };
-
- freader.readAsDataURL(fileobj);
+ });
},
- upload_to_server: function(fileobj, args, opts, dataurl) {
- // var msgbox = frappe.msgprint(__("Uploading..."));
+ upload_to_server: function(file, args, opts) {
if(opts.start) {
opts.start();
}
+
var ajax_args = {
"method": "uploadfile",
args: args,
@@ -368,7 +367,7 @@ frappe.upload = {
d.hide();
opts.loopcallback = function (){
if (i < j) {
- args.is_private = d.fields_dict[fileobjs[i].name + "_is_private"].get_value()
+ args.is_private = d.fields_dict[fileobjs[i].name + "_is_private"].get_value();
frappe.upload.upload_file(fileobjs[i], args, opts);
i++;
}
diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less
index afec19cd44..196d225027 100644
--- a/frappe/public/less/form.less
+++ b/frappe/public/less/form.less
@@ -749,11 +749,9 @@ select.form-control {
.control-code, .control-code.bold {
height: 400px;
font-family: Monaco, "Courier New", monospace;
- background-color: black;
- color: @light-yellow;
+ color: @text-color;
font-size: 12px;
line-height: 1.7em;
- border: none;
}
.delivery-status-indicator {
diff --git a/frappe/public/less/sidebar.less b/frappe/public/less/sidebar.less
index aacb83b17e..e45cc91bd7 100644
--- a/frappe/public/less/sidebar.less
+++ b/frappe/public/less/sidebar.less
@@ -130,15 +130,6 @@ body[data-route^="Module"] .main-menu {
right: 5px;
}
- // .attachment-row .icon-lock {
- // color: @text-warning;
- // display: inline-block;
- // margin-top: 1px;
- // }
-
- .attachment-row a.close {
- margin-top: -5px;
- }
.form-shared, .form-viewers {
.share-doc-btn {
diff --git a/frappe/tests/ui/test_control_html.js b/frappe/tests/ui/test_control_html.js
new file mode 100644
index 0000000000..8c0276508c
--- /dev/null
+++ b/frappe/tests/ui/test_control_html.js
@@ -0,0 +1,51 @@
+QUnit.module('controls');
+
+QUnit.test("Test ControlHTML", function(assert) {
+ assert.expect(3);
+ const random_name = frappe.utils.get_random(3).toLowerCase();
+
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Custom Field', [
+ {dt: 'ToDo'},
+ {fieldtype: 'HTML'},
+ {label: random_name},
+ {options: ' Test
'}
+ ]);
+ },
+ () => {
+ return frappe.tests.make('Custom Field', [
+ {dt: 'ToDo'},
+ {fieldtype: 'HTML'},
+ {label: random_name + "_template"},
+ {options: ' Test {%= doc.status %}
'}
+ ]);
+ },
+ () => frappe.set_route('List', 'ToDo'),
+ () => frappe.new_doc('ToDo'),
+ () => {
+ if (frappe.quick_entry)
+ {
+ frappe.quick_entry.dialog.$wrapper.find('.edit-full').click();
+ return frappe.timeout(1);
+ }
+ },
+ () => {
+ const control = $(`.frappe-control[data-fieldname="${random_name}"]`)[0];
+ return assert.ok(control.innerHTML === ' Test
');
+ },
+ () => {
+ const control = $(`.frappe-control[data-fieldname="${random_name}_template"]`)[0];
+ return assert.ok(control.innerHTML === ' Test Open
');
+ },
+ () => frappe.tests.set_control("status", "Closed"),
+ () => frappe.timeout(1),
+ () => {
+ const control = $(`.frappe-control[data-fieldname="${random_name}_template"]`)[0];
+ return assert.ok(control.innerHTML === ' Test Closed
');
+ },
+ () => done()
+ ]);
+});
diff --git a/frappe/tests/ui/tests.txt b/frappe/tests/ui/tests.txt
index bcdd1c6cee..3c0bb98ebb 100644
--- a/frappe/tests/ui/tests.txt
+++ b/frappe/tests/ui/tests.txt
@@ -13,3 +13,4 @@ frappe/custom/doctype/customize_form/test_customize_form.js
frappe/desk/doctype/event/test_event.js
frappe/workflow/doctype/workflow/tests/test_workflow_create.js
frappe/workflow/doctype/workflow/tests/test_workflow_test.js
+frappe/tests/ui/test_control_html.js
diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py
index d3a7b74ac4..845f4a623c 100644
--- a/frappe/utils/backups.py
+++ b/frappe/utils/backups.py
@@ -179,11 +179,13 @@ def delete_temp_backups(older_than=24):
"""
Cleans up the backup_link_path directory by deleting files older than 24 hours
"""
- file_list = os.listdir(get_backup_path())
- for this_file in file_list:
- this_file_path = os.path.join(get_backup_path(), this_file)
- if is_file_old(this_file_path, older_than):
- os.remove(this_file_path)
+ backup_path = get_backup_path()
+ if os.path.exists(backup_path):
+ file_list = os.listdir(get_backup_path())
+ for this_file in file_list:
+ this_file_path = os.path.join(get_backup_path(), this_file)
+ if is_file_old(this_file_path, older_than):
+ os.remove(this_file_path)
def is_file_old(db_file_name, older_than=24):
"""
diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py
index 2d32215d6a..de0ebdf12f 100644
--- a/frappe/utils/file_manager.py
+++ b/frappe/utils/file_manager.py
@@ -15,6 +15,7 @@ from six import text_type
class MaxFileSizeReachedError(frappe.ValidationError): pass
+
def get_file_url(file_data_name):
data = frappe.db.get_value("File", file_data_name, ["file_name", "file_url"], as_dict=True)
return data.file_url or data.file_name
@@ -23,38 +24,51 @@ def upload():
# get record details
dt = frappe.form_dict.doctype
dn = frappe.form_dict.docname
- folder = frappe.form_dict.folder
file_url = frappe.form_dict.file_url
filename = frappe.form_dict.filename
- is_private = cint(frappe.form_dict.is_private)
+ frappe.form_dict.is_private = cint(frappe.form_dict.is_private)
if not filename and not file_url:
frappe.msgprint(_("Please select a file or url"),
raise_exception=True)
- # save
- if frappe.form_dict.filedata:
- filedata = save_uploaded(dt, dn, folder, is_private)
- elif file_url:
- filedata = save_url(file_url, filename, dt, dn, folder, is_private)
+ file_doc = get_file_doc()
comment = {}
if dt and dn:
comment = frappe.get_doc(dt, dn).add_comment("Attachment",
_("added {0}").format("{file_name}{icon}".format(**{
- "icon": ' ' if filedata.is_private else "",
- "file_url": filedata.file_url.replace("#", "%23") if filedata.file_name else filedata.file_url,
- "file_name": filedata.file_name or filedata.file_url
+ "icon": ' ' \
+ if file_doc.is_private else "",
+ "file_url": file_doc.file_url.replace("#", "%23") \
+ if file_doc.file_name else file_doc.file_url,
+ "file_name": file_doc.file_name or file_doc.file_url
})))
return {
- "name": filedata.name,
- "file_name": filedata.file_name,
- "file_url": filedata.file_url,
- "is_private": filedata.is_private,
+ "name": file_doc.name,
+ "file_name": file_doc.file_name,
+ "file_url": file_doc.file_url,
+ "is_private": file_doc.is_private,
"comment": comment.as_dict() if comment else {}
}
+def get_file_doc(dt=None, dn=None, folder=None, is_private=None):
+ '''returns File object (Document) from given parameters or form_dict'''
+ r = frappe.form_dict
+
+ if dt is None: dt = r.doctype
+ if dn is None: dn = r.docname
+ if folder is None: folder = r.folder
+ if is_private is None: is_private = r.is_private
+
+ if r.filedata:
+ file_doc = save_uploaded(dt, dn, folder, is_private)
+ elif r.file_url:
+ file_doc = save_url(r.file_url, r.filename, dt, dn, folder, is_private)
+
+ return file_doc
+
def save_uploaded(dt, dn, folder, is_private):
fname, content = get_uploaded_content()
if content:
@@ -97,55 +111,6 @@ def get_uploaded_content():
frappe.msgprint(_('No file attached'))
return None, None
-def extract_images_from_doc(doc, fieldname):
- content = doc.get(fieldname)
- content = extract_images_from_html(doc, content)
- if frappe.flags.has_dataurl:
- doc.set(fieldname, content)
-
-def extract_images_from_html(doc, content):
- frappe.flags.has_dataurl = False
-
- def _save_file(match):
- data = match.group(1)
- data = data.split("data:")[1]
- headers, content = data.split(",")
-
- if "filename=" in headers:
- filename = headers.split("filename=")[-1]
-
- # decode filename
- if not isinstance(filename, text_type):
- filename = text_type(filename, 'utf-8')
- else:
- mtype = headers.split(";")[0]
- filename = get_random_filename(content_type=mtype)
-
- doctype = doc.parenttype if doc.parent else doc.doctype
- name = doc.parent or doc.name
-
- # TODO fix this
- file_url = save_file(filename, content, doctype, name, decode=True).get("file_url")
- if not frappe.flags.has_dataurl:
- frappe.flags.has_dataurl = True
-
- return '
]*src\s*=\s*["\'](?=data:)(.*?)["\']', _save_file, content)
-
- return content
-
-def get_random_filename(extn=None, content_type=None):
- if extn:
- if not extn.startswith("."):
- extn = "." + extn
-
- elif content_type:
- extn = mimetypes.guess_extension(content_type)
-
- return random_string(7) + (extn or "")
-
def save_file(fname, content, dt, dn, folder=None, decode=False, is_private=0):
if decode:
if isinstance(content, text_type):
@@ -370,3 +335,52 @@ def download_file(file_url):
frappe.local.response.filename = file_url[file_url.rfind("/")+1:]
frappe.local.response.filecontent = filedata
frappe.local.response.type = "download"
+
+def extract_images_from_doc(doc, fieldname):
+ content = doc.get(fieldname)
+ content = extract_images_from_html(doc, content)
+ if frappe.flags.has_dataurl:
+ doc.set(fieldname, content)
+
+def extract_images_from_html(doc, content):
+ frappe.flags.has_dataurl = False
+
+ def _save_file(match):
+ data = match.group(1)
+ data = data.split("data:")[1]
+ headers, content = data.split(",")
+
+ if "filename=" in headers:
+ filename = headers.split("filename=")[-1]
+
+ # decode filename
+ if not isinstance(filename, text_type):
+ filename = text_type(filename, 'utf-8')
+ else:
+ mtype = headers.split(";")[0]
+ filename = get_random_filename(content_type=mtype)
+
+ doctype = doc.parenttype if doc.parent else doc.doctype
+ name = doc.parent or doc.name
+
+ # TODO fix this
+ file_url = save_file(filename, content, doctype, name, decode=True).get("file_url")
+ if not frappe.flags.has_dataurl:
+ frappe.flags.has_dataurl = True
+
+ return '
]*src\s*=\s*["\'](?=data:)(.*?)["\']', _save_file, content)
+
+ return content
+
+def get_random_filename(extn=None, content_type=None):
+ if extn:
+ if not extn.startswith("."):
+ extn = "." + extn
+
+ elif content_type:
+ extn = mimetypes.guess_extension(content_type)
+
+ return random_string(7) + (extn or "")
diff --git a/frappe/website/doctype/website_slideshow/website_slideshow.js b/frappe/website/doctype/website_slideshow/website_slideshow.js
index acb49d6045..471729bda5 100644
--- a/frappe/website/doctype/website_slideshow/website_slideshow.js
+++ b/frappe/website/doctype/website_slideshow/website_slideshow.js
@@ -1,12 +1,10 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
-cur_frm.cscript.refresh = function(doc) {
- cur_frm.set_intro("");
- if(doc.__islocal) {
- cur_frm.set_intro("First set the name and save the record.");
+frappe.ui.form.on("Website Slideshow", {
+ refresh: (frm) => {
+ let intro = frm.doc.__islocal?
+ "First set the name and save the record.": "Attach files / urls and add in table.";
+ frm.set_intro(intro);
}
- else {
- cur_frm.set_intro("Attach files / urls and add in table.");
- }
-}
\ No newline at end of file
+})
\ No newline at end of file
diff --git a/frappe/website/doctype/website_slideshow/website_slideshow.json b/frappe/website/doctype/website_slideshow/website_slideshow.json
index ca8daf59b5..ab6f928a2e 100644
--- a/frappe/website/doctype/website_slideshow/website_slideshow.json
+++ b/frappe/website/doctype/website_slideshow/website_slideshow.json
@@ -1,5 +1,6 @@
{
"allow_copy": 0,
+ "allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:slideshow_name",
@@ -13,33 +14,7 @@
"editable_grid": 0,
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "slideshow_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Slideshow Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -52,6 +27,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
@@ -69,6 +45,36 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "slideshow_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Slideshow Name",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -80,6 +86,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Slideshow Items",
@@ -98,6 +105,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -110,6 +118,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Header",
@@ -127,18 +136,18 @@
"unique": 0
}
],
+ "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-play",
"idx": 1,
"image_view": 0,
"in_create": 0,
- "in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 10,
- "modified": "2016-12-29 14:40:39.690960",
+ "modified": "2017-09-18 11:19:31.627585",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Slideshow",
@@ -154,7 +163,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
- "is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -169,6 +177,7 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
+ "show_name_in_global_search": 0,
"track_changes": 1,
"track_seen": 0
}
\ No newline at end of file
diff --git a/frappe/website/doctype/website_slideshow/website_slideshow.py b/frappe/website/doctype/website_slideshow/website_slideshow.py
index ee19b85df2..467fd80e2e 100644
--- a/frappe/website/doctype/website_slideshow/website_slideshow.py
+++ b/frappe/website/doctype/website_slideshow/website_slideshow.py
@@ -5,15 +5,26 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.model.document import Document
class WebsiteSlideshow(Document):
+ def validate(self):
+ self.validate_images()
+
def on_update(self):
# a slide show can be in use and any change in it should get reflected
from frappe.website.render import clear_cache
clear_cache()
+ def validate_images(self):
+ ''' atleast one image file should be public for slideshow '''
+ files = map(lambda row: row.image, self.slideshow_items)
+ result = frappe.get_all("File", filters={ "file_url":("in", files) }, fields="is_private")
+ if any([file.is_private for file in result]):
+ frappe.throw(_("All Images attached to Website Slideshow should be public"))
+
def get_slideshow(doc):
if not doc.slideshow:
return {}
diff --git a/frappe/website/render.py b/frappe/website/render.py
index 33e82efd18..0291185bf4 100644
--- a/frappe/website/render.py
+++ b/frappe/website/render.py
@@ -168,6 +168,9 @@ def build_page(path):
frappe.local.path = path
context = get_context(path)
+ if "{{" in context.title:
+ title_template = context.pop('title')
+ context.title = frappe.render_template(title_template, context)
if context.source:
html = frappe.render_template(context.source, context)
diff --git a/socketio.js b/socketio.js
index cd3eb64aa8..15e9263398 100644
--- a/socketio.js
+++ b/socketio.js
@@ -3,11 +3,22 @@ var http = require('http').Server(app);
var io = require('socket.io')(http);
var cookie = require('cookie')
var fs = require('fs');
+var path = require('path');
var redis = require("redis");
var request = require('superagent');
var conf = get_conf();
var flags = {};
+var files_struct = {
+ name: null,
+ type: null,
+ size: 0,
+ data: [],
+ slice: 0,
+ site_name: null,
+ is_private: 0
+};
+
var subscriber = redis.createClient(conf.redis_socketio || conf.redis_async_broker_port);
// serve socketio
@@ -21,7 +32,7 @@ app.get('/', function(req, res) {
});
// on socket connection
-io.on('connection', function(socket){
+io.on('connection', function(socket) {
if (get_hostname(socket.request.headers.host) != get_hostname(socket.request.headers.origin)) {
return;
}
@@ -41,6 +52,7 @@ io.on('connection', function(socket){
setTimeout(function() { flags[sid] = null; }, 10000);
socket.user = cookie.parse(socket.request.headers.cookie).user_id;
+ socket.files = {};
// console.log("firing get_user_info");
request.get(get_url(socket, '/api/method/frappe.async.get_user_info'))
@@ -61,6 +73,10 @@ io.on('connection', function(socket){
}
});
+ socket.on('disconnect', function() {
+ delete socket.files;
+ })
+
socket.on('task_subscribe', function(task_id) {
var room = get_task_room(socket, task_id);
socket.join(room);
@@ -134,9 +150,46 @@ io.on('connection', function(socket){
});
});
- // socket.on('disconnect', function (arguments) {
- // console.log("user disconnected", arguments);
- // });
+ socket.on('upload-accept-slice', (data) => {
+ try {
+ if (!socket.files[data.name]) {
+ socket.files[data.name] = Object.assign({}, files_struct, data);
+ socket.files[data.name].data = [];
+ }
+
+ //convert the ArrayBuffer to Buffer
+ data.data = new Buffer(new Uint8Array(data.data));
+ //save the data
+ socket.files[data.name].data.push(data.data);
+ socket.files[data.name].slice++;
+
+ if (socket.files[data.name].slice * 100000 >= socket.files[data.name].size) {
+ // do something with the data
+ var fileBuffer = Buffer.concat(socket.files[data.name].data);
+
+ const file_url = path.join((socket.files[data.name].is_private ? 'private' : 'public'),
+ 'files', data.name);
+ const file_path = path.join('sites', get_site_name(socket), file_url);
+
+ fs.writeFile(file_path, fileBuffer, (err) => {
+ delete socket.files[data.name];
+ if (err) return socket.emit('upload error');
+ socket.emit('upload-end', {
+ file_url: '/' + file_url
+ });
+ });
+ } else {
+ socket.emit('upload-request-slice', {
+ currentSlice: socket.files[data.name].slice
+ });
+ }
+ } catch (e) {
+ console.log(e);
+ socket.emit('upload-error', {
+ error: e.message
+ });
+ }
+ });
});
subscriber.on("message", function(channel, message) {