@@ -214,6 +214,8 @@ def set_user(username): | |||
local.session.user = username | |||
local.session.sid = username | |||
local.cache = {} | |||
local.form_dict = _dict() | |||
local.jenv = None | |||
local.session.data = {} | |||
local.user = User(username) | |||
local.role_permissions = {} | |||
@@ -10,7 +10,8 @@ frappe.upload = { | |||
+ __('Upload a file') + '</a> | <a class="action-link" href="#"><i class="icon-link"></i> ' | |||
+ __('Attach as web link') + '</a></p>\ | |||
<div class="action-attach-input">\ | |||
<input class="alert alert-info" style="padding: 7px; margin: 7px 0px;" type="file" name="filedata" />\ | |||
<input class="alert alert-info" style="padding: 7px; margin: 7px 0px;" \ | |||
type="file" name="filedata" />\ | |||
</div>\ | |||
<div class="action-link-input" style="display: none; margin-top: 7px;">\ | |||
<input class="form-control" style="max-width: 300px;" type="text" name="file_url" />\ | |||
@@ -5,11 +5,19 @@ | |||
{% endblock %} | |||
{% block content %} | |||
{% if login_required and user=="Guest" %} | |||
{% if introduction_text %} | |||
<p class="lead">{{ introduction_text }}</p> | |||
<hr> | |||
{% endif %} | |||
{% if login_required and frappe.user=="Guest" %} | |||
<div class="alert alert-info"> | |||
{{ _("Please login to create a new {0}").format(_(doc_type)) }} | |||
</div> | |||
<p> | |||
<a href="/login?redirect-to=/{{ pathname }}" class="btn btn-primary"> | |||
{{ _("Login") }} | |||
</a> | |||
</p> | |||
{% elif (login_required and doc_name and not params.name) %} | |||
<div class="alert alert-info"> | |||
{{ _("{0} already exists").format(_(doc_type)) }} | |||
@@ -38,6 +46,19 @@ | |||
{% endfor %} | |||
</div> | |||
{% else %} | |||
{%- macro properties(field) %}name="{{ field.fieldname }}" id="{{ field.fieldname }}" {% if field.placeholder %}placeholder="{{ field.placeholder }}"{% endif %} data-label="{{ field.label }}" data-fieldtype="{{ field.fieldtype }}" {{ field.reqd and "required" or "" }} {{ field.read_only and "disabled" or "" }}{% endmacro -%} | |||
{%- macro value(field) -%}{% if doc %}{{ doc.get(field.fieldname) or "" }}{% else %}{{ getCookie(field.options) or "" }}{% endif %}{%- endmacro -%} | |||
{%- macro help(field) -%} | |||
{% if field.description -%} | |||
<span class="help-block">{{ field.description }}</span> | |||
{%- endif -%} | |||
{%- endmacro %} | |||
{% macro label(field) %}<label for="{{ field.fieldname }}" class="col-sm-3 control-label">{{ field.label }}</label>{% endmacro %} | |||
<div class="form-message alert alert-info hide"></div> | |||
<form class="form-horizontal" role="form" | |||
data-web-form="{{ name }}"> | |||
@@ -47,51 +68,60 @@ | |||
<input type="hidden" name="name" value="{{ params.name }}"> | |||
{%- endif %} | |||
{% for field in web_form_fields %} | |||
{% if field.fieldtype=="Data" %} | |||
{% if field.hidden %} | |||
<input type="hidden" | |||
name="{{ field.fieldname }}" value="{{ field.default }}"> | |||
{% elif field.fieldtype in ("Data", "Date", "Datetime") %} | |||
<div class="form-group"> | |||
<label for="{{ field.fieldname }}" class="col-sm-3 control-label"> | |||
{{ field.label }}</label> | |||
{{ label(field) }} | |||
<div class="col-sm-9"> | |||
<input type="text" class="form-control" name="{{ field.fieldname }}" | |||
id="{{ field.fieldname }}" placeholder="{{ field.placeholder or '' }}" | |||
{{ field.reqd and "required" or "" }} data-label="{{ field.label }}" | |||
value="{{ doc and doc.get(field.fieldname) or '' }}"> | |||
{% if field.description -%} | |||
<span class="help-block">{{ field.description }}</span> | |||
{%- endif %} | |||
<input type="text" class="form-control" {{ properties(field) }} | |||
value="{{ value(field) }}"> | |||
{{ help(field) }} | |||
</div> | |||
</div> | |||
{% elif field.fieldtype=="Select" %} | |||
<div class="form-group"> | |||
<label for="{{ field.fieldname }}" class="col-sm-3 control-label"> | |||
{{ field.label }}</label> | |||
{{ label(field) }} | |||
<div class="col-sm-9"> | |||
<select class="form-control" name="{{ field.fieldname }}" | |||
data-label="{{ field.label }}" | |||
id="{{ field.fieldname }}" {{ field.reqd and "required" or "" }}> | |||
<select class="form-control" {{ properties(field) }}> | |||
{% for option in field.options.split("\n") %} | |||
<option value="{{ option }}" | |||
"{{ doc and doc.get(field.fieldname)==option and 'selected' or '' }}"> | |||
"value(field)==option and 'selected' or '' }}"> | |||
{{ option }}</option> | |||
{% endfor %} | |||
</select> | |||
{% if field.description -%} | |||
<span class="help-block">{{ field.description }}</span> | |||
{%- endif %} | |||
{{ help(field) }} | |||
</div> | |||
</div> | |||
{% elif field.fieldtype=="Text" %} | |||
<div class="form-group"> | |||
<label for="{{ field.fieldname }}" class="col-sm-3 control-label"> | |||
{{ field.label }}</label> | |||
{{ label(field) }} | |||
<div class="col-sm-9"> | |||
<textarea class="form-control" style="height: 100px;" | |||
{{ properties(field) }}>{{ value(field) }}</textarea> | |||
{{ help(field) }} | |||
</div> | |||
</div> | |||
{% elif field.fieldtype=="Attach" %} | |||
<div class="form-group"> | |||
{{ label(field) }} | |||
<div class="col-sm-9"> | |||
<textarea class="form-control" name="{{ field.fieldname }}" | |||
id="{{ field.fieldname }}" style="height: 100px;" | |||
data-label="{{ field.label }}" | |||
{{ field.reqd and "required" or "" }}>{{ doc and doc.get(field.fieldname) or '' }}</textarea> | |||
{% if field.description -%} | |||
<span class="help-block">{{ field.description }}</span> | |||
{% if value(field) -%} | |||
<p> | |||
<i class="icon-paperclip"></i> | |||
<a href="{{ doc.get(field.fieldname) }}" target="blank"> | |||
{{ doc.get(field.fieldname) }} | |||
</a> | |||
<br><button class="btn btn-small btn-default | |||
change-attach" style="margin-top: 5px;">Change</button> | |||
</p> | |||
{%- endif %} | |||
<p class="{{ value(field) and 'hide' or '' }} attach-input-wrap"> | |||
<input type="file" style="margin-top: 7px;" | |||
{{ properties(field) }}> | |||
</p> | |||
{{ help(field) }} | |||
</div> | |||
</div> | |||
{% elif field.fieldtype=="Check" %} | |||
@@ -104,6 +134,7 @@ | |||
"{{ doc and doc.get(field.fieldname) and 'checked' or '' }}"> | |||
{{ field.label }} | |||
</label> | |||
{{ help(field) }} | |||
</div> | |||
</div> | |||
</div> | |||
@@ -116,24 +147,85 @@ | |||
</div> | |||
</div> | |||
</form> | |||
{% if allow_comments -%} | |||
<div class="row"> | |||
<div class="col-sm-offset-3 col-sm-9"> | |||
<hr> | |||
<h3>{{ _("Comments") }}</h3> | |||
{% include 'templates/includes/comments.html' %} | |||
</div> | |||
</div> | |||
{%- endif %} | |||
{% endif %} | |||
{% endblock %} | |||
{% block script %} | |||
<script> | |||
frappe.ready(function() { | |||
window.file_reading = false; | |||
var $form = $("form[data-web-form='{{ name }}']"); | |||
// read file attachment | |||
$form.on("change", "[type='file']", function() { | |||
var $input = $(this); | |||
if($input.attr("type")==="file") { | |||
var input = $input.get(0); | |||
var reader = new FileReader(); | |||
input.filedata = null; | |||
if(input.files.length) { | |||
file = input.files[0]; | |||
window.file_reading = true; | |||
reader.onload = function(e) { | |||
input.filedata = { | |||
"__file_attachment": 1, | |||
"filename": file.name, | |||
"dataurl": reader.result | |||
}; | |||
window.file_reading = false; | |||
} | |||
reader.readAsDataURL(file); | |||
} | |||
} | |||
}); | |||
// change attach | |||
$form.on("click", ".change-attach", function() { | |||
$(this).parent().addClass("hide") | |||
.parent().find(".attach-input-wrap").removeClass("hide"); | |||
return false; | |||
}); | |||
$("form[data-web-form='{{ name }}']").on("submit", function() { | |||
var args = {}; | |||
if(window.saving) | |||
return; | |||
window.saving = true; | |||
if(window.file_reading) { | |||
window.saving = false; | |||
frappe.msgprint("Reading file, please retry."); | |||
return; | |||
} | |||
$form.find("[name]").each(function() { | |||
var $input = $(this), val = $input.val(); | |||
var $input = $(this); | |||
if($input.attr("type")==="file") { | |||
var val = $input.get(0).filedata; | |||
} else { | |||
var val = $input.val(); | |||
} | |||
if($input.prop("required") && val===undefined) { | |||
frappe.msgprint($) | |||
frappe.msgprint(__("{0} is required", | |||
$input.attr("data-label"))); | |||
window.saving = false; | |||
throw "mandatory missing"; | |||
} | |||
args[$(this).attr("name")] = $(this).val(); | |||
args[$input.attr("name")] = val; | |||
}); | |||
frappe.call({ | |||
@@ -154,10 +246,42 @@ frappe.ready(function() { | |||
}, 1000); | |||
{%- endif %}; | |||
} | |||
window.saving = false; | |||
} | |||
}) | |||
}); | |||
return false; | |||
}); | |||
// import date picker / timepicker if required | |||
{% if "Date" in types %} | |||
frappe.require("assets/frappe/js/lib/jquery/jquery.ui.min.js"); | |||
frappe.require("assets/frappe/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css"); | |||
$form.find("[data-fieldtype='Date']").datepicker({ | |||
altFormat:'yy-mm-dd', | |||
changeYear: true, | |||
yearRange: "-70Y:+10Y", | |||
dateFormat: "{{ frappe.date_format.replace('yyyy', 'yy') }}", | |||
}) | |||
{% endif %} | |||
{% if "Datetime" in types %} | |||
frappe.require("assets/frappe/js/lib/jquery/jquery.ui.slider.min.js"); | |||
frappe.require("assets/frappe/js/lib/jquery/jquery.ui.sliderAccess.js"); | |||
frappe.require("assets/frappe/js/lib/jquery/jquery.ui.timepicker-addon.css"); | |||
frappe.require("assets/frappe/js/lib/jquery/jquery.ui.timepicker-addon.js"); | |||
$form.find("[data-fieldtype='Date']").datetimepicker({ | |||
altFormat:'yy-mm-dd', | |||
changeYear: true, | |||
yearRange: "-70Y:+10Y", | |||
dateFormat: "{{ frappe.date_format.replace('yyyy', 'yy') }}", | |||
}) | |||
{% endif %} | |||
{% for f in web_form_fields %} | |||
{% endfor %} | |||
}); | |||
</script> | |||
{% endblock %} |
@@ -100,10 +100,13 @@ login.login_handlers = (function() { | |||
var login_handlers = { | |||
200: function(data) { | |||
if(data.message=="Logged In") { | |||
window.location.href = "desk"; | |||
window.location.href = get_url_arg("redirect-to") || "/desk"; | |||
} else if(data.message=="No App") { | |||
if(localStorage) { | |||
var last_visited = localStorage.getItem("last_visited") || "/index"; | |||
var last_visited = | |||
localStorage.getItem("last_visited") | |||
|| get_url_arg("redirect-to") | |||
|| "/index"; | |||
localStorage.removeItem("last_visited"); | |||
window.location.href = last_visited; | |||
} else { | |||
@@ -1,7 +1,6 @@ | |||
{% block title %}{{ title }}{% endblock %} | |||
{% block header %}<span {% if success!=None %}class="text-{{ 'success' if success else 'danger'}}"{% endif %}> | |||
{{ title }}</span>{% endblock %} | |||
{% block header %}{{ title }}{% endblock %} | |||
{% block content %} | |||
<div class="message-content"> | |||
@@ -107,6 +107,9 @@ def save_file(fname, content, dt, dn, decode=False): | |||
if decode: | |||
if isinstance(content, unicode): | |||
content = content.encode("utf-8") | |||
if "," in content: | |||
content = content.split(",")[1] | |||
content = base64.b64decode(content) | |||
file_size = check_max_file_size(content) | |||
@@ -180,6 +183,18 @@ def remove_all(dt, dn): | |||
except Exception, e: | |||
if e.args[0]!=1054: raise # (temp till for patched) | |||
def remove_file_by_url(file_url, doctype=None, name=None): | |||
if doctype and name: | |||
fid = frappe.db.get_value("File Data", {"file_url": file_url, | |||
"attached_to_doctype": doctype, "attached_to_name": name}) | |||
else: | |||
fid = frappe.db.get_value("File Data", {"file_url": file_url}) | |||
print file_url, doctype, name, fid | |||
if fid: | |||
return remove_file(fid) | |||
def remove_file(fid, attached_to_doctype=None, attached_to_name=None): | |||
"""Remove file and File Data entry""" | |||
file_name = None | |||
@@ -52,7 +52,8 @@ def get_allowed_functions_for_jenv(): | |||
"get_doc": frappe.get_doc, | |||
"get_list": frappe.get_list, | |||
"utils": datautils, | |||
"user": frappe.session.user | |||
"user": frappe.session.user, | |||
"date_format": frappe.db.get_default("date_format") or "yyyy-mm-dd" | |||
}, | |||
"get_visible_columns": \ | |||
frappe.get_attr("frappe.templates.pages.print.get_visible_columns"), | |||
@@ -1,6 +1,48 @@ | |||
[ | |||
{ | |||
"doctype": "Web Form", | |||
"name": "_Test Web Form 1" | |||
} | |||
] | |||
[{ | |||
"allow_comments": 0, | |||
"allow_edit": 1, | |||
"allow_multiple": 1, | |||
"doc_type": "Event", | |||
"docstatus": 0, | |||
"doctype": "Web Form", | |||
"introduction_text": "Add events", | |||
"login_required": 1, | |||
"name": "manage-events", | |||
"owner": "Administrator", | |||
"page_name": "manage-events", | |||
"published": 1, | |||
"success_url": "/manage-events", | |||
"title": "Manage Events", | |||
"web_form_fields": [ | |||
{ | |||
"doctype": "Web Form Field", | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"idx": 1, | |||
"label": "Title", | |||
"read_only": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"doctype": "Web Form Field", | |||
"fieldname": "starts_on", | |||
"fieldtype": "Date", | |||
"hidden": 0, | |||
"idx": 2, | |||
"label": "Event Date", | |||
"read_only": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"doctype": "Web Form Field", | |||
"fieldname": "description", | |||
"fieldtype": "Text", | |||
"hidden": 0, | |||
"idx": 3, | |||
"label": "Description", | |||
"read_only": 0, | |||
"reqd": 0 | |||
} | |||
] | |||
}] |
@@ -4,7 +4,61 @@ | |||
import frappe | |||
import unittest | |||
from frappe.website.render import build_page | |||
from frappe.website.doctype.web_form.web_form import accept | |||
test_records = frappe.get_test_records('Web Form') | |||
class TestWebForm(unittest.TestCase): | |||
pass | |||
def setUp(self): | |||
frappe.conf.disable_website_cache = True | |||
def tearDown(self): | |||
frappe.conf.disable_website_cache = False | |||
def test_basic(self): | |||
frappe.set_user("Guest") | |||
html = build_page("manage-events") | |||
self.assertTrue("Please login to create a new Event" in html) | |||
def test_logged_in(self): | |||
frappe.set_user("Administrator") | |||
html = build_page("manage-events") | |||
self.assertFalse("Please login to create a new Event" in html) | |||
self.assertTrue('"/manage-events?new=1"' in html) | |||
def test_new(self): | |||
frappe.set_user("Administrator") | |||
frappe.local.form_dict.new = 1 | |||
html = build_page("manage-events") | |||
self.assertTrue('name="subject"' in html) | |||
def test_accept(self): | |||
frappe.set_user("Administrator") | |||
frappe.form_dict.web_form = "manage-events" | |||
frappe.form_dict.doctype = "Event" | |||
frappe.form_dict.subject = "_Test Event Web Form" | |||
frappe.form_dict.description = "_Test Event Description" | |||
frappe.form_dict.starts_on = "2014-09-09" | |||
accept() | |||
self.event_name = frappe.db.get_value("Event", | |||
{"subject": "_Test Event Web Form"}) | |||
self.assertTrue(self.event_name) | |||
def test_edit(self): | |||
self.test_accept() | |||
frappe.form_dict.web_form = "manage-events" | |||
frappe.form_dict.doctype = "Event" | |||
frappe.form_dict.name = self.event_name | |||
frappe.form_dict.subject = "_Test Event Web Form" | |||
frappe.form_dict.description = "_Test Event Description 1" | |||
frappe.form_dict.starts_on = "2014-09-09" | |||
self.assertNotEquals(frappe.db.get_value("Event", | |||
self.event_name, "description"), frappe.form_dict.description) | |||
accept() | |||
self.assertEquals(frappe.db.get_value("Event", | |||
self.event_name, "description"), frappe.form_dict.description) |
@@ -18,6 +18,7 @@ frappe.web_form = { | |||
frappe.ui.form.on("Web Form", "refresh", function(frm) { | |||
frappe.web_form.set_fieldname_select(frm); | |||
cur_frm.set_intro(""); | |||
if (!frm.doc.__islocal && frm.doc.published) { | |||
cur_frm.set_intro(__("Published on website at: {0}", | |||
[repl('<a href="/%(website_route)s" target="_blank">/%(website_route)s</a>', | |||
@@ -26,6 +27,14 @@ frappe.ui.form.on("Web Form", "refresh", function(frm) { | |||
}); | |||
frappe.ui.form.on("Web Form", "title", function(frm) { | |||
if(frm.doc.__islocal) { | |||
var page_name = frm.doc.title.toLowerCase().replace(/ /g, "-"); | |||
frm.set_value("page_name", page_name); | |||
frm.set_value("success_url", "/" + page_name); | |||
} | |||
}); | |||
frappe.ui.form.on("Web Form", "doc_type", function(frm) { | |||
frappe.web_form.set_fieldname_select(frm); | |||
}); | |||
@@ -177,6 +177,12 @@ | |||
"label": "Fields", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "introduction_text", | |||
"fieldtype": "Text", | |||
"label": "Introduction", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "web_form_fields", | |||
"fieldtype": "Table", | |||
@@ -193,7 +199,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2014-09-02 13:52:53.494298", | |||
"modified": "2014-09-03 14:47:17.181589", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Web Form", | |||
@@ -2,9 +2,10 @@ | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import frappe, json | |||
from frappe.website.website_generator import WebsiteGenerator | |||
from frappe import _ | |||
from frappe.utils.file_manager import save_file, remove_file_by_url | |||
class WebForm(WebsiteGenerator): | |||
template = "templates/generators/web_form.html" | |||
@@ -31,18 +32,35 @@ class WebForm(WebsiteGenerator): | |||
if frappe.form_dict.name: | |||
context.doc = frappe.get_doc(self.doc_type, frappe.form_dict.name) | |||
context.types = [f.fieldtype for f in self.web_form_fields] | |||
return context | |||
@frappe.whitelist(allow_guest=True) | |||
def accept(): | |||
args = frappe.form_dict | |||
files = [] | |||
if args.name: | |||
# update | |||
doc = frappe.get_doc(args.doctype, args.name) | |||
else: | |||
# insert | |||
doc = frappe.new_doc(args.doctype) | |||
# set values | |||
for fieldname, value in args.iteritems(): | |||
if fieldname not in ("web_form", "cmd"): | |||
if value and value.startswith("{"): | |||
try: | |||
filedata = json.loads(value) | |||
if "__file_attachment" in filedata: | |||
files.append((fieldname, filedata)) | |||
continue | |||
except ValueError: | |||
pass | |||
doc.set(fieldname, value) | |||
if args.name: | |||
@@ -59,3 +77,21 @@ def accept(): | |||
frappe.throw(_("You must login to submit this form")) | |||
doc.insert(ignore_permissions = True) | |||
# add files | |||
if files: | |||
for f in files: | |||
fieldname, filedata = f | |||
# remove earlier attachmed file (if exists) | |||
if doc.get(fieldname): | |||
remove_file_by_url(doc.get(fieldname), doc.doctype, doc.name) | |||
# save new file | |||
filedoc = save_file(filedata["filename"], filedata["dataurl"], | |||
doc.doctype, doc.name, decode=True) | |||
# update values | |||
doc.set(fieldname, filedoc.file_url) | |||
doc.save() |
@@ -36,7 +36,7 @@ | |||
"in_list_view": 1, | |||
"label": "Fieldtype", | |||
"no_copy": 0, | |||
"options": "Attach\nCheck\nData\nHTML\nSelect\nText", | |||
"options": "Attach\nCheck\nData\nDate\nDatetime\nHTML\nSelect\nText", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -69,6 +69,18 @@ | |||
"label": "Mandatory", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "read_only", | |||
"fieldtype": "Check", | |||
"label": "Read Only", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "hidden", | |||
"fieldtype": "Check", | |||
"label": "Hidden", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "column_break_4", | |||
@@ -182,7 +194,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 1, | |||
"modified": "2014-09-01 14:14:40.425829", | |||
"modified": "2014-09-03 15:47:51.643284", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Web Form Field", | |||
@@ -3,6 +3,7 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe import _ | |||
from frappe.utils import cstr | |||
import mimetypes, json | |||
from werkzeug.wrappers import Response | |||
@@ -36,10 +37,10 @@ def render(path, http_status_code=None): | |||
try: | |||
data = render_page(path) | |||
except frappe.PermissionError, e: | |||
data, http_status_code = render_403(e) | |||
data, http_status_code = render_403(e, path) | |||
except frappe.PermissionError, e: | |||
data, http_status_code = render_403(e) | |||
data, http_status_code = render_403(e, path) | |||
except Exception: | |||
path = "error" | |||
@@ -49,10 +50,13 @@ def render(path, http_status_code=None): | |||
return build_response(path, data, http_status_code or 200) | |||
def render_403(e): | |||
def render_403(e, pathname): | |||
path = "message" | |||
frappe.local.message = "<p><strong>{error}</strong></p><p>Did you log out?</p>".format(error=cstr(e)) | |||
frappe.local.message_title = "Not Permitted" | |||
frappe.local.message = """<p><strong>{error}</strong></p> | |||
<p> | |||
<a href="/login?redirect-to=/{pathname}" class="btn btn-primary>{login}</a> | |||
</p>""".format(error=cstr(e), login=_("Login"), pathname=pathname) | |||
frappe.local.message_title = _("Not Permitted") | |||
return render_page(path), e.http_status_code | |||
def get_doctype_from_path(path): | |||