Professional Documents
Culture Documents
Frappe is a full stack, batteries-included, web framework written in Python and Javascript.
It is the framework which powers ERPNext. It is pretty generic and can be used to build
database driven apps.
Table of Contents
1. Introduction to Frappe Framework
3. Bench Architecture
5. Understanding Doctypes
6. Desk
8. Reports
9. Printing
13. Deployment
15. Testing
16. Debugging
The key difference in Frappe compared to other frameworks is that meta-data is also
treated as data and is used to build front-ends very easily. We believe in a monolithic
architecture, so Frappe comes with almost everything you need to build a modern web
application. It has a full blown Admin UI called the Desk that handles forms, navigation,
lists, menus, permissions, file attachment and much more out of the box.
Getting Started
Installation
Before you can use Frappe, you need to install it. We have a complete installation guide
which covers all possibilities, this guide will also help you understand the backend stack.
Documentation
The official documentation covers everything you need to know about the Frappe
Framework.
Join Us →
Introduction
What is Frappe Framework?
Frappe is a full stack, batteries-included, web framework written in Python and Javascript.
It is the framework which powers ERPNext. It is pretty generic and can be used to build
database driven apps.
Meta-data driven
Meta-data is a first class citizen in Frappe. It is used to generate database tables, design
forms and configure a lot of features. Meta-data is stored in a Model which is known as
DocType in Frappe.
{
"name": "ToDo",
"module": "Desk",
"field_order": [
"status",
"date",
"description"
],
"fields": [
{
"default": "Open",
"fieldname": "status",
"fieldtype": "Select",
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "Open\nClosed"
},
{
"fieldname": "date",
"fieldtype": "Date",
"in_standard_filter": 1,
"label": "Due Date"
},
{
"fieldname": "description",
"fieldtype": "Text Editor",
"in_global_search": 1,
"label": "Description",
"reqd": 1
}
]
}
A configuration like this will generate a database table whose schema might look like
Frappe does not only manage the backend, it also comes with a feature rich admin
interface called the Desk. When you create a DocType in Frappe, a number of views are
generated for it. Here are some of them:
The List View supports paging, filtering, sorting and bulk editing
records.
List View
The Form View used for editing records also supports file attachments, PDF format,
comments, email,
etc.
Form View
The Report Builder supports adding columns, grouping, filtering, sorting and saving it as
a
configuration.
Report Builder
Frappe comes with User and Role management out of the box. A User is someone who
can login to the system and perform authorized actions like creating, updating or
deleting records. A Role is a mapping of DocTypes and actions allowed to perform on it.
Python, JS and MariaDB
Frappe Framework uses Python for the backend. It comes with a simple yet powerful
ORM as an abstraction over CRUD operations. The default database is MariaDB. Postgres
support is in beta.
doc = frappe.new_doc('ToDo')
doc.description = 'Buy Eggs'
doc.insert()
The front-end is an SPA built using Javascript (jQuery).
Realtime
Frappe also supports realtime pub/sub events using NodeJS and socketio.
# server
frappe.publish_realtime('update_progress', {
'progress': 42,
'total': 100
})
# client
frappe.realtime.on('update_progress', (data) => {
console.log(data)
});
Background Jobs
Frappe supports sending and receiving emails, which can also be linked to individual
documents.
Printing
Frappe supports generating PDF print formats based on Jinja Templates. It also comes
with a drag-and-drop Print Format Builder.
Print Preview
Print Format Builder
Batteries Included
Frappe has tons of features that are essential to building a modern complex app. Only
the basic features are introduced here. The rest of this guide will cover them and other
advanced features with much finer detail, so make sure to read it all!
Installation
These steps assume you want to install Bench in developer mode. If you want install in
production mode, follow the Easy Install method.
System Requirements
This guide assumes you are using a personal computer, VPS or a bare-metal server. You
also need to be on a *nix system, so any Linux Distribution and MacOS is supported.
However, we officially support only the following distributions.
1. MacOS
2. Debian / Ubuntu
3. Arch Linux
4. CentOS
Pre-requisites
1. Python 2.7 (Python 3.5+ also supported)
2. MariaDB 10+
4. Git
5. Nodejs
6. yarn
7. Redis
MacOS
nvm install 8
Verify the installation, by running:
node -v
# output
v8.11.3
Finally, install yarn using npm
Debian / Ubuntu
mysql_secure_installation
Remember: only run it if you're not prompted the password during setup.
It is really important that you remember this password, since it'll be useful later on. You'll
also need the MySQL database development files.
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[mysql]
default-character-set = utf8mb4
Now, just restart the mysql service and you are good to good
nvm install 8
Verify the installation, by running:
node -v
# output
v8.11.3
Finally, install yarn using npm
Arch Linux
pacman -Syu
pacman -S mariadb redis python2-pip wkhtmltopdf git npm cronie nginx openssl
npm install -g yarn
Setup MariaDB
nano /etc/mysql/my.cnf
Add the following configuration
[mysqld]
innodb-file-format=barracuda
innodb-file-per-table=1
innodb-large-prefix=1
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[mysql]
default-character-set = utf8mb4
Start services
Install Bench
Install bench as a non-root user
bench --version
# output
4.1.0
Create your first bench folder.
cd ~
bench init frappe-bench
After the frappe-bench folder is created, change your directory to it and run this
command
bench start
Congratulations, you have installed bench on to your system.
Bench Architecture
Bench is a command-line utility that helps you install apps, manage multiple sites and
update Frappe based apps on *nix based systems (macOS, CentOS, Debian, Ubuntu, etc)
for development and production. Bench will also create nginx and supervisor config files,
setup backups and much more.
Bench Architecture
Directory structure
When you install bench, you will have a directory structure similar to
.
├── apps
│ ├── frappe
├── config
├── env
├── logs
├── Procfile
└── sites
├── apps.txt
├── assets
├── common_site_config.json
└── site1.local
├── private
├── public
└── site_config.json
Apps
A Frappe app is a python package that use the Frappe framework. Frappe apps live in a
directory called apps. As you can see, there is an app named frappe, since frappe is an
app which acts as the framework. A Frappe app should have an entry in apps.txt.
Sites
Frappe is a multitenant platform and each tenant is called a site. Sites exist in a directory
called sites, assumed as the current working directory when running a bench command.
Every site contains a private and public directory which stores private and public files
respectively. common_site_config.json is a configuration file common to all
sites. site_config.json is a configuration file specific to each site and will override
options from common_site_config.json.
Virtual Environment
Processes
Processes are defined in a file called Procfile. This is only for developement mode.
redis_cache:
redis_socketio:
Redis used as a pub/sub between web and socketio processes for realtime
communication.
redis_queue:
web:
socketio:
Node server for a socketio connection with the browser for realtime communication.
watch:
Node server for bundling JS/CSS assets using Rollup. It will also rebuild files as they
change.
schedule:
worker_short:
worker_default:
frappe@frappe:~/frappe-bench$ ls apps
frappe my_app
The my_app directory will now be created in the apps directory. It will also be added
to apps.txt.
To install an app, you must have a site. To install this app to a site, run the following
command.
Installing my_app...
frappe@frappe:~/frappe-bench$ bench --site site_name list-apps
frappe
my_app
frappe@frappe:~/frappe-bench$
Running the list-apps sub-command, you can verify if the app was installed correctly.
Understanding DocTypes
1. DocType
2. DocField
3. Naming
4. Document
5. Controllers
o Controller Methods
o Controller Hooks
6. Child DocType
7. Single DocType
DocType
A DocType is the core building block of any application based on the Frappe Framework.
It describes the Model and the View of your data. It contains what fields are stored for
your data, and how they behave with respect to each other. It contains information
about how your data is named. It also enables rich Object Relational Mapper
(ORM) pattern which we will discuss later in this guide. When you create a DocType, a
JSON object is created which in turn creates a database table.
ORM is just an easy way to read, write and update data in a database without writing
explicit SQL statements.
Conventions
1. DocType is always singular. If you want to store a list of articles in the database,
you should name the doctype Article.
2. Table names are prefixed with tab. So the table name for Article doctype
is tabArticle.
The standard way to create a DocType is by typing new doctype in the search bar in
the Desk.
ToDo DocType
A DocType not only stores fields, but also other information about how your data
behaves in the system. We call this Meta. Since this meta-data is also stored in a
database table, it makes it easy to change meta-data on the fly without writing much
code. Learn more about Meta.
A DocType is also a DocType. This means that we store meta-data as the part of the
data.
After creating a DocType, Frappe can provide many features out-of-the-box. If you go
to /desk#List/ToDo you will be routed to the List View in the desk.
ToDo List
Similarly, you get a Form View at the route /desk#Form/ToDo/000001. The Form is used to
create new docs and view them.
ToDo Form
Module
A DocType will always belong to a module, to enable easier grouping of related models.
Frappe comes with a lot of built-in modules. For e.g
1. Core - contains doctypes like DocType, DocField, Report, System Settings, etc
2. Desk - contains doctypes like ToDo, Event, Note, Kanban Board, etc
3. Email - contains doctypes like Email Account, Newsletter, Email Group, etc
Modules also helps in grouping of code files in directories. The controller files generated
by a DocType will live in it's respective Module directory.
frappe
├── commands
├── config
├── core
│ ├── doctype
│ │ ├── doctype
│ │ ├── docfield
│ │ ├── report
│ │ ├── system_settings
│ │ ├── ...
│ │ └── ...
│ ├── page
│ ├── report
│ └── web_form
├── desk
│ ├── doctype
│ │ ├── ...
│ │ ├── event
│ │ ├── kanban_board
│ │ ├── note
│ │ └── todo
│ ├── form
│ ├── page
DocField
A DocField is a list of fields which describes what properties a DocType will have. For
instance, a ToDo doctype has fields description, status and priority. These ultimately
become columns in the database table tabToDo.
Example
The DocField stores meta-data about the field. Some of them are described below.
[
{
"label": "Description", // the value shown to the user (Form, Print,
etc)
"fieldname": "description", // the property name we refer in code, also
the column name
"fieldtype": "Text Editor", // the fieldtype which also decides how to
store this value
"reqd": 1 // whether this field is mandatory
},
{
"label": "Status",
"fieldname": "status",
"fieldtype": "Select",
"options": [
"Open",
"Pending",
"Closed"
]
},
{
"label": "Priority",
"fieldname": "priority",
"fieldtype": "Select",
"options": [ // list of options for select
"Low",
"Medium",
"High"
],
"default": "Low" // the default value to be set
}
]
Frappe comes with moret than 30 different fieldtypes out-of-the-box. These fieldtypes
serve a variety of use-cases. Learn more about Fieldtypes.
Naming
All docs in Frappe have a primary key called name. This is the unique id by which you will
be finding records and manipulating them using the ORM. You can configure
how docsshould be named when a new document is created. The following are the ways
you can setup naming in a DocType.
1. field:[fieldname]
The doc name is fetched from the value of the field provided.
2. [series]
You can provide a naming pattern which will be incremented automatically. For e.g, if
you set it as PRE.#####, the first document created will have the name as PRE00001, and
second one will be PRE00002 and so on.
3. naming_series:
The naming pattern is derived from a field in the document. For e.g, you have a
field naming_series in your document and it's value is set as PRE.#####, then that will be
the pattern used for generating the name. This value can change per document. So the
next document can have a different pattern.
This works only if you have a field called naming_series in your DocType.
4. Prompt
EXAMPLE-{MM}-test-{fieldname1}-{fieldname2}-{#####}
Everything outside the curly braces are plain text. Keywords inside the curly braces will
be evaluated based on what they represent. In this case:
Document
An instance of a DocType is called a doc (shorthand for document). Usually a doc directly
maps to a single row in the table.
Example
description
status
priority
Now, if we want to query a document from the database, we can use the ORM.
>>> doc.as_dict()
{u'creation': datetime.datetime(2018, 8, 14, 12, 57, 4, 568148),
u'description': u'Buy Groceries',
u'modified': datetime.datetime(2018, 8, 14, 12, 57, 16, 622779),
u'modified_by': u'faris@erpnext.com',
u'name': u'0000001',
u'owner': u'faris@erpnext.com',
u'priority': u'Medium',
u'status': u'Open',
...
}
You get the values of description, status and priority, but you also get fields
like creation, owner and modified_by which are fields added by default by the framework
on all docs.
Controllers
A Controller is a normal python class which extends from frappe.model.Document base
class. This base class is the core logic of a DocType. It handles how values are loaded
from the database, how they are parsed and saved back to the database.
When you create a DocType named Person, a python file is created by the
name person.pyand the contents look like:
class Person(Document):
pass
All the fields are available to the class as attributes.
Controller Methods
You can add custom methods to your Controller and it will be callable using
the doc object. For example,
# controller definition
class Person(Document):
def get_full_name(self):
"Returns the person's full name"
return self.first_name + ' ' + self.last_name
class Person(Document):
def save(self, *args, **kwargs):
do_something()
super().save(*args, **kwargs) # call the base save method
do_something_else()
There are a lot of methods provided by default on the doc object. You can find the
complete list here.
Controller Hooks
To add custom behaviour during the lifecycle of a document, we have controller hooks.
Method Name
Description
before_submit Called before a document is submitted.
before_cancel This is called before a submitted document is cancelled.
before_update_after_submi This is called before a submitted document values are updated.
t
before_insert This is called before a document is inserted into the database.
before_naming This is called before the name property of the document is set.
autoname This is an optional method which is called only when it is defined in
the controller. Use this method to customize how the name property
of the document is set.
validate Use this method to throw any validation errors and prevent the
document from saving.
before_save This method is called before the document is saved.
Method Name
Description
after_insert This is called after the document is inserted into the database.
on_update This is called when values of an existing document is updated.
on_submit This is called when a document is submitted.
on_update_after_submit This is called when a submitted document values are updated.
on_cancel This is called when a submitted is cancelled.
on_change This is called to indicate that a document's values has been
changed.
on_trash This is called when a document is being deleted.
after_delete This is called after a document has been deleted.
To use a controller hook, just define a class method with that name. For e.g
class Person(Document):
def validate(self):
if self.age > 60:
frappe.throw('Age must be less than 60')
def after_insert(self):
frappe.sendmail(recipients=[self.email], message="Thank you for
registering!")
1. Create a document
doc = frappe.get_doc({
'doctype': 'Person',
'first_name': 'John',
'last_name': 'Doe'
})
doc.insert()
doc.name # 000001
2. Load a document
# doctype fields
doc.first_name # John
doc.last_name # Doe
# standard fields
doc.creation # datetime.datetime(2018, 9, 20, 12, 39, 34, 236801)
doc.owner # faris@erpnext.com
Child DocType
Up until now we have only seen DocTypes that can have a single value for each field.
However, there might be a need for storing multiple records against one record, also
known as many-to-one relationships. A Child DocType is doctype which can only be
linked to a parent DocType. To make a Child DocType make sure to check Is Child
Table while creating the doctype.
Child DocType records are directly attached to the parent doc.
Single DocType
A Single DocType is a DocType that holds only one record in the database. It is useful for
persisting things like System Settings, which don't make sense to have multiple records.
>>> settings = frappe.get_doc('System Settings')
>>> settings.notification_frequency
'Daily'
Desk
Frappe Framework comes with a rich admin interface called the Desk. It reads meta-data
from DocTypes and automatically builds list views, form views, report views, etc for your
DocTypes. Desk is to be used by users of the type "System User".
In this section we will discuss what views are provided by Desk and how to configure
them.
Desktop
Awesomebar
List View
Form View
Report Builder
Tree View
Calendar View
Gantt View
Kanban View
Desktop
When you login to the Desk, the first screen you see is called the Desktop. Each card is
organized based on related functionalities and their DocTypes.
Desktop
Awesomebar
Awesomebar helps you to navigate anywhere in the system, create new records, search
in documents and even perform math operations.
Navigating ToDo using Awesomebar
List View
List View is generated for all DocTypes except which are Child Tables and Singles.
1. Filters
2. Sorting
3. Paging
4. Filter by tags
To customize the List View you must have a {doctype}_list.js file in the doctype
directory. Here are all the options that can be customized. This examples assumes the
Note DocType.
frappe.listview_settings['Note'] = {
// add fields to fetch
add_fields: ['title', 'public'],
// set default filters
filters: [
['public', '=', 1]
],
hide_name_column: true, // hide the last column which shows the `name`
onload(listview) {
// triggers once before the list is loaded
},
before_render() {
// triggers before every render of list records
},
get_indicator(doc) {
// customize indicator color
if(doc.public) {
return [__("Public"), "green", "public,=,Yes"];
} else {
return [__("Private"), "darkgrey", "public,=,No"];
}
},
primary_action() {
// triggers when the primary action is clicked
},
get_form_link(doc) {
// override the form route for this doc
},
// add a custom button for each row
button: {
show(doc) {
return doc.reference_name;
},
get_label() {
return 'View';
},
get_description(doc) {
return __('View {0}', [`${doc.reference_type} $
{doc.reference_name}`])
},
action(doc) {
frappe.set_route('Form', doc.reference_type, doc.reference_name);
}
},
// format how a field value is shown
formatters: {
title(val) {
return val.bold();
},
public(val) {
return val ? 'Yes' : 'No';
}
}
}
Form View
Form view is used to view the records in a Form Layout. This view has a lot of things
going on. But the primary purpose of it is to view and edit records. A document can be
assigned to or shared with other users and it can have arbitrary attachments and tags, all
of which can be seen in the form sidebar.
Form View
When you scroll down to the bottom of the form, you will see the form timeline. The
form timeline shows emails, comments, edits and other events in a reverse chronological
order.
Form Timeline
Report Builder
Report Builder is a generic tool to customize and build tabular data from a DocType. You
can select columns to show, filters to apply, sort order and save this configuration by
giving your report a name. You can also show Child Table data and also filter documents
by their child records. You can also apply Group By on a column with aggregation
methods like Count, Sum and Average.
Calendar View
Calendar view can be configured for DocTypes with a start date and end date.
Calendar View
The configuration file should be named {doctype}_calendar.js and should exist in the
doctype directory. Here is an example configuration file for calendar view for Event
doctype.
frappe.views.calendar['Event'] = {
field_map: {
start: 'starts_on',
end: 'ends_on',
id: 'name',
allDay: 'all_day',
title: 'subject',
status: 'event_type',
color: 'color'
},
style_map: {
Public: 'success',
Private: 'info'
},
get_events_method: 'frappe.desk.doctype.event.event.get_events'
}
event_calendar.js
Gantt View
Gantt view uses the same configuration file as calendar, so every DocType that has a
Calendar view has a Gantt view too.
Gantt View
Kanban View
Kanban view can be created for any DocType that has a Select field with options. These
options become the column names for the Kanban Board.
Users and Permissions
Frappe comes with a user authentication system. It handles user accounts, role based
permissions and cookie based user sessions.
User authentication system in Frappe comes with a lot of features out of the box:
1. User
2. Role
3. DocType Permissions
4. Permission Level
6. User Permissions
8. Password Hashing
For example, the role Blogger has read, write and create permission on the
doctype Blog Post, but only read permission on Blog Category.
Role Blogger
DocType Permissions
DocTypes can have a default set of Roles applied when you install your app. To configure
roles for a DocType you must add them in the Permissions table in DocType.
DocType Permissions
If you expand the row, you will see many more options that can be
configured.
DocType Permissions
Option Explanation
Level Permission Level assigned to this role
If the user is owner The restrictions will apply only if the user is the one who created that
document
Read Allow read access to the document
Write Allow edit access to the document
Create Allow create access to the document
Delete Allow user to delete the document
Submit Allow user to submit the document
Cancel Allow user to cancel the document
Amend Allow user to amend the document
Report Allow user to view the report view
Export Allow user to export records in Excel/CSV
Import Allow user to import records using the Data Import Tool
Set User Allow user to apply user permissions for other users
Permissions
Share Allow user to share the document with other users
Print Allow user to print the document or generate PDF
Email Allow user to send emails for that document
Permission Level
Permission Levels can be used to group fields in a document and apply separate roles to
each level. By default all fields have permlevel set as 0.
Permission Level
User Permissions
User Permissions are another set of rules that can be applied per user basis. It can be
used to restrict documents which contain a specific value for a Link field.
For example, to restrict the User John such that he can only view Blog Posts that were
created by him, i.e, Blogger John. A user permission record with the following values
should be created.
User Permission Record
After creating the user permission configuration, when the User logs in to see the Blog
Post list, he will have a restricted view of blog posts that were created by him.
Restricted Blog Post List
To control permissions for Pages and Reports, use the Role Permission for Page and
Reporttool.
Password Hashing
Frappe handles password hashing out of the box. They are encrypted and saved in a
separate database table named __Auth.
Password Policy
Frappe also supports password strength checking. It can be enabled from System
Settingsin the Security section. The Minimum Password Score field validates how strong
the password should be.
Password Policy
Login Attempts
Frappe allows you to configure how many consecutive login attempts should be allowed
before locking the account for a set time period.
Third Party Authentication
Frappe supports third party login providers. To setup a login provider you need to setup
a Social Login Key. Learn more about it here.
Reports
Frappe framework is best suited for business applications and reporting is an important
part of any business. Frappe supports 3 different ways to build reports depending on
their complexity.
Let's discuss each type and how to build them using Frappe.
Report Builder
Report Builder is the simplest type of Report and can be created without any code. It will
only show the records of single DocType as well as child records of Child Tables.
Saving Reports
By default a Report View is generated for all DocTypes. The user can also save multiple
Reports based on different combination of filters, ordering, columns, etc.
Report Builder
Printing
Group By
You can also apply group by clause on columns and use aggregate functions like Count,
Sum and Average.
Report Builder Group By
Since report builder is a view of single DocType, they can also be edited.
Report Builder Editing
Query Report
Query Reports are reports that can be generated using a single SQL query. The query
can be simple or complex as long as it generates columns and records. These reports can
only be created by a System Manager and are stored in the database.
To create a Query Report, type "new report" in the awesomebar and hit enter.
3. Set the Module - The report will appear in the "Custom Reports" section of the
module.
If you set Standard as "Yes" and Developer Mode is enabled, then a JSON file will be
generated which you will have to check in to your version control. You should do this
only if you want to bundle Query Reports with your app. The Module will decide where
the JSON file will go.
Here is what a query may look like:
SELECT
`tabWork Order`.name as "Work Order:Link/Work Order:200",
`tabWork Order`.creation as "Date:Date:120",
`tabWork Order`.production_item as "Item:Link/Item:150",
`tabWork Order`.qty as "To Produce:Int:100",
`tabWork Order`.produced_qty as "Produced:Int:100",
`tabWork Order`.company as "Company:Link/Company:"
FROM
`tabWork Order`
WHERE
`tabWork Order`.docstatus=1
AND ifnull(`tabWork Order`.produced_qty,0) < `tabWork Order`.qty
AND NOT EXISTS (SELECT name from `tabStock Entry` where work_order =`tabWork
Order`.name)
If you notice there is a special syntax for each column, we use this information to format
the Report View.
For example: The first column Work Order:Link/Work Order:200 will be rendered as a Link
Field with the DocType Work Order and the column width would be 200px.
Query Report View
Script Report
Anything that can't be achieved using Report Builder or Query Report can be achieved
using Script Reports. As the name suggests, these reports are built using Python scripts.
Since these reports give you unrestricted access via Python scripts, they can only be
created by Administrators. These reports are meant to be written during development
and be a part of your app.
4. In the module folder (for example if it is Accounts in ERPnext the folder will
be erpnext/accounts/report/[report-name] ) you will see that templates for the
report files will be created.
The generated .py file comes with a boilerplate for your report. There is one method
named execute which takes filters and returns columns and data. You can use any
combination of python modules and SQL queries to generate your report.
Here is what the shape of columns and data must look like:
import frappe
from frappe import _
def execute(filters=None):
columns = [
{
'fieldname': 'account',
'label': _('Account'),
'fieldtype': 'Link',
'options': 'Account'
},
{
'fieldname': 'currency',
'label': _('Currency'),
'fieldtype': 'Link',
'options': 'Currency'
},
{
'fieldname': 'balance',
'label': _('Balance'),
'fieldtype': 'Currency',
'options': 'currency'
}
]
data = [
{
'account': 'Application of Funds (Assets)',
'currency': 'INR',
'balance': '15182212.738'
},
{
'account': 'Current Assets - GTPL',
'currency': 'INR',
'balance': '17117932.738'
},
...
]
To add filters in your report define the fields and their fieldtypes in the {report-
name}.jsfile. The filter values will be available in the execute method as a dict.
frappe.query_reports['Balance Sheet'] = {
filters: [
{
fieldname: 'company',
label: __('Company'),
fieldtype: 'Link',
options: 'Company',
default: frappe.defaults.get_user_default('company')
},
{
fieldname: 'periodicity',
label: __('Periodicity'),
fieldtype: 'Select',
options: [
'Monthly',
'Quarterly',
'Half-Yearly',
'Yearly'
],
default: 'Yearly'
}
]
}
Balance Sheet
Protip: To navigate directly to a Report of any of the above type, type its name in the
awesomebar and hit enter.
Print View
The Print View can be accessed from the form view of any document. A Standard print
format is generated for all DocTypes based on the form layout and mandatory fields in
it.
Print View
Custom HTML
You can also add Custom HTML to your Print Format. Just drag and drop the Custom
HTML button in left sidebar into your Print Format Editor.
In the Custom HTML field you can use any valid HTML with Bootstrap 3 classes for
styling. You can also use Jinja Templating to add dynamic content to your HTML. See list
of methods available to use in Jinja templates.
Custom HTML
Custom CSS
To change styling in your Print Format you can also add custom CSS.
To create a new Print Format, type "new print format" in awesomebar and hit enter.
If you set Standard as "Yes" and Developer Mode is enabled, then a JSON file will be
generated for your Print Format and you will have to check it in to your version control
with your app.
Custom HTML in Print Format
To create a Print Format for reports, create a HTML file named {report-name}.html in the
Report folder.
JS Templating
These print formats are generated on the client side, so we can't use Jinja. We use an
adapted version of John Resig's Templating. It looks similar to Jinja so you don't need to
learn anything new.
Adding pages
Every frappe app including frappe comes with a www folder which directly maps to
website urls. Here is what the directory structure looks like:
frappe/www
├── about.html
├── about.py
├── contact.html
├── contact.py
├── desk.html
├── desk.py
├── login.html
├── login.py
├── me.html
└── me.py
This structure enables the routes /about, /contact, /desk, /login and /me.
To add your own page, just add an HTML file in the www folder of your app. There are
multiple ways to organize these portal pages. For example,
custom_app/www
├── custom_page.html
└── custom_page.py
Will be rendered on the route /custom_page.
To add subpages you can convert your main page into a folder and add its content in an
index file. For example,
custom_app/www
└── custom_page
├── index.html
├── index.py
├── subpage.html
└── subpage.py
Will still be rendered on the route /custom_page and /custom_page/subpage will also be
available.
You can write .md files instead of .html for simple static pages like documentation. This
documentation you are reading is written as a markdown file.
Overriding standard pages
Frappe also allows you to override standard pages through your custom app. For
example, to override the standard /about provided by frappe, just add a file
named about.html in the www folder of your app and it will take precedence.
Templating
You can add dynamic content to Portal Pages using Jinja templates. All of the portal
pages extend from the base template frappe/templates/web.html which itself extends
from frappe/templates/base.html.
{% block page_content %}
<h1>{{ _("About Us") }}</h1>
<div class="row">
<div class="col-sm-6">
We believe that great companies are driven by excellence,
and add value to both its customers and society.
You will find our team embibes these values.
</div>
</div>
{% endblock %}
You can also omit the extend and block if you want to the use the default base template.
Context
Every portal page can have a python controller which will build context for the page. The
controller should have the same name as the .html or .md file with a .py extension.
custom_app/www
├── custom_page.html
└── custom_page.py
The controller should have a get_context method which takes a context dict, adds any
data to it and then returns it. Here is what a sample page controller might look like:
# about.py
import frappe
def get_context(context):
context.about_us_settings = frappe.get_doc('About Us Settings')
return context
Usage in template
{% if about_us_settings.show_contact_us %}
<a href="/contact" class="btn btn-primary">Contact Us</a>
{% endif %}
</div>
Since Portal Pages are built using Jinja, frappe provides a standard API to use in jinja
templates.
You can add custom CSS and JS for your pages by dropping a .css or .js file of the
same name.
custom_app/www
├── custom_page.html
├── custom_page.css
├── custom_page.js
└── custom_page.py
Magic Comments
You can configure some functionalities by adding magic comments in your pages.
For example by adding <!-- add-breadcrumbs --> to your .html or .md file, frappe will
automatically generate breadcrumbs based on folder structure.
{% if about_us_settings.show_contact_us %}
<a href="/contact" class="btn btn-primary">Contact Us</a>
{% endif %}
</div>
Here is a list of all magic comments and their functionalities.
Comment Functionality
<!-- add-breadcrumbs --> Add breadcrumbs to page
<!-- no-breadcrumbs --> Remove breadcrumbs from page
<!-- show-sidebar --> Show web sidebar
<!-- no-cache --> Disable caching for this page
<!-- no-sitemap --> Don't include page in sitemap
<!-- sitemap --> Include page in sitemap
<!-- add-next-prev-links --> Add Next and Previous navigation
buttons
<!-- title: Custom Title --> Set the page title
<!-- base_template: custom_app/path/to/custom_base.html Override base_template for this page
-->
Web Forms
Frappe provides an easy way to generate forms for your website with very little
configuration. These forms may be public (anyone can fill them up) or can be configured
to require login.
Creating a Web Form
To create a Web Form, type "new web form" in awesomebar and hit enter.
1. Enter Title
3. Select fields for your web form. These fields are populated from the DocType.
If you check the "Is Standard" checkbox, a new folder will be created in the module of the
Web Form. In this folder, you will see a .py and .js file that you can use to configure the
web form. These files need to be checked into version control with your custom app. You
can install this app on any site and it will have this web form installed.
Custom Script
Introduced in Version 11
You can also add a custom client script to the web form
Event Handler
frappe.web_form.on([fieldname], [handler]);
Get Value
value = frappe.web_form.get_value([fieldname]);
Set Value
frappe.web_form.set_value([fieldname], [value])
Validate
frappe.web_form.validate = () => {
// return false if not valid
}
frappe.web_form.after_load = () => {
// init script here
}
Examples
Custom Validation
frappe.web_form.validate = () => {
let data = frappe.web_form.get_values();
if (data.amount < 1000) {
frappe.msgprint('Value must be more than 1000');
return false;
}
});
frappe.web_form.after_load = () => {
frappe.msgprint('Please fill all values carefully');
}
Breadcrumbs
You can customize the breadcrumbs in a Web Form by adding JSON object.
Example:
When there are schema changes in your app, to migrate your existing site's database to
the new schema, you should run the command.
Schema changes
You can edit a DocType to add, remove or change fields. On saving a DocType, a JSON
file containing the DocType data is added to source tree of your app. When you add an
app to a site, the DocTypes are installed (database tables are created) using this JSON
file.
When you remove or rename fields in the DocType, the corresponding database
columns are not removed from the database table, but they will not be visible in the
form view. This is done to avoid any potential data loss situations and to allow you write
related data migrations (patches) which might need values from old fields.
Data Migrations
On introducing data related changes, you might want to run one off scripts to change
existing data to match expectations as per new code. We call these scripts patch in
frappe.
Writing a patch
To write a patch, you must write an execute method in a python script and add it
topatches.txt of your app.
It is recommended to make a file with a patch number and name in its path and add it to
a patches package (directory) in your app. You can then add a line with dotted path to
the patch module to patches.txt.
frappe.patches.v12_0.my_awesome_patch
The DocType meta available in the execute function will be as per the old JSON. This is
so that you can write migration code assuming you still have the old fields. After the
patch is run, the new schema is applied to the DocType.
If you want to have the new schema during your patch execution, use
the reload_docmethod.
import frappe
def execute():
frappe.reload_doc(module_name, "doctype", doctype_name)
You can also add one off python statements in patches.txt using the syntax,
frappe.patches.v12_0.my_awesome_patch
execute:frappe.delete_doc('Page', 'applications', ignore_missing=True)
Patches run in the order they are defined. All lines in patches.txt have to be unique. If a
patch has been run before, it won't run again. If you want to run a patch again, add a
comment that will make the line appear as new.
For Example,
frappe.patches.v12_0.my_awesome_patch #2019-09-08
Deployment
Bench is the CLI tool to manage deployments for sites based on Frappe Framework. Here
are steps to deploy your frappe based sites on production.
Install Bench
Deploying frappe sites is not too different from setting it up on your local system. Install
bench using the Easy Install script if your server is one of the supported linux
distributions (Debian, Ubuntu, CentOS). Make sure you pass the --production flag to the
script.
Check supervisor
If everything is setup properly, you should see frappe processes as the supervisor status
output.
Updating
To update your sites, just run the following command. It will update all of your apps ( git
pull), run patches on all sites, build JS and CSS assets and restart supervisor.
# update everything
bench update
# update apps
bench update --pull
1. Test can be anywhere in your repository but must begin with test_ and should be
a .py file.
2. The test runner will automatically build test records for dependent DocTypes
identified by the Link type field (Foreign Key).
3. For non-DocType tests, you can write simple unit tests and prefix your file names
with test_.
Writing Tests
When you create a new DocType (in developer mode), the boilerplate files also contain
the test_{doctype}.py file. The test file should handle creating dependencies and
cleaning them up.
import frappe
import unittest
def create_events():
if frappe.flags.test_events_created:
return
frappe.set_user("Administrator")
doc = frappe.get_doc({
"doctype": "Event",
"subject":"_Test Event 1",
"starts_on": "2014-01-01",
"event_type": "Public"
}).insert()
doc = frappe.get_doc({
"doctype": "Event",
"subject":"_Test Event 3",
"starts_on": "2014-01-01",
"event_type": "Public"
"event_individuals": [{
"person": "test1@example.com"
}]
}).insert()
frappe.flags.test_events_created = True
class TestEvent(unittest.TestCase):
def setUp(self):
create_events()
def tearDown(self):
frappe.set_user("Administrator")
def test_allowed_public(self):
frappe.set_user("test1@example.com")
doc = frappe.get_doc("Event", frappe.db.get_value("Event",
{"subject":"_Test Event 1"}))
self.assertTrue(frappe.has_permission("Event", doc=doc))
def test_not_allowed_private(self):
frappe.set_user("test1@example.com")
doc = frappe.get_doc("Event", frappe.db.get_value("Event",
{"subject":"_Test Event 2"}))
self.assertFalse(frappe.has_permission("Event", doc=doc))
Running Tests
Run the following command to run all your tests. It will build all the test dependencies
once and run your tests. You should run tests from frappe_bench folder.
OK
▶ bench start
14:55:17 system | redis_cache.1 started (pid=4085)
14:55:17 system | redis_socketio.1 started (pid=4086)
14:55:17 system | redis_queue.1 started (pid=4088)
14:55:17 system | web.1 started (pid=4089)
14:55:17 system | socketio.1 started (pid=4090)
14:55:17 system | watch.1 started (pid=4094)
14:55:17 system | worker_short.1 started (pid=4096)
14:55:17 system | schedule.1 started (pid=4095)
14:55:17 redis_queue.1 | 4088:C 22 May 14:55:17.257 # oO0OoO0OoO0Oo Redis is
starting oO0OoO0OoO0Oo
14:55:17 redis_queue.1 | 4088:C 22 May 14:55:17.264 # Redis version=4.0.11,
bits=64, commit=00000000, modified=0, pid=4088, just started
14:55:17 redis_queue.1 | 4088:C 22 May 14:55:17.264 # Configuration loaded
14:55:17 redis_queue.1 | 4088:M 22 May 14:55:17.265 * Increased maximum number
of open files to 10032 (it was originally set to 4864).
14:55:17 redis_cache.1 | 4085:C 22 May 14:55:17.262 # oO0OoO0OoO0Oo Redis is
starting oO0OoO0OoO0Oo
14:55:17 redis_cache.1 | 4085:C 22 May 14:55:17.268 # Redis version=4.0.11,
bits=64, commit=00000000, modified=0, pid=4085, just started
14:55:17 redis_cache.1 | 4085:C 22 May 14:55:17.268 # Configuration loaded
14:55:17 redis_cache.1 | 4085:M 22 May 14:55:17.269 * Increased maximum number
of open files to 10032 (it was originally set to 4864).
14:55:17 redis_socketio.1 | 4086:C 22 May 14:55:17.262 # oO0OoO0OoO0Oo Redis is
starting oO0OoO0OoO0Oo
14:55:17 redis_socketio.1 | 4086:C 22 May 14:55:17.270 # Redis version=4.0.11,
bits=64, commit=00000000, modified=0, pid=4086, just started
14:55:17 redis_socketio.1 | 4086:C 22 May 14:55:17.270 # Configuration loaded
14:55:17 redis_socketio.1 | 4086:M 22 May 14:55:17.272 * Increased maximum number
of open files to 10032 (it was originally set to 4864).
14:55:17 redis_queue.1 | 4088:M 22 May 14:55:17.285 * Running mode=standalone,
port=11002.
14:55:17 redis_queue.1 | 4088:M 22 May 14:55:17.285 # Server initialized
14:55:17 redis_queue.1 | 4088:M 22 May 14:55:17.286 * Ready to accept
connections
14:55:17 redis_cache.1 | 4085:M 22 May 14:55:17.287 * Running mode=standalone,
port=13002.
14:55:17 redis_cache.1 | 4085:M 22 May 14:55:17.292 # Server initialized
14:55:17 redis_cache.1 | 4085:M 22 May 14:55:17.292 * Ready to accept
connections
14:55:17 redis_socketio.1 | 4086:M 22 May 14:55:17.294 * Running mode=standalone,
port=12002.
14:55:17 redis_socketio.1 | 4086:M 22 May 14:55:17.294 # Server initialized
14:55:17 redis_socketio.1 | 4086:M 22 May 14:55:17.295 * Ready to accept
connections
14:55:17 system | worker_long.1 started (pid=4098)
14:55:17 system | worker_default.1 started (pid=4100)
14:55:18 socketio.1 | listening on *: 9002
14:55:20 socketio.1 | { Error: connect ECONNREFUSED 0.0.0.0:8002
14:55:20 socketio.1 | at TCPConnectWrap.afterConnect [as oncomplete]
(net.js:1191:14)
14:55:20 socketio.1 | errno: 'ECONNREFUSED',
14:55:20 socketio.1 | code: 'ECONNREFUSED',
14:55:20 socketio.1 | syscall: 'connect',
14:55:20 socketio.1 | address: '0.0.0.0',
14:55:20 socketio.1 | port: 8002,
14:55:20 socketio.1 | response: undefined }
14:55:24 web.1 | * Running on http://0.0.0.0:8002/ (Press CTRL+C to
quit)
14:55:24 web.1 | * Restarting with fsevents reloader
14:55:24 watch.1 | yarn run v1.10.1
14:55:24 watch.1 | $ node rollup/watch.js
14:55:25 web.1 | * Debugger is active!
14:55:25 web.1 | * Debugger PIN: 321-355-865
14:55:26 watch.1 |
14:55:26 watch.1 | Rollup Watcher Started
14:55:26 watch.1 |
14:55:26 watch.1 | Watching...
14:55:26 watch.1 | Rebuilding frappe-web.css
14:55:27 watch.1 | Rebuilding frappe-web-b4.css
14:55:27 watch.1 | Rebuilding chat.js
14:55:28 web.1 |
14:55:28 web.1 | test print
When you write any print statements in your python code, it will show up in
the web:process log if it is a request/response, or in one of worker_ processes if the code
runs in a background job.
If you are a VSCode user, you can debug right in your editor by setting breakpoints in
your code. Follow these steps to set it up.
Console
To play with Python API, bench provides an iPython shell. After you run the following
command, it will import frappe, initialize it and also connect to database.
Profiling
Bench's execute command runs a dotted path to method and it also supports profiling.
Client
Client side debugging is as simple as adding a debugger statement in your JS file. You
must open your DevTools in your browser for it to pause on the statement.
Console
To play with Client API, you can open your browser's console and use the globally
available frappe object to explore and run methods and access properties.
Browser Console
Great projects also work because of great quality. Open source or not, the user really
cares that things should work as they are advertised, and consistently. New features
should follow the same pattern and so that users don't have to learn things again
and again.
Developers who maintain open source also expect that you follow certain guidelines.
These guidelines ensure that developers are able to quickly give feedback on your
contribution and how to make it better. Most probably you might have to go back
and change a few things, but it will be in the interest of making this process better
for everyone. So be prepared for some back and forth.
Contribution Guides
Please read the following guidelines carefully when contributing:
2. Module Maintainers
3. Naming Guidelines
7. Updating Documentation
8. Coding Standards
Feedback Policy
We will strive for a "Zero Pull Request Pending" policy, inspired by "Zero Inbox". This
means, that if the pull request is good, it will be merged within a day and if it does
not meet the requirements, it will be closed.
Happy contributing!