Frappe Migrating to Version 14
By Khongpob Trisuriyasaengchot on June 7, 2022
IntermediateMigrating to Version 14
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 jenv hook to jinja
- For each string in the methods list 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.jsimport './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.
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.
Let's take the example of the build.json file in Frappe App:
// frappe/core/workspace/build/build.jsonfrappe│└───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:...dt can 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.dn is needed only if dt was 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.dict or list: filters in the standard frappe syntax. Use this to check if a document matching the filter values exists.cache only works if dt and dn are both strings. In this case we cache the result, if cache is set to True.
More articles on Artery Developer's Guide