Merge branch 'develop' of frappe/frappe into web_hooks

This commit is contained in:
Revant Nandgaonkar 2017-09-18 17:12:46 +05:30
commit 620b7f482e
36 changed files with 799 additions and 260 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,65 @@
{% extends "templates/web.html" %}
{% block title %} {{ _("Newsletter") }} {% endblock %}
{% block page_content %}
<style>
.blog-container {
max-width: 720px;
margin: auto;
}
.blog-header {
font-weight: 700;
font-size: 1.5em;
}
.blog-info {
text-align:center;
margin-top: 30px;
}
.blog-text {
padding-top: 50px;
padding-bottom: 50px;
font-size: 15px;
line-height: 1.5;
}
.blog-text p {
margin-bottom: 30px;
}
</style>
<div class="blog-container">
<article class="blog-content" itemscope>
<div class="blog-info">
<h1 itemprop="headline" class="blog-header">{{ doc.subject }}</h1>
<p class="post-by text-muted">
{{ frappe.format_date(doc.modified) }}
</p>
</div>
<div itemprop="articleBody" class="longform blog-text">
{{ doc.message }}
</div>
</article>
{% if attachments %}
<div>
<div class="row text-muted">
<div class="col-sm-12 h6 text-uppercase">
{{ _("Attachments") }}
</div>
</div>
<div class="row">
<div class="col-sm-12">
{% for attachment in attachments %}
<p class="small">
<a href="{{ attachment.file_url }}" target="blank">
{{ attachment.file_name }}
</a>
</p>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -0,0 +1,15 @@
<div class="web-list-item transaction-list-item">
<a href = "{{ route }}/">
<div class="row">
<div class="col-sm-8 text-left bold">
{{ doc.subject }}
</div>
<div class="col-sm-4">
<div class="text-muted text-right"
title="{{ frappe.utils.format_datetime(doc.modified, "medium") }}">
{{ frappe.utils.pretty_date(doc.modified) }}
</div>
</div>
</div>
</a>
</div>

View file

@ -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()
]);
});

View file

@ -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 <test_sender@example.com>",
"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"]

View file

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

View file

@ -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/<category>", "to_route": "Blog Post"},
{"from_route": "/kb/<category>", "to_route": "Help Article"}
{"from_route": "/kb/<category>", "to_route": "Help Article"},
{"from_route": "/newsletters", "to_route": "Newsletter"}
]
write_file_keys = ["file_url", "file_name"]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,10 +9,10 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
});
this.$value = $('<div style="margin-top: 5px;">\
<div class="ellipsis" style="display: inline-block; width: 90%;">\
<i class="fa fa-paper-clip"></i> \
<i class="fa fa-paperclip"></i> \
<a class="attached-file" target="_blank"></a>\
</div>\
<a class="close">&times;</a></div>')
<a class="close" style="position: absolute; right: 15px;">&times;</a></div>')
.prependTo(me.input_area)
.toggle(false);
this.input = this.$input.get(0);

View file

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

View file

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

View file

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

View file

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

View file

