Table of Contents
- Replace jenv hook with jinja
- New Build System based on esbuild
- Website routing and rendering refactor
- Workspace 2.0
- return value of frappe.db.exists
- Using video player
- Defining developer dependencies
- Change in frappe.get_cached_value behaviour
- Changes to Document.get
- Behaviour of array values passed in a request
- Data Migration Tool Deprecated
- Change of arguments on frappe.log_error
- Removal of standard fields - parent, parenttype and parentfield
- Change in the package build system
- Breaking change in Virtual DocType interface
- Rating field value
- Dependencies
- Separation(s)
This page is intended to make it easier for users who maintain custom apps/forks to migrate their installations to Version 14.
Replace jenv hook with jinja
- Open hooks.py
- Rename the
jenvhook tojinja - For each string in the
methodslist remove the part before:(colon) including the colon. It should only be a list of method paths. - Repeat the same for strings in
filters.
For e.g.,
- jenv = {
+ jinja = {
"methods": [
- "get_fullname:custom_app.jinja.get_fullname"
+ "custom_app.jinja.get_fullname"
],
"filters": [
- "format_currency:custom_app.jinja.currency_filter"
+ "custom_app.jinja.format_currency"
]
}
custom_app/jinja.py
- def currency_filter():
+ def format_currency():
...
Docs: https://frappeframework.com/docs/user/en/python-api/hooks#jinja-customization
New Build System based on esbuild
The new build system does not support build.json. To make sure bundles are built correctly, you need to create a bundle file for each key in build.json.
Let's say your build.json looks like this:
{
"js/my_app.js": [
"public/js/utils.js",
"public/js/main.js",
"public/js/support.js"
],
"js/another_file.js": [
"public/js/another_file.js"
],
"css/my_app.css": [
"public/less/components.less",
"public/less/style.less"
],
}
Create a file named my_app.bundle.js in the public/js directory and import the 3 files.
// public/js/my_app.bundle.js
import './utils';
import './main';
import './support';
Now, since another_file.js is built with a single file, we can just rename that file from another_file.js to another_file.bundle.js
Now, create a file named my_app.bundle.less in the public/less directory and import the 2 less files.
@import './components.less';
@import './style.less';
Now, assuming you included some of these files as part of app bundle or website bundle in hooks.py, you may need to do the following changes:
- app_include_js = ['/assets/js/my_app.js']
+ app_include_js = ['my_app.bundle.js']
- app_include_css = ['/assets/css/my_app.css']
+ app_include_css = ['my_app.bundle.css']
If you included the assets manually by explicitly writing the script tag in HTML files then you need to do the following changes:
// for js files
- <script src="/assets/js/my_app.js" type="text/javascript">
+ {{ include_script('my_app.bundle.js') }}
// for css files
- <link href="/assets/css/my_app.css" rel="stylesheet">
+ {{ include_style('my_app.bundle.css') }}
If you were lazy loading assets using frappe.require you need to do the following changes:
- frappe.require('/assets/js/another_file.js', ...)
+ frappe.require('another_file.bundle.js', ...)
You can test if your bundles are being compiled by running the bench build command for your app:
bench build --app my_app
Finally, you can delete the build.json file, you no longer need it.
Bundling Target
Version 14 now targets ES2017 aka ES8 by default. So if you use any modern JS syntax like optional chaining in your bundled javascript then it will be automatically transpiled to support previous versions. This lets developers use modern syntax without having to worry about browser compatibility.
Read more here: https://github.com/frappe/frappe/pull/16491
Website routing and rendering refactor
There was a major refractor done for website routing and rendering. During this refactor, few methods were moved to different files. You might have to change the following code in your custom app.
- from frappe.website.render import render
+ from frappe.website.serve import get_response
...
- response = render()
+ response = get_response()
- from frappe.website.render import clear_cache
+ from frappe.website.utils import clear_cache
Workspace 2.0
The Workspace is now upgraded. There were standard workspaces which get generated by JSON files stored in a particular module folder inside the workspace folder. (I will suggest creating a new workspace (copy of your existing workspace) using the awesome Workspace 2.0 feature. It is a much faster approach)
Let's take the example of the build.json file in Frappe App:
// frappe/core/workspace/build/build.json
frappe
│
└───core
│ │
│ └───workspace
| └───build
| build.json
If you have any such JSON files in your Custom App you might have to do the following changes in that JSON file.
- "category": "Modules",
- "is_standard": 1,
- "developer_mode_only": 0,
- "disable_user_customization": 0,
- "extends_another_page": 0,
- "pin_to_bottom": 0,
- "pin_to_top": 0,
- "extends": "",
+ "for_user": "",
+ "parent_page": "",
+ "public": 1,
+ "restrict_to_domain": "",
+ "roles": [],
+ "sequence_id": 31,
+ "title": "Build",
if you have links update each links in following way:
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Data",
+ "link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Import Data",
+ "link_count": 0,
"link_to": "Data Import",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
...
]
Also need to add the content on the page which is rendered based on json array which we can create using below content.
Header:
{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}
Chart:
{\"type\": \"chart\", \"data\": {\"chart_name\": \"Profit and Loss\", \"col\": 12}}
Shortcut: shortcut_name is the label of shortcuts.
{\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"System Settings\", \"col\": 4}}
Card: card_name is the label of links of type Card Break.
{\"type\": \"card\", \"data\": {\"card_name\": \"Data\", \"col\": 4}}
On-Boarding:
{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Accounts\", \"col\": 12}}
Spacer: It is used to add gap between Shortcuts and Cards.
{\"type\": \"spacer\", \"data\": {\"col\": 12}}
Combine this based on onboarding, charts, shortcuts, or links in the page to get the content as shown below.
+ "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"DocType\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Workspace\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Report\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Elements\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Modules\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Models\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Views\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Scripting\", \"col\": 4}}]",
return value of frappe.db.exists
frappe.db.exists now only returns value of name if any were found, else None. Previously when passing document dictionary a list of documents were retrieved. If you relied on this behaviour, consider replacing exists with get_all.
New signature of db.exists:
def exists(dt: str | dict, dn: str | dict | list = None, cache: bool = False) -> str | None:
...
dtcan be of typestr: the name of a doctypedict: filters in the standard frappe syntax. Filters have to include the"doctype"key. When passing filters, all other parameters will have no effect and can be left empty.
dnis needed only ifdtwas passed as a string. It can be of type:str: name of one specific document. Use this if you want to check if a document with this name exists.dictorlist: filters in the standard frappe syntax. Use this to check if a document matching the filter values exists.
cacheonly works ifdtanddnare both strings. In this case we cache the result, ifcacheis set toTrue.
Using video player
Frappe's video player is now asynchronously loaded on demand. So if you're using frappe.Plyr you need to first load the required libraries using utility function like this.
frappe.utils.load_video_player().then(() => {
plyr = new frappe.Plyr(...);
})
Defining developer dependencies
In v14 and Frappe bench 5.12, dependencies that are only used during development are specificied in separate section in pyproject.toml section. Example of Frappe Framework's own developer dependencies.
# pyproject.toml
[tool.bench.dev-dependencies]
coverage = "~=6.4.1"
Faker = "~=13.12.1"
pyngrok = "~=5.0.5"
unittest-xml-reporting = "~=3.0.4"
These development dependencies are installed by default in developer mode. They can also be manually installed by using bench setup requirements --dev
Change in frappe.get_cached_value behaviour
get_cached_value uses get_cached_doc internally which used to raise DoesNotExistError if document wasn't found. Since this behavior was inconsistent with other database APIs like frappe.db.get_value the behavior was changed to return None instead of raising an exception.
Changes to Document.get
key is now a mandatory parameter when using doc.get, it no longer returns the the internal __dict__ if key is not passed. This is to avoid hard to trace bugs and maintain consistency with how dict.get works.
Behaviour of array values passed in a request
PR: https://github.com/frappe/frappe/pull/15784
Consider the following API call from a browser client:
fetch('/api/method/app.api.buy_fruits', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
fruits: ['Apple', 'Orange']
})
})
api.py
@frappe.whitelist()
def buy_fruits(fruits):
print(fruits) # 'Apple' instead of ['Apple', 'Orange']
As you can see, the value passed in fruits is 'Apple' instead of the list we passed. This is weird and incorrect behavior but is now fixed in Version 14. If you relied on this behavior, you need to make changes on your client-side API calls or server-side whitelisted methods based on your use case.
Data Migration Tool Deprecated
If you are one of the 2 people who is using the Data Migration Tool for syncing data between an external service and a Frappe site, you need to write your own syncing code because Data Migration Tool is removed from Version 14.
If you absolutely want to use the tool, you can pick up the code from the older version and move it into an app and use that instead.
Change of arguments on frappe.log_error
log_error now supports two more parameters to link errors to a particular document.
To simplify error logging this function's signature was changed to flip order of title and message`.
def log_error(title=None, message=None, reference_doctype=None, reference_name=None):
...
If you did not specify keyword argument manually, you don't need to change anyting. If you have specified title explicitly and not specified message then you should update function call as per new signature. Example:
- frappe.log_error("Some message", title="error title")
+ frappe.log_error(message="Some message", title="error title")
Removal of standard fields - parent, parenttype and parentfield
These three fields are removed from non-child doctypes. These fields were never used on non-child doctypes. If your code directly accesses three fields you might need to check if the doctype is child type first. Alternatively, you can use doc.get to safely check and get value if it exists.
Change in the package build system
Frappe and ERPNext are now built using a single pyproject.toml file specifying all the dependencies, development dependencies etc. This build system replaces old setup.py and requirements.txt
Read more about this on the PR: https://github.com/frappe/frappe/pull/17174
Breaking change in Virtual DocType interface
Virtual doctypes need to implement a certain set of methods on doctype controller so it can use other data sources as backend. Version 14 improves this interface requirement by converting few object methods to static methods like get_list and get_count.
You can read more about the changed requirements and a practical example on the documentation page: https://frappeframework.com/docs/v14/user/en/basics/doctypes/virtual-doctype
Rating field value
Rating fields used to show value from 0 to 5 start before. Number of stars are now made configurable. This however also means that the storage underlying value needs to be changed to reflect the actual number of starts. Now Rating fields store a decimal number between 0 and 1 representing the rating out of the specified number of stars.
| Stars | Previous Value | New Value |
|---|---|---|
* * * * * |
5 | 1.0 |
* * * * . |
4 | 0.8 |
. . . . . |
0 | 0.0 |
* * * * * * * * . . |
Not possible | 0.8 |
If you performed any arithmetic operations on the rating field values you need to change the code to consider normalized values.
Dependencies
We have removed following libraries from the codebase in version 14. If you have used these library in your app, please add them manually for your app.
Javascript
- fluxify
- bootstrap.js (v3)
- jquery.hotkeys.js
- prettyDate.js
- bootstrap_theme
- express
- fuse.js
- hyper.js
Python
- html2text
- md5
- paytmchecksum
- razorpay
- stripe
- braintree
Icons
- glyphicons
- font-awesome (moved to separate folder)
- /assets/frappe/css/font-awesome.css
+ /assets/frappe/css/fonts/fontawesome/font-awesome.min.css # use minified version
We have also removed local copy of sockect.io & sortableJS and used these libraries that are pulled from package managers. So you might have to update the path of these libraries in your application.
Separation(s)
We have moved all the payment gateways (Braintree, Stripe, PayPal, PayTM, Razorpay - along with Payment Gateway DocType) as well as it's dependencies from frappe to a separate app payments. To install this app, run following commands:
$ bench get-app payments
$ bench --site <your-site-name> install-app payments