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:
Update netforce_tasks/__init__.py:
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>
Menu Layout¶
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¶
- Open your browser to the Netforce frontend
- Look for "Tasks" in the main menu
- Navigate to Tasks → All Tasks
- Click "New" to create your first task
3. Create Test Data¶
Create some sample data to test your module:
- Create Categories:
- Go to Configuration → Categories
-
Create: "Bug Fix" (Red), "Feature" (Blue), "Documentation" (Green)
-
Create Tasks:
- Title: "Fix login bug", Category: Bug Fix, Priority: High
- Title: "Add user dashboard", Category: Feature, Priority: Medium
-
Title: "Update API docs", Category: Documentation, Priority: Low
-
Test Functionality:
- Mark tasks as "In Progress" and "Done"
- Assign tasks to different users
- 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¶
- Add More Fields: Tags, attachments, time tracking
- Create Workflows: Approval processes, automated assignments
- Build Reports: Detailed analytics and charts
- Add Permissions: Role-based access control
- Integrate APIs: Connect with external tools
- 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_accountmodule
Deploy to Production¶
- Add Tests: Create unit tests in
tests/directory - Add Migrations: Handle database schema changes
- Optimize Performance: Add database indexes
- Security Review: Check permissions and validation
- 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! 🚀