@ -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<l; i++) {
var d = frappe.socket.open_docs[i];
for (var i=0, l=frappe.socketio.open_docs.length; i<l; i++) {
var d = frappe.socketio.open_docs[i];
if (frm.doctype==d.doctype && frm.docname==d.name) {
// already subscribed
return false;
}
}
frappe.socket.doc_subscribe(frm.doctype, frm.docname);
frappe.socketio.doc_subscribe(frm.doctype, frm.docname);
});
$(document).on("form_refresh", function(e, frm) {
@ -77,7 +78,7 @@ frappe.socket = {
return;
}
frappe.socket.doc_open(frm.doctype, frm.docname);
frappe.socketio.doc_open(frm.doctype, frm.docname);
});
$(document).on('form-unload', function(e, frm) {
@ -85,8 +86,8 @@ frappe.socket = {
return;
}
// frappe.socket.doc_unsubscribe(frm.doctype, frm.docname);
frappe.socket.doc_close(frm.doctype, frm.docname);
// frappe.socketio.doc_unsubscribe(frm.doctype, frm.docname);
frappe.socketio.doc_close(frm.doctype, frm.docname);
});
window.onbeforeunload = function() {
@ -96,7 +97,7 @@ frappe.socket = {
// if tab/window is closed, notify other users
if (cur_frm.doc) {
frappe.socket.doc_close(cur_frm.doctype, cur_frm.docname);
frappe.socketio.doc_close(cur_frm.doctype, cur_frm.docname);
}
}
},
@ -115,16 +116,16 @@ frappe.socket = {
subscribe: function(task_id, opts) {
// TODO DEPRECATE
frappe.socket.socket.emit('task_subscribe', task_id);
frappe.socket.socket.emit('progress_subscribe', task_id);
frappe.socketio.socket.emit('task_subscribe', task_id);
frappe.socketio.socket.emit('progress_subscribe', task_id);
frappe.socket.open_tasks[task_id] = opts;
frappe.socketio.open_tasks[task_id] = opts;
},
task_subscribe: function(task_id) {
frappe.socket.socket.emit('task_subscribe', task_id);
frappe.socketio.socket.emit('task_subscribe', task_id);
},
task_unsubscribe: function(task_id) {
frappe.socket.socket.emit('task_unsubscribe', task_id);
frappe.socketio.socket.emit('task_unsubscribe', task_id);
},
doc_subscribe: function(doctype, docname) {
if (frappe.flags.doc_subscribe) {
@ -137,12 +138,12 @@ frappe.socket = {
// throttle to 1 per sec
setTimeout(function() { frappe.flags.doc_subscribe = false }, 1000);
frappe.socket.socket.emit('doc_subscribe', doctype, docname);
frappe.socket.open_docs.push({doctype: doctype, docname: docname});
frappe.socketio.socket.emit('doc_subscribe', doctype, docname);
frappe.socketio.open_docs.push({doctype: doctype, docname: docname});
},
doc_unsubscribe: function(doctype, docname) {
frappe.socket.socket.emit('doc_unsubscribe', doctype, docname);
frappe.socket.open_docs = $.filter(frappe.socket.open_docs, function(d) {
frappe.socketio.socket.emit('doc_unsubscribe', doctype, docname);
frappe.socketio.open_docs = $.filter(frappe.socketio.open_docs, function(d) {
if(d.doctype===doctype && d.name===docname) {
return null;
} else {
@ -152,44 +153,44 @@ frappe.socket = {
},
doc_open: function(doctype, docname) {
// notify that the user has opened this doc, if not already notified
if(!frappe.socket.last_doc
|| (frappe.socket.last_doc[0]!=doctype && frappe.socket.last_doc[0]!=docname)) {
frappe.socket.socket.emit('doc_open', doctype, docname);
if(!frappe.socketio.last_doc
|| (frappe.socketio.last_doc[0]!=doctype && frappe.socketio.last_doc[0]!=docname)) {
frappe.socketio.socket.emit('doc_open', doctype, docname);
}
frappe.socket.last_doc = [doctype, docname];
frappe.socketio.last_doc = [doctype, docname];
},
doc_close: function(doctype, docname) {
// notify that the user has closed this doc
frappe.socket.socket.emit('doc_close', doctype, docname);
frappe.socketio.socket.emit('doc_close', doctype, docname);
},
setup_listeners: function() {
frappe.socket.socket.on('task_status_change', function(data) {
frappe.socket.process_response(data, data.status.toLowerCase());
frappe.socketio.socket.on('task_status_change', function(data) {
frappe.socketio.process_response(data, data.status.toLowerCase());
});
frappe.socket.socket.on('task_progress', function(data) {
frappe.socket.process_response(data, "progress");
frappe.socketio.socket.on('task_progress', function(data) {
frappe.socketio.process_response(data, "progress");
});
},
setup_reconnect: function() {
// subscribe again to open_tasks
frappe.socket.socket.on("connect", function() {
frappe.socketio.socket.on("connect", function() {
// wait for 5 seconds before subscribing again
// because it takes more time to start python server than nodejs server
// and we use validation requests to python server for subscribing
setTimeout(function() {
$.each(frappe.socket.open_tasks, function(task_id, opts) {
frappe.socket.subscribe(task_id, opts);
$.each(frappe.socketio.open_tasks, function(task_id, opts) {
frappe.socketio.subscribe(task_id, opts);
});
// re-connect open docs
$.each(frappe.socket.open_docs, function(d) {
$.each(frappe.socketio.open_docs, function(d) {
if(locals[d.doctype] && locals[d.doctype][d.name]) {
frappe.socket.doc_subscribe(d.doctype, d.name);
frappe.socketio.doc_subscribe(d.doctype, d.name);
}
});
if (cur_frm && cur_frm.doc) {
frappe.socket.doc_open(cur_frm.doc.doctype, cur_frm.doc.name);
frappe.socketio.doc_open(cur_frm.doc.doctype, cur_frm.doc.name);
}
}, 5000);
});
@ -208,9 +209,9 @@ frappe.socket = {
}
host = host + ':' + port;
frappe.socket.file_watcher = io.connect(host);
frappe.socketio.file_watcher = io.connect(host);
// css files auto reload
frappe.socket.file_watcher.on('reload_css', function(filename) {
frappe.socketio.file_watcher.on('reload_css', function(filename) {
let abs_file_path = "assets/" + filename;
const link = $(`link[href*="${abs_file_path}"]`);
abs_file_path = abs_file_path.split('?')[0] + '?v='+ moment();
@ -221,7 +222,7 @@ frappe.socket = {
}, 5);
});
// js files show alert
frappe.socket.file_watcher.on('reload_js', function(filename) {
frappe.socketio.file_watcher.on('reload_js', function(filename) {
filename = "assets/" + filename;
var msg = $(`
<span>${filename} changed <a data-action="reload">Click to Reload</a></span>
@ -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'
});
}
}
}
}

View file

@ -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 = $('<div class="progress"><div class="progress-bar"></div></div>')
dialog.progress = $(`<div class="progress">
<div class="progress-bar"></div>
<p class="description text-muted small"></p>
</div>`)
.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() {

View file

@ -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++;
}

View file

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

View file

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

View file

@ -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: '<h3> Test </h3>'}
]);
},
() => {
return frappe.tests.make('Custom Field', [
{dt: 'ToDo'},
{fieldtype: 'HTML'},
{label: random_name + "_template"},
{options: '<h3> Test {%= doc.status %} </h3>'}
]);
},
() => 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 === '<h3> Test </h3>');
},
() => {
const control = $(`.frappe-control[data-fieldname="${random_name}_template"]`)[0];
return assert.ok(control.innerHTML === '<h3> Test Open </h3>');
},
() => frappe.tests.set_control("status", "Closed"),
() => frappe.timeout(1),
() => {
const control = $(`.frappe-control[data-fieldname="${random_name}_template"]`)[0];
return assert.ok(control.innerHTML === '<h3> Test Closed </h3>');
},
() => done()
]);
});

View file

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

View file

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

View file

@ -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("<a href='{file_url}' target='_blank'>{file_name}</a>{icon}".format(**{
"icon": ' <i class="fa fa-lock text-warning"></i>' 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": ' <i class="fa fa-lock text-warning"></i>' \
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 '<img src="{file_url}"'.format(file_url=file_url)
if content:
content = re.sub('<img[^>]*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 '<img src="{file_url}"'.format(file_url=file_url)
if content:
content = re.sub('<img[^>]*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 "")

View file

@ -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.");
}
}
})

View file

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

View file

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

View file

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

View file

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