Skip to content

Boost Your Productivity: Master the Odoo 18 Systray Widget with OWL 2

keyphrase odoo 18 systray widget

Hello, Odoo enthusiasts and developers! Welcome back to Odoistic. Today, we’re diving into an incredibly exciting and powerful feature in Odoo 18 development: creating a custom Odoo 18 Systray Widget. Whether you’re a seasoned developer looking to enhance user experience or simply curious about customizing the Odoo backend, you’ve landed in the perfect spot.

In this comprehensive tutorial, we will walk you through building a dynamic OWL 2 widget that displays recent project tasks directly in the systray, just like native Odoo notifications. Imagine having critical task updates at your fingertips, elegantly integrated into your Odoo interface. This is not just about aesthetics; it’s about boosting productivity and ensuring vital information is always visible. Mastering the Odoo 18 Systray Widget will significantly upgrade your Odoo experience.

This guide is inspired by a detailed walkthrough available on Odoistic’s YouTube channel. You can follow along with the original video tutorial here: https://www.youtube.com/watch?v=1Gmxra8OqAI.

By the end of this guide, you will master:

  • How to construct an advanced systray widget using the OWL 2 framework.
  • Techniques for fetching and displaying real-time project tasks from the Odoo backend.
  • Leveraging RPC controllers to establish seamless communication between your frontend and backend.
  • Refining the user experience with Bootstrap for polished dropdown styling.

All these capabilities will be encapsulated within a lightweight, reusable Odoo module. So, let’s stop talking and start building this essential OWL task notification widget, a truly effective Odoo 18 Systray Widget!

Why a Custom Odoo 18 Systray Widget is a Game Changer for Your Business

In the fast-paced world of business, access to real-time, relevant information can significantly impact efficiency and decision-making. A custom Odoo 18 Systray Widget isn’t just a minor UI tweak; it’s a strategic enhancement that can revolutionize how users interact with their Odoo instance. This particular Odoo 18 Systray Widget will put crucial project tasks right where they need to be.

Imagine these scenarios:

  • Project Managers: Get instant alerts for new tasks, overdue tasks, or tasks awaiting their review without navigating through various menus. Your Odoo 18 Systray Widget becomes a project nerve center.
  • Sales Teams: See pending quotes or leads that require immediate action, ensuring no opportunity is missed. A custom Odoo 18 Systray Widget could highlight top priorities.
  • Support Agents: Receive notifications for new support tickets or urgent customer inquiries, leading to quicker response times and improved customer satisfaction. This highlights the power of a well-implemented Odoo 18 Systray Widget.

Key benefits of integrating an Odoo Systray Widget:

  1. Enhanced User Experience (UX): By placing critical information in an easily accessible location (the top bar), you reduce clicks and navigation time. Users get a personalized dashboard of urgent items, leading to a smoother, more intuitive workflow. This makes the Odoo 18 Systray Widget a user-centric improvement.
  2. Increased Productivity: Immediate visibility of relevant tasks or notifications means users can prioritize their work effectively. No more digging through lists; the most important items are presented upfront. This direct access to vital data minimizes context switching, a notorious productivity killer. The Odoo 18 Systray Widget acts as a constant reminder of immediate priorities.
  3. Real-Time Insights: The ability to fetch and display data in real-time ensures that users are always working with the most current information, which is crucial for dynamic environments like project management or customer service. Our Odoo 18 Systray Widget will automatically refresh tasks, keeping data fresh and actionable.
  4. Customization and Flexibility: Odoo’s modular architecture, combined with OWL 2, offers unparalleled flexibility. You’re not just limited to project tasks; you can adapt this approach to display any critical data from any Odoo model. This makes the Odoo 18 Systray Widget a versatile tool for bespoke business needs.
  5. Modern UI Integration: OWL 2 components blend seamlessly with Odoo’s modern backend UI. Your custom widget will look and feel like an integral part of Odoo, maintaining a consistent and professional aesthetic. The Odoo 18 Systray Widget perfectly complements Odoo’s native design.

