From 0499eb8a7ae477b46b9b544ae9f0f6dff764f7b9 Mon Sep 17 00:00:00 2001 From: Anoop Date: Tue, 13 Dec 2022 12:36:55 +0530 Subject: [PATCH] Rebranded Commit --- .gitignore | 8 + MANIFEST.in | 18 + README.md | 49 ++ influxframework_helper/__init__.py | 5 + influxframework_helper/api.py | 73 ++ influxframework_helper/config/__init__.py | 0 influxframework_helper/config/desktop.py | 14 + influxframework_helper/config/docs.py | 11 + influxframework_helper/hooks.py | 193 +++++ .../influxframework_helper/__init__.py | 0 .../doctype/__init__.py | 0 .../doctype/desk_form/__init__.py | 0 .../doctype/desk_form/desk_form.js | 116 +++ .../doctype/desk_form/desk_form.json | 274 +++++++ .../doctype/desk_form/desk_form.py | 634 +++++++++++++++ .../desk_form/templates/desk_form.html | 7 + .../desk_form/templates/desk_form_row.html | 4 + .../doctype/desk_form/test_desk_form.py | 10 + .../doctype/desk_form_field/__init__.py | 0 .../desk_form_field/desk_form_field.json | 159 ++++ .../desk_form_field/desk_form_field.py | 10 + influxframework_helper/modules.txt | 1 + influxframework_helper/patches.txt | 0 .../public/css/desk-form.css | 9 + .../public/css/influxframework-helper.css | 50 ++ influxframework_helper/public/css/num-pad.css | 90 +++ .../public/js/desk-form-class.js | 208 +++++ .../public/js/desk-modal.js | 174 +++++ .../public/js/influxframework-form-class.js | 378 +++++++++ .../public/js/influxframework-helper-api.js | 29 + .../public/js/jshtml-class.js | 721 ++++++++++++++++++ .../public/js/num-pad-class.js | 88 +++ influxframework_helper/setup/install.py | 45 ++ influxframework_helper/templates/__init__.py | 0 .../templates/pages/__init__.py | 0 license.txt | 675 ++++++++++++++++ requirements.txt | 0 setup.py | 20 + 38 files changed, 4073 insertions(+) create mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 influxframework_helper/__init__.py create mode 100644 influxframework_helper/api.py create mode 100644 influxframework_helper/config/__init__.py create mode 100644 influxframework_helper/config/desktop.py create mode 100644 influxframework_helper/config/docs.py create mode 100644 influxframework_helper/hooks.py create mode 100644 influxframework_helper/influxframework_helper/__init__.py create mode 100644 influxframework_helper/influxframework_helper/doctype/__init__.py create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form/__init__.py create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.js create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.json create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.py create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form/templates/desk_form.html create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form/templates/desk_form_row.html create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form/test_desk_form.py create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form_field/__init__.py create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form_field/desk_form_field.json create mode 100644 influxframework_helper/influxframework_helper/doctype/desk_form_field/desk_form_field.py create mode 100644 influxframework_helper/modules.txt create mode 100644 influxframework_helper/patches.txt create mode 100644 influxframework_helper/public/css/desk-form.css create mode 100644 influxframework_helper/public/css/influxframework-helper.css create mode 100644 influxframework_helper/public/css/num-pad.css create mode 100644 influxframework_helper/public/js/desk-form-class.js create mode 100644 influxframework_helper/public/js/desk-modal.js create mode 100644 influxframework_helper/public/js/influxframework-form-class.js create mode 100644 influxframework_helper/public/js/influxframework-helper-api.js create mode 100644 influxframework_helper/public/js/jshtml-class.js create mode 100644 influxframework_helper/public/js/num-pad-class.js create mode 100644 influxframework_helper/setup/install.py create mode 100644 influxframework_helper/templates/__init__.py create mode 100644 influxframework_helper/templates/pages/__init__.py create mode 100644 license.txt create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97adc5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +*.pyc +*.egg-info +*.swp +tags +.idea +/.idea/ +%SystemDrive%/ProgramData/Microsoft/Windows/Caches/cversions.2.db diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..b3f799f --- /dev/null +++ b/MANIFEST.in @@ -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 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..23b179d --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +
+ +

