Skip to content

Quick Start Tutorial

This tutorial will guide you through creating a complete Netforce module from scratch. You'll build a simple Task Management module that demonstrates all key concepts: models, layouts, actions, and menus.

What We'll Build

A task management system with: - Task Model: Title, description, priority, status, assignee - Category Model: Task categories with colors - Forms: Create and edit tasks - Lists: Browse tasks with filtering - Dashboard: Task statistics and charts - Menu: Navigation structure

Prerequisites

  • Netforce framework installed and running
  • Access to the backend codebase (/home/nf/nf_base/)
  • Basic Python and XML knowledge

Step 1: Create Module Structure

Create the module directory structure:

mkdir -p /home/nf/nf_base/netforce_tasks
cd /home/nf/nf_base/netforce_tasks

mkdir -p netforce_tasks/{models,layouts,actions}
touch netforce_tasks/__init__.py
touch netforce_tasks/models/__init__.py

Your structure should look like:

netforce_tasks/
├── __init__.py
└── netforce_tasks/
    ├── __init__.py
    ├── models/
    │   └── __init__.py
    ├── layouts/
    └── actions/

Step 2: Create Models

Task Category Model

Create netforce_tasks/models/task_category.py:

from netforce.model import Model, fields

class TaskCategory(Model):
    _name = "task.category"
    _string = "Task Category"
    _key = ["name"]

    _fields = {
        "name": fields.Char("Name", required=True, search=True),
        "color": fields.Selection([
            ["blue", "Blue"],
            ["green", "Green"], 
            ["yellow", "Yellow"],
            ["red", "Red"],
            ["purple", "Purple"]
        ], "Color", required=True),
        "description": fields.Text("Description"),
        "active": fields.Boolean("Active"),
        "task_count": fields.Integer("Task Count", function="get_task_count"),
    }

    _defaults = {
        "active": True,
        "color": "blue"
    }

    def get_task_count(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            count = get_model("task.task").search_count([["category_id", "=", obj.id]])
            vals[obj.id] = count
        return vals

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            name = f"{obj.name} ({obj.task_count} tasks)"
            vals.append((obj.id, name))
        return vals

Task Model

Create netforce_tasks/models/task.py:

from netforce.model import Model, fields, get_model
from netforce.access import get_active_user
import time

class Task(Model):
    _name = "task.task"
    _string = "Task"
    _name_field = "title"
    _audit_log = True

    _fields = {
        "title": fields.Char("Title", required=True, search=True, size=256),
        "description": fields.Text("Description"),
        "priority": fields.Selection([
            ["low", "Low"],
            ["medium", "Medium"], 
            ["high", "High"],
            ["urgent", "Urgent"]
        ], "Priority", required=True, search=True),
        "state": fields.Selection([
            ["todo", "To Do"],
            ["in_progress", "In Progress"],
            ["done", "Done"],
            ["cancelled", "Cancelled"]
        ], "Status", required=True, search=True),
        "category_id": fields.Many2One("task.category", "Category", search=True),
        "assigned_user_id": fields.Many2One("base.user", "Assigned To", search=True),
        "reporter_user_id": fields.Many2One("base.user", "Reporter", search=True),
        "start_date": fields.Date("Start Date"),
        "due_date": fields.Date("Due Date", search=True),
        "completed_date": fields.DateTime("Completed Date"),
        "estimated_hours": fields.Decimal("Estimated Hours", scale=1),
        "actual_hours": fields.Decimal("Actual Hours", scale=1),
        "tags": fields.Char("Tags", search=True),
        "progress": fields.Integer("Progress %"),
        "comments": fields.One2Many("message", "related_id", "Comments"),

        # Computed fields
        "is_overdue": fields.Boolean("Overdue", function="get_computed_fields", function_multi=True),
        "days_remaining": fields.Integer("Days Remaining", function="get_computed_fields", function_multi=True),
        "priority_score": fields.Integer("Priority Score", function="get_computed_fields", function_multi=True),

        # Audit fields (automatically managed)
        "create_uid": fields.Many2One("base.user", "Created By"),
        "create_time": fields.DateTime("Created Time"),
    }

    _defaults = {
        "state": "todo",
        "priority": "medium", 
        "progress": 0,
        "reporter_user_id": lambda *a: get_active_user(),
        "start_date": lambda *a: time.strftime("%Y-%m-%d"),
    }

    _order = "priority desc, due_date, create_time desc"

    def get_computed_fields(self, ids, context={}):
        vals = {}
        today = time.strftime("%Y-%m-%d")

        priority_scores = {"low": 1, "medium": 2, "high": 3, "urgent": 4}

        for obj in self.browse(ids):
            is_overdue = False
            days_remaining = None

            if obj.due_date and obj.state not in ["done", "cancelled"]:
                due_date = obj.due_date
                if due_date < today:
                    is_overdue = True
                    # Calculate negative days (overdue)
                    from datetime import datetime
                    due = datetime.strptime(due_date, "%Y-%m-%d")
                    now = datetime.strptime(today, "%Y-%m-%d")
                    days_remaining = (due - now).days
                else:
                    # Calculate positive days (remaining)
                    from datetime import datetime
                    due = datetime.strptime(due_date, "%Y-%m-%d")
                    now = datetime.strptime(today, "%Y-%m-%d")
                    days_remaining = (due - now).days

            vals[obj.id] = {
                "is_overdue": is_overdue,
                "days_remaining": days_remaining,
                "priority_score": priority_scores.get(obj.priority, 2)
            }

        return vals

    def mark_done(self, ids, context={}):
        """Mark tasks as completed"""
        for obj in self.browse(ids):
            if obj.state == "done":
                continue

            obj.write({
                "state": "done",
                "progress": 100,
                "completed_date": time.strftime("%Y-%m-%d %H:%M:%S")
            })

    def start_task(self, ids, context={}):
        """Start working on task"""
        for obj in self.browse(ids):
            obj.write({
                "state": "in_progress",
                "assigned_user_id": get_active_user()
            })

    def copy(self, ids, context={}):
        """Override copy to clear dates and state"""
        new_ids = []
        for obj in self.browse(ids):
            vals = obj.read()[0]
            # Clear fields that shouldn't be copied
            vals.update({
                "title": vals["title"] + " (Copy)",
                "state": "todo",
                "progress": 0,
                "completed_date": None,
                "actual_hours": None
            })
            new_id = self.create(vals, context)
            new_ids.append(new_id)
        return new_ids

Register Models

Update netforce_tasks/models/__init__.py:

from . import task_category
from . import task

Update netforce_tasks/__init__.py:

from . import models

Step 3: Create Layouts

Task Form Layout

Create netforce_tasks/layouts/task_form.xml:

<form model="task.task" attrs='{"readonly":[["state","=","done"]]}'>
    <top>
        <button string="Start Task" method="start_task" states="todo" 
                attrs='{"invisible":[["assigned_user_id","!=",null]]}' icon="play"/>
        <button string="Mark Done" method="mark_done" states="in_progress,todo" icon="check"/>
        <button string="Copy Task" method="copy" icon="copy"/>
    </top>

    <head>
        <field name="state"/>
    </head>

    <group string="Basic Information" span="8">
        <field name="title" span="12"/>
        <field name="category_id" span="6"/>
        <field name="priority" span="6"/>
        <field name="assigned_user_id" span="6"/>
        <field name="reporter_user_id" span="6"/>
        <newline/>
        <field name="description" span="12" height="100"/>
    </group>

    <group string="Scheduling" span="4">
        <field name="start_date"/>
        <field name="due_date"/>
        <field name="progress"/>
        <field name="estimated_hours"/>
        <field name="actual_hours"/>
        <field name="completed_date" readonly="1" attrs='{"invisible":[["state","!=","done"]]}'/>
    </group>

    <group string="Status Information" span="8">
        <field name="tags" span="6"/>
        <field name="is_overdue" readonly="1" span="3" 
               attrs='{"invisible":[["is_overdue","=",false]]}'/>
        <field name="days_remaining" readonly="1" span="3"
               attrs='{"invisible":[["days_remaining","=",null]]}'/>
    </group>

    <field name="comments" nolabel="1" span="12"/>

    <template span="12">
        <![CDATA[
        {{#if is_overdue}}
        <div class="alert alert-danger">
            <strong>Warning:</strong> This task is overdue by {{Math.abs(days_remaining)}} day(s).
        </div>
        {{/if}}

        {{#if days_remaining}}
        {{#if (and (not is_overdue) (lt days_remaining 3))}}
        <div class="alert alert-warning">
            <strong>Notice:</strong> This task is due in {{days_remaining}} day(s).
        </div>
        {{/if}}
        {{/if}}
        ]]>
    </template>
</form>

Task List Layout

Create netforce_tasks/layouts/task_list.xml:

<list model="task.task" colors='{"todo":"muted","in_progress":"info","done":"success","cancelled":"danger"}'>
    <field name="title" link="1"/>
    <field name="category_id"/>
    <field name="priority"/>
    <field name="assigned_user_id"/>
    <field name="due_date"/>
    <field name="progress" align="center"/>
    <field name="state"/>
    <field name="is_overdue" invisible="1"/>  <!-- Hidden but used for coloring -->
</list>

Category Form Layout

Create netforce_tasks/layouts/task_category_form.xml:

<form model="task.category">
    <group>
        <field name="name" span="8"/>
        <field name="color" span="4"/>
        <field name="active" span="4"/>
        <field name="task_count" span="4" readonly="1"/>
        <field name="description" span="12" height="80"/>
    </group>
</form>

Category List Layout

Create netforce_tasks/layouts/task_category_list.xml:

<list model="task.category">
    <field name="name"/>
    <field name="color"/>
    <field name="task_count" align="center"/>
    <field name="active"/>
</list>

Dashboard Layout

Create netforce_tasks/layouts/task_board.xml:

<board>
    <hpanel>
        <widget action="task_stats_widget" string="Task Statistics" span="12"/>
    </hpanel>

    <hpanel>
        <vpanel span="8">
            <widget action="task_priority_chart" string="Tasks by Priority" height="300"/>
            <widget action="task_timeline" string="Task Timeline"/>
        </vpanel>

        <vpanel span="4">
            <widget action="overdue_tasks" string="Overdue Tasks"/>
            <widget action="my_tasks" string="My Tasks"/>
            <widget action="task_categories" string="Categories"/>
        </vpanel>
    </hpanel>
</board>

Create netforce_tasks/layouts/task_menu.xml:

<menu string="Tasks">
    <item string="Dashboard" icon="fa_tachometer_alt" action="task_board"/>

    <item string="Tasks" icon="fa_tasks">
        <item string="All Tasks" action="all_tasks"/>
        <item string="My Tasks" action="my_tasks"/>
        <item string="Overdue Tasks" action="overdue_tasks"/>
        <divider/>
        <item string="To Do" action="todo_tasks"/>
        <item string="In Progress" action="progress_tasks"/>
        <item string="Completed" action="done_tasks"/>
    </item>

    <item string="Reports" icon="fa_chart_bar">
        <item string="Task Analysis" action="task_analysis"/>
        <item string="User Performance" action="user_performance"/>
        <item string="Category Report" action="category_report"/>
    </item>

    <item string="Configuration" icon="fa_cog">
        <item string="Categories" action="task_categories"/>
        <item string="Task Settings" action="task_settings"/>
    </item>
</menu>

Step 4: Create Actions

Create netforce_tasks/actions/task_actions.xml:

<!-- Main task actions -->
<action>
    <field name="view">form</field>
    <field name="string">Task</field>
    <field name="model">task.task</field>
    <field name="layout">task_form</field>
</action>

<action>
    <field name="view">list</field>
    <field name="string">All Tasks</field>
    <field name="model">task.task</field>
    <field name="layout">task_list</field>
    <field name="menu">task_menu</field>
</action>

<action>
    <field name="view">list</field>
    <field name="string">My Tasks</field>
    <field name="model">task.task</field>
    <field name="condition">[["assigned_user_id","=",uid]]</field>
    <field name="tabs">[["Active",[["state","in",["todo","in_progress"]]]],["All",[]],["Done",[["state","=","done"]]]]</field>
</action>

<action>
    <field name="view">list</field>
    <field name="string">Overdue Tasks</field>
    <field name="model">task.task</field>
    <field name="condition">[["is_overdue","=",true]]</field>
</action>

<action>
    <field name="view">list</field>
    <field name="string">To Do Tasks</field>
    <field name="model">task.task</field>
    <field name="condition">[["state","=","todo"]]</field>
</action>

<action>
    <field name="view">list</field>
    <field name="string">In Progress Tasks</field>
    <field name="model">task.task</field>
    <field name="condition">[["state","=","in_progress"]]</field>
</action>

<action>
    <field name="view">list</field>
    <field name="string">Completed Tasks</field>
    <field name="model">task.task</field>
    <field name="condition">[["state","=","done"]]</field>
</action>

<!-- Category actions -->
<action>
    <field name="view">list</field>
    <field name="string">Task Categories</field>
    <field name="model">task.category</field>
    <field name="layout">task_category_list</field>
</action>

<!-- Dashboard action -->
<action>
    <field name="view">board</field>
    <field name="string">Task Dashboard</field>
    <field name="layout">task_board</field>
</action>

Step 5: Test Your Module

1. Restart the Backend

Restart the Netforce backend server to load your new module:

# Stop the backend process
pkill -f netforce

# Start it again (adjust path as needed)
cd /home/nf/nf_base
python -m netforce.server

2. Access the Frontend

  1. Open your browser to the Netforce frontend
  2. Look for "Tasks" in the main menu
  3. Navigate to Tasks → All Tasks
  4. Click "New" to create your first task

3. Create Test Data

Create some sample data to test your module:

  1. Create Categories:
  2. Go to Configuration → Categories
  3. Create: "Bug Fix" (Red), "Feature" (Blue), "Documentation" (Green)

  4. Create Tasks:

  5. Title: "Fix login bug", Category: Bug Fix, Priority: High
  6. Title: "Add user dashboard", Category: Feature, Priority: Medium
  7. Title: "Update API docs", Category: Documentation, Priority: Low

  8. Test Functionality:

  9. Mark tasks as "In Progress" and "Done"
  10. Assign tasks to different users
  11. Set due dates and test overdue detection

Step 6: Add Advanced Features

Custom Dashboard Widgets

Create netforce_tasks/models/task_dashboard.py:

from netforce.model import Model, fields, get_model

class TaskDashboard(Model):
    _name = "task.dashboard"
    _string = "Task Dashboard"

    def get_task_stats(self, context={}):
        stats = {}

        # Count by status
        for state in ["todo", "in_progress", "done", "cancelled"]:
            count = get_model("task.task").search_count([["state", "=", state]])
            stats[f"{state}_count"] = count

        # Count by priority
        for priority in ["low", "medium", "high", "urgent"]:
            count = get_model("task.task").search_count([["priority", "=", priority]])
            stats[f"{priority}_count"] = count

        # Overdue count
        stats["overdue_count"] = get_model("task.task").search_count([["is_overdue", "=", True]])

        return stats

    def get_priority_chart_data(self, context={}):
        data = []
        colors = {"low": "#28a745", "medium": "#ffc107", "high": "#fd7e14", "urgent": "#dc3545"}

        for priority in ["low", "medium", "high", "urgent"]:
            count = get_model("task.task").search_count([["priority", "=", priority]])
            if count > 0:
                data.append({
                    "label": priority.title(),
                    "value": count,
                    "color": colors[priority]
                })

        return data

Email Notifications

Add email notification when tasks are assigned:

# Add to task.py
def write(self, ids, vals, context={}):
    # Send notification if assigned user changed
    if "assigned_user_id" in vals:
        for obj in self.browse(ids):
            old_user_id = obj.assigned_user_id.id if obj.assigned_user_id else None
            new_user_id = vals["assigned_user_id"]

            if new_user_id and new_user_id != old_user_id:
                self.send_assignment_email([obj.id], context)

    return super().write(ids, vals, context)

def send_assignment_email(self, ids, context={}):
    for obj in self.browse(ids):
        if not obj.assigned_user_id or not obj.assigned_user_id.email:
            continue

        # Send email notification
        get_model("email.message").create({
            "to": obj.assigned_user_id.email,
            "subject": f"Task Assigned: {obj.title}",
            "body": f"""
            <p>You have been assigned a new task:</p>
            <ul>
                <li><strong>Title:</strong> {obj.title}</li>
                <li><strong>Priority:</strong> {obj.priority}</li>
                <li><strong>Due Date:</strong> {obj.due_date or 'Not set'}</li>
            </ul>
            <p>Please log in to view the full details.</p>
            """,
            "type": "out"
        })

Step 7: Add Reports

Task Analysis Report

Create netforce_tasks/reports/task_analysis.py:

from netforce.model import Model, fields
from netforce.template import render_template

class TaskAnalysisReport(Model):
    _name = "report.task.analysis"
    _string = "Task Analysis Report"

    def render_report(self, context={}):
        # Gather data
        data = {
            "total_tasks": get_model("task.task").search_count([]),
            "completed_tasks": get_model("task.task").search_count([["state", "=", "done"]]),
            "overdue_tasks": get_model("task.task").search_count([["is_overdue", "=", True]]),
        }

        # Calculate completion rate
        if data["total_tasks"] > 0:
            data["completion_rate"] = (data["completed_tasks"] / data["total_tasks"]) * 100
        else:
            data["completion_rate"] = 0

        # Get tasks by category
        categories = get_model("task.category").search_browse([])
        data["categories"] = []
        for cat in categories:
            cat_data = {
                "name": cat.name,
                "task_count": cat.task_count,
                "color": cat.color
            }
            data["categories"].append(cat_data)

        # Render HTML report
        html = render_template("task_analysis_template.html", data)
        return {"data": html, "type": "html"}

Congratulations! 🎉

You've successfully created a complete Netforce module with:

  • ✅ Two Models with relationships and computed fields
  • ✅ Form and List Layouts with conditional formatting
  • ✅ Custom Methods for business logic
  • ✅ Dashboard with widgets
  • ✅ Menu Structure for navigation
  • ✅ Actions linking everything together

Next Steps

Enhance Your Module

  1. Add More Fields: Tags, attachments, time tracking
  2. Create Workflows: Approval processes, automated assignments
  3. Build Reports: Detailed analytics and charts
  4. Add Permissions: Role-based access control
  5. Integrate APIs: Connect with external tools
  6. Mobile Layout: Optimize for mobile devices

Learn More

  • Study the Models guide for advanced field types
  • Explore Layouts for complex UI patterns
  • Check Frontend for custom components
  • Review API for integration possibilities
  • Browse real examples in netforce_account module

Deploy to Production

  1. Add Tests: Create unit tests in tests/ directory
  2. Add Migrations: Handle database schema changes
  3. Optimize Performance: Add database indexes
  4. Security Review: Check permissions and validation
  5. Documentation: Document your API and workflows

You now have a solid foundation in Netforce development. The same patterns you learned here can be applied to build complex ERP systems, CRM applications, inventory management, and much more!

Happy coding! 🚀