This tutorial focuses on displaying project tasks, but the underlying principles can be applied to myriad business requirements. By the end, you’ll have the foundational knowledge to extend this functionality across your Odoo deployments and build even more sophisticated Odoo 18 Systray Widget implementations.

Understanding the Architecture: Deconstructing the Odoo 18 Systray Widget

Building a sophisticated feature like a custom systray notification requires a clear understanding of its underlying architecture. The Odoo 18 Systray Widget we’re constructing relies on a robust interplay between frontend and backend components, primarily utilizing OWL 2 for the UI and Python for data handling. This architectural clarity is key to developing a powerful Odoo 18 Systray Widget.

Here’s a breakdown of the key architectural components involved in creating your Odoo 18 Systray Widget:

  • OWL 2 Frontend Component (JavaScript): This is the core of our interactive widget. Written in JavaScript using the OWL 2 framework, it defines the widget’s behavior, state management, and interaction logic. It’s responsible for:
    • Fetching data from the Odoo backend using Remote Procedure Calls (RPC).
    • Managing its internal state (e.g., the list of tasks to display).
    • Handling user interactions, such as clicking on a task to open it.
    • Registering itself in the Odoo systray category, making it visible as an Odoo 18 Systray Widget.
    • For more on OWL 2, refer to the Odoo OWL documentation.
  • QWeb Template (XML): This XML file defines the visual structure and appearance of our OWL 2 component. It uses Odoo’s QWeb templating engine to render the HTML markup. It’s responsible for:
    • Defining the button that appears in the systray.
    • Structuring the dropdown menu.
    • Iterating over the tasks provided by the OWL component and displaying them.
    • Applying Bootstrap classes for responsive and elegant styling. This ensures your Odoo 18 Systray Widget looks polished. You can find more about Bootstrap on their official website.
  • Backend Controller (Python): This is the server-side logic that acts as an API endpoint for our frontend. Written in Python, it exposes a custom route that the OWL component can call to request data. It’s responsible for:
    • Querying the Odoo database for specific information (e.g., the latest project tasks).
    • Processing the data and returning it in a structured format (JSON) that the frontend can easily consume.
    • Ensuring proper authentication and authorization for data access. This Python component is crucial for the data behind the Odoo 18 Systray Widget.
  • Odoo Module Structure: All these components are neatly organized within a standard Odoo module. This structure ensures that our widget is easily installable, manageable, and compatible with Odoo’s ecosystem. The __manifest__.py file plays a crucial role in declaring dependencies and registering the frontend assets for the Odoo 18 Systray Widget.

This separation of concerns—frontend logic and presentation, backend data retrieval, and modular packaging—is fundamental to building scalable and maintainable custom features in Odoo. Now, let’s get our hands dirty and start implementing each part of this powerful Odoo 18 Systray Widget.

Step-by-Step Tutorial: Building Your Odoo 18 Systray Widget

Let’s begin by structuring our custom Odoo module. This systematic approach ensures maintainability and adherence to Odoo’s best practices for creating a robust Odoo 18 Systray Widget.

1. Setting Up the Module Structure

First, navigate to your custom Odoo addons path (e.g., ~/odoo/custom_addons/ or ~/odoo-dev/custom_modules/). Inside this directory, create your new module folder.

  • Create the Module Folder:
mkdir owl_notification_widget
cd owl_notification_widget
  • Create Base Files:
touch __init__.py
touch __manifest__.py
  • __init__.py: This file is essential for Python to recognize the folder as a package. For now, it can remain empty, but we’ll add content later.
  • __manifest__.py: This file contains metadata about our module and defines its dependencies and assets, essential for your Odoo 18 Systray Widget.
  • Create Subfolders:
mkdir controllers
mkdir static
mkdir static/description
mkdir static/src
mkdir static/src/js
mkdir static/src/xml
  • controllers/: Will house our Python backend controller.
  • static/: Contains static assets like JavaScript, XML templates, and module icons.
  • static/description/: Typically used for the module’s icon.png or icon.svg file, displayed in the Odoo Apps list.
  • static/src/: Contains source files for frontend assets.
  • static/src/js/: Where our OWL 2 JavaScript component will reside.
  • static/src/xml/: Where our QWeb XML template will reside.