InfluxFramework Helper

+
+ +___ +> ### 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). \ No newline at end of file diff --git a/influxframework_helper/__init__.py b/influxframework_helper/__init__.py new file mode 100644 index 0000000..58d8172 --- /dev/null +++ b/influxframework_helper/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +__version__ = '0.1.9' + diff --git a/influxframework_helper/api.py b/influxframework_helper/api.py new file mode 100644 index 0000000..79f6978 --- /dev/null +++ b/influxframework_helper/api.py @@ -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 {0} 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' + diff --git a/influxframework_helper/config/__init__.py b/influxframework_helper/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/influxframework_helper/config/desktop.py b/influxframework_helper/config/desktop.py new file mode 100644 index 0000000..d54c5ff --- /dev/null +++ b/influxframework_helper/config/desktop.py @@ -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") + } + ] diff --git a/influxframework_helper/config/docs.py b/influxframework_helper/config/docs.py new file mode 100644 index 0000000..70e97ee --- /dev/null +++ b/influxframework_helper/config/docs.py @@ -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" diff --git a/influxframework_helper/hooks.py b/influxframework_helper/hooks.py new file mode 100644 index 0000000..d7c5fff --- /dev/null +++ b/influxframework_helper/hooks.py @@ -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 +# ------------------ + +# 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 +# ------------------ + +# 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}" + } +] + diff --git a/influxframework_helper/influxframework_helper/__init__.py b/influxframework_helper/influxframework_helper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/influxframework_helper/influxframework_helper/doctype/__init__.py b/influxframework_helper/influxframework_helper/doctype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form/__init__.py b/influxframework_helper/influxframework_helper/doctype/desk_form/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.js b/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.js new file mode 100644 index 0000000..cea473b --- /dev/null +++ b/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.js @@ -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"]; + } +}); diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.json b/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.json new file mode 100644 index 0000000..de0ce1c --- /dev/null +++ b/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.json @@ -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 Client Script API and Examples", + "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 +} \ No newline at end of file diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.py b/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.py new file mode 100644 index 0000000..3390363 --- /dev/null +++ b/influxframework_helper/influxframework_helper/doctype/desk_form/desk_form.py @@ -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 + '
' + '
'.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", + "
")).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:') + '

' + + '
'.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)) diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form/templates/desk_form.html b/influxframework_helper/influxframework_helper/doctype/desk_form/templates/desk_form.html new file mode 100644 index 0000000..db12309 --- /dev/null +++ b/influxframework_helper/influxframework_helper/doctype/desk_form/templates/desk_form.html @@ -0,0 +1,7 @@ +{% extends "templates/web.html" %} + +{% block page_content %} +

{{ title }}

