How to create an "Absolute Minimum Script Report"?

With a huge vote of gratitude to @Maxwell_Morais, here’s what I have been able to put together.

Hopefully this will be helpful to others in the future.


Before

Beginning with the state of the system before trying to create a Script Report, here is the initial content of the directory hierarchy of my new app “returnables”:

├── LICENSE
├── license.txt
├── MANIFEST.in
├── __pycache__
├── README.md
├── requirements.txt
├── returnable
│   ├── config
│   ├── fixtures
│   ├── hooks.py
│   ├── __init__.py
│   ├── modules.txt
│   ├── patches.txt
│   ├── __pycache__
│   ├── returnable
│   │   ├── doctype
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   ├── returnable
│   │   ├── __init__.py
│   │   └── __pycache__
│   └── templates
├── returnable.egg-info
└── setup.py

Here’s the database query to get the Script Report:

MariaDB [_100fd7dc74a5feb0]> select * from `tabReport` where name = 'Minimal Script Report' \G
Empty set (0.000 sec)

I prepare a new report form like this:

After saving it and adding the minimal code shown in the first post it looks like this:

Re-examining my app directory hierarchy I find that it is unchanged from before. This would be because is_standard is not yet selected (as @Maxwell_Morais pointed out).

Meanwhile, the database query now returns:

MariaDB [_100fd7dc74a5feb0]> select * from `tabReport` where name = 'Minimal Script Report' \G
*************************** 1. row ***************************
                   name: Minimal Script Report
               creation: 2020-09-28 19:32:39.672696
               modified: 2020-09-28 19:37:32.407572
            modified_by: Administrator
                  owner: Administrator
              docstatus: 0
                 parent: NULL
            parentfield: NULL
             parenttype: NULL
                    idx: 0
            report_name: Minimal Script Report
            ref_doctype: Returnable
       reference_report: NULL
            is_standard: No
                 module: Returnable
            report_type: Script Report
            letter_head: NULL
          add_total_row: 1
               disabled: 0
disable_prepared_report: 0
        prepared_report: 0
                  query: NULL
             javascript: frappe.query_reports["Minimal Script Report"] = {
  "filters": [
    {
      fieldname: "someField",
      label: "Some Field",
      fieldtype: "Select",
      options: "30\n45\n60",
      default: 30
    },
  ]
};

          report_script: def get_columns():
  return [
    {
      "fieldname": "someField",
      "fieldtype": "Int",
      "label": "Some Field",
      "width": 100
    },
  ]

def get_data(fltr):
  values = [
    { "someField":  30 },
    { "someField":  45 },
    { "someField":  60 },
    { "someField":  75 },
    { "someField":  90 },
    { "someField": 105 }
  ]

  return [ v for v in values if v["someField"] > int(fltr.someField) ]

def execute(filters=None):
  return get_columns(), get_data(filters)

                   json: NULL
             _user_tags: NULL
              _comments: NULL
                _assign: NULL
              _liked_by: NULL
1 row in set (0.000 sec)

Clearly, this is not meaningfully different from the result I showed in my original post.

Also, sadly, adding filters as a parameter to the execute function (def execute(filters=None):) made only a little difference; running the report produces:


The next thing I tried was to set is_standard to true, and save again. I discovered that:

  • The advantage of doing so is to 1) have the required files generated on disk 2) have a report that actually displays data!
  • The disadvantage of doing it is that the report metadata can no longer be altered. Only the newly created files can be altered.

With that done the app directory now contains:

     :     :     :     :     :
│   ├── patches.txt
│   ├── __pycache__
│   ├── returnable
│   │   ├── doctype
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   ├── returnable
│   │   ├── __init__.py
│   │   └── __pycache__
│   │   └── report
│   │       ├── __init__.py
│   │       └── minimal_script_report
│   │           ├── __init__.py
│   │           ├── minimal_script_report.js
│   │           ├── minimal_script_report.json
│   │           └── minimal_script_report.py
│   └── templates
     :     :     :     :     :

The previous database query result is unchanged except for …

     :     :     :     :     :
            is_standard: Yes
     :     :     :     :     :

… which is reflected in the contents of the json file :

erpdev@erpserver:~/frappe-bench-DYPW/apps$ jq -r '.' returnable/returnable/returnable/report/minimal_script_report/minimal_script_report.json 
{
  "add_total_row": 1,
  "creation": "2020-09-28 10:01:52.986921",
  "disable_prepared_report": 0,
  "disabled": 0,
  "docstatus": 0,
  "doctype": "Report",
  "idx": 0,
  "is_standard": "Yes",
  "javascript": "frappe.query_reports[\"Minimal Script Report\"] = {\n  \"filters\": [\n    {\n      fieldname: \"someField\",\n      label: \"Some Field\",\n      fieldtype: \"Select\",\n      options: [30, 60, 90],\n      default: 30\n    },\n  ]\n};\n",
  "modified": "2020-09-28 15:20:52.097912",
  "modified_by": "Administrator",
  "module": "Returnable",
  "name": "Minimal Script Report",
  "owner": "Administrator",
  "prepared_report": 0,
  "ref_doctype": "Returnable",
  "report_name": "Minimal Script Report",
  "report_script": "def get_columns():\n  return [\n    {\n      \"fieldname\": \"someField\",\n      \"fieldtype\": \"Int\",\n      \"label\": \"Some Field\",\n      \"width\": 100\n    },\n  ]\n\n\ndef get_data():\n  return [ { \"someField\": 30 }, { \"someField\": 60 } ]\n\n\ndef execute(filters=None):\n  return get_columns(), get_data()\n",
  "report_type": "Script Report",
  "roles": [
    {
      "role": "System Manager"
    },
    {
      "role": "Stock User"
    },
    {
      "role": "Stock Manager"
    },
    {
      "role": "Purchase User"
    },
    {
      "role": "Sales User"
    },
    {
      "role": "Sales Manager"
    },
    {
      "role": "Sales Master Manager"
    },
    {
      "role": "Purchase Manager"
    },
    {
      "role": "Purchase Master Manager"
    }
  ]
}
erpdev@erpserver:~/frappe-bench-DYPW/apps$

A critical fact is the lack of code in the JavaScript and the Python file!

You only get …

frappe.query_reports["Minimal Script Report"] = {
	"filters": [

	]
};

… and …

from __future__ import unicode_literals
# import frappe

def execute(filters=None):
	columns, data = [], []
	return columns, data

Getting it working

I replaced the above placeholder code snippets with the intended code …

minimal_script_report.js

frappe.query_reports["Minimal Script Report"] = {
  "filters": [
    {
      fieldname: "someField",
      label: "Some Field",
      fieldtype: "Select",
      options: "30\n45\n60",
      default: 30
    },
  ]
};

minimal_script_report.py

def get_columns():
  return [
    {
      "fieldname": "someField",
      "fieldtype": "Int",
      "label": "Some Field",
      "width": 100
    },
  ]

def get_data(fltr):
  values = [
    { "someField":  30 },
    { "someField":  45 },
    { "someField":  60 },
    { "someField":  75 },
    { "someField":  90 },
    { "someField": 105 }
  ]
  return [ v for v in values if v["someField"] > int(fltr.someField) ]

def execute(filters=None):
  return get_columns(), get_data(filters)

… which results in:

Curious fact

I deleted the javascript and reportscript field values both 1) in the database record and 2) in the file returnable/returnable/returnable/report/minimal_script_report/minimal_script_report.json.

They seem to serve no purpose at all. The file contents are everything.

5 Likes