Your module structure, a blueprint for your Odoo 18 Systray Widget, should now look like this:

owl_notification_widget/
├── __init__.py
├── __manifest__.py
├── controllers/
│   └── __init__.py (will be created in step 5)
│   └── main.py (will be created in step 4)
└── static/
    ├── description/
    │   └── icon.png (optional, will be added in step 7)
    └── src/
        ├── js/
        │   └── task_notification_widget.js (will be created in step 2)
        └── xml/
            └── task_notification_widget.xml (will be created in step 3)

2. Configuring the Manifest File (__manifest__.py)

Open __manifest__.py and paste the following code. This file is crucial as it tells Odoo about your module, its dependencies, and where to find its assets for the Odoo 18 Systray Widget.

{
    'name': 'OWL Task Notification Widget',
    'version': '1.0',
    'author': 'Odoistic', # You can replace with your name
    'summary': 'Displays recent project tasks in the Odoo systray using OWL 2.',
    'category': 'Tools',
    'depends': ['base', 'web', 'project'],
    'assets': {
        'web.assets_backend': [
            'owl_notification_widget/static/src/js/task_notification_widget.js',
            'owl_notification_widget/static/src/xml/task_notification_widget.xml',
        ],
    },
    'installable': True,
    'application': False,
    'license': 'LGPL-3',
}
  • Explanation:
    • name, version, author, summary, category: Standard metadata describing your module. A good summary helps users understand its purpose.
    • depends: Specifies that this module requires base (core Odoo functionalities), web (for web client components and OWL support), and project (because we’re dealing with project tasks). These modules must be installed for your custom Odoo 18 Systray Widget to function.
    • assets: This is vital! It defines which JavaScript and XML files should be loaded into the backend asset bundle. web.assets_backend is the bundle used for the Odoo backend interface. Our JS and XML files are declared here, ensuring Odoo loads them correctly when a user accesses the backend. This is how the Odoo 18 Systray Widget components are registered.
    • installable: True marks the module as installable.
    • application: False indicates that this module adds functionality to existing applications rather than being a standalone application in the Apps menu.
    • license: Specifies the module’s license.

3. Creating the JavaScript File (static/src/js/task_notification_widget.js)

This file contains the OWL 2 component logic for our Odoo 18 Systray Widget. Create task_notification_widget.js inside static/src/js/ and paste the following code:

/** @odoo-module */

import { Component, useState, onMounted, onWillUnmount } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { _t } from "@web/core/l10n/translation"; // Import translation function

export class TaskNotificationWidget extends Component {
    setup() {
        this.state = useState({ tasks: [] }); // Reactive state to hold tasks
        this.rpc = useService("rpc"); // Odoo RPC service for backend calls
        this.notification = useService("notification"); // Odoo notification service
        this.action = useService("action"); // Odoo action service to open records

        // Bind 'this' to methods that will be used as event handlers
        this.openTask = this.openTask.bind(this);
        
        // Register lifecycle hooks
        onMounted(this.onMounted);
        onWillUnmount(this.onWillUnmount);
    }

    async onMounted() {
        await this.fetchTasks(); // Fetch tasks immediately on component mount
        this.timer = setInterval(this.fetchTasks.bind(this), 60000); // Refresh every minute (60000 ms)
    }

    onWillUnmount() {
        clearInterval(this.timer); // Clean up timer to prevent memory leaks
    }

    async fetchTasks() {
        try {
            // Call the custom backend route to get tasks
            this.state.tasks = await this.rpc(
                "/task_notification/tasks", 
                {} // No parameters needed for this RPC call
            );
        } catch (error) {
            // Display an error notification if tasks cannot be loaded
            this.notification.add(_t("Could not load tasks."), { 
                type: "danger",
                sticky: false, // Notification will disappear after a short time
            });
            console.error("Failed to fetch tasks for Odoo 18 Systray Widget:", error); // Log error for debugging
        }
    }