+{% endblock %} + + \ No newline at end of file diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form/templates/desk_form_row.html b/influxframework_helper/influxframework_helper/doctype/desk_form/templates/desk_form_row.html new file mode 100644 index 0000000..d7014b4 --- /dev/null +++ b/influxframework_helper/influxframework_helper/doctype/desk_form/templates/desk_form_row.html @@ -0,0 +1,4 @@ +
+ {{ doc.title or doc.name }} +
+ diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form/test_desk_form.py b/influxframework_helper/influxframework_helper/doctype/desk_form/test_desk_form.py new file mode 100644 index 0000000..eb803f0 --- /dev/null +++ b/influxframework_helper/influxframework_helper/doctype/desk_form/test_desk_form.py @@ -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 diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form_field/__init__.py b/influxframework_helper/influxframework_helper/doctype/desk_form_field/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form_field/desk_form_field.json b/influxframework_helper/influxframework_helper/doctype/desk_form_field/desk_form_field.json new file mode 100644 index 0000000..08c57a5 --- /dev/null +++ b/influxframework_helper/influxframework_helper/doctype/desk_form_field/desk_form_field.json @@ -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" +} \ No newline at end of file diff --git a/influxframework_helper/influxframework_helper/doctype/desk_form_field/desk_form_field.py b/influxframework_helper/influxframework_helper/doctype/desk_form_field/desk_form_field.py new file mode 100644 index 0000000..94690e2 --- /dev/null +++ b/influxframework_helper/influxframework_helper/doctype/desk_form_field/desk_form_field.py @@ -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 diff --git a/influxframework_helper/modules.txt b/influxframework_helper/modules.txt new file mode 100644 index 0000000..217b6b2 --- /dev/null +++ b/influxframework_helper/modules.txt @@ -0,0 +1 @@ +InfluxFramework Helper \ No newline at end of file diff --git a/influxframework_helper/patches.txt b/influxframework_helper/patches.txt new file mode 100644 index 0000000..e69de29 diff --git a/influxframework_helper/public/css/desk-form.css b/influxframework_helper/public/css/desk-form.css new file mode 100644 index 0000000..b05adca --- /dev/null +++ b/influxframework_helper/public/css/desk-form.css @@ -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; +} \ No newline at end of file diff --git a/influxframework_helper/public/css/influxframework-helper.css b/influxframework_helper/public/css/influxframework-helper.css new file mode 100644 index 0000000..9f8d97c --- /dev/null +++ b/influxframework_helper/public/css/influxframework-helper.css @@ -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%; + } +} \ No newline at end of file diff --git a/influxframework_helper/public/css/num-pad.css b/influxframework_helper/public/css/num-pad.css new file mode 100644 index 0000000..df5e434 --- /dev/null +++ b/influxframework_helper/public/css/num-pad.css @@ -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; +} \ No newline at end of file diff --git a/influxframework_helper/public/js/desk-form-class.js b/influxframework_helper/public/js/desk-form-class.js new file mode 100644 index 0000000..89e08e8 --- /dev/null +++ b/influxframework_helper/public/js/desk-form-class.js @@ -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; + } +} \ No newline at end of file diff --git a/influxframework_helper/public/js/desk-modal.js b/influxframework_helper/public/js/desk-modal.js new file mode 100644 index 0000000..5f6b6cc --- /dev/null +++ b/influxframework_helper/public/js/desk-modal.js @@ -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("").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( + "
" + __("Loading") + "...
" + ); + } + + 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 ` +
+
+ +
+
+ `; + } + + 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; + } +} \ No newline at end of file diff --git a/influxframework_helper/public/js/influxframework-form-class.js b/influxframework_helper/public/js/influxframework-form-class.js new file mode 100644 index 0000000..a51cc62 --- /dev/null +++ b/influxframework_helper/public/js/influxframework-form-class.js @@ -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); + } +} \ No newline at end of file diff --git a/influxframework_helper/public/js/influxframework-helper-api.js b/influxframework_helper/public/js/influxframework-helper-api.js new file mode 100644 index 0000000..765f2b7 --- /dev/null +++ b/influxframework_helper/public/js/influxframework-helper-api.js @@ -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(); \ No newline at end of file diff --git a/influxframework_helper/public/js/jshtml-class.js b/influxframework_helper/public/js/jshtml-class.js new file mode 100644 index 0000000..1a68869 --- /dev/null +++ b/influxframework_helper/public/js/jshtml-class.js @@ -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}}`; + } + + 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) +} \ No newline at end of file diff --git a/influxframework_helper/public/js/num-pad-class.js b/influxframework_helper/public/js/num-pad-class.js new file mode 100644 index 0000000..a7d49f6 --- /dev/null +++ b/influxframework_helper/public/js/num-pad-class.js @@ -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: '', + 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: '

', + 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 = ""; + num_pads.map(row => { + html += ""; + + 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 += ""; + }); + html += "
"; + + this.html = html; + + if (typeof this.wrapper != "undefined") { + $(this.wrapper).empty().append(this.html); + } + } +} \ No newline at end of file diff --git a/influxframework_helper/setup/install.py b/influxframework_helper/setup/install.py new file mode 100644 index 0000000..e198830 --- /dev/null +++ b/influxframework_helper/setup/install.py @@ -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") diff --git a/influxframework_helper/templates/__init__.py b/influxframework_helper/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/influxframework_helper/templates/pages/__init__.py b/influxframework_helper/templates/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..a238a97 --- /dev/null +++ b/license.txt @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 . + +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 . \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fa2c957 --- /dev/null +++ b/setup.py @@ -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 +)