@@ -0,0 +1,8 @@ | |||
.DS_Store | |||
*.pyc | |||
*.egg-info | |||
*.swp | |||
tags | |||
.idea | |||
/.idea/ | |||
%SystemDrive%/ProgramData/Microsoft/Windows/Caches/cversions.2.db |
@@ -0,0 +1,18 @@ | |||
include MANIFEST.in | |||
include requirements.txt | |||
include *.json | |||
include *.md | |||
include *.py | |||
include *.txt | |||
recursive-include influxframework_helper *.css | |||
recursive-include influxframework_helper *.csv | |||
recursive-include influxframework_helper *.html | |||
recursive-include influxframework_helper *.ico | |||
recursive-include influxframework_helper *.js | |||
recursive-include influxframework_helper *.json | |||
recursive-include influxframework_helper *.md | |||
recursive-include influxframework_helper *.png | |||
recursive-include influxframework_helper *.py | |||
recursive-include influxframework_helper *.svg | |||
recursive-include influxframework_helper *.txt | |||
recursive-exclude influxframework_helper *.pyc |
@@ -0,0 +1,49 @@ | |||
<div align = "center"> | |||
<img src = "https://influxframeworkcloud.com/files/FHa94573.png" height = "128"> | |||
<h2>InfluxFramework Helper</h2> | |||
</div> | |||
___ | |||
> ### InfluxFramework Helper includes the following functionalities: | |||
1. Customized Desk Form based on InfluxFramework Web Form functionalities. | |||
2. Integrated NumPad to manage inputs. | |||
3. Automatic installation of the desk forms created in the different applications. | |||
4. Dynamic rendering of desk forms. | |||
5. Compatible with Dark Theme. | |||
___ | |||
### InfluxFramework Helper requires | |||
1. [InfluxFramework](https://github.com/quantumbitcore/influxframework_helper.git) | |||
___ | |||
### How to Install | |||
#### Self Host: | |||
1. `bench get-app https://github.com/quantumbitcore/influxframework_helper.git` | |||
2. `bench setup requirements` | |||
3. `bench build --app influxframework_helper` | |||
4. `bench restart` | |||
5. `bench --site [site.name] install-app influxframework_helper` | |||
6. `bench --site [site.name] migrate` | |||
#### InfluxFramework Cloud: | |||
>Available in your hosting on InfluxFramework [here](https://influxframeworkcloud.com/marketplace/apps/influxframework_helper) | |||
___ | |||
### How to Use | |||
> See the documentation [here](https://github.com/quantumbitcore/influxframework_helper/wiki) | |||
___ | |||
### Compatibility | |||
> V13, V14 | |||
___ | |||
InfluxFramework Helper is based on [InfluxFramework](https://github.com/influxframework/influxframework). | |||
___ | |||
### License | |||
> GNU / General Public License (see [license.txt](license.txt)) | |||
> The InfluxFramework Helper code is licensed under the GNU General Public License (v3). |
@@ -0,0 +1,5 @@ | |||
# -*- coding: utf-8 -*- | |||
from __future__ import unicode_literals | |||
__version__ = '0.1.9' | |||
@@ -0,0 +1,73 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2021, Quantum Bit Core and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import influxframework | |||
import json | |||
import hashlib | |||
@influxframework.whitelist() | |||
def call(model, name, method, args=None): | |||
doc = influxframework.get_doc(model, name) | |||
if args is not None: | |||
_args = json.loads(args) | |||
# args = [_args[arg] for arg in _args] | |||
kwargs = {arg: _args[arg] for arg in _args} | |||
return getattr(doc, method)(**kwargs) | |||
# return doc.run_method(method, **kwargs) | |||
else: | |||
return getattr(doc, method) | |||
def encrypt(data, method): | |||
if not isinstance(data, bytes): | |||
data = data.encode('utf-8') | |||
if method == 'md5': | |||
return hashlib.md5(data).hexdigest() | |||
if method == 'sha1': | |||
return hashlib.sha1(data).hexdigest() | |||
if method == 'sha224': | |||
return hashlib.sha3_224(data).hexdigest() | |||
@influxframework.whitelist() | |||
def validate_link(): | |||
"""validate link when updated by user""" | |||
import influxframework | |||
import influxframework.utils | |||
value, options, fetch = influxframework.form_dict.get('value'), influxframework.form_dict.get('options'), influxframework.form_dict.get('fetch') | |||
# no options, don't validate | |||
if not options or options=='null' or options=='undefined': | |||
influxframework.response['message'] = 'Ok' | |||
return | |||
valid_value = influxframework.db.get_all(options, filters=dict(name=value), as_list=1, limit=1) | |||
if valid_value: | |||
valid_value = valid_value[0][0] | |||
# get fetch values | |||
if fetch: | |||
# escape with "`" | |||
fetch = ", ".join(("`{0}`".format(f.strip()) for f in fetch.split(","))) | |||
fetch_value = None | |||
try: | |||
fetch_value = influxframework.db.sql("select %s from `tab%s` where name=%s" | |||
% (fetch, options, '%s'), (value,))[0] | |||
except Exception as e: | |||
error_message = str(e).split("Unknown column '") | |||
fieldname = None if len(error_message)<=1 else error_message[1].split("'")[0] | |||
influxframework.msgprint(_("Wrong fieldname <b>{0}</b> in add_fetch configuration of custom client script").format(fieldname)) | |||
influxframework.errprint(influxframework.get_traceback()) | |||
if fetch_value: | |||
influxframework.response['fetch_values'] = [influxframework.utils.parse_val(c) for c in fetch_value] | |||
influxframework.response['valid_value'] = valid_value | |||
influxframework.response['message'] = 'Ok' | |||
@@ -0,0 +1,14 @@ | |||
# -*- coding: utf-8 -*- | |||
from __future__ import unicode_literals | |||
from influxframework import _ | |||
def get_data(): | |||
return [ | |||
{ | |||
"module_name": "InfluxFramework Helper", | |||
"color": "grey", | |||
"icon": "octicon octicon-file-directory", | |||
"type": "module", | |||
"label": _("InfluxFramework Helper") | |||
} | |||
] |
@@ -0,0 +1,11 @@ | |||
""" | |||
Configuration for docs | |||
""" | |||
# source_link = "https://github.com/[org_name]/influxframework_helper" | |||
# docs_base_url = "https://[org_name].github.io/influxframework_helper" | |||
# headline = "App that does everything" | |||
# sub_heading = "Yes, you got that right the first time, everything" | |||
def get_context(context): | |||
context.brand_html = "InfluxFramework Helper" |
@@ -0,0 +1,193 @@ | |||
# -*- coding: utf-8 -*- | |||
from __future__ import unicode_literals | |||
from . import __version__ as app_version | |||
app_name = "influxframework_helper" | |||
app_title = "InfluxFramework Helper" | |||
app_publisher = "Quantum Bit Core" | |||
app_description = "InfluxFramework Helper" | |||
app_icon = "octicon octicon-file-directory" | |||
app_color = "grey" | |||
app_email = "quantumbitcore.io@gmail.com" | |||
app_license = "MIT" | |||
app_include_css = [ | |||
"/assets/influxframework_helper/css/desk-form.css", | |||
"/assets/influxframework_helper/css/influxframework-helper.css", | |||
"/assets/influxframework_helper/css/num-pad.css", | |||
] | |||
after_migrate = "influxframework_helper.setup.install.after_install" | |||
after_install = "influxframework_helper.setup.install.after_install" | |||
# Includes in <head> | |||
# ------------------ | |||
# include js, css files in header of desk.html | |||
app_include_js = [ | |||
"/assets/influxframework_helper/js/jshtml-class.js", | |||
"/assets/influxframework_helper/js/num-pad-class.js", | |||
"/assets/influxframework_helper/js/desk-modal.js", | |||
"/assets/influxframework_helper/js/influxframework-helper-api.js", | |||
"/assets/influxframework_helper/js/influxframework-form-class.js", | |||
"/assets/influxframework_helper/js/desk-form-class.js" | |||
] | |||
# Includes in <head> | |||
# ------------------ | |||
# include js, css files in header of desk.html | |||
# app_include_css = "/assets/influxframework_helper/css/influxframework_helper.css" | |||
# app_include_js = "/assets/influxframework_helper/js/influxframework_helper.js" | |||
# include js, css files in header of web template | |||
# web_include_css = "/assets/influxframework_helper/css/influxframework_helper.css" | |||
# web_include_js = "/assets/influxframework_helper/js/influxframework_helper.js" | |||
# include custom scss in every website theme (without file extension ".scss") | |||
# website_theme_scss = "influxframework_helper/public/scss/website" | |||
# include js, css files in header of web form | |||
# webform_include_js = {"doctype": "public/js/doctype.js"} | |||
# webform_include_css = {"doctype": "public/css/doctype.css"} | |||
# include js in page | |||
# page_js = {"page" : "public/js/file.js"} | |||
# include js in doctype views | |||
# doctype_js = {"doctype" : "public/js/doctype.js"} | |||
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} | |||
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} | |||
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} | |||
# Home Pages | |||
# ---------- | |||
# application home page (will override Website Settings) | |||
# home_page = "login" | |||
# website user home page (by Role) | |||
# role_home_page = { | |||
# "Role": "home_page" | |||
# } | |||
# Generators | |||
# ---------- | |||
# automatically create page for each record of this doctype | |||
# website_generators = ["Web Page"] | |||
# Installation | |||
# ------------ | |||
# before_install = "influxframework_helper.install.before_install" | |||
# after_install = "influxframework_helper.install.after_install" | |||
# Desk Notifications | |||
# ------------------ | |||
# See influxframework.core.notifications.get_notification_config | |||
# notification_config = "influxframework_helper.notifications.get_notification_config" | |||
# Permissions | |||
# ----------- | |||
# Permissions evaluated in scripted ways | |||
# permission_query_conditions = { | |||
# "Event": "influxframework.desk.doctype.event.event.get_permission_query_conditions", | |||
# } | |||
# | |||
# has_permission = { | |||
# "Event": "influxframework.desk.doctype.event.event.has_permission", | |||
# } | |||
# DocType Class | |||
# --------------- | |||
# Override standard doctype classes | |||
# override_doctype_class = { | |||
# "ToDo": "custom_app.overrides.CustomToDo" | |||
# } | |||
# Document Events | |||
# --------------- | |||
# Hook on document methods and events | |||
# doc_events = { | |||
# "*": { | |||
# "on_update": "method", | |||
# "on_cancel": "method", | |||
# "on_trash": "method" | |||
# } | |||
# } | |||
# Scheduled Tasks | |||
# --------------- | |||
# scheduler_events = { | |||
# "all": [ | |||
# "influxframework_helper.tasks.all" | |||
# ], | |||
# "daily": [ | |||
# "influxframework_helper.tasks.daily" | |||
# ], | |||
# "hourly": [ | |||
# "influxframework_helper.tasks.hourly" | |||
# ], | |||
# "weekly": [ | |||
# "influxframework_helper.tasks.weekly" | |||
# ] | |||
# "monthly": [ | |||
# "influxframework_helper.tasks.monthly" | |||
# ] | |||
# } | |||
# Testing | |||
# ------- | |||
# before_tests = "influxframework_helper.install.before_tests" | |||
# Overriding Methods | |||
# ------------------------------ | |||
# | |||
# override_whitelisted_methods = { | |||
# "influxframework.desk.doctype.event.event.get_events": "influxframework_helper.event.get_events" | |||
# } | |||
# | |||
# each overriding function accepts a `data` argument; | |||
# generated from the base implementation of the doctype dashboard, | |||
# along with any modifications made in other InfluxFramework apps | |||
# override_doctype_dashboards = { | |||
# "Task": "influxframework_helper.task.get_dashboard_data" | |||
# } | |||
# exempt linked doctypes from being automatically cancelled | |||
# | |||
# auto_cancel_exempted_doctypes = ["Auto Repeat"] | |||
# User Data Protection | |||
# -------------------- | |||
user_data_fields = [ | |||
{ | |||
"doctype": "{doctype_1}", | |||
"filter_by": "{filter_by}", | |||
"redact_fields": ["{field_1}", "{field_2}"], | |||
"partial": 1, | |||
}, | |||
{ | |||
"doctype": "{doctype_2}", | |||
"filter_by": "{filter_by}", | |||
"partial": 1, | |||
}, | |||
{ | |||
"doctype": "{doctype_3}", | |||
"strict": False, | |||
}, | |||
{ | |||
"doctype": "{doctype_4}" | |||
} | |||
] | |||
@@ -0,0 +1,116 @@ | |||
// Copyright (c) 2021, Quantum Bit Core and contributors | |||
// For license information, please see license.txt | |||
influxframework.desk_form = { | |||
set_fieldname_select: function(frm) { | |||
return new Promise(resolve => { | |||
var me = this, doc = frm.doc; | |||
if (doc.doc_type) { | |||
influxframework.model.with_doctype(doc.doc_type, function() { | |||
var fields = $.map(influxframework.get_doc("DocType", frm.doc.doc_type).fields, function(d) { | |||
if (influxframework.model.no_value_type.indexOf(d.fieldtype) === -1 || | |||
d.fieldtype === 'Table') { | |||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; | |||
} else { | |||
return null; | |||
} | |||
}); | |||
var currency_fields = $.map(influxframework.get_doc("DocType", frm.doc.doc_type).fields, function(d) { | |||
if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') { | |||
return { label: d.label, value: d.fieldname }; | |||
} else { | |||
return null; | |||
} | |||
}); | |||
frm.fields_dict.desk_form_fields.grid.update_docfield_property( | |||
'fieldname', 'options', fields | |||
); | |||
resolve(); | |||
}); | |||
} | |||
}); | |||
} | |||
}; | |||
influxframework.ui.form.on("Desk Form", { | |||
refresh: function(frm) { | |||
// show is-standard only if developer mode | |||
frm.get_field("is_standard").toggle(influxframework.boot.developer_mode); | |||
influxframework.desk_form.set_fieldname_select(frm); | |||
if (frm.doc.is_standard && !influxframework.boot.developer_mode) { | |||
frm.set_read_only(); | |||
frm.disable_save(); | |||
} | |||
frm.add_custom_button(__('Get Fields'), () => { | |||
let webform_fieldtypes = influxframework.meta.get_field('Desk Form Field', 'fieldtype').options.split('\n'); | |||
let fieldnames = (frm.doc.fields || []).map(d => d.fieldname); | |||
influxframework.model.with_doctype(frm.doc.doc_type, () => { | |||
let meta = influxframework.get_meta(frm.doc.doc_type); | |||
for (let field of meta.fields) { | |||
if (webform_fieldtypes.includes(field.fieldtype) | |||
&& !fieldnames.includes(field.fieldname)) { | |||
frm.add_child('desk_form_fields', { | |||
fieldname: field.fieldname, | |||
label: field.label, | |||
fieldtype: field.fieldtype, | |||
options: field.options, | |||
reqd: field.reqd, | |||
default: field.default, | |||
read_only: field.read_only, | |||
depends_on: field.depends_on, | |||
mandatory_depends_on: field.mandatory_depends_on, | |||
read_only_depends_on: field.read_only_depends_on, | |||
hidden: field.hidden, | |||
description: field.description | |||
}); | |||
} | |||
} | |||
frm.refresh(); | |||
}); | |||
}); | |||
}, | |||
title: function(frm) { | |||
console.log(["set form title"]) | |||
if (frm.doc.__islocal) { | |||
var page_name = frm.doc.title.toLowerCase().replace(/ /g, "-"); | |||
frm.set_value("route", page_name); | |||
frm.set_value("success_url", "/" + page_name); | |||
} | |||
}, | |||
doc_type: function(frm) { | |||
influxframework.desk_form.set_fieldname_select(frm); | |||
} | |||
}); | |||
influxframework.ui.form.on("Desk Form Field", { | |||
fieldtype: function(frm, doctype, name) { | |||
var doc = influxframework.get_doc(doctype, name); | |||
if (['Section Break', 'Column Break', 'Page Break'].includes(doc.fieldtype)) { | |||
doc.fieldname = ''; | |||
frm.refresh_field("desk_form_fields"); | |||
} | |||
}, | |||
fieldname: function(frm, doctype, name) { | |||
var doc = influxframework.get_doc(doctype, name); | |||
var df = $.map(influxframework.get_doc("DocType", frm.doc.doc_type).fields, function(d) { | |||
return doc.fieldname == d.fieldname ? d : null; | |||
})[0]; | |||
doc.label = df.label; | |||
doc.reqd = df.reqd; | |||
doc.options = df.options; | |||
doc.fieldtype = influxframework.meta.get_docfield("Desk Form Field", "fieldtype") | |||
.options.split("\n").indexOf(df.fieldtype) === -1 ? "Data" : df.fieldtype; | |||
doc.description = df.description; | |||
doc["default"] = df["default"]; | |||
} | |||
}); |
@@ -0,0 +1,274 @@ | |||
{ | |||
"actions": [], | |||
"autoname": "field:route", | |||
"creation": "2021-07-15 19:11:24.656019", | |||
"doctype": "DocType", | |||
"document_type": "Document", | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"title", | |||
"route", | |||
"doc_type", | |||
"module", | |||
"column_break_4", | |||
"is_standard", | |||
"published", | |||
"login_required", | |||
"route_to_success_link", | |||
"allow_edit", | |||
"allow_multiple", | |||
"show_in_grid", | |||
"allow_delete", | |||
"allow_print", | |||
"print_format", | |||
"allow_comments", | |||
"show_attachments", | |||
"allow_incomplete", | |||
"introduction", | |||
"introduction_text", | |||
"fields", | |||
"desk_form_fields", | |||
"max_attachment_size", | |||
"client_script_section", | |||
"client_script", | |||
"custom_css_section", | |||
"custom_css", | |||
"actions", | |||
"button_label", | |||
"success_message", | |||
"success_url", | |||
"advanced", | |||
"breadcrumbs" | |||
], | |||
"fields": [ | |||
{ | |||
"fieldname": "title", | |||
"fieldtype": "Data", | |||
"label": "Title", | |||
"no_copy": 1, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "route", | |||
"fieldtype": "Data", | |||
"label": "Route", | |||
"unique": 1 | |||
}, | |||
{ | |||
"fieldname": "doc_type", | |||
"fieldtype": "Link", | |||
"in_list_view": 1, | |||
"in_standard_filter": 1, | |||
"label": "Select DocType", | |||
"options": "DocType", | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "module", | |||
"fieldtype": "Link", | |||
"label": "Module", | |||
"options": "Module Def" | |||
}, | |||
{ | |||
"fieldname": "column_break_4", | |||
"fieldtype": "Column Break" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "is_standard", | |||
"fieldtype": "Check", | |||
"label": "Is Standard" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "published", | |||
"fieldtype": "Check", | |||
"label": "Published" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "login_required", | |||
"fieldtype": "Check", | |||
"label": "Login Required" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "eval:doc.login_required", | |||
"fieldname": "route_to_success_link", | |||
"fieldtype": "Check", | |||
"label": "Route to Success Link" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "login_required", | |||
"fieldname": "allow_edit", | |||
"fieldtype": "Check", | |||
"label": "Allow Edit" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "login_required", | |||
"fieldname": "allow_multiple", | |||
"fieldtype": "Check", | |||
"label": "Allow Multiple" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "allow_multiple", | |||
"fieldname": "show_in_grid", | |||
"fieldtype": "Check", | |||
"label": "Show as Grid" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "allow_multiple", | |||
"fieldname": "allow_delete", | |||
"fieldtype": "Check", | |||
"label": "Allow Delete" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "allow_print", | |||
"fieldtype": "Check", | |||
"label": "Allow Print" | |||
}, | |||
{ | |||
"depends_on": "allow_print", | |||
"fieldname": "print_format", | |||
"fieldtype": "Link", | |||
"label": "Print Format", | |||
"options": "Print Format" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "login_required", | |||
"fieldname": "allow_comments", | |||
"fieldtype": "Check", | |||
"label": "Allow Comments" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "login_required", | |||
"fieldname": "show_attachments", | |||
"fieldtype": "Check", | |||
"label": "Show Attachments" | |||
}, | |||
{ | |||
"default": "0", | |||
"description": "Allow saving if mandatory fields are not filled", | |||
"fieldname": "allow_incomplete", | |||
"fieldtype": "Check", | |||
"label": "Allow Incomplete Forms" | |||
}, | |||
{ | |||
"collapsible": 1, | |||
"fieldname": "introduction", | |||
"fieldtype": "Section Break", | |||
"label": "Introduction" | |||
}, | |||
{ | |||
"fieldname": "introduction_text", | |||
"fieldtype": "Text Editor", | |||
"label": "Introduction" | |||
}, | |||
{ | |||
"fieldname": "fields", | |||
"fieldtype": "Section Break", | |||
"label": "Fields" | |||
}, | |||
{ | |||
"fieldname": "desk_form_fields", | |||
"fieldtype": "Table", | |||
"label": "Desk Form Fields", | |||
"options": "Desk Form Field" | |||
}, | |||
{ | |||
"fieldname": "max_attachment_size", | |||
"fieldtype": "Int", | |||
"label": "Max Attachment Size (in MB)" | |||
}, | |||
{ | |||
"collapsible": 1, | |||
"fieldname": "client_script_section", | |||
"fieldtype": "Section Break", | |||
"label": "Client Script" | |||
}, | |||
{ | |||
"description": "For help see <a href=\"https://influxframework.com/docs/user/en/guides/portal-development/web-forms\" target=\"_blank\">Client Script API and Examples</a>", | |||
"fieldname": "client_script", | |||
"fieldtype": "Code", | |||
"label": "Client Script" | |||
}, | |||
{ | |||
"collapsible": 1, | |||
"fieldname": "custom_css_section", | |||
"fieldtype": "Section Break", | |||
"label": "Custom CSS" | |||
}, | |||
{ | |||
"fieldname": "custom_css", | |||
"fieldtype": "Code", | |||
"label": "Custom CSS", | |||
"options": "CSS" | |||
}, | |||
{ | |||
"collapsible": 1, | |||
"fieldname": "actions", | |||
"fieldtype": "Section Break", | |||
"label": "Actions" | |||
}, | |||
{ | |||
"default": "Save", | |||
"fieldname": "button_label", | |||
"fieldtype": "Data", | |||
"label": "Button Label" | |||
}, | |||
{ | |||
"description": "Message to be displayed on successful completion (only for Guest users)", | |||
"fieldname": "success_message", | |||
"fieldtype": "Text", | |||
"label": "Success Message" | |||
}, | |||
{ | |||
"description": "Go to this URL after completing the form (only for Guest users)", | |||
"fieldname": "success_url", | |||
"fieldtype": "Data", | |||
"label": "Success URL" | |||
}, | |||
{ | |||
"collapsible": 1, | |||
"fieldname": "advanced", | |||
"fieldtype": "Section Break", | |||
"label": "Advanced" | |||
}, | |||
{ | |||
"description": "List as [{\"label\": _(\"Jobs\"), \"route\":\"jobs\"}]", | |||
"fieldname": "breadcrumbs", | |||
"fieldtype": "Code", | |||
"label": "Breadcrumbs" | |||
} | |||
], | |||
"icon": "icon-edit", | |||
"is_published_field": "published", | |||
"links": [], | |||
"modified": "2022-09-13 17:06:33.102087", | |||
"modified_by": "Administrator", | |||
"module": "InfluxFramework Helper", | |||
"name": "Desk Form", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"write": 1 | |||
} | |||
], | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"title_field": "title", | |||
"track_changes": 1 | |||
} |
@@ -0,0 +1,634 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2021, Quantum Bit Core and contributors | |||
# For license information, please see license.txt | |||
# Copyright (c) 2015, InfluxFramework Technologies Pvt. Ltd. and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import json | |||
import os | |||
from six import iteritems | |||
from six.moves.urllib.parse import urlencode | |||
import influxframework | |||
from influxframework import _, scrub | |||
from influxframework.core.doctype.file.file import get_max_file_size, remove_file_by_url | |||
from influxframework.custom.doctype.customize_form.customize_form import docfield_properties | |||
from influxframework.desk.form.meta import get_code_files_via_hooks | |||
from influxframework.modules.utils import export_module_json, get_doc_module | |||
from influxframework.utils import cstr | |||
from influxframework.website.utils import get_comment_list | |||
from influxframework.model.document import Document | |||
class DeskForm(Document): | |||
def updateJsonFile(self): | |||
app = influxframework.db.get_value('Module Def', self.module, 'app_name') | |||
path = os.path.abspath(os.path.dirname(__file__)) | |||
path = os.path.join(path.split( | |||
"apps")[0], "apps", app, app, app, 'desk_form') | |||
file_name = self.name.replace('-', '_') | |||
file_path = os.path.join(path, file_name, file_name + '.json') | |||
jsonFile = open(file_path, "r") # Open the JSON file for reading | |||
data = json.load(jsonFile) # Read the JSON into the buffer | |||
jsonFile.close() # Close the JSON file | |||
## Working with buffered content | |||
tmp = data | |||
tmp["docstatus"] = 1 | |||
## Save our changes to JSON file | |||
jsonFile = open(file_path, "w+") | |||
jsonFile.write(json.dumps(tmp)) | |||
jsonFile.close() | |||
def after_delete(self): | |||
self.updateJsonFile() | |||
def onload(self): | |||
#super(DeskForm, self).onload() | |||
if self.is_standard: | |||
self.use_meta_fields() | |||
def validate(self): | |||
#super(DeskForm, self).validate() | |||
if not self.module: | |||
self.module = influxframework.db.get_value('DocType', self.doc_type, 'module') | |||
if not influxframework.flags.in_import: | |||
self.validate_fields() | |||
def validate_fields(self): | |||
'''Validate all fields are present''' | |||
from influxframework.model import no_value_fields | |||
missing = [] | |||
meta = influxframework.get_meta(self.doc_type) | |||
for df in self.desk_form_fields: | |||
if not df.fieldname and df.label: | |||
df.fieldname = scrub(df.label) | |||
if df.fieldname and (df.fieldtype not in no_value_fields and not meta.has_field(df.fieldname) and not df.extra_field): | |||
missing.append(df.fieldname) | |||
if missing: | |||
influxframework.throw(_('Following fields are missing:') + ' in DeskForm ' + self.title + '<br>' + '<br>'.join(missing)) | |||
def reset_field_parent(self): | |||
'''Convert link fields to select with names as options''' | |||
for df in self.desk_form_fields: | |||
df.parent = self.doc_type | |||
def use_meta_fields(self): | |||
'''Override default properties for standard web forms''' | |||
meta = influxframework.get_meta(self.doc_type) | |||
for df in self.desk_form_fields: | |||
meta_df = meta.get_field(df.fieldname) | |||
if not meta_df: | |||
continue | |||
for prop in docfield_properties: | |||
if df.fieldtype==meta_df.fieldtype and prop not in ("idx", | |||
"reqd", "default", "description", "default", "options", | |||
"hidden", "read_only", "label"): | |||
df.set(prop, meta_df.get(prop)) | |||
# TODO translate options of Select fields like Country | |||
# export | |||
def on_update(self): | |||
""" | |||
Writes the .txt for this page and if write_content is checked, | |||
it will write out a .html file | |||
""" | |||
path = export_module_json(self, self.is_standard, self.module) | |||
if path: | |||
# js | |||
if not os.path.exists(path + '.js'): | |||
with open(path + '.js', 'w') as f: | |||
f.write("""influxframework.ready(function() { | |||
// bind events here | |||
})""") | |||
# py | |||
if not os.path.exists(path + '.py'): | |||
with open(path + '.py', 'w') as f: | |||
f.write("""from __future__ import unicode_literals | |||
import influxframework | |||
def get_context(context): | |||
# do your desk here | |||
pass | |||
""") | |||
def get_context(self, context): | |||
'''Build context to render the `desk_form.html` template''' | |||
self.set_desk_form_module() | |||
context._login_required = False | |||
if self.login_required and influxframework.session.user == "Guest": | |||
context._login_required = True | |||
doc, delimeter = make_route_string(influxframework.form_dict) | |||
context.doc = doc | |||
context.delimeter = delimeter | |||
# check permissions | |||
if influxframework.session.user == "Guest" and influxframework.form_dict.name: | |||
influxframework.throw(_("You need to be logged in to access this {0}.").format(self.doc_type), influxframework.PermissionError) | |||
if influxframework.form_dict.name and not has_desk_form_permission(self.doc_type, influxframework.form_dict.name): | |||
influxframework.throw(_("You don't have the permissions to access this document"), influxframework.PermissionError) | |||
self.reset_field_parent() | |||
if self.is_standard: | |||
self.use_meta_fields() | |||
if not context._login_required: | |||
if self.allow_edit: | |||
if self.allow_multiple: | |||
if not influxframework.form_dict.name and not influxframework.form_dict.new: | |||
# list data is queried via JS | |||
context.is_list = True | |||
else: | |||
if influxframework.session.user != 'Guest' and not influxframework.form_dict.name: | |||
influxframework.form_dict.name = influxframework.db.get_value(self.doc_type, {"owner": influxframework.session.user}, "name") | |||
if not influxframework.form_dict.name: | |||
# only a single doc allowed and no existing doc, hence new | |||
influxframework.form_dict.new = 1 | |||
# always render new form if login is not required or doesn't allow editing existing ones | |||
if not self.login_required or not self.allow_edit: | |||
influxframework.form_dict.new = 1 | |||
self.load_document(context) | |||
context.parents = self.get_parents(context) | |||
if self.breadcrumbs: | |||
context.parents = influxframework.safe_eval(self.breadcrumbs, { "_": _ }) | |||
context.has_header = ((influxframework.form_dict.name or influxframework.form_dict.new) | |||
and (influxframework.session.user!="Guest" or not self.login_required)) | |||
if context.success_message: | |||
context.success_message = influxframework.db.escape(context.success_message.replace("\n", | |||
"<br>")).strip("'") | |||
self.add_custom_context_and_script(context) | |||
if not context.max_attachment_size: | |||
context.max_attachment_size = get_max_file_size() / 1024 / 1024 | |||
context.show_in_grid = self.show_in_grid | |||
self.load_translations(context) | |||
def load_translations(self, context): | |||
translated_messages = influxframework.translate.get_dict('doctype', self.doc_type) | |||
# Sr is not added by default, had to be added manually | |||
translated_messages['Sr'] = _('Sr') | |||
context.translated_messages = influxframework.as_json(translated_messages) | |||
def load_document(self, context): | |||
'''Load document `doc` and `layout` properties for template''' | |||
if influxframework.form_dict.name or influxframework.form_dict.new: | |||
context.layout = self.get_layout() | |||
context.parents = [{"route": self.route, "label": _(self.title) }] | |||
if influxframework.form_dict.name: | |||
context.doc = influxframework.get_doc(self.doc_type, influxframework.form_dict.name) | |||
context.title = context.doc.get(context.doc.meta.get_title_field()) | |||
context.doc.add_seen() | |||
context.reference_doctype = context.doc.doctype | |||
context.reference_name = context.doc.name | |||
if self.show_attachments: | |||
context.attachments = influxframework.get_all('File', filters= {"attached_to_name": context.reference_name, "attached_to_doctype": context.reference_doctype, "is_private": 0}, | |||
fields=['file_name','file_url', 'file_size']) | |||
if self.allow_comments: | |||
context.comment_list = get_comment_list(context.doc.doctype, | |||
context.doc.name) | |||
def add_custom_context_and_script(self, context): | |||
'''Update context from module if standard and append script''' | |||
if self.desk_form_module: | |||
new_context = self.desk_form_module.get_context(context) | |||
if new_context: | |||
context.update(new_context) | |||
js_path = os.path.join(os.path.dirname(self.desk_form_module.__file__), scrub(self.name) + '.js') | |||
if os.path.exists(js_path): | |||
script = influxframework.render_template(open(js_path, 'r').read(), context) | |||
for path in get_code_files_via_hooks("webform_include_js", context.doc_type): | |||
custom_js = influxframework.render_template(open(path, 'r').read(), context) | |||
script = "\n\n".join([script, custom_js]) | |||
context.script = script | |||
css_path = os.path.join(os.path.dirname(self.desk_form_module.__file__), scrub(self.name) + '.css') | |||
if os.path.exists(css_path): | |||
style = open(css_path, 'r').read() | |||
for path in get_code_files_via_hooks("webform_include_css", context.doc_type): | |||
custom_css = open(path, 'r').read() | |||
style = "\n\n".join([style, custom_css]) | |||
context.style = style | |||
def get_layout(self): | |||
layout = [] | |||
def add_page(df=None): | |||
new_page = {'sections': []} | |||
layout.append(new_page) | |||
if df and df.fieldtype=='Page Break': | |||
new_page.update(df.as_dict()) | |||
return new_page | |||
def add_section(df=None): | |||
new_section = {'columns': []} | |||
if layout: | |||
layout[-1]['sections'].append(new_section) | |||
if df and df.fieldtype=='Section Break': | |||
new_section.update(df.as_dict()) | |||
return new_section | |||
def add_column(df=None): | |||
new_col = [] | |||
if layout: | |||
layout[-1]['sections'][-1]['columns'].append(new_col) | |||
return new_col | |||
page, section, column = None, None, None | |||
for df in self.desk_form_fields: | |||
# breaks | |||
if df.fieldtype=='Page Break': | |||
page = add_page(df) | |||
section, column = None, None | |||
if df.fieldtype=='Section Break': | |||
section = add_section(df) | |||
column = None | |||
if df.fieldtype=='Column Break': | |||
column = add_column(df) | |||
# input | |||
if df.fieldtype not in ('Section Break', 'Column Break', 'Page Break'): | |||
if not page: | |||
page = add_page() | |||
section, column = None, None | |||
if not section: | |||
section = add_section() | |||
column = None | |||
if column==None: | |||
column = add_column() | |||
column.append(df) | |||
return layout | |||
def get_parents(self, context): | |||
parents = None | |||
if context.is_list and not context.parents: | |||
parents = [{"title": _("My Account"), "name": "me"}] | |||
elif context.parents: | |||
parents = context.parents | |||
return parents | |||
def set_desk_form_module(self): | |||
'''Get custom web form module if exists''' | |||
self.desk_form_module = self.get_desk_form_module() | |||
def get_desk_form_module(self): | |||
if self.is_standard: | |||
return get_doc_module(self.module, self.doctype, self.name) | |||
def validate_mandatory(self, doc): | |||
'''Validate mandatory web form fields''' | |||
missing = [] | |||
for f in self.desk_form_fields: | |||
if f.reqd and doc.get(f.fieldname) in (None, [], ''): | |||
missing.append(f) | |||
if missing: | |||
influxframework.throw(_('Mandatory Information missing:') + '<br><br>' | |||
+ '<br>'.join(['{0} ({1})'.format(d.label, d.fieldtype) for d in missing])) | |||
@influxframework.whitelist() | |||
def accept(desk_form, data, doc_name=None): | |||
'''Save the desk form''' | |||
data = influxframework._dict(json.loads(data)) | |||
doctype = data.doctype if not data.doctype else influxframework.db.get_value('Desk Form', desk_form, 'doc_type') | |||
files = [] | |||
files_to_delete = [] | |||
desk_form = influxframework.get_doc("Desk Form", desk_form) | |||
if data.name and not desk_form.allow_edit: | |||
influxframework.throw(_("You are not allowed to update this Desk Form Document")) | |||
influxframework.flags.in_desk_form = True | |||
meta = influxframework.get_meta(doctype) | |||
doc = get_doc(doctype, doc_name) | |||
# set values | |||
for field in desk_form.desk_form_fields: | |||
fieldname = field.fieldname# or field.label.replace(' ', '_').lower() | |||
#influxframework.throw(fieldname) | |||
df = meta.get_field(fieldname) | |||
value = data.get(fieldname, None) | |||
if df and df.fieldtype in ('Attach', 'Attach Image'): | |||
if value and 'data:' and 'base64' in value: | |||
files.append((fieldname, value)) | |||
if not doc.name: | |||
doc.set(fieldname, '') | |||
continue | |||
elif not value and doc.get(fieldname): | |||
files_to_delete.append(doc.get(fieldname)) | |||
doc.set(fieldname, value) | |||
if doc.new: | |||
# insert | |||
if desk_form.login_required and influxframework.session.user == "Guest": | |||
influxframework.throw(_("You must login to submit this form")) | |||
ignore_mandatory = True if files else False | |||
doc.insert(ignore_permissions=True, ignore_mandatory=ignore_mandatory) | |||
else: | |||
if has_desk_form_permission(doctype, doc.name, "write"): | |||
doc.save(ignore_permissions=True) | |||
else: | |||
# only if permissions are present | |||
doc.save() | |||
# add files | |||
if files: | |||
for f in files: | |||
fieldname, filedata = f | |||
# remove earlier attached file (if exists) | |||
if doc.get(fieldname): | |||
remove_file_by_url(doc.get(fieldname), doctype=doctype, name=doc.name) | |||
# save new file | |||
filename, dataurl = filedata.split(',', 1) | |||
_file = influxframework.get_doc({ | |||
"doctype": "File", | |||
"file_name": filename, | |||
"attached_to_doctype": doctype, | |||
"attached_to_name": doc.name, | |||
"content": dataurl, | |||
"decode": True}) | |||
_file.save() | |||
# update values | |||
doc.set(fieldname, _file.file_url) | |||
doc.save(ignore_permissions = True) | |||
if files_to_delete: | |||
for f in files_to_delete: | |||
if f: | |||
remove_file_by_url(doc.get(fieldname), doctype=doctype, name=doc.name) | |||
influxframework.flags.desk_form_doc = doc | |||
return doc | |||
@influxframework.whitelist() | |||
def delete(desk_form_name, docname): | |||
desk_form = influxframework.get_doc("Desk Form", desk_form_name) | |||
owner = influxframework.db.get_value(desk_form.doc_type, docname, "owner") | |||
if influxframework.session.user == owner and desk_form.allow_delete: | |||
influxframework.delete_doc(desk_form.doc_type, docname, ignore_permissions=True) | |||
else: | |||
raise influxframework.PermissionError("Not Allowed") | |||
@influxframework.whitelist() | |||
def delete_multiple(desk_form_name, docnames): | |||
desk_form = influxframework.get_doc("Desk Form", desk_form_name) | |||
docnames = json.loads(docnames) | |||
allowed_docnames = [] | |||
restricted_docnames = [] | |||
for docname in docnames: | |||
owner = influxframework.db.get_value(desk_form.doc_type, docname, "owner") | |||
if influxframework.session.user == owner and desk_form.allow_delete: | |||
allowed_docnames.append(docname) | |||
else: | |||
restricted_docnames.append(docname) | |||
for docname in allowed_docnames: | |||
influxframework.delete_doc(desk_form.doc_type, docname, ignore_permissions=True) | |||
if restricted_docnames: | |||
raise influxframework.PermissionError("You do not have permisssion to delete " + ", ".join(restricted_docnames)) | |||
def has_desk_form_permission(doctype, name, ptype='read'): | |||
if influxframework.session.user=="Guest": | |||
return False | |||
# owner matches | |||
elif influxframework.db.get_value(doctype, name, "owner")==influxframework.session.user: | |||
return True | |||
elif influxframework.has_website_permission(name, ptype=ptype, doctype=doctype): | |||
return True | |||
elif check_webform_perm(doctype, name): | |||
return True | |||
else: | |||
return False | |||
def check_webform_perm(doctype, name): | |||
doc = influxframework.get_doc(doctype, name) | |||
if hasattr(doc, "has_webform_permission"): | |||
if doc.has_webform_permission(): | |||
return True | |||
@influxframework.whitelist(allow_guest=False) | |||
def get_desk_form_filters(desk_form_name): | |||
desk_form = influxframework.get_doc("Desk Form", desk_form_name) | |||
return [field for field in desk_form.desk_form_fields if field.show_in_filter] | |||
@influxframework.whitelist(allow_guest=False) | |||
def get_fetch_values(doctype, txt, searchfield, start, page_len, filters): | |||
if not influxframework.has_permission(doctype): | |||
influxframework.msgprint(_("No Permission"), raise_exception=True) | |||
if not filters: | |||
filters = {} | |||
filters.update({searchfield: ["like", "%" + txt + "%"]}) | |||
return influxframework.get_all(doctype, fields=["name", searchfield], filters=filters, | |||
order_by=searchfield, limit_start=start, limit_page_length=page_len) | |||
def make_route_string(parameters): | |||
route_string = "" | |||
delimeter = '?' | |||
if isinstance(parameters, dict): | |||
for key in parameters: | |||
if key != "desk_form_name": | |||
route_string += route_string + delimeter + key + "=" + cstr(parameters[key]) | |||
delimeter = '&' | |||
return (route_string, delimeter) | |||
@influxframework.whitelist(allow_guest=False) | |||
def get_meta(doctype = None): | |||
return influxframework.get_doc("DocType", doctype) | |||
@influxframework.whitelist(allow_guest=False) | |||
def get_doc(doctype, doc_name=None): | |||
name = influxframework.db.get_value(doctype, {"name": doc_name}) if doc_name else None | |||
if name: | |||
doc = influxframework.get_doc(doctype, name) | |||
doc.new = False | |||
return doc | |||
else: | |||
doc = influxframework.new_doc(doctype) | |||
if doc_name: | |||
doc.set("name", doc_name) | |||
doc.new = True | |||
return doc | |||
@influxframework.whitelist(allow_guest=False) | |||
def get_form(form_name=None): | |||
desk_form = influxframework.get_doc('Desk Form', form_name) | |||
if desk_form.login_required and influxframework.session.user == 'Guest': | |||
influxframework.throw(_("Not Permitted"), influxframework.PermissionError) | |||
out = influxframework._dict() | |||
out.desk_form = desk_form | |||
# For Table fields, server-side processing for meta | |||
for field in out.desk_form.desk_form_fields: | |||
if field.fieldtype == "Table": | |||
field.fields = get_in_list_view_fields(field.options) | |||
out.update({field.fieldname: field.fields}) | |||
return out | |||
@influxframework.whitelist(allow_guest=False) | |||
def get_form_data(form_name=None, doc_name=None): | |||
desk_form = influxframework.get_doc('Desk Form', form_name) | |||
if desk_form.login_required and influxframework.session.user == 'Guest': | |||
influxframework.throw(_("Not Permitted"), influxframework.PermissionError) | |||
out = influxframework._dict() | |||
out.desk_form = desk_form | |||
if influxframework.session.user != 'Guest' and not doc_name and not desk_form.allow_multiple: | |||
doc_name = influxframework.db.get_value( | |||
desk_form.doc_type, {"owner": influxframework.session.user}, "name") | |||
doc = get_doc(desk_form.doc_type, doc_name) | |||
out.doc = doc | |||
#if doc_name: | |||
# doc = influxframework.get_doc(desk_form.doc_type, doc_name) | |||
# out.doc = doc | |||
# For Table fields, server-side processing for meta | |||
for field in out.desk_form.desk_form_fields: | |||
if field.fieldtype == "Table": | |||
field.fields = get_in_list_view_fields(field.options) | |||
out.update({field.fieldname: field.fields}) | |||
return out | |||
@influxframework.whitelist() | |||
def get_in_list_view_fields(doctype): | |||
meta = influxframework.get_meta(doctype) | |||
fields = [] | |||
if meta.title_field: | |||
fields.append(meta.title_field) | |||
else: | |||
fields.append('name') | |||
if meta.has_field('status'): | |||
fields.append('status') | |||
fields += [df.fieldname for df in meta.fields if df.in_list_view and df.fieldname not in fields] | |||
def get_field_df(fieldname): | |||
if fieldname == 'name': | |||
return { 'label': 'Name', 'fieldname': 'name', 'fieldtype': 'Data' } | |||
return meta.get_field(fieldname).as_dict() | |||
return [get_field_df(f) for f in fields] | |||
@influxframework.whitelist(allow_guest=True) | |||
def get_link_options(desk_form_name, doctype, allow_read_on_all_link_options=False): | |||
desk_form_doc = influxframework.get_doc("Desk Form", desk_form_name) | |||
doctype_validated = False | |||
limited_to_user = False | |||
if desk_form_doc.login_required: | |||
# check if influxframework session user is not guest or admin | |||
if influxframework.session.user != 'Guest': | |||
doctype_validated = True | |||
if not allow_read_on_all_link_options: | |||
limited_to_user = True | |||
else: | |||
for field in desk_form_doc.desk_form_fields: | |||
if field.options == doctype: | |||
doctype_validated = True | |||
break | |||
if doctype_validated: | |||
link_options = [] | |||
if limited_to_user: | |||
link_options = "\n".join([doc.name for doc in influxframework.get_all(doctype, filters = {"owner":influxframework.session.user})]) | |||
else: | |||
link_options = "\n".join([doc.name for doc in influxframework.get_all(doctype)]) | |||
return link_options | |||
else: | |||
raise influxframework.PermissionError('Not Allowed, {0}'.format(doctype)) |
@@ -0,0 +1,7 @@ | |||
{% extends "templates/web.html" %} | |||
{% block page_content %} | |||
<h1>{{ title }}</h1> | |||
{% endblock %} | |||
<!-- this is a sample default web page template --> |
@@ -0,0 +1,4 @@ | |||
<div> | |||
<a href="{{ doc.route }}">{{ doc.title or doc.name }}</a> | |||
</div> | |||
<!-- this is a sample default list template --> |
@@ -0,0 +1,10 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2021, Quantum Bit Core and Contributors | |||
# See license.txt | |||
from __future__ import unicode_literals | |||
# import influxframework | |||
import unittest | |||
class TestDeskForm(unittest.TestCase): | |||
pass |
@@ -0,0 +1,159 @@ | |||
{ | |||
"actions": [], | |||
"creation": "2021-07-15 19:10:01.107899", | |||
"doctype": "DocType", | |||
"editable_grid": 1, | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"fieldname", | |||
"fieldtype", | |||
"label", | |||
"allow_read_on_all_link_options", | |||
"reqd", | |||
"depends_on", | |||
"read_only", | |||
"show_in_filter", | |||
"hidden", | |||
"extra_field", | |||
"collapsible", | |||
"column_break_4", | |||
"options", | |||
"max_length", | |||
"max_value", | |||
"fetch_from", | |||
"fetch_if_empty", | |||
"section_break_6", | |||
"description", | |||
"column_break_8", | |||
"default" | |||
], | |||
"fields": [ | |||
{ | |||
"fieldname": "fieldname", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Fieldname" | |||
}, | |||
{ | |||
"fieldname": "fieldtype", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Fieldtype", | |||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature" | |||
}, | |||
{ | |||
"fieldname": "label", | |||
"fieldtype": "Data", | |||
"in_list_view": 1, | |||
"label": "Label" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "eval:doc.fieldtype === 'Link'", | |||
"fieldname": "allow_read_on_all_link_options", | |||
"fieldtype": "Check", | |||
"label": "Allow Read On All Link Options" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "reqd", | |||
"fieldtype": "Check", | |||
"label": "Mandatory" | |||
}, | |||
{ | |||
"fieldname": "depends_on", | |||
"fieldtype": "Code", | |||
"label": "Depends On" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "read_only", | |||
"fieldtype": "Check", | |||
"label": "Read Only" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "show_in_filter", | |||
"fieldtype": "Check", | |||
"label": "Show in filter" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "hidden", | |||
"fieldtype": "Check", | |||
"label": "Hidden" | |||
}, | |||
{ | |||
"fieldname": "column_break_4", | |||
"fieldtype": "Column Break" | |||
}, | |||
{ | |||
"fieldname": "options", | |||
"fieldtype": "Text", | |||
"in_list_view": 1, | |||
"label": "Options" | |||
}, | |||
{ | |||
"fieldname": "max_length", | |||
"fieldtype": "Int", | |||
"label": "Max Length" | |||
}, | |||
{ | |||
"depends_on": "eval:doc.fieldtype=='Int'", | |||
"fieldname": "max_value", | |||
"fieldtype": "Int", | |||
"label": "Max Value" | |||
}, | |||
{ | |||
"fieldname": "section_break_6", | |||
"fieldtype": "Section Break" | |||
}, | |||
{ | |||
"fieldname": "description", | |||
"fieldtype": "Text", | |||
"label": "Description" | |||
}, | |||
{ | |||
"fieldname": "column_break_8", | |||
"fieldtype": "Column Break" | |||
}, | |||
{ | |||
"fieldname": "default", | |||
"fieldtype": "Data", | |||
"label": "Default" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "extra_field", | |||
"fieldtype": "Check", | |||
"label": "Extra Field" | |||
}, | |||
{ | |||
"fieldname": "fetch_from", | |||
"fieldtype": "Small Text", | |||
"label": "Fetch From" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "fetch_if_empty", | |||
"fieldtype": "Check", | |||
"label": "Fetch If Empty" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "collapsible", | |||
"fieldtype": "Check", | |||
"label": "Collapsible" | |||
} | |||
], | |||
"istable": 1, | |||
"links": [], | |||
"modified": "2022-11-25 16:02:05.981701", | |||
"modified_by": "Administrator", | |||
"module": "InfluxFramework Helper", | |||
"name": "Desk Form Field", | |||
"owner": "Administrator", | |||
"permissions": [], | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
} |
@@ -0,0 +1,10 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2021, Quantum Bit Core and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
# import influxframework | |||
from influxframework.model.document import Document | |||
class DeskFormField(Document): | |||
pass |
@@ -0,0 +1 @@ | |||
InfluxFramework Helper |
@@ -0,0 +1,9 @@ | |||
.desk-form .grid-delete-row, | |||
.desk-form .grid-duplicate-row, | |||
.desk-form .grid-move-row, | |||
.desk-form .grid-append-row, | |||
.desk-form .grid-insert-row, | |||
.desk-form .grid-remove-all-rows, | |||
.desk-form .grid-insert-row-below { | |||
display: none !important; | |||
} |
@@ -0,0 +1,50 @@ | |||
:root { | |||
--default-line: 1px solid rgba(119, 136, 153, 0.39); | |||
--fill-color: rgba(119, 136, 153, 0.07); | |||
--selected-color: rgba(119, 136, 153, 0.39); | |||
--margin-grid: 5px; | |||
} | |||
.jstmlefc65a6efc9cb8afbc85 { | |||
opacity: 0.4; | |||
pointer-events: none !important; | |||
cursor: not-allowed !important; | |||
filter: alpha(opacity=65); | |||
-webkit-box-shadow: none; | |||
box-shadow: none; | |||
-moz-user-select: none; | |||
-webkit-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
} | |||
.jstmlefc65a6efc9cb8afbc85-confirm { | |||
background-color: #ff5900 !important; | |||
color: #ffffff !important; | |||
font-weight: bold; | |||
animation: blinker 1.5s linear infinite; | |||
} | |||
@keyframes blinker { | |||
50% { | |||
opacity: 0.5; | |||
} | |||
} | |||
.pdf-container { | |||
background: linear-gradient(-45deg, #161e23, #414449, #1d2226, #494c4f); | |||
background-size: 400% 400%; | |||
animation: gradient 15s ease infinite; | |||
} | |||
@keyframes gradient { | |||
0% { | |||
background-position: 0 50%; | |||
} | |||
50% { | |||
background-position: 100% 50%; | |||
} | |||
100% { | |||
background-position: 0 50%; | |||
} | |||
} |
@@ -0,0 +1,90 @@ | |||
.pad-container { | |||
position: relative; | |||
width: calc(100% + 4px); | |||
height: calc(100% + 4px); | |||
border-collapse: separate; | |||
margin-left: -2px; | |||
} | |||
.pad-container .pad-col.pad-btn { | |||
background-color: var(--fg-color); | |||
border-color: rgba(119, 136, 153, 0.39); | |||
border-width: 1px; | |||
border-style: ridge; | |||
} | |||
.pad-container .pad-col.pad-btn.btn-success { | |||
background-color: var(--success); | |||
} | |||
.pad-container .pad-col.pad-btn.btn-success:hover { | |||
background-color: rgba(3, 121, 49, 0.6); | |||
} | |||
.pad-container .pad-btn:hover { | |||
background-color: rgba(37, 75, 111, 0.03); | |||
} | |||
.pad-container .pad-col { | |||
width: 100%; | |||
-webkit-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
-webkit-box-shadow: none; | |||
text-align: center; | |||
cursor: pointer; | |||
margin-bottom: 0; | |||
font-weight: 400; | |||
white-space: nowrap; | |||
vertical-align: middle; | |||
-ms-touch-action: manipulation; | |||
touch-action: manipulation; | |||
background-image: none; | |||
padding: 6px 12px; | |||
font-size: 14px; | |||
line-height: 1.42857143; | |||
box-shadow: none; | |||
border-radius: 3px; | |||
} | |||
.pad-container .pad-col.disabled { | |||
pointer-events: none; | |||
color: #a9a9a9 !important; | |||
} | |||
.pad-container .pad-row { | |||
height: 25%; | |||
} | |||
.pad-container .pad-col.sm { | |||
width: 15%; | |||
} | |||
.pad-container .pad-col.md { | |||
width: 20%; | |||
} | |||
.pad-container .pad-col.lg { | |||
text-align: left; | |||
width: 35%; | |||
} | |||
.pad-container .pad-col.text-lg { | |||
font-size: 25px; | |||
} | |||
.pad-container .pad-col span { | |||
font-size: 20px; | |||
} | |||
.pad-container .pad-label { | |||
background-color: transparent; | |||
text-align: right !important; | |||
padding: 0 10px 0 0; | |||
cursor: default; | |||
} | |||
.pad-container .pad-label.label-lg { | |||
font-size: 25px !important; | |||
} |
@@ -0,0 +1,208 @@ | |||
class DeskForm extends InfluxfFrameworkForm { | |||
is_hide = true; | |||
has_footer = true; | |||
has_primary_action = true; | |||
base_url = "influxframework_helper.influxframework_helper.doctype.desk_form.desk_form."; | |||
constructor(options) { | |||
super(options); | |||
this.in_modal = !this.location; | |||
if (this.form_name) this.initialize(); | |||
} | |||
remove(){ | |||
this._wrapper && this._wrapper.$wrapper.remove(); | |||
} | |||
get parent() { | |||
return this.body; | |||
} | |||
get body() { | |||
return this.in_modal ? $(this._wrapper.$wrapper).find('.modal-body') : this._wrapper; | |||
} | |||
get footer() { | |||
return this.in_modal ? $(this._wrapper.$wrapper).find('.modal-footer') : this.body; | |||
} | |||
get footer_buttons_wrapper() { | |||
return this.footer.find('.standard-actions'); | |||
} | |||
get primary_btn() { | |||
return this.footer.find('.btn-primary'); | |||
} | |||
get_primary_btn() { | |||
return this.primary_btn; | |||
} | |||
field(field_name) { | |||
return this.in_modal ? this._wrapper.fields_dict[field_name].$wrapper : null; | |||
} | |||
async initialize() { | |||
this._wrapper = this.location || new influxframework.ui.Dialog({ | |||
title: this.title, | |||
on_hide: () => { | |||
close_grid_and_dialog(); | |||
} | |||
}); | |||
await super.initialize(); | |||
function close_grid_and_dialog() { | |||
// close open grid row | |||
var open_row = $(".grid-row-open"); | |||
if (open_row.length) { | |||
var grid_row = open_row.data("grid_row"); | |||
grid_row.toggle_view(false); | |||
return false; | |||
} | |||
// close open dialog | |||
if (cur_dialog && !cur_dialog.no_cancel_flag) { | |||
//cur_dialog.cancel(); | |||
return false; | |||
} | |||
} | |||
this.in_modal && this._wrapper && this._wrapper.wrapper.classList.add('modal-lg'); | |||
this.body.show(); | |||
this.show(); | |||
} | |||
execute_primary_action() { | |||
this.primary_btn.focus(); | |||
if (this.primary_action) { | |||
if (this.last_data != JSON.stringify(this.doc)) { | |||
this.last_data = JSON.stringify(this.doc); | |||
this.primary_action(); | |||
} | |||
} else { | |||
this.save(); | |||
} | |||
} | |||
async make() { | |||
await super.make(); | |||
this.customize(); | |||
if (this.has_primary_action && this.in_modal) { | |||
const button = this.primary_btn; | |||
if(button){ | |||
button.on('click', () => { | |||
this.execute_primary_action(); | |||
}); | |||
button.text(this.primary_action_label || __('Save')); | |||
button.removeClass('hide'); | |||
} | |||
this.footer.removeClass('hide'); | |||
this.footer.css('display', 'flex'); | |||
}else{ | |||
this.footer.hide(); | |||
} | |||
/*if (this.close_only_button) { | |||
const button = this.get_field("close_only_button"); | |||
button ? button.attr({ "data-keyboard": "false", "data-backdrop": "static", "id": `${this.identifier}` }) : ""; | |||
}*/ | |||
} | |||
customize() { | |||
this.body.addClass('desk-form'); | |||
Object.entries(this.field_properties || {}).forEach(([f, props]) => { | |||
const child_field = f.split("."); | |||
let field, grid; | |||
if (child_field.length > 1) { | |||
grid = this.get_field(child_field[0]).grid; | |||
field = grid.get_field(child_field[1]); | |||
} else { | |||
field = this.get_field(child_field[0]); | |||
} | |||
Object.entries(props).forEach(([prop, value]) => { | |||
if(field){ | |||
field.df ??= {}; | |||
if (prop === "get_query") { | |||
field.get_query = value; | |||
return; | |||
} else if (prop === "value") { | |||
field.set_value(value); | |||
return; | |||
} | |||
if(prop === "on_change"){ | |||
field.df.on_change = value; | |||
}else{ | |||
field.df[prop] = value; | |||
/*if(grid){ | |||
field.df[prop] = value; | |||
}else{ | |||
self.set_field_property(field.df.fieldname, prop, value); | |||
}*/ | |||
} | |||
} | |||
}); | |||
}); | |||
} | |||
load() { | |||
this.before_load && this.before_load(); | |||
super.initialize(); | |||
this.initialize_fetches(); | |||
} | |||
async reload(doc=null, from_server=false) { | |||
this.reloading = true; | |||
this.before_load && this.before_load(); | |||
this.doc = doc || await this.get_doc(from_server); | |||
this.refresh(); | |||
this.customize(); | |||
this.reloading = false; | |||
if(this.on_reload && typeof this.on_reload === "function"){ | |||
setTimeout(() => { | |||
this.on_reload(); | |||
}, 100); | |||
} | |||
return this; | |||
} | |||
background_reload() { | |||
this.get_doc(true).then(doc => { | |||
this.doc = doc; | |||
this.refresh(); | |||
}); | |||
} | |||
show() { | |||
this.is_hide = false; | |||
this._wrapper.show(); | |||
return this; | |||
} | |||
hide() { | |||
this.is_hide = true; | |||
this._wrapper.hide(); | |||
return this; | |||
} | |||
toggle() { | |||
this.is_hide ? this.show() : this.hide(); | |||
return this; | |||
} | |||
} |
@@ -0,0 +1,174 @@ | |||
class DeskModal { | |||
constructor(options) { | |||
Object.assign(this, options); | |||
this.id = "desk-modal-" + Math.random().toString(36).substr(2, 15); | |||
this.modal = null; | |||
this.construct(); | |||
} | |||
set_props(props){ | |||
Object.assign(this, props); | |||
} | |||
remove(){ | |||
this.modal && this.modal.$wrapper.remove(); | |||
} | |||
construct(){ | |||
this.modal = new influxframework.ui.Dialog({ | |||
title: this.title, | |||
primary_action_label: __("Save") | |||
}); | |||
this.show(); | |||
if(this.full_page){ | |||
this.modal.$wrapper.find('.modal-dialog').css({ | |||
"width": "100%", "height": "100%", "left": "0", "top": "0", "margin": "0", "padding":"0", "border-style": "none", | |||
"max-width": "unset", "max-height": "unset" | |||
}); | |||
this.modal.$wrapper.find('.modal-content').css({ | |||
"width": "100%", "height": "100%", "left": "0", "top": "0", "border-style": "none", "border-radius": "0", | |||
"max-width": "unset", "max-height": "unset" | |||
}); | |||
} | |||
setTimeout(() => { | |||
this.render(); | |||
}, 200); | |||
} | |||
_adjust_height(){ | |||
return typeof this.adjust_height == "undefined" ? 0 : this.adjust_height; | |||
} | |||
render(){ | |||
this.set_title(); | |||
if(typeof this.customize != "undefined"){ | |||
this.modal.$wrapper.find(".modal-body").empty(); | |||
this.modal.$wrapper.css({ | |||
"height": `calc(100% - ${this._adjust_height()}px)`, | |||
"border-bottom": "var(--default-line)", | |||
}); | |||
this.modal.$wrapper.find('.modal-header').css({ | |||
"padding": "5px", | |||
"border-bottom": "var(--default-line)", | |||
"border-radius": "0", | |||
/*"min-height": "50px"*/ | |||
}); | |||
this.modal.$wrapper.find('.modal-body').css({ | |||
"background-color": "transparent", | |||
"padding": "0", | |||
"border-style": "none", | |||
"border-radius": "0", | |||
"overflow-y": "auto" | |||
}); | |||
this.modal.$wrapper.find('.modal-title').css({ | |||
"margin": "0" | |||
}); | |||
this.modal.$wrapper.find(".modal-actions").prepend("<span class='btn-container'></span>").css({ | |||
"top": "5px" | |||
}); | |||
} | |||
if(typeof this.from_server == "undefined") { | |||
if(this.call_back){ | |||
this.call_back(); | |||
} | |||
}else{ | |||
this.load_data(); | |||
} | |||
} | |||
set_title(title){ | |||
this.modal.set_title(title); | |||
} | |||
get container(){return this.modal.$wrapper.find(".modal-body")} | |||
get title_container(){return this.modal.$wrapper.find(".modal-title")} | |||
get buttons_container(){return this.modal.$wrapper.find(".modal-actions .btn-container")} | |||
show(){ | |||
this.modal.show(); | |||
} | |||
hide(){ | |||
this.modal.hide(); | |||
} | |||
loading() { | |||
this.modal.fields_dict.ht.$wrapper.html( | |||
"<div class='loading-form' style='font-size: xx-large; text-align: center; color: #8D99A6'>" + __("Loading") + "...</div>" | |||
); | |||
} | |||
stop_loading() { | |||
//this.modal.fields_dict.ht.$wrapper.html(""); | |||
} | |||
get _is_pdf(){ | |||
return typeof this.is_pdf != "undefined" && this.is_pdf === true; | |||
} | |||
get _args() { | |||
let args = this.args; | |||
return (typeof args == "undefined" || args == null ? {} : this.args); | |||
} | |||
get _pdf_url(){ | |||
let url = `/api/method/influxframework.utils.print_format.download_pdf?doctype=${this.model}&name=${this.model_name}`; | |||
let args = Object.assign({ | |||
no_letterhead: 1, | |||
letterhead: 'No%20Letterhead', | |||
settings: '%7B%7D' | |||
}, this._args); | |||
Object.keys(args).forEach(k => { | |||
url += '&' + k + '=' + args[k]; | |||
}); | |||
return url; | |||
} | |||
get pdf_template(){ | |||
return ` | |||
<div class="col-md-12" style="margin-top: 15px"> | |||
<div class="pdf-container"> | |||
<embed src="${this._pdf_url}" frameborder="0" width="100%" height="400px"> | |||
</div> | |||
</div> | |||
`; | |||
} | |||
load_data(){ | |||
if(this._is_pdf){ | |||
this.container.empty().append(this.pdf_template); | |||
}else{ | |||
influxframeworkHelper.api.call({ | |||
model: this.model, | |||
name: this.model_name, | |||
method: this.action, | |||
args:{}, | |||
always: (r) => { | |||
this.container.empty().append(r.message); | |||
this.stop_loading(); | |||
if(this.call_back){ | |||
this.call_back(); | |||
} | |||
}, | |||
}); | |||
} | |||
} | |||
reload(){ | |||
this.load_data(); | |||
return this; | |||
} | |||
} |
@@ -0,0 +1,378 @@ | |||
influxframework.provide("influxframework.ui"); | |||
class InfluxfFrameworkForm extends influxframework.ui.FieldGroup { | |||
background = false; | |||
buttons = {}; | |||
button_label = "Save"; | |||
fetch_dict = {}; | |||
constructor(props) { | |||
super(props); | |||
} | |||
get_meta() { | |||
return this.#get("get_meta", {doctype: this.doctype}); | |||
} | |||
async initialize() { | |||
this.form_name = this.desk_form ? this.desk_form.name : this.form_name; | |||
this.form_name = this.form_name.replaceAll(" ", "-").toLowerCase(); | |||
if(!this.desk_form && !this.doc) { | |||
await this.get_all(); | |||
} | |||
if(!this.desk_form) this.desk_form = await this.get_form(); | |||
this.doctype = this.desk_form.doc_type; | |||
if (!this.doc && this.doc_name) this.doc = await this.get_doc(); | |||
this.fields = this.desk_form.desk_form_fields; | |||
await this.make(); | |||
} | |||
initialize_fetches() { | |||
this.desk_form.desk_form_fields.forEach(df => { | |||
if (df.fetch_from) { | |||
this.trigger(df.fetch_from.split(".")[0] , "change"); | |||
} | |||
}); | |||
} | |||
async make() { | |||
const setup_add_fetch = (df_fetch_from, df_fetch_to, parent=null) => { | |||
df_fetch_from.listeners ??= {}; | |||
df_fetch_from.listeners.change = []; | |||
df_fetch_from.listeners.change.push((e) => { | |||
if (parent) { | |||
const table_input = this.get_field(parent.fieldname).grid; | |||
const data = table_input.data; | |||
data.forEach((row, index) => { | |||
const row_input = table_input.get_row(index); | |||
const link_fetch = row_input.columns[df_fetch_from.fieldname].field; | |||
const target_fetch_inputs = Object.entries(df_fetch_to).map(([key, _df_fetch_to]) => { | |||
return row_input.columns[_df_fetch_to.fieldname].field | |||
}).reduce((acc, cur) => { | |||
if(cur) acc[cur.df.fieldname] = cur; | |||
return acc; | |||
}, {}); | |||
this.fetch_link(link_fetch, target_fetch_inputs); | |||
}); | |||
} else { | |||
const link_fetch = this.get_field(df_fetch_from.fieldname); | |||
const target_fetch_inputs = Object.entries(df_fetch_to).map(([key, df_fetch_to]) => { | |||
return this.get_field(df_fetch_to.fieldname); | |||
}).reduce((acc, cur) => { | |||
if(cur) acc[cur.df.fieldname] = cur; | |||
return acc; | |||
}, {}); | |||
this.fetch_link(link_fetch, target_fetch_inputs); | |||
} | |||
}); | |||
df_fetch_from.onchange = (e) => { | |||
df_fetch_from.listeners.change.forEach((listener) => { | |||
listener(e); | |||
}); | |||
}; | |||
} | |||
return new Promise(resolve => { | |||
const fetches = {}; | |||
const setup_fetch = (fields, df, parent=null) => { | |||
if (!df.fetch_from) return; | |||
const fetch_from = fields.find(field => field.fieldname === df.fetch_from.split(".")[0]) || {}; | |||
if (([ | |||
'Data', 'Read Only', 'Text', 'Small Text', 'Currency', 'Check', | |||
'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date', 'Select' | |||
].includes(fetch_from.fieldtype) || [true, 1, "true", "1"].includes(fetch_from.read_only))) { | |||
const fetch_from_field = fetch_from.fieldname; | |||
const fetch_to = df.fieldname; | |||
fetches[fetch_from_field] ??= {}; | |||
fetches[fetch_from_field].fetch_from = fetch_from; | |||
fetches[fetch_from_field].fetch_to ??= []; | |||
fetches[fetch_from_field].fetch_to[fetch_to] = df | |||
fetches[fetch_from_field].parent = parent; | |||
} | |||
} | |||
this.desk_form.desk_form_fields.forEach(df => { | |||
setup_fetch(this.desk_form.desk_form_fields, df); | |||
if (df.fieldtype === 'Table') { | |||
df.get_data = () => { | |||
return this.doc ? this.doc[df.fieldname] : []; | |||
}; | |||
if (this.data.hasOwnProperty(df.fieldname)) { | |||
df.fields = this.data[df.fieldname]; | |||
} | |||
(df.fields || []).forEach(f => { | |||
setup_fetch(df.fields, f, df); | |||
if (f.fieldname === 'name') f.hidden = 1; | |||
}); | |||
df.options = null; | |||
}else{ | |||
if(df.read_only){ | |||
df.doctype = null; | |||
df.docname = null; | |||
} | |||
} | |||
delete df.parent; | |||
delete df.parentfield; | |||
delete df.parenttype; | |||
delete df.doctype; | |||
}); | |||
Object.values(fetches).forEach(fetch => { | |||
setup_add_fetch(fetch.fetch_from, fetch.fetch_to, fetch.parent); | |||
}); | |||
super.make(); | |||
setTimeout(() => { | |||
this.after_load && this.after_load(this); | |||
this.initialize_fetches(); | |||
}, 200); | |||
resolve(); | |||
}); | |||
} | |||
fetch_link(link_fetch, fetches_to={}) { | |||
if (Object.keys(fetches_to).length === 0) return; | |||
const doctype = link_fetch.df.options; | |||
const from_cols = Object.values(fetches_to).map((fetch_to_df) => fetch_to_df.df.fetch_from.split('.')[1]); | |||
const doc_name = link_fetch.get_value(); | |||
//if (link_fetch.last_value === doc_name) return; | |||
link_fetch.last_value = doc_name; | |||
influxframework.call({ | |||
method: 'influxframework_helper.api.validate_link', | |||
type: "GET", | |||
args: { | |||
'value': doc_name, | |||
'options': doctype, | |||
'fetch': from_cols.join(",") | |||
}, | |||
no_spinner: true, | |||
callback: (r) => { | |||
const fetch_values = r.fetch_values || []; | |||
Object.values(fetches_to).map((fetch_to_df, index) => fetch_to_df.set_value(r.message == 'Ok' ? fetch_values[index] : '')); | |||
} | |||
}); | |||
} | |||
refresh() { | |||
super.refresh(this.doc); | |||
this.refresh_fields(); | |||
this.on_refresh && this.on_refresh(); | |||
} | |||
refresh_fields(){ | |||
this.desk_form.desk_form_fields.forEach(df => { | |||
if (df.read_only) { | |||
df.doctype = null; | |||
df.docname = null; | |||
this.set_field_property(df.fieldname, "read_only", true); | |||
} | |||
}); | |||
} | |||
async get_doc(from_server = false) { | |||
if (this.doc && !from_server) return this.doc; | |||
const data = await this.#get("get_doc", {doctype: this.doctype, doc_name: this.doc_name}); | |||
return data; | |||
} | |||
async get_form() { | |||
const data = await this.#get("get_form", {form_name: this.form_name}); | |||
return data.desk_form | |||
} | |||
async get_all() { | |||
this.data = await this.#get("get_form_data", {form_name: this.form_name, doc_name: this.doc_name}); | |||
this.doc = this.data.doc; | |||
this.desk_form = this.data.desk_form; | |||
} | |||
async #get(method, args) { | |||
return new Promise(resolve => { | |||
influxframework.call({ | |||
method: `influxframework_helper.influxframework_helper.doctype.desk_form.desk_form.${method}`, | |||
args: args, | |||
freeze: this.background === false, | |||
}).then(r => { | |||
return resolve(r.message); | |||
}); | |||
}); | |||
} | |||
set_field_property(field_name, property, value) { | |||
if(Array.isArray(field_name)){ | |||
field_name.forEach(field => { | |||
this.set_field_property(field, property, value); | |||
}); | |||
return; | |||
} | |||
if(typeof property === 'object'){ | |||
Object.keys(property).forEach(key => { | |||
this.set_field_property(field_name, key, property[key]); | |||
}); | |||
return; | |||
} | |||
const field = this.get_field(field_name); | |||
field.doctype = field.df.doctype; | |||
field.docname = field.df.docname; | |||
this.set_df_property(field_name, property, value); | |||
} | |||
get_fields() { | |||
return this.fields_dict; | |||
} | |||
get_section(section_name) { | |||
return this.get_field(section_name); | |||
} | |||
on(fieldname, event, fn) { | |||
if(Array.isArray(fieldname)){ | |||
fieldname.forEach(f => this.on(f, event, fn)); | |||
return; | |||
} | |||
const field = this.get_field(fieldname); | |||
if(field && field.df){ | |||
const df = field.df; | |||
df.listeners ??= {}; | |||
df.listeners[event] ??= []; | |||
df.listeners[event].push(fn); | |||
df[`on${event}`] = () => { | |||
df.listeners[event].forEach(fn => { | |||
fn(field, fieldname); | |||
}); | |||
} | |||
} | |||
} | |||
trigger(fieldname, event) { | |||
if(Array.isArray(fieldname)){ | |||
fieldname.forEach(f => this.trigger(f, event)); | |||
return; | |||
} | |||
const field = this.get_field(fieldname); | |||
const e = field && field.df[`on${event}`] | |||
e && typeof e === 'function' && e(this.get_value(fieldname)); | |||
} | |||
execute_event(fieldname, event) { | |||
const field = this.get_field(fieldname); | |||
if (field && field.df) { | |||
const df = field.df; | |||
df.listeners ??= {}; | |||
df.listeners[event] ??= []; | |||
df.listeners[event].push(fn); | |||
df[`on${event}`] = () => { | |||
df.listeners[event].forEach(fn => { | |||
fn(this.get_value(fieldname)); | |||
}); | |||
} | |||
} | |||
} | |||
save(options={}, force=false) { | |||
// validation hack: get_values will check for missing data | |||
return new Promise(resolve => { | |||
const doc_values = super.get_values(force); | |||
if (!doc_values){ | |||
options.error && options.error(false); | |||
return; | |||
} | |||
if (window.saving){ | |||
options.error && options.error(__("Please wait for the other operation to complete")); | |||
return; | |||
} | |||
Object.assign(this.doc, doc_values || {}); | |||
this.doc.doctype = this.doctype; | |||
window.saving = true; | |||
influxframework.form_dirty = false; | |||
influxframework.call({ | |||
type: "POST", | |||
method: this.base_url + 'accept', | |||
args: { | |||
desk_form: this.form_name, | |||
data: this.doc, | |||
doc_name: this.doc_name, | |||
}, | |||
freeze: true, | |||
btn: this.buttons[this.button_label], | |||
callback: (data) => { | |||
if (!data.exc) { | |||
this.doc_name = data.message.name; | |||
this.call_back && this.call_back(this); | |||
this.on_save && this.on_save(data); | |||
options.success && options.success(data); | |||
} else { | |||
options.error && options.error(__('There were errors. Please report this.')); | |||
} | |||
}, | |||
always: (r) => { | |||
options.always && options.always(r); | |||
window.saving = false; | |||
}, | |||
error: function (r) { | |||
options.always && options.always(r); | |||
options.error && options.error(__('There were errors. Please report this.')); | |||
}, | |||
}); | |||
return true; | |||
}); | |||
} | |||
refresh_dependency() { | |||
super.refresh_dependency(); | |||
if(this.reloading) return; | |||
this.on_refresh_dependency && this.on_refresh_dependency(this); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
class InfluxfFrameworkHelperApi { | |||
#api = this; | |||
constructor() {} | |||
get api(){return this.#api} | |||
/**option{model, name, method}**/ | |||
call(options={}){ | |||
influxframework.call({ | |||
method: "influxframework_helper.api.call", | |||
args: {model: options.model, name: options.name, method: options.method, args: options.args}, | |||
always: function (r) { | |||
options.always && options.always(r); | |||
}, | |||
callback: function (r) { | |||
options.callback && options.callback(r); | |||
}, | |||
success: function (r) { | |||
options.success && options.success(r); | |||
}, | |||
error: function (r) { | |||
options.error && options.error(r); | |||
}, | |||
freeze: !!options.freeze | |||
}); | |||
} | |||
} | |||
const influxframeworkHelper = new InfluxfFrameworkHelperApi(); |
@@ -0,0 +1,721 @@ | |||
class JSHtml { | |||
#obj = null; | |||
#disabled = false; | |||
#cursor_position = 0; | |||
#click_attempts = 0; | |||
#confirming = false; | |||
#jshtml_identifier = 'jstmlefc65a6efc9cb8afbc85'; | |||
#$ = null; | |||
#properties = {}; | |||
#identifier = this.uuid(); | |||
#listeners = {}; | |||
#content = undefined; | |||
#text = undefined; | |||
#value = undefined; | |||
#is_float = false; | |||
#is_int = false; | |||
#decimals = 2; | |||
constructor(options) { | |||
Object.assign(this, options); | |||
this.#properties = (typeof options.properties == "undefined" ? {} : options.properties); | |||
this.fusion_props(); | |||
this.make(); | |||
this.set_obj(); | |||
this.default_listeners(); | |||
this.pad_editing = false; | |||
return this; | |||
} | |||
set properties(val) { this.#properties = val } | |||
set content(val) { this.#content = val } | |||
set text(val) { this.#text = val } | |||
set cursor_position(val) { | |||
this.#cursor_position = val; | |||
if (this.#cursor_position < 0) this.#cursor_position = 0; | |||
} | |||
get obj() { return this.#obj } | |||
get disabled() { return this.#disabled } | |||
get cursor_position() { return this.#cursor_position } | |||
get click_attempts() { return this.#click_attempts } | |||
get confirming() { return this.#confirming } | |||
get jshtml_identifier() { return this.#jshtml_identifier } | |||
get $() { return this.#$ } | |||
get identifier() { return this.#identifier } | |||
get properties() { return this.#properties } | |||
get listeners() { return this.#listeners } | |||
get float_val() { return isNaN(parseFloat(this.val())) ? 0.0 : parseFloat(this.val()) } | |||
get int_val() { return parseInt(isNaN(this.val()) ? 0 : this.val()) } | |||
get content() { return this.#content } | |||
get text() { return this.#text } | |||
set_obj() { | |||
if (this.obj != null) return; | |||
setTimeout(() => { | |||
this.#obj = this.from_html ? this.from_html : document.querySelector(`${this.tag}[${this.identifier}='${this.identifier}']`); | |||
setTimeout(() => { | |||
if (this.obj != null) this.#obj.removeAttribute(this.identifier); | |||
}, 0); | |||
this.#$ = this.JQ(); | |||
}, 0); | |||
} | |||
fusion_props() { | |||
this.#properties[this.identifier] = this.identifier; | |||
} | |||
get type() { | |||
let type = null; | |||
["input", "button", "select", "check", "radio"].forEach(t => { | |||
if (t === this.tag) type = t; | |||
}); | |||
return type; | |||
} | |||
make() { | |||
this.make_dom(); | |||
} | |||
toggle_common(base_class, toggle_class) { | |||
this.add_class(toggle_class).JQ().siblings(`.${base_class}.${toggle_class}`).removeClass(toggle_class); | |||
} | |||
float(decimals = 2) { | |||
this.#is_float = true; | |||
this.is_editable = true; | |||
this.#decimals = decimals; | |||
if (this.type === 'input') { | |||
this.setInputFilter((value) => { | |||
return /^-?\d*[.,]?\d*$/.test(value); | |||
}); | |||
} | |||
return this; | |||
} | |||
int() { | |||
this.#is_int = true; | |||
this.is_editable = true; | |||
if (this.type === 'input') { | |||
this.setInputFilter((value) => { | |||
return /^-?\d*$/.test(value); | |||
}); | |||
} | |||
return this; | |||
} | |||
on(listener, fn, method = null, callBack = null) { | |||
if (typeof listener == "object") { | |||
for (let listen in listener) { | |||
if (!listener.hasOwnProperty(listen)) continue; | |||
this.set_listener(listener[listen], fn); | |||
} | |||
} else { | |||
this.set_listener(listener, fn, method, callBack); | |||
} | |||
return this; | |||
} | |||
on_listener(fn, listener) { | |||
Object.keys(this.listeners[listener]).forEach((f) => { | |||
fn(this.listeners[listener][f]); | |||
}); | |||
} | |||
set_listener(listener, fn, method = null, callBack = null) { | |||
if (typeof this.listeners[listener] == "object") { | |||
this.#listeners[listener].push(fn); | |||
} else { | |||
this.#listeners[listener] = [fn]; | |||
} | |||
setTimeout(() => { | |||
if (this.obj == null) return; | |||
this.on_listener((listen) => { | |||
this.obj.addEventListener(listener, (event) => { | |||
event.preventDefault(); | |||
event.stopPropagation(); | |||
if (this.is_disabled) return; | |||
if (method != null && method === "double_click") { | |||
if (this.click_attempts === 0) { | |||
this.#confirming = true; | |||
if (typeof this.text != "undefined" && this.text.length > 0) { | |||
this.val(__("Confirm")); | |||
} | |||
this.#click_attempts = 1; | |||
this.add_class(`${this.jshtml_identifier}-confirm`).JQ().delay(5000).queue((next) => { | |||
if (this.confirming) { | |||
this.reset_confirm(); | |||
next(); | |||
} | |||
}); | |||
} else { | |||
this.reset_confirm(); | |||
listen(this, this.obj, event, callBack); | |||
} | |||
} else { | |||
listen(this, this.obj, event, callBack); | |||
} | |||
}); | |||
}, listener, fn); | |||
}, 10); | |||
} | |||
set_content(content) { | |||
this.content = content; | |||
this.val(""); | |||
return this; | |||
} | |||
make_dom() { | |||
//setTimeout(() => { | |||
if (typeof this.wrapper == "undefined" || this.wrapper == null) { | |||
return this.html(); | |||
} else { | |||
$(this.wrapper).append(this.html()); | |||
} | |||
//}, 0); | |||
} | |||
html() { | |||
let template = this.template(); | |||
this.set_obj(); | |||
return template.replace("{{content_rendered}}", this.get_content_rendered()); | |||
} | |||
refresh() { | |||
this.content = this.get_content_rendered(); | |||
return this; | |||
} | |||
reset_confirm() { | |||
this.#confirming = false; | |||
this.#click_attempts = 0; | |||
this.remove_class(`${this.jshtml_identifier}-confirm`); | |||
this.val(this.text); | |||
return this; | |||
} | |||
get_content_rendered(text = null) { | |||
let _text = this.confirming ? __("Confirm") : this.text; | |||
if (typeof this.content != "undefined") { | |||
if (typeof _text != "undefined") { | |||
if (this.content.toString().search("{{text}}") === -1) { | |||
this.content = _text; | |||
return this.content; | |||
} else { | |||
return this.content.toString().replace("{{text}}", text || _text); | |||
} | |||
} else { | |||
if (text) { | |||
this.content = text; | |||
} | |||
return this.content; | |||
} | |||
} else { | |||
return ""; | |||
} | |||
} | |||
template() { | |||
return `<${this.tag} ${this.props_by_json(this.properties)}>{{content_rendered}}</${this.tag}>`; | |||
} | |||
set_selection() { | |||
if (this.type === 'input') { | |||
this.cursor_position = this.obj.selectionStart; | |||
} else { | |||
if ((this.is_int || this.is_float)) { | |||
if (!isNaN(parseFloat(this.value))) { | |||
this.in_decimal = false; | |||
this.cursor_position = parseInt(this.val()).toString().length | |||
} | |||
} else { | |||
this.#cursor_position = this.val().toString().length | |||
} | |||
} | |||
} | |||
default_listeners() { | |||
setTimeout(() => { | |||
if (this.is_editable) { | |||
this.on(["click", "onkeydown", "onkeypress"], (jshtml, obj, event) => { | |||
this.set_selection(); | |||
}); | |||
this.on("change", (jshtml) => { | |||
if (jshtml && !jshtml.pad_editing) this.value = this.type == 'input' ? this.JQ().val() : this.JQ().html(); | |||
}); | |||
} | |||
}, 0); | |||
} | |||
name() { | |||
return this.get_attr("name"); | |||
} | |||
get_attr(attr = "") { | |||
return this.obj.getAttribute(attr); | |||
} | |||
find(selector) { | |||
return this.JQ().find(selector); | |||
} | |||
enable(on_enable = true) { | |||
this.#disabled = false; | |||
setTimeout(() => { | |||
if (on_enable) { | |||
this.prop("disabled", false); | |||
} | |||
this.remove_class(this.jshtml_identifier); | |||
}, 0) | |||
return this; | |||
} | |||
disable(on_disable = true) { | |||
this.#disabled = true; | |||
setTimeout(() => { | |||
if (on_disable) { | |||
this.prop("disabled", true); | |||
} | |||
this.add_class(this.jshtml_identifier); | |||
}, 0); | |||
return this; | |||
} | |||
css(prop = "", val = "") { | |||
setTimeout(() => { | |||
if (this.obj) { | |||
if (typeof prop == "object") { | |||
prop.forEach((row) => { | |||
this.obj.style[row.prop] = row.value; | |||
}); | |||
} else { | |||
this.obj.style[prop] = val; | |||
} | |||
} | |||
}, 0); | |||
return this; | |||
} | |||
add_class(class_name) { | |||
if (typeof class_name == "object") { | |||
for (let c in class_name) { | |||
if (!class_name.hasOwnProperty(c)) continue; | |||
this.JQ().addClass(c); | |||
} | |||
} else { | |||
this.JQ().addClass(class_name); | |||
} | |||
return this; | |||
} | |||
has_class(class_name) { | |||
return this.obj && this.obj.classList.contains(class_name); | |||
} | |||
has_classes(classes) { | |||
let has_class = false; | |||
for (let c in classes) { | |||
if (!classes.hasOwnProperty(c)) continue; | |||
if (this.has_class(classes[c])) has_class = true; | |||
} | |||
return has_class; | |||
} | |||
JQ() { | |||
return $(this.obj); | |||
} | |||
select() { | |||
setTimeout(() => { | |||
this.obj.select(); | |||
}, 0) | |||
return this; | |||
} | |||
remove_class(class_name) { | |||
if (typeof class_name == "object") { | |||
for (let c in class_name) { | |||
if (!class_name.hasOwnProperty(c)) continue; | |||
this.JQ().removeClass(c); | |||
} | |||
} else { | |||
this.JQ().removeClass(class_name); | |||
} | |||
return this; | |||
} | |||
get is_disabled() { | |||
return this.disabled; | |||
} | |||
delete_selection(value, move_position = 1) { | |||
let current_value = this.val().toString(); | |||
let current_selection = window.getSelection().toString(); | |||
this.cursor_position = current_value.search(current_selection) + move_position; | |||
this.val(current_value.replace(current_selection, value)); | |||
} | |||
has_selection() { | |||
return window.window.getSelection().toString().length; | |||
} | |||
write(value) { | |||
if (this.is_disabled) return; | |||
value = value.toString(); | |||
if (this.has_selection()) { | |||
this.delete_selection(value); | |||
return; | |||
} | |||
let raw_value = this.type != 'input' ? this.raw_value : this.val(); | |||
let editable_value = raw_value | |||
let decimal_value = 0; | |||
if (raw_value.toString().length === 0) this.cursor_position = 0; | |||
if (this.is_float && this.type != 'input') { | |||
if (value === ".") { | |||
this.in_decimal = true; | |||
return; | |||
} | |||
if (!isNaN(parseFloat(raw_value))) { | |||
decimal_value = raw_value.toString().split(".")[1]; | |||
decimal_value = typeof decimal_value != "undefined" && !isNaN(parseInt(decimal_value)) ? parseInt(decimal_value).toString() : ""; | |||
raw_value = parseInt(raw_value).toString(); | |||
editable_value = raw_value; | |||
} | |||
} | |||
if (this.in_decimal) { | |||
if (this.cursor_position >= this.val().toString().length && this.val().toString().length > 0) return; | |||
if (this.cursor_position == 0) { | |||
if (decimal_value.toString().length > 1) { | |||
decimal_value = ""; | |||
} else { | |||
this.cursor_position = decimal_value.toString().length; | |||
} | |||
} | |||
editable_value = decimal_value; | |||
} | |||
let left_value = editable_value.toString().substring(0, this.cursor_position).toString(); | |||
let right_value = editable_value.toString().substring(this.cursor_position, editable_value.length).toString(); | |||
let new_vslue = ""; | |||
if (this.type != 'input') { | |||
if (this.in_decimal) { | |||
new_vslue = `${raw_value}.${left_value}${value}${right_value}`; | |||
} else { | |||
new_vslue = `${left_value}${value}${(parseInt(right_value) > 0 ? right_value : "")}${(decimal_value > 0 ? "." + decimal_value : "")}`; | |||
} | |||
} else { | |||
new_vslue = `${left_value}${value}${right_value}`; | |||
} | |||
this.pad_editing = true; | |||
this.val(new_vslue); | |||
this.cursor_position = this.cursor_position + 1; | |||
if (this.in_decimal && this.cursor_position > this.#decimals) { | |||
this.cursor_position = this.raw_value.toString().length; | |||
} | |||
if (this.raw_value.toString().length == 0) this.in_decimal = false; | |||
this.trigger("change"); | |||
this.pad_editing = false; | |||
} | |||
plus(value = 1) { | |||
this.val(this.float_val + value); | |||
this.focus(); | |||
return this; | |||
} | |||
minus(value = 1) { | |||
this.val(this.float_val - value); | |||
this.focus(); | |||
return this; | |||
} | |||
get is_int() { return typeof this.#is_int == 'undefined' ? false : this.#is_int } | |||
get is_float() { return typeof this.#is_float == 'undefined' ? false : this.#is_float } | |||
get value() { | |||
return this.#value | |||
} | |||
get raw_value() { | |||
if ((this.is_float || this.is_int)) { | |||
return isNaN(parseFloat(this.value)) ? "" : parseFloat(this.value).toString(); | |||
} else { | |||
return this.value.toString(); | |||
} | |||
} | |||
set value(val) { | |||
if (this.is_float) { | |||
this.#value = parseFloat(val).toFixed(2); | |||
if (isNaN(this.#value)) this.#value = ""; | |||
} else if (this.is_int) { | |||
this.#value = parseInt(val); | |||
if (isNaN(this.#value)) this.#value = ""; | |||
} else { | |||
this.#value = val; | |||
} | |||
} | |||
val(val = null, change = true, filter = false) { | |||
if (val == null) { | |||
if (typeof this.value == 'undefined') { | |||
this.value = this.type == 'input' ? this.JQ().val() : this.JQ().html(); | |||
} | |||
return this.value; | |||
} else { | |||
this.value = val; | |||
if (!filter && this.properties.input_type === "number") { | |||
if (this.value.toString().length > 0) { | |||
this.filter_number(); | |||
} | |||
} | |||
if (!this.#confirming) this.text = this.value; | |||
setTimeout(() => { | |||
if (this.type === "input") { | |||
this.JQ().val(this.value); | |||
if (change) this.trigger("change"); | |||
} else { | |||
this.empty().JQ().html(this.get_content_rendered(this.value)); | |||
} | |||
}, 0); | |||
return this; | |||
} | |||
} | |||
prepend(content) { | |||
this.JQ().prepend(content); | |||
return this; | |||
} | |||
append(content) { | |||
this.JQ().append(content); | |||
return this; | |||
} | |||
empty() { | |||
this.JQ().empty(); | |||
return this; | |||
} | |||
remove() { | |||
this.JQ().remove(); | |||
} | |||
hide() { | |||
this.add_class("hide"); | |||
this.css("display", 'none !important'); | |||
return this; | |||
} | |||
show() { | |||
this.remove_class("hide"); | |||
this.css("display", ''); | |||
return this; | |||
} | |||
prop(prop, value = "") { | |||
if (typeof prop == "object") { | |||
for (let p in prop) { | |||
if (!prop.hasOwnProperty(p)) continue; | |||
if (p === "disabled") { | |||
if (prop[p]) { | |||
this.disable(false) | |||
} else { | |||
this.enable(false); | |||
} | |||
} | |||
this.JQ().prop(p, prop[p]); | |||
} | |||
} else { | |||
if (prop === "disabled") { | |||
if (value) { | |||
this.disable(false) | |||
} else { | |||
this.enable(false); | |||
} | |||
} | |||
this.JQ().prop(prop, value); | |||
} | |||
return this | |||
} | |||
check_changes(last_val) { | |||
setTimeout(() => { | |||
let save_cursor_position = this.cursor_position; | |||
if (this.val() !== last_val) { | |||
this.trigger("change"); | |||
} | |||
this.cursor_position = save_cursor_position; | |||
this.focus(); | |||
}, 0); | |||
} | |||
delete_value() { | |||
if (this.is_disabled) return; | |||
let raw_value = this.raw_value; | |||
if (raw_value.toString().length == 0) return; | |||
if (this.has_selection()) { | |||
this.delete_selection("", 0); | |||
return; | |||
} | |||
let new_value = ""; | |||
let left_value = raw_value.substring(0, this.cursor_position); | |||
let right_value = raw_value.substring(this.cursor_position, raw_value.length); | |||
if (this.cursor_position === raw_value.length) { | |||
new_value = left_value.substring(0, raw_value.length - 1); | |||
this.cursor_position = this.cursor_position - 1;; | |||
} else { | |||
new_value = left_value.substring(0, this.cursor_position - 1) + right_value; | |||
this.cursor_position = this.cursor_position - 1; | |||
} | |||
if (this.cursor_position === 0 && raw_value.length > 0) { | |||
this.cursor_position = raw_value.length; | |||
} | |||
if (new_value.toString().length == 0) { | |||
this.in_decimal = false; | |||
} | |||
this.val(new_value); | |||
} | |||
trigger(event) { | |||
if (typeof this.listeners[event] != "undefined") { | |||
for (let listen in this.listeners[event]) { | |||
if (this.listeners[event].hasOwnProperty(listen)) { | |||
this.listeners[event][listen](); | |||
} | |||
} | |||
} | |||
this.focus(); | |||
} | |||
focus() { | |||
//this.cursor_position = this.cursor_position < 0 ? 0 : this.cursor_position; | |||
this.JQ().focus(); | |||
let pos = this.cursor_position; | |||
this.JQ().each(function (index, elem) { | |||
if (elem.setSelectionRange) { | |||
elem.setSelectionRange(pos, pos); | |||
} else if (elem.createTextRange) { | |||
let range = elem.createTextRange(); | |||
range.moveEnd('character', pos); | |||
range.moveStart('character', pos); | |||
range.select(); | |||
} | |||
}); | |||
}; | |||
setInputFilter(inputFilter) { | |||
setTimeout(() => { | |||
["input", "keydown", "keyup", "mousedown", "mouseup", "select", "contextmenu", "drop"].forEach((event) => { | |||
this.#obj.addEventListener(event, function () { | |||
if (inputFilter(this.value)) { | |||
this.oldValue = this.value; | |||
this.oldSelectionStart = this.selectionStart; | |||
this.oldSelectionEnd = this.selectionEnd; | |||
} else if (this.hasOwnProperty("oldValue")) { | |||
this.value = this.oldValue; | |||
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd); | |||
} else { | |||
this.value = ""; | |||
} | |||
}); | |||
}); | |||
}) | |||
} | |||
filter_number() { | |||
let inputFilter = (value) => { | |||
return /^-?\d*[.,]?\d*$/.test(value); | |||
} | |||
if (inputFilter(this.val())) { | |||
this.oldValue = this.val(); | |||
this.oldSelectionStart = this.selectionStart; | |||
this.oldSelectionEnd = this.selectionEnd; | |||
} else if (this.hasOwnProperty("oldValue")) { | |||
this.value = this.oldValue; | |||
if (this.type === 'inpunt') { | |||
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd); | |||
} | |||
} else { | |||
this.value = ""; | |||
} | |||
} | |||
props_by_json(props = {}) { | |||
let _html = ""; | |||
for (let prop in props) { | |||
if (!props.hasOwnProperty(prop)) continue; | |||
_html += `${prop}='${props[prop]}'`; | |||
} | |||
return _html; | |||
} | |||
uuid() { | |||
let id = 'xxxxxxxx4xxxyxxxxxxx'.replace(/[xy]/g, function (c) { | |||
let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); | |||
return v.toString(16); | |||
}); | |||
return "jshtml" + id; | |||
} | |||
highlight() { | |||
this.add_class(`${this.jshtml_identifier}-confirm`).JQ().delay(10000).queue((next) => { | |||
this.remove_class(`${this.jshtml_identifier}-confirm`); | |||
next(); | |||
}); | |||
} | |||
} | |||
influxframework.jshtml = (options) => { | |||
return new JSHtml(options) | |||
} |
@@ -0,0 +1,88 @@ | |||
class NumPad { | |||
#input = null; | |||
#html = ""; | |||
constructor(options) { | |||
Object.assign(this, options); | |||
this.make(); | |||
} | |||
set html(val){this.#html = val} | |||
set input(val){this.#input = val} | |||
get input(){return this.#input} | |||
get html(){return this.#html} | |||
make() { | |||
const default_class = `pad-col button btn-default`; | |||
let num_pads = [ | |||
{ | |||
7: {props: {class: "sm pad-btn"}}, | |||
8: {props: {class: "sm pad-btn"}}, | |||
9: {props: {class: "sm pad-btn"}}, | |||
Del: { | |||
props: {class: "md pad-btn"}, | |||
content: '<span class="fa fa-arrow-left pull-left" style="font-size: 16px; padding-top: 3px"></span>', | |||
action: "delete" | |||
}, | |||
}, | |||
{ | |||
4: {props: {class: "sm pad-btn"}}, | |||
5: {props: {class: "sm pad-btn"}}, | |||
6: {props: {class: "sm pad-btn"}}, | |||
Enter: { | |||
props: {class: "md pad-btn", rowspan: "3"}, | |||
content: '<br><br><span class="fa fa-level-down" style="font-size: 25px; transform: rotate(90deg);"></span>', | |||
action: "enter" | |||
}, | |||
}, | |||
{ | |||
1: {props: {class: "sm pad-btn"}}, | |||
2: {props: {class: "sm pad-btn"}}, | |||
3: {props: {class: "sm pad-btn"}}, | |||
}, | |||
{ | |||
0: {props: {class: "sm pad-btn", colspan: 2}}, | |||
'.': {props: {class: "sm pad-btn"}, action: "key"}, | |||
} | |||
]; | |||
let html = "<table class='pad-container'><tbody>"; | |||
num_pads.map(row => { | |||
html += "<tr class='pad-row'>"; | |||
Object.keys(row).map((key) => { | |||
let col = row[key]; | |||
col.props.class += ` ${default_class}-${key}`; | |||
html += `${ | |||
new JSHtml({ | |||
tag: "td", | |||
properties: col.props, | |||
content: `{{text}} ${typeof col.content != "undefined" ? col.content : ""}`, | |||
text: __(key), | |||
}).on("click", () => { | |||
if (col.action === "enter") { | |||
if (this.on_enter != null) { | |||
this.on_enter(); | |||
} | |||
} else if (this.input) { | |||
if (col.action === "delete") { | |||
this.input.delete_value(); | |||
} else { | |||
this.input.write(key); | |||
} | |||
} | |||
}, "").html() | |||
}` | |||
}); | |||
html += "</tr>"; | |||
}); | |||
html += "</tbody></table>"; | |||
this.html = html; | |||
if (typeof this.wrapper != "undefined") { | |||
$(this.wrapper).empty().append(this.html); | |||
} | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
from __future__ import unicode_literals | |||
import influxframework | |||
from itertools import chain | |||
import os | |||
import json | |||
from influxerp.setup.utils import insert_record | |||
from itertools import chain | |||
def after_install(): | |||
create_desk_forms() | |||
def insert_desk_form(form_data): | |||
desk_form = influxframework.new_doc("Desk Form") | |||
desk_form.update(form_data) | |||
desk_form.set("docstatus", 0) | |||
print(" Inserting Desk Form: {}".format(form_data.get("name"))) | |||
desk_form.insert() | |||
def create_desk_forms(): | |||
basedir = os.path.abspath(os.path.dirname(__file__)) | |||
apps_dir = basedir.split("apps")[0] + "apps" | |||
influxframework.db.sql("""DELETE FROM `tabDesk Form`""") | |||
influxframework.db.sql("""DELETE FROM `tabDesk Form Field`""") | |||
print("Building Desk Forms") | |||
for app_name in os.listdir(apps_dir): | |||
print(" Processing Desk Forms for {} App".format(app_name)) | |||
for dirpath, dirnames, filenames in os.walk(os.path.join(apps_dir, app_name, app_name, app_name, "desk_form")): | |||
for filename in filenames: | |||
_, extension = os.path.splitext(filename) | |||
if extension in ['.json']: | |||
abspath = os.path.join(dirpath, filename) | |||
f = open(abspath) | |||
insert_desk_form(json.load(f)) | |||
f.close() | |||
print("Building Desk Forms Complete") |
@@ -0,0 +1,675 @@ | |||
### GNU GENERAL PUBLIC LICENSE | |||
Version 3, 29 June 2007 | |||
Copyright (C) 2007 Free Software Foundation, Inc. | |||
<http://fsf.org/> | |||
Everyone is permitted to copy and distribute verbatim copies of this | |||
license document, but changing it is not allowed. | |||
### Preamble | |||
The GNU General Public License is a free, copyleft license for | |||
software and other kinds of works. | |||
The licenses for most software and other practical works are designed | |||
to take away your freedom to share and change the works. By contrast, | |||
the GNU General Public License is intended to guarantee your freedom | |||
to share and change all versions of a program--to make sure it remains | |||
free software for all its users. We, the Free Software Foundation, use | |||
the GNU General Public License for most of our software; it applies | |||
also to any other work released this way by its authors. You can apply | |||
it to your programs, too. | |||
When we speak of free software, we are referring to freedom, not | |||
price. Our General Public Licenses are designed to make sure that you | |||
have the freedom to distribute copies of free software (and charge for | |||
them if you wish), that you receive source code or can get it if you | |||
want it, that you can change the software or use pieces of it in new | |||
free programs, and that you know you can do these things. | |||
To protect your rights, we need to prevent others from denying you | |||
these rights or asking you to surrender the rights. Therefore, you | |||
have certain responsibilities if you distribute copies of the | |||
software, or if you modify it: responsibilities to respect the freedom | |||
of others. | |||
For example, if you distribute copies of such a program, whether | |||
gratis or for a fee, you must pass on to the recipients the same | |||
freedoms that you received. You must make sure that they, too, receive | |||
or can get the source code. And you must show them these terms so they | |||
know their rights. | |||
Developers that use the GNU GPL protect your rights with two steps: | |||
(1) assert copyright on the software, and (2) offer you this License | |||
giving you legal permission to copy, distribute and/or modify it. | |||
For the developers' and authors' protection, the GPL clearly explains | |||
that there is no warranty for this free software. For both users' and | |||
authors' sake, the GPL requires that modified versions be marked as | |||
changed, so that their problems will not be attributed erroneously to | |||
authors of previous versions. | |||
Some devices are designed to deny users access to install or run | |||
modified versions of the software inside them, although the | |||
manufacturer can do so. This is fundamentally incompatible with the | |||
aim of protecting users' freedom to change the software. The | |||
systematic pattern of such abuse occurs in the area of products for | |||
individuals to use, which is precisely where it is most unacceptable. | |||
Therefore, we have designed this version of the GPL to prohibit the | |||
practice for those products. If such problems arise substantially in | |||
other domains, we stand ready to extend this provision to those | |||
domains in future versions of the GPL, as needed to protect the | |||
freedom of users. | |||
Finally, every program is threatened constantly by software patents. | |||
States should not allow patents to restrict development and use of | |||
software on general-purpose computers, but in those that do, we wish | |||
to avoid the special danger that patents applied to a free program | |||
could make it effectively proprietary. To prevent this, the GPL | |||
assures that patents cannot be used to render the program non-free. | |||
The precise terms and conditions for copying, distribution and | |||
modification follow. | |||
### TERMS AND CONDITIONS | |||
#### 0. Definitions. | |||
"This License" refers to version 3 of the GNU General Public License. | |||
"Copyright" also means copyright-like laws that apply to other kinds | |||
of works, such as semiconductor masks. | |||
"The Program" refers to any copyrightable work licensed under this | |||
License. Each licensee is addressed as "you". "Licensees" and | |||
"recipients" may be individuals or organizations. | |||
To "modify" a work means to copy from or adapt all or part of the work | |||
in a fashion requiring copyright permission, other than the making of | |||
an exact copy. The resulting work is called a "modified version" of | |||
the earlier work or a work "based on" the earlier work. | |||
A "covered work" means either the unmodified Program or a work based | |||
on the Program. | |||
To "propagate" a work means to do anything with it that, without | |||
permission, would make you directly or secondarily liable for | |||
infringement under applicable copyright law, except executing it on a | |||
computer or modifying a private copy. Propagation includes copying, | |||
distribution (with or without modification), making available to the | |||
public, and in some countries other activities as well. | |||
To "convey" a work means any kind of propagation that enables other | |||
parties to make or receive copies. Mere interaction with a user | |||
through a computer network, with no transfer of a copy, is not | |||
conveying. | |||
An interactive user interface displays "Appropriate Legal Notices" to | |||
the extent that it includes a convenient and prominently visible | |||
feature that (1) displays an appropriate copyright notice, and (2) | |||
tells the user that there is no warranty for the work (except to the | |||
extent that warranties are provided), that licensees may convey the | |||
work under this License, and how to view a copy of this License. If | |||
the interface presents a list of user commands or options, such as a | |||
menu, a prominent item in the list meets this criterion. | |||
#### 1. Source Code. | |||
The "source code" for a work means the preferred form of the work for | |||
making modifications to it. "Object code" means any non-source form of | |||
a work. | |||
A "Standard Interface" means an interface that either is an official | |||
standard defined by a recognized standards body, or, in the case of | |||
interfaces specified for a particular programming language, one that | |||
is widely used among developers working in that language. | |||
The "System Libraries" of an executable work include anything, other | |||
than the work as a whole, that (a) is included in the normal form of | |||
packaging a Major Component, but which is not part of that Major | |||
Component, and (b) serves only to enable use of the work with that | |||
Major Component, or to implement a Standard Interface for which an | |||
implementation is available to the public in source code form. A | |||
"Major Component", in this context, means a major essential component | |||
(kernel, window system, and so on) of the specific operating system | |||
(if any) on which the executable work runs, or a compiler used to | |||
produce the work, or an object code interpreter used to run it. | |||
The "Corresponding Source" for a work in object code form means all | |||
the source code needed to generate, install, and (for an executable | |||
work) run the object code and to modify the work, including scripts to | |||
control those activities. However, it does not include the work's | |||
System Libraries, or general-purpose tools or generally available free | |||
programs which are used unmodified in performing those activities but | |||
which are not part of the work. For example, Corresponding Source | |||
includes interface definition files associated with source files for | |||
the work, and the source code for shared libraries and dynamically | |||
linked subprograms that the work is specifically designed to require, | |||
such as by intimate data communication or control flow between those | |||
subprograms and other parts of the work. | |||
The Corresponding Source need not include anything that users can | |||
regenerate automatically from other parts of the Corresponding Source. | |||
The Corresponding Source for a work in source code form is that same | |||
work. | |||
#### 2. Basic Permissions. | |||
All rights granted under this License are granted for the term of | |||
copyright on the Program, and are irrevocable provided the stated | |||
conditions are met. This License explicitly affirms your unlimited | |||
permission to run the unmodified Program. The output from running a | |||
covered work is covered by this License only if the output, given its | |||
content, constitutes a covered work. This License acknowledges your | |||
rights of fair use or other equivalent, as provided by copyright law. | |||
You may make, run and propagate covered works that you do not convey, | |||
without conditions so long as your license otherwise remains in force. | |||
You may convey covered works to others for the sole purpose of having | |||
them make modifications exclusively for you, or provide you with | |||
facilities for running those works, provided that you comply with the | |||
terms of this License in conveying all material for which you do not | |||
control copyright. Those thus making or running the covered works for | |||
you must do so exclusively on your behalf, under your direction and | |||
control, on terms that prohibit them from making any copies of your | |||
copyrighted material outside their relationship with you. | |||
Conveying under any other circumstances is permitted solely under the | |||
conditions stated below. Sublicensing is not allowed; section 10 makes | |||
it unnecessary. | |||
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. | |||
No covered work shall be deemed part of an effective technological | |||
measure under any applicable law fulfilling obligations under article | |||
11 of the WIPO copyright treaty adopted on 20 December 1996, or | |||
similar laws prohibiting or restricting circumvention of such | |||
measures. | |||
When you convey a covered work, you waive any legal power to forbid | |||
circumvention of technological measures to the extent such | |||
circumvention is effected by exercising rights under this License with | |||
respect to the covered work, and you disclaim any intention to limit | |||
operation or modification of the work as a means of enforcing, against | |||
the work's users, your or third parties' legal rights to forbid | |||
circumvention of technological measures. | |||
#### 4. Conveying Verbatim Copies. | |||
You may convey verbatim copies of the Program's source code as you | |||
receive it, in any medium, provided that you conspicuously and | |||
appropriately publish on each copy an appropriate copyright notice; | |||
keep intact all notices stating that this License and any | |||
non-permissive terms added in accord with section 7 apply to the code; | |||
keep intact all notices of the absence of any warranty; and give all | |||
recipients a copy of this License along with the Program. | |||
You may charge any price or no price for each copy that you convey, | |||
and you may offer support or warranty protection for a fee. | |||
#### 5. Conveying Modified Source Versions. | |||
You may convey a work based on the Program, or the modifications to | |||
produce it from the Program, in the form of source code under the | |||
terms of section 4, provided that you also meet all of these | |||
conditions: | |||
- a) The work must carry prominent notices stating that you modified | |||
it, and giving a relevant date. | |||
- b) The work must carry prominent notices stating that it is | |||
released under this License and any conditions added under | |||
section 7. This requirement modifies the requirement in section 4 | |||
to "keep intact all notices". | |||
- c) You must license the entire work, as a whole, under this | |||
License to anyone who comes into possession of a copy. This | |||
License will therefore apply, along with any applicable section 7 | |||
additional terms, to the whole of the work, and all its parts, | |||
regardless of how they are packaged. This License gives no | |||
permission to license the work in any other way, but it does not | |||
invalidate such permission if you have separately received it. | |||
- d) If the work has interactive user interfaces, each must display | |||
Appropriate Legal Notices; however, if the Program has interactive | |||
interfaces that do not display Appropriate Legal Notices, your | |||
work need not make them do so. | |||
A compilation of a covered work with other separate and independent | |||
works, which are not by their nature extensions of the covered work, | |||
and which are not combined with it such as to form a larger program, | |||
in or on a volume of a storage or distribution medium, is called an | |||
"aggregate" if the compilation and its resulting copyright are not | |||
used to limit the access or legal rights of the compilation's users | |||
beyond what the individual works permit. Inclusion of a covered work | |||
in an aggregate does not cause this License to apply to the other | |||
parts of the aggregate. | |||
#### 6. Conveying Non-Source Forms. | |||
You may convey a covered work in object code form under the terms of | |||
sections 4 and 5, provided that you also convey the machine-readable | |||
Corresponding Source under the terms of this License, in one of these | |||
ways: | |||
- a) Convey the object code in, or embodied in, a physical product | |||
(including a physical distribution medium), accompanied by the | |||
Corresponding Source fixed on a durable physical medium | |||
customarily used for software interchange. | |||
- b) Convey the object code in, or embodied in, a physical product | |||
(including a physical distribution medium), accompanied by a | |||
written offer, valid for at least three years and valid for as | |||
long as you offer spare parts or customer support for that product | |||
model, to give anyone who possesses the object code either (1) a | |||
copy of the Corresponding Source for all the software in the | |||
product that is covered by this License, on a durable physical | |||
medium customarily used for software interchange, for a price no | |||
more than your reasonable cost of physically performing this | |||
conveying of source, or (2) access to copy the Corresponding | |||
Source from a network server at no charge. | |||
- c) Convey individual copies of the object code with a copy of the | |||
written offer to provide the Corresponding Source. This | |||
alternative is allowed only occasionally and noncommercially, and | |||
only if you received the object code with such an offer, in accord | |||
with subsection 6b. | |||
- d) Convey the object code by offering access from a designated | |||
place (gratis or for a charge), and offer equivalent access to the | |||
Corresponding Source in the same way through the same place at no | |||
further charge. You need not require recipients to copy the | |||
Corresponding Source along with the object code. If the place to | |||
copy the object code is a network server, the Corresponding Source | |||
may be on a different server (operated by you or a third party) | |||
that supports equivalent copying facilities, provided you maintain | |||
clear directions next to the object code saying where to find the | |||
Corresponding Source. Regardless of what server hosts the | |||
Corresponding Source, you remain obligated to ensure that it is | |||
available for as long as needed to satisfy these requirements. | |||
- e) Convey the object code using peer-to-peer transmission, | |||
provided you inform other peers where the object code and | |||
Corresponding Source of the work are being offered to the general | |||
public at no charge under subsection 6d. | |||
A separable portion of the object code, whose source code is excluded | |||
from the Corresponding Source as a System Library, need not be | |||
included in conveying the object code work. | |||
A "User Product" is either (1) a "consumer product", which means any | |||
tangible personal property which is normally used for personal, | |||
family, or household purposes, or (2) anything designed or sold for | |||
incorporation into a dwelling. In determining whether a product is a | |||
consumer product, doubtful cases shall be resolved in favor of | |||
coverage. For a particular product received by a particular user, | |||
"normally used" refers to a typical or common use of that class of | |||
product, regardless of the status of the particular user or of the way | |||
in which the particular user actually uses, or expects or is expected | |||
to use, the product. A product is a consumer product regardless of | |||
whether the product has substantial commercial, industrial or | |||
non-consumer uses, unless such uses represent the only significant | |||
mode of use of the product. | |||
"Installation Information" for a User Product means any methods, | |||
procedures, authorization keys, or other information required to | |||
install and execute modified versions of a covered work in that User | |||
Product from a modified version of its Corresponding Source. The | |||
information must suffice to ensure that the continued functioning of | |||
the modified object code is in no case prevented or interfered with | |||
solely because modification has been made. | |||
If you convey an object code work under this section in, or with, or | |||
specifically for use in, a User Product, and the conveying occurs as | |||
part of a transaction in which the right of possession and use of the | |||
User Product is transferred to the recipient in perpetuity or for a | |||
fixed term (regardless of how the transaction is characterized), the | |||
Corresponding Source conveyed under this section must be accompanied | |||
by the Installation Information. But this requirement does not apply | |||
if neither you nor any third party retains the ability to install | |||
modified object code on the User Product (for example, the work has | |||
been installed in ROM). | |||
The requirement to provide Installation Information does not include a | |||
requirement to continue to provide support service, warranty, or | |||
updates for a work that has been modified or installed by the | |||
recipient, or for the User Product in which it has been modified or | |||
installed. Access to a network may be denied when the modification | |||
itself materially and adversely affects the operation of the network | |||
or violates the rules and protocols for communication across the | |||
network. | |||
Corresponding Source conveyed, and Installation Information provided, | |||
in accord with this section must be in a format that is publicly | |||
documented (and with an implementation available to the public in | |||
source code form), and must require no special password or key for | |||
unpacking, reading or copying. | |||
#### 7. Additional Terms. | |||
"Additional permissions" are terms that supplement the terms of this | |||
License by making exceptions from one or more of its conditions. | |||
Additional permissions that are applicable to the entire Program shall | |||
be treated as though they were included in this License, to the extent | |||
that they are valid under applicable law. If additional permissions | |||
apply only to part of the Program, that part may be used separately | |||
under those permissions, but the entire Program remains governed by | |||
this License without regard to the additional permissions. | |||
When you convey a copy of a covered work, you may at your option | |||
remove any additional permissions from that copy, or from any part of | |||
it. (Additional permissions may be written to require their own | |||
removal in certain cases when you modify the work.) You may place | |||
additional permissions on material, added by you to a covered work, | |||
for which you have or can give appropriate copyright permission. | |||
Notwithstanding any other provision of this License, for material you | |||
add to a covered work, you may (if authorized by the copyright holders | |||
of that material) supplement the terms of this License with terms: | |||
- a) Disclaiming warranty or limiting liability differently from the | |||
terms of sections 15 and 16 of this License; or | |||
- b) Requiring preservation of specified reasonable legal notices or | |||
author attributions in that material or in the Appropriate Legal | |||
Notices displayed by works containing it; or | |||
- c) Prohibiting misrepresentation of the origin of that material, | |||
or requiring that modified versions of such material be marked in | |||
reasonable ways as different from the original version; or | |||
- d) Limiting the use for publicity purposes of names of licensors | |||
or authors of the material; or | |||
- e) Declining to grant rights under trademark law for use of some | |||
trade names, trademarks, or service marks; or | |||
- f) Requiring indemnification of licensors and authors of that | |||
material by anyone who conveys the material (or modified versions | |||
of it) with contractual assumptions of liability to the recipient, | |||
for any liability that these contractual assumptions directly | |||
impose on those licensors and authors. | |||
All other non-permissive additional terms are considered "further | |||
restrictions" within the meaning of section 10. If the Program as you | |||
received it, or any part of it, contains a notice stating that it is | |||
governed by this License along with a term that is a further | |||
restriction, you may remove that term. If a license document contains | |||
a further restriction but permits relicensing or conveying under this | |||
License, you may add to a covered work material governed by the terms | |||
of that license document, provided that the further restriction does | |||
not survive such relicensing or conveying. | |||
If you add terms to a covered work in accord with this section, you | |||
must place, in the relevant source files, a statement of the | |||
additional terms that apply to those files, or a notice indicating | |||
where to find the applicable terms. | |||
Additional terms, permissive or non-permissive, may be stated in the | |||
form of a separately written license, or stated as exceptions; the | |||
above requirements apply either way. | |||
#### 8. Termination. | |||
You may not propagate or modify a covered work except as expressly | |||
provided under this License. Any attempt otherwise to propagate or | |||
modify it is void, and will automatically terminate your rights under | |||
this License (including any patent licenses granted under the third | |||
paragraph of section 11). | |||
However, if you cease all violation of this License, then your license | |||
from a particular copyright holder is reinstated (a) provisionally, | |||
unless and until the copyright holder explicitly and finally | |||
terminates your license, and (b) permanently, if the copyright holder | |||
fails to notify you of the violation by some reasonable means prior to | |||
60 days after the cessation. | |||
Moreover, your license from a particular copyright holder is | |||
reinstated permanently if the copyright holder notifies you of the | |||
violation by some reasonable means, this is the first time you have | |||
received notice of violation of this License (for any work) from that | |||
copyright holder, and you cure the violation prior to 30 days after | |||
your receipt of the notice. | |||
Termination of your rights under this section does not terminate the | |||
licenses of parties who have received copies or rights from you under | |||
this License. If your rights have been terminated and not permanently | |||
reinstated, you do not qualify to receive new licenses for the same | |||
material under section 10. | |||
#### 9. Acceptance Not Required for Having Copies. | |||
You are not required to accept this License in order to receive or run | |||
a copy of the Program. Ancillary propagation of a covered work | |||
occurring solely as a consequence of using peer-to-peer transmission | |||
to receive a copy likewise does not require acceptance. However, | |||
nothing other than this License grants you permission to propagate or | |||
modify any covered work. These actions infringe copyright if you do | |||
not accept this License. Therefore, by modifying or propagating a | |||
covered work, you indicate your acceptance of this License to do so. | |||
#### 10. Automatic Licensing of Downstream Recipients. | |||
Each time you convey a covered work, the recipient automatically | |||
receives a license from the original licensors, to run, modify and | |||
propagate that work, subject to this License. You are not responsible | |||
for enforcing compliance by third parties with this License. | |||
An "entity transaction" is a transaction transferring control of an | |||
organization, or substantially all assets of one, or subdividing an | |||
organization, or merging organizations. If propagation of a covered | |||
work results from an entity transaction, each party to that | |||
transaction who receives a copy of the work also receives whatever | |||
licenses to the work the party's predecessor in interest had or could | |||
give under the previous paragraph, plus a right to possession of the | |||
Corresponding Source of the work from the predecessor in interest, if | |||
the predecessor has it or can get it with reasonable efforts. | |||
You may not impose any further restrictions on the exercise of the | |||
rights granted or affirmed under this License. For example, you may | |||
not impose a license fee, royalty, or other charge for exercise of | |||
rights granted under this License, and you may not initiate litigation | |||
(including a cross-claim or counterclaim in a lawsuit) alleging that | |||
any patent claim is infringed by making, using, selling, offering for | |||
sale, or importing the Program or any portion of it. | |||
#### 11. Patents. | |||
A "contributor" is a copyright holder who authorizes use under this | |||
License of the Program or a work on which the Program is based. The | |||
work thus licensed is called the contributor's "contributor version". | |||
A contributor's "essential patent claims" are all patent claims owned | |||
or controlled by the contributor, whether already acquired or | |||
hereafter acquired, that would be infringed by some manner, permitted | |||
by this License, of making, using, or selling its contributor version, | |||
but do not include claims that would be infringed only as a | |||
consequence of further modification of the contributor version. For | |||
purposes of this definition, "control" includes the right to grant | |||
patent sublicenses in a manner consistent with the requirements of | |||
this License. | |||
Each contributor grants you a non-exclusive, worldwide, royalty-free | |||
patent license under the contributor's essential patent claims, to | |||
make, use, sell, offer for sale, import and otherwise run, modify and | |||
propagate the contents of its contributor version. | |||
In the following three paragraphs, a "patent license" is any express | |||
agreement or commitment, however denominated, not to enforce a patent | |||
(such as an express permission to practice a patent or covenant not to | |||
sue for patent infringement). To "grant" such a patent license to a | |||
party means to make such an agreement or commitment not to enforce a | |||
patent against the party. | |||
If you convey a covered work, knowingly relying on a patent license, | |||
and the Corresponding Source of the work is not available for anyone | |||
to copy, free of charge and under the terms of this License, through a | |||
publicly available network server or other readily accessible means, | |||
then you must either (1) cause the Corresponding Source to be so | |||
available, or (2) arrange to deprive yourself of the benefit of the | |||
patent license for this particular work, or (3) arrange, in a manner | |||
consistent with the requirements of this License, to extend the patent | |||
license to downstream recipients. "Knowingly relying" means you have | |||
actual knowledge that, but for the patent license, your conveying the | |||
covered work in a country, or your recipient's use of the covered work | |||
in a country, would infringe one or more identifiable patents in that | |||
country that you have reason to believe are valid. | |||
If, pursuant to or in connection with a single transaction or | |||
arrangement, you convey, or propagate by procuring conveyance of, a | |||
covered work, and grant a patent license to some of the parties | |||
receiving the covered work authorizing them to use, propagate, modify | |||
or convey a specific copy of the covered work, then the patent license | |||
you grant is automatically extended to all recipients of the covered | |||
work and works based on it. | |||
A patent license is "discriminatory" if it does not include within the | |||
scope of its coverage, prohibits the exercise of, or is conditioned on | |||
the non-exercise of one or more of the rights that are specifically | |||
granted under this License. You may not convey a covered work if you | |||
are a party to an arrangement with a third party that is in the | |||
business of distributing software, under which you make payment to the | |||
third party based on the extent of your activity of conveying the | |||
work, and under which the third party grants, to any of the parties | |||
who would receive the covered work from you, a discriminatory patent | |||
license (a) in connection with copies of the covered work conveyed by | |||
you (or copies made from those copies), or (b) primarily for and in | |||
connection with specific products or compilations that contain the | |||
covered work, unless you entered into that arrangement, or that patent | |||
license was granted, prior to 28 March 2007. | |||
Nothing in this License shall be construed as excluding or limiting | |||
any implied license or other defenses to infringement that may | |||
otherwise be available to you under applicable patent law. | |||
#### 12. No Surrender of Others' Freedom. | |||
If conditions are imposed on you (whether by court order, agreement or | |||
otherwise) that contradict the conditions of this License, they do not | |||
excuse you from the conditions of this License. If you cannot convey a | |||
covered work so as to satisfy simultaneously your obligations under | |||
this License and any other pertinent obligations, then as a | |||
consequence you may not convey it at all. For example, if you agree to | |||
terms that obligate you to collect a royalty for further conveying | |||
from those to whom you convey the Program, the only way you could | |||
satisfy both those terms and this License would be to refrain entirely | |||
from conveying the Program. | |||
#### 13. Use with the GNU Affero General Public License. | |||
Notwithstanding any other provision of this License, you have | |||
permission to link or combine any covered work with a work licensed | |||
under version 3 of the GNU Affero General Public License into a single | |||
combined work, and to convey the resulting work. The terms of this | |||
License will continue to apply to the part which is the covered work, | |||
but the special requirements of the GNU Affero General Public License, | |||
section 13, concerning interaction through a network will apply to the | |||
combination as such. | |||
#### 14. Revised Versions of this License. | |||
The Free Software Foundation may publish revised and/or new versions | |||
of the GNU General Public License from time to time. Such new versions | |||
will be similar in spirit to the present version, but may differ in | |||
detail to address new problems or concerns. | |||
Each version is given a distinguishing version number. If the Program | |||
specifies that a certain numbered version of the GNU General Public | |||
License "or any later version" applies to it, you have the option of | |||
following the terms and conditions either of that numbered version or | |||
of any later version published by the Free Software Foundation. If the | |||
Program does not specify a version number of the GNU General Public | |||
License, you may choose any version ever published by the Free | |||
Software Foundation. | |||
If the Program specifies that a proxy can decide which future versions | |||
of the GNU General Public License can be used, that proxy's public | |||
statement of acceptance of a version permanently authorizes you to | |||
choose that version for the Program. | |||
Later license versions may give you additional or different | |||
permissions. However, no additional obligations are imposed on any | |||
author or copyright holder as a result of your choosing to follow a | |||
later version. | |||
#### 15. Disclaimer of Warranty. | |||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | |||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | |||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT | |||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND | |||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE | |||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR | |||
CORRECTION. | |||
#### 16. Limitation of Liability. | |||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR | |||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | |||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES | |||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT | |||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR | |||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM | |||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER | |||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. | |||
#### 17. Interpretation of Sections 15 and 16. | |||
If the disclaimer of warranty and limitation of liability provided | |||
above cannot be given local legal effect according to their terms, | |||
reviewing courts shall apply local law that most closely approximates | |||
an absolute waiver of all civil liability in connection with the | |||
Program, unless a warranty or assumption of liability accompanies a | |||
copy of the Program in return for a fee. | |||
END OF TERMS AND CONDITIONS | |||
### How to Apply These Terms to Your New Programs | |||
If you develop a new program, and you want it to be of the greatest | |||
possible use to the public, the best way to achieve this is to make it | |||
free software which everyone can redistribute and change under these | |||
terms. | |||
To do so, attach the following notices to the program. It is safest to | |||
attach them to the start of each source file to most effectively state | |||
the exclusion of warranty; and each file should have at least the | |||
"copyright" line and a pointer to where the full notice is found. | |||
<one line to give the program's name and a brief idea of what it does.> | |||
Copyright (C) <year> <name of author> | |||
This program is free software: you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation, either version 3 of the License, or | |||
(at your option) any later version. | |||
This program is distributed in the hope that it will be useful, | |||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
GNU General Public License for more details. | |||
You should have received a copy of the GNU General Public License | |||
along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
Also add information on how to contact you by electronic and paper | |||
mail. | |||
If the program does terminal interaction, make it output a short | |||
notice like this when it starts in an interactive mode: | |||
<program> Copyright (C) <year> <name of author> | |||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |||
This is free software, and you are welcome to redistribute it | |||
under certain conditions; type `show c' for details. | |||
The hypothetical commands \`show w' and \`show c' should show the | |||
appropriate parts of the General Public License. Of course, your | |||
program's commands might be different; for a GUI interface, you would | |||
use an "about box". | |||
You should also get your employer (if you work as a programmer) or | |||
institute, if any, to sign a "copyright disclaimer" for the program, if | |||
necessary. For more information on this, and how to apply and follow | |||
the GNU GPL, see <http://www.gnu.org/licenses/>. | |||
The GNU General Public License does not permit incorporating your | |||
program into proprietary programs. If your program is a subroutine | |||
library, you may consider it more useful to permit linking proprietary | |||
applications with the library. If this is what you want to do, use the | |||
GNU Lesser General Public License instead of this License. But first, | |||
please read <http://www.gnu.org/philosophy/why-not-lgpl.html>. |
@@ -0,0 +1,20 @@ | |||
# -*- coding: utf-8 -*- | |||
from setuptools import setup, find_packages | |||
with open('requirements.txt') as f: | |||
install_requires = f.read().strip().split('\n') | |||
# get version from __version__ variable in influxframework_helper/__init__.py | |||
from influxframework_helper import __version__ as version | |||
setup( | |||
name='influxframework_helper', | |||
version=version, | |||
description='InfluxFramework Helper', | |||
author='Quantum Bit Core', | |||
author_email='qubitcore.io@gmail.com', | |||
packages=find_packages(), | |||
zip_safe=False, | |||
include_package_data=True, | |||
install_requires=install_requires | |||
) |