    openTask(taskId) {
        // Use the action service to open the task in a form view
        this.action.doAction({
            type: 'ir.actions.act_window', // Action type for opening a window
            res_model: 'project.task',     // Model name
            res_id: taskId,                // ID of the record to open
            views: [[false, 'form']],      // Display only the form view
            target: 'current'              // Open in the current window/tab
        });
    }
}

// Associate the OWL component with its QWeb template
TaskNotificationWidget.template = "owl_notification_widget.TaskNotificationWidget";

// Register the component in the systray category
registry.category("systray").add("owl_notification_widget.task_notification_widget", {
    Component: TaskNotificationWidget,
    sequence: 1, // Determines the order in the systray (lower number means further left)
});
  • Explanation:
    • @odoo-module: A directive telling Odoo this is an OWL JavaScript module.
    • import: Brings in necessary functionalities:
      • Component, useState, onMounted, onWillUnmount from @odoo/owl for core OWL component features.
      • useService from @web/core/utils/hooks to access Odoo services like rpc (for backend communication), notification (for user feedback), and action (to trigger Odoo actions like opening records).
      • registry from @web/core/registry to register our widget.
      • _t for internationalization and translation.
    • setup(): The constructor-like method where component setup occurs.
      • this.state = useState({ tasks: [] }): Initializes a reactive state variable tasks as an empty array. Any changes to this.state.tasks will automatically trigger a re-render of the component.
      • useService(...): Injects Odoo services for RPC calls, notifications, and actions, crucial for the functionality of our Odoo 18 Systray Widget.
      • this.openTask = this.openTask.bind(this): Binds the openTask method to the component instance, ensuring this refers to the component when the method is used as an event handler (e.g., in a click).
      • onMounted and onWillUnmount: Lifecycle hooks. onMounted calls fetchTasks immediately and sets up a setInterval to refresh tasks every minute. onWillUnmount clears this timer to prevent memory leaks when the widget is removed.
    • fetchTasks(): An asynchronous method that uses this.rpc to call our custom backend route /task_notification/tasks. It updates this.state.tasks with the fetched data. Includes error handling using the notification service for the Odoo 18 Systray Widget.
    • openTask(taskId): Triggered when a task in the dropdown is clicked. It uses this.action.doAction to open the project.task record in a form view within the current Odoo window.
    • TaskNotificationWidget.template: Links this OWL component to its visual template, defined in the XML file (next step).
    • registry.category("systray").add(...): This is where the magic happens for the Odoo 18 Systray Widget. It registers our TaskNotificationWidget component in the systray category, making it appear in the top Odoo bar. sequence: 1 determines its position (lower numbers appear further left).

4. Crafting the XML Template (static/src/xml/task_notification_widget.xml)

This XML file defines the visual representation of our Odoo 18 Systray Widget. Create task_notification_widget.xml inside static/src/xml/ and add the following code:

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <!-- Template definition for the Task Notification Widget -->
    <t t-name="owl_notification_widget.TaskNotificationWidget" owl="1">
        <div class="dropdown">
            <!-- Button that triggers the dropdown for our Odoo 18 Systray Widget -->
            <button class="btn btn-sm btn-secondary dropdown-toggle" 
                    data-bs-toggle="dropdown" 
                    aria-haspopup="true" 
                    aria-expanded="false"
                    title="View Latest Tasks">
                <i class="fa fa-tasks"/> <!-- Font Awesome icon for tasks -->
                Tasks <t t-esc="state.tasks.length"/> <!-- Displays the number of tasks -->
            </button>

            <!-- The actual dropdown menu content for the Odoo 18 Systray Widget -->
            <div class="dropdown-menu dropdown-menu-end p-2">
                <!-- Conditionally render tasks if they exist -->
                <t t-if="state.tasks.length">
                    <!-- Loop through each task and display it as a clickable link -->
                    <t t-foreach="state.tasks" t-as="task" t-key="task.id">
                        <a class="dropdown-item" t-on-click.prevent="() => openTask(task.id)">
                            <t t-esc="task.name"/> <!-- Display task name -->
                        </a>
                    </t>
                </t>
                <!-- Fallback message if no tasks are available -->
                <t t-else="">
                    <div class="dropdown-item text-muted">No tasks found.</div>
                </t>
            </div>
        </div>
    </t>
