Understanding and Utilizing the Controller Feature

Introduction

Welcome, developers! Today, we are diving deep into one of the core features of Empress, the Controller. This feature lies at the heart of your software development journey with Empress. It plays a pivotal role in managing how values are loaded from the database, parsed and saved back into the database.

Understanding Controllers

In Empress, a Controller is a standard Python class that extends from the frappe.model.Document base class. This base class is the core logic of a DocType, which is a model that defines the type of a document like a User, a Blog Post, etc.

Let’s say you create a DocType named Person. Empress automatically generates a Python file named person.py in the backend. This file might look something like:

import frappe
from frappe.model.document import Document

class Person(Document):
    pass

In this scenario, all the fields of the Person class are available as attributes.

Controller Methods

You can enhance the functionality of your Controller by adding custom methods, which can then be called using the doc object. Let’s see an example:

# controller class
class Person(Document):
    def get_full_name(self):
        """Returns the person's full name"""
        return f"{self.first_name} {self.last_name}"

# somewhere in your code
>>> doc = frappe.get_doc("Person", "000001")
>>> doc.get_full_name()
John Doe

Controller Hooks

To add custom behaviour during the lifecycle of a document, we have controller hooks. These hooks can be used to invoke specific functions at different stages of a document’s lifecycle. Below is the list of these hooks:

Method Name Description Insert Save Submit Cancel Update after submit
before_insert Called before a document is prepared for insertion. X
before_naming Called before the name property of the document is set. X
autoname If defined in the controller, this method is used to set name property of the document. X
before_validate Called before validation. Use this for auto setting missing values. X X X
validate Use this method to throw any validation errors and prevent the document from saving. X X X
before_save Called before the document is saved. X X
before_submit Called before the document is submitted. X X
before_cancel Called before the document is cancelled. X
before_update_after_submit Called when doc fields are updated on submitted document. X
db_insert Inserts document in database, do not override this unless you’re working on virtual DocType. X
after_insert Called after the document is inserted into the database. X
db_update Updates document in database, do not override this unless you’re working on virtual DocType. X X X X
on_update Called when values of an existing document are updated. X X
on_submit Called when a document is submitted. X
on_cancel Called when a submitted document is cancelled. X
on_update_after_submit Called when a submitted document values are updated. X
on_change Called when a document’s values has been changed. This method is also called when db_set is performed, so operation performed in this this method should be idempotent. X X X X X

To use a controller hook, just define a class method with that name. For example:

class Person(Document):
    def validate(self):
        if self.age <= 18:
            frappe.throw("Person's age must be at least 18")

    def after_insert(self):
        frappe.sendmail(recipients=[self.email], message="Thank you for registering!")

Document Handling

One of the key functionalities that the Controller feature offers is the handling of documents. A document is an instance of a DocType and usually maps to a single row in the database table. We refer to it as doc in code.

For instance, to create a new document and save it to the database, we can use:

doc = frappe.get_doc({
    'doctype': 'Person',
    'first_name': 'John',
    'last_name': 'Doe'
})
doc.insert()

doc.name # 000001

Type Annotations

Starting from Version 15, Empress supports automatically generating Python type annotations in controller files. These annotations can be used for auto-completion, reference, and type-checking inside the controller file.

class Person(Document):
    # begin: auto-generated types
    # This code is auto-generated. Do not modify anything in this block.

    from typing import TYPE_CHECKING

    if TYPE_CHECKING:
        from frappe.types import DF

        first_name: DF.Data
        last_name: DF.Data 
        user: DF.Link
    # end: auto-generated types
    pass

These annotations are generated when creating or updating doctypes. If you modify the code block, it will get overridden on the next update. You can configure automatic exporting in your app by adding the following hook:

# hooks.py

export_python_type_annotations = True

Wrapping Up

Understanding and effectively utilizing the Controller feature is a significant step towards mastering backend programming with Empress. From managing how values are fetched and stored in the database to adding custom methods and using hooks, the Controller feature allows developers to customize and optimize their software development process.

Remember, the key to effective software development is understanding and leveraging the features and functionalities at your disposal. So, dive deep, explore, and make the most out of the feature-rich world of Empress. Happy coding!