</templates>
  • Explanation:
    • <templates>: The root element for QWeb templates.
    • <t t-name="owl_notification_widget.TaskNotificationWidget" owl="1">: Defines our template. The t-name attribute must match the TaskNotificationWidget.template value in our JavaScript file. owl="1" indicates it’s an OWL-compatible template for our Odoo 18 Systray Widget.
    • <div class="dropdown">: A standard Bootstrap class to create a dropdown container.
    • <button ...>: This is the interactive element in the systray.
      • class="btn btn-sm btn-secondary dropdown-toggle": Bootstrap classes for styling a small, secondary (gray) button with dropdown toggle functionality.
      • data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false": Essential Bootstrap attributes to enable dropdown behavior.
      • <i class="fa fa-tasks"/>: Displays a Font Awesome task icon for better visual recognition.
      • Tasks <t t-esc="state.tasks.length"/>: Displays the word “Tasks” followed by the current number of tasks, dynamically fetched from this.state.tasks.length in our JS component. This is the visual feedback of your Odoo 18 Systray Widget.
    • <div class="dropdown-menu dropdown-menu-end p-2">: The container for the dropdown’s content. dropdown-menu-end aligns it to the right, p-2 adds padding.
    • <t t-if="state.tasks.length">: A QWeb directive that conditionally renders the content inside if state.tasks (from our JS component) has elements.
    • <t t-foreach="state.tasks" t-as="task" t-key="task.id">: Loops through each task in the state.tasks array. t-key is important for efficient rendering in OWL.
    • <a class="dropdown-item" t-on-click.prevent="() => openTask(task.id)">: Each task is rendered as a clickable link.
      • t-on-click.prevent: An OWL event binding. When clicked, it calls our openTask JavaScript function, passing the task.id. .prevent stops the default link behavior (navigating to a new page). This interaction is key for the Odoo 18 Systray Widget.
      • <t t-esc="task.name"/>: Displays the name of the task.
    • <t t-else="">: If state.tasks.length is 0, this block is rendered, showing a “No tasks found” message.

5. Creating the Backend Controller (controllers/main.py)

Now, let’s build the Python backend logic that our frontend component will communicate with to retrieve task data for the Odoo 18 Systray Widget. Create main.py inside controllers/ and paste the following:

from odoo import http
from odoo.http import request
import logging

_logger = logging.getLogger(__name__)

class TaskNotificationController(http.Controller):
    """
    Odoo HTTP controller to provide recent project task data via JSON API.
    This controller serves data for the OWL 2 Odoo 18 Systray Widget.
    """
    @http.route('/task_notification/tasks', type='json', auth='user', methods=['GET'])
    def get_tasks(self):
        """
        Fetches the last 5 project tasks assigned to the current user.
        Returns them as a list of dictionaries (JSON response).
        This provides the data for the Odoo 18 Systray Widget.
        """
        try:
            # Fetch the current user's ID
            current_user_id = request.session.uid
            
            # Search for project tasks assigned to the current user
            # Ordered by creation date (DESC) and limited to 5
            tasks = request.env['project.task'].sudo().search(
                [('user_id', '=', current_user_id)],
                order='create_date DESC',
                limit=5
            )
            
            # Convert project.task records into a list of dictionaries
            # containing only the 'id' and 'name' for the frontend.
            task_data = [{'id': task.id, 'name': task.name} for task in tasks]
            
            _logger.info(f"Fetched {len(task_data)} tasks for user {current_user_id} for Odoo 18 Systray Widget.")
            return task_data
        except Exception as e:
            _logger.error(f"Error fetching tasks for user {request.session.uid} for Odoo 18 Systray Widget: {e}")
            # In case of an error, return an empty list or an error message
            return []
  • Explanation:
    • from odoo import http and from odoo.http import request: Imports necessary modules for HTTP controllers and accessing the Odoo environment.
    • _logger = logging.getLogger(__name__): Sets up logging for debugging and error tracking.
    • class TaskNotificationController(http.Controller):: Declares our controller class. You can define multiple routes within a single controller class.
    • @http.route(...): This decorator defines the API endpoint for our Odoo 18 Systray Widget.
      • /task_notification/tasks: The URL path that our frontend will call.
      • type='json': Crucially, this specifies that the route expects and returns JSON data, perfect for RPC calls from JavaScript.
      • auth='user': Ensures that only logged-in Odoo users can access this route, providing a layer of security.
      • methods=['GET']: Specifies that this route should be accessed using GET requests (though RPC often abstracts this).
    • get_tasks(self): The method that executes when the route is accessed.
      • current_user_id = request.session.uid: Retrieves the ID of the currently logged-in user.
      • request.env['project.task'].sudo().search(...): This is where we query the Odoo database.
        • request.env['project.task']: Accesses the project.task model.
        • .sudo(): Temporarily elevates permissions to ensure the query can be performed even if the current user lacks specific access rights, though it’s generally good practice to design your security appropriately. For an Odoo 18 Systray Widget, sudo() might be acceptable for read operations if tasks are generally visible.
        • search([('user_id', '=', current_user_id)], order='create_date DESC', limit=5): Searches for tasks where the user_id (assigned user) matches the current_user_id. It orders them by create_date in descending order (most recent first) and limits the results to the top 5 tasks.
      • return [{'id': task.id, 'name': task.name} for task in tasks]: A list comprehension that converts the Odoo project.task records into a list of Python dictionaries, each containing only the id and name of the task. This simplified format is then automatically converted to JSON and sent back to the frontend, powering our Odoo 18 Systray Widget.
      • try-except block: Basic error handling to catch exceptions during task fetching and log them.

6. Updating Init Files (__init__.py)

To ensure Odoo recognizes our new Python controller, we need to update the __init__.py files.

  • Create controllers/__init__.py:
from . import main

This line tells Python to import main.py when the controllers package is loaded.

  • Update top-level __init__.py (in owl_notification_widget/):
from . import controllers

This line tells Odoo to load the controllers package (and thus main.py) when our owl_notification_widget module is loaded, completing the backend integration for your Odoo 18 Systray Widget.

7. Adding a Module Icon (Optional but Recommended)

For a professional look, add an icon to your module. This icon will appear in the Odoo Apps list.

  • Place your icon: Save an image file (e.g., icon.png or icon.svg, preferably 128×128 pixels) as icon.png in the static/description/ folder.
owl_notification_widget/
└── static/
    └── description/
        └── icon.png

This icon enhances the visibility and branding of your custom Odoo 18 Systray Widget within the Odoo ecosystem.

8. Installing Your Odoo 18 Systray Widget Module

With all files in place, it’s time to install and see your new widget in action!

  1. Restart Odoo Service: After making changes to Python files (.py) or manifest files, you must restart your Odoo service (e.g., sudo systemctl restart odoo.service or restart from your IDE). This allows Odoo to recognize the new module and its updated backend logic for the Odoo 18 Systray Widget.
  2. Update Apps List:
    • Log in to your Odoo instance as an administrator.
    • Go to the “Apps” menu.
    • Click on “Update Apps List” (sometimes found under the “Developer Tools” menu if not visible directly). This step ensures Odoo scans for new or updated modules.
  3. Search and Install:
    • In the Apps search bar, type “OWL Task Notification Widget” (or whatever name you put in your __manifest__.py).
    • You should see your module appear. Click on “Activate” (or “Install”).

Once installed, refresh your Odoo page. You should now observe a new “Tasks” button with an icon and a number (representing the count of tasks) prominently displayed in your Odoo systray (the top bar). Clicking it will reveal a dropdown with your last five project tasks. Clicking on any task will open its form view! This custom Odoo 18 Systray Widget is now fully operational and ready to boost your productivity!

Expanding Your Odoo 18 Systray Widget


Discover more from teguhteja.id

Subscribe to get the latest posts sent to your email.

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com