PT Proweb Indonesia
The City Tower Level 12 Unit 1-N, Jakarta Pusat

Tampilan Kanban yang Interaktif pada Pembuatan Modul Odoo

Aug 6, 2026 | Odoo 17 Development

Kanban pada Odoo mendukung semua tag HTML sehingga kita dapat merancang tampilan yang interaktif.

Tampilan kanban yang interaktif pada pembuatan modul Odoo adalah seperti berikut ini

  1. Start SSH file system

  2. Source code view asrama.hostel

    <?xml version="1.0" encoding="utf-8"?>
    <odoo>
    
    <!-- asrama.hostel search view -->
    <record id="view_hostel_search_view" model="ir.ui.view">
        <field name="name">Hostel Search</field>
        <field name="model">asrama.hostel</field>
        <field name="arch" type="xml">
            <search>
                <field name="name" />
                <field name="hostel_code" />            
            </search>
        </field>
    </record>
    
    <!-- asrama.hostel tree view -->
    <record id="View_hostel_tree_view" model="ir.ui.view">
        <field name="name">asrama.hostel.view.tree</field>
        <field name="model">asrama.hostel</field>
        <field name="arch" type="xml">
            <tree>
                <field name="name" />
                <field name="hostel_code" />
                <!-- Add your fields here -->            
            </tree>
        </field>
    </record>
    
    <!-- asrama.hostel form view -->
    <record id="view_hostel_form_view" model="ir.ui.view">
        <field name="name">asrama.hostel.view.form</field>
        <field name="model">asrama.hostel</field>
        <field name="arch" type="xml">
            <form string="Hostel">
                <sheet>
                    <div class="oe_title">
                        <h3>
                            <table>
                                <tr>
                                    <td style="padding-right:10px;">
                                        <field name="name" required="1" placeholder="Name" />
                                    </td>
                                    <td style="padding-right:10px;">
                                        <field name="hostel_code" placeholder="Code" />
                                    </td>
                                </tr>
                            </table>
                        </h3>
                    </div>
                     <field name="image" widget="image" class="oe_avatar"/>
                    <group>
                        <group>
                            <label for="street" string="Address"/>
                            <div class="o_address_format">
                                <field name="street" placeholder="Street..." class="o_address_street"/>
                                <field name="street2" placeholder="Street 2..." class="o_address_street"/>
                                <field name="city" placeholder="City" class="o_address_city"/>
                                <field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}'/>
                                <field name="zip" placeholder="ZIP" class="o_address_zip"/>
                                <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
                            </div>
                        </group>
                             <group>
                            <field name="phone" widget="phone"/>
                            <field name="mobile" widget="phone"/>
                            <field name="email" widget="email" context="{'gravatar_image': True}"/>
                        </group>
                        <group>
                            <field name="hostel_floors"/>
                            <field name="active"/>
                            <field name="type"/>
                            <field name="hostel_rating"/>
                            <field name="other_info"/>
                        </group>                    
                    </group>
                    <group>
                        <field name="description"/>
                    </group>
                </sheet>
            </form>
        </field>
    </record>
    
    <!-- asrama.hostel action window -->
    <record id="action_hostel" model="ir.actions.act_window">
        <field name="name">Hostel</field>
        <field name="type">ir.actions.act_window</field>
        <field name="res_model">asrama.hostel</field>
        <field name="view_mode">tree,form</field>
        <field name="help" type="html">
            <p class="oe_view_nocontent_create">
                Membuat Hostel
            </p>
        </field>
    </record>
    
    <menuitem id="hostel_main_menu" name="Hostel" sequence="1"/>
    <menuitem id="hostel_type_menu" name="Hostel" parent="hostel_main_menu" action="action_hostel" groups="group_hostel_manager" sequence="10"/>
    
    <menuitem id="hostel_sub1" name="Menu-01" parent="hostel_main_menu" sequence="1"/>
    </odoo>
    
  3. Source code model asrama.amenities

    from odoo import fields, models
    
    class AsramaAmenities(models.Model):
        _name = 'asrama.amenities'
        _description = 'Asrama Amenities'
    
        name = fields.Char('Nama',help='Amenities yang disediakan')
        active = fields.Boolean(
            'Active',
            default=True ,
            help='Activate/Deactivated record'
        )
    
        color = fields.Integer()
    
  4. Source code akses keamanan

    id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
    access_hostel_manager_id,access.hostel.manager,asramaku.model_asrama_hostel,group_hostel_manager,1,1,1,1
    access_hostel_user_id,access.hostel.user,asramaku.model_asrama_hostel,group_hostel_user,1,0,0,0
    access_hostel_room_manager_id,access.hostel.room.manager,asramaku.model_asrama_room,asramaku.group_hostel_manager,1,1,1,1
    access_hostel_room_user_id,access.hostel.room.user,asramaku.model_asrama_room,asramaku.group_hostel_user,1,0,0,0
    access_hostel_amenities_manager_id,access.hostel.amenities.manager,asramaku.model_asrama_amenities,asramaku.group_hostel_manager,1,1,1,1
    access_hostel_amenities_user_id,access.hostel.amenities.user,asramaku.model_asrama_amenities,asramaku.group_hostel_user,1,0,0,0
    access_hostel_student_manager_id,access.hostel.student.manager,asramaku.model_asrama_student,asramaku.group_hostel_manager,1,1,1,1
    access_hostel_student_user_id,access.hostel.student.user,asramaku.model_asrama_student,asramaku.group_hostel_user,1,0,0,0
    access_hostel_category_manager_id,access.hostel.category.manager,asramaku.model_asrama_category,asramaku.group_hostel_manager,1,1,1,1
    access_hostel_category_user_id,access.hostel.category.user,asramaku.model_asrama_category,asramaku.group_hostel_user,1,0,0,0
    access_hostel_assign_room_student_wizard_id,access.hostel.assign.room.student.wizard,asramaku.model_assign_room_student_wizard,asramaku.group_hostel_manager,1,1,1,1
    access_hostel_asrama_room_availability_id,access.hostel.asrama.room.availability,asramaku.model_asrama_room_availability,asramaku.group_hostel_manager,1,1,1,1
    access_hostel_room_stage,hostel_room_stage,model_asrama_room_stage,base.group_user,1,1,1,1
    
  5. Model asrama.room

    import logging
    from odoo import fields, models, api
    from odoo.exceptions import ValidationError, UserError
    from odoo.tools.translate import _
    
    _logger = logging.getLogger(__name__)
    
    class BaseArchive(models.AbstractModel):
    	_name = 'base.archive'
    	active = fields.Boolean(default=True)
    	
    	def do_archive(self):
    		for record in self:
    			record.active = not record.active
    
    class AsramaRoom(models.Model):
        _name = 'asrama.room'
        _inherit = ['base.archive']
        _description = 'Informasi Kamar Asrama'
        _rec_name = 'room_no'
    
        name = fields.Char('Nama kamar',required=True)
        room_no = fields.Char('No kamar',required=True)
        floor_no = fields.Integer('No Lantai',default=1, help='No Lantai')
        currency_id = fields.Many2one('res.currency',string='Mata uang')
        rent_amount = fields.Monetary('Harga sewa',help='Masukkan harga sewa')
    
        hostel_id = fields.Many2one('asrama.hostel','Hostel', help='Nama hostel')    
    
        student_per_room = fields.Integer('Jml pelajar per kamar',required=True,
            help='Jml yang dialokasikan per kamar')
    
        @api.depends('student_per_room','student_ids')
        def _compute_check_availability(self):
            """Mengecek ketersediaan"""
            for rec in self:
                rec.availability = rec.student_per_room - len (rec.student_ids.ids)
        
        availability = fields.Float(compute='_compute_check_availability',store=True,
            string='Ketersediaan',help='Ketersediaan kamar pada asrama')
    
        student_ids = fields.One2many('asrama.student','room_id','Daftar pelajar',
            help='Pelajar-pelajar dalam room ini')
    
        color = fields.Integer()
        popularity = fields.Selection([
            ('no','No Demand'),
            ('low','Low Demand'),
            ('medium','Medium Demand'),
            ('high','High Demand'),
        ])
    
        asrama_amenities_ids = fields.Many2many('asrama.amenities',
            'asrama_amenities_rel','room_id','amenity_id',
            string='Amenities', domain="[('active','=',True)]",
            help='Pilih fasilitas room' 
        )
    
        _sql_constraints = [('room_no_unique','unique(room_no)','Nomor room harus unik!')]
    
        @api.constrains('rent_amount')
        def _check_rent_amount(self):
            """Constraint pada nilai negatif"""
            if self.rent_amount < 0:
                raise ValidationError(_('Nilai rental tidak boleh negatif'))
        
        room_rating = fields.Float('Rata-rata Rating dari Room',digits=(14,4))
     
        '''
        state = fields.Selection([
                ('draft','Unavailable'),
                ('available','Available'),
                ('closed','Closed')
            ],
            'State',
            default='draft'
        )
        '''
    
        remarks = fields.Text('Remarks')
    
        @api.model
    
        def create(self,values):
            if not self.user_has_groups('base.group_allow_export'):
                if values.get('remarks'):
                    raise UserError('Anda tidak diijinkan membuat remark')
            return super(AsramaRoom, self).create(values)
        
        def write(self,values):
            if not self.user_has_groups('base.group_allow_export'):
                if values.get('remarks'):
                    raise UserError('Anda tidak diijinkan mengubah remark')
            return super(AsramaRoom, self).write(values)
    
        def is_allowed_transition(self,old_state,new_state):
            allowed = [
                 ('draft','available'),
                 ('available','closed'),
                 ('closed','draft')             
            ]
            return (old_state,new_state) in allowed
        
        def change_state(self,new_state):
            for room in self:
                if room.is_allowed_transition(room.state, new_state):
                    room.state = new_state
                else:
                    msg = _('Pindah dari %s ke %s tidak diperbolehkan!') % (room.state,new_state)
                    raise UserError(msg)
    
        def make_available(self):
            self.change_state('available')
        
        def make_closed(self):
            self.change_state('closed')
    
        def log_all_room_members(self):
            asrama_student_obj = self.env['asrama.student']
            all_members = asrama_student_obj.search([])
            print('Semua anggota',all_members)
            return True
        
        def create_categories(self):
            categ1 = {
                'name': 'Child category 1',
                'description': 'Description for child 1'              
            }
            categ2 = {
                'name': 'Child category 2',
                'description':  'Description child 2'
            }
            parent_category_val = {
                'name': 'Parent category',
                'description': 'Description for parent category',
                'child_ids': [
                    (0,0,categ1),
                    (0,0,categ2),
                ]
            }
    
            record = self.env['asrama.category'].create(parent_category_val)
            return True
    
        def update_room_no(self):
            self.ensure_one()
            self.room_no = 'RM2772'
        
        def find_room(self):
            domain = [
                '|',
                    '&',('name','ilike','bunga'),
                        ('hostel_id.name','=','Kemayoran'),
                    '&',('name','ilike','galo'),
                        ('hostel_id.name','=','BSD')
            ]
            rooms = self.search(domain)
            #_logger.info('Room ditemukan %s',rooms)
            print('Room ditemukan %s',rooms)
            return True
    
        def filter_members(self):
            all_rooms = self.search([])
            filtered_rooms = self.rooms_with_multiple_members(all_rooms)
            #_logger.info('Filtered Rooms: %s',filtered_rooms)
            print('Filtered Rooms:', filtered_rooms)
        
        def rooms_with_multiple_members(self, all_rooms):
            def predicate(room):
                if (len(room.student_ids))>1:
                    return True
            return all_rooms.filtered(predicate)
        
        def mapped_rooms(self):
            all_rooms = self.search([])
            room_authors = self.get_member_names(all_rooms)
            #_logger.info('Room Members: %s', room_authors)
            print('Room Members:', room_authors)
            
        def get_member_names(self,all_rooms):
            return all_rooms.mapped('student_ids.name')
        
        def sort_room(self):
            all_rooms = self.search([])
            rooms_sorted = self.sort_by_rent_amount(all_rooms)
            #_logger.info('Rooms before sorting: %s', all_rooms)
            #_logger.info('Rooms after sorting: %s', rooms_sorted)
            print('Rooms before sorting:', all_rooms)
            print('Rooms after sorting:', rooms_sorted)
    
        def sort_by_rent_amount(self,all_rooms):
            return all_rooms.sorted(key='rent_amount')
    
        def average_rent(self):
            avg_result = self.read_group(
                [('rent_amount', '!=', False)],
                ['category_id','rent_amount:avg'],
                ['category_id']
            )
    
            print('Rata-rata harga sewa %s' % (avg_result))
            return True
        
        def action_remove_room_members(self):
            for student in self.student_ids:
                student.with_context(is_hostel_room=True).action_remove_room()
        
        def action_category_with_amounts(self):
    
            sql = 'SELECT name, amount FROM asrama_category WHERE id = %s' % self.category_id.id
            print('SQL:', sql) 
            
            self.env.cr.execute(sql)
            result = self.env.cr.fetchall()
            print('Result:', result) 
        
        def _default_room_stage(self):
            Stage = self.env['asrama.room.stage']
            return Stage.search([], limit=1)
    
        def _group_expand_stages(self,stages,domain,order):
            return stages.search([],order=order)
    
        stage_id = fields.Many2one(
            'asrama.room.stage', 
            default=_default_room_stage,
            group_expand ='_group_expand_stages'
        )
    
  6. View asrama_amenities

    <?xml version="1.0" encoding="utf-8"?>
    <odoo>
    
        <!-- asrama.amenities action window -->
        <record id="asrama_amenities_action" model="ir.actions.act_window">
            <field name="name">Asrama Amenities</field>
            <field name="res_model">asrama.amenities</field>
            <field name="view_mode">tree,form</field>
        </record>
    
        <menuitem
            id="asrama_amenities_menu"
            name="Asrama Amenities"
            action="asrama_amenities_action"
            groups="asramaku.group_hostel_manager"
            parent="hostel_sub1"
            sequence="20"/>
    
    </odoo>
    

  7. View asrama_room

    <?xml version='1.0' encoding='utf-8'?>
    <odoo>
        <!-- asrama.room tree view -->
        <record id="asrama_room_view_tree" model="ir.ui.view">
            <field name="name">asrama.room.view.tree</field>
            <field name="model">asrama.room</field>
            <field name="arch" type="xml">
                <tree string='Room'>
                    <field name="name"/>
                    <field name="room_no" />
                    <field name="floor_no" />
                    <field name="stage_id" />
                </tree>
            </field>
        </record>
    
        <!-- asrama.room form view -->
        <record id="asrama_room_view_form" model="ir.ui.view">
            <field name="name">asrama.room.view.form</field>
            <field name="model">asrama.room</field>
            <field name="arch" type="xml">
                <form string="Room">
                    <sheet>
                        <header>
                            <field name="stage_id" widget='statusbar' options="{'clickable': '1', 'fold_field':'fold'}" />
                        </header>
                        <header>
                            <button name="make_available"  string="Make Available" type="object"/>
                            <button name="make_closed"  string="Make Closed" type="object"/>
                            <button name="log_all_room_members"  string="Log semua kamar members" type="object"/>
                            <button name="average_rent"  string="Rata-rata sewa" type="object"/>
                        </header>
                        <header>
                            <button name="create_categories"  string="Create categories" type="object"/>
                            <button name="update_room_no"  string="Update Room No" type="object"/>
                            <button name="find_room"  string="Find Room" type="object"/>
                            <button name="action_remove_room_members"  string="Remove Room Members" type="object"/>
                        </header>
                        <header>
                            <button name="filter_members"  string="Filter Members" type="object"/>
                            <button name="mapped_rooms"  string="Mapped Rooms" type="object"/>
                            <button name="sort_room"  string="Sort Room" type="object"/>
                            <button name="action_category_with_amounts"  string="LOG dengan SQL Query" type="object"/>
                        </header>
                        <group>
                            <group description='Kamar'>
                                <field name="name" />
                                <field name="room_no" />
                                <field name="hostel_id" required="1"/>
                                <field name="floor_no" />
                                <field name="active" />
                                <field name="category_id" />
                                <field name="date_terminate" />
                            </group>
                            <group description='Harga'>
                                <field name="student_per_room"/>
                                <field name="availability"/>
                                <field name="rent_amount" />
                                <field name="currency_id" />
                                <field name="remarks" />
                            </group>
                        </group>
                        <notebook>
                            <page name="room_studennt" string="Para Pelajar">
                                <field name="student_ids"/>
                            </page>
                            <page name="asrama_amenities" string="Room Amenities">
                                <group>
                                    <field name="asrama_amenities_ids" widget="many2many_tags"/>
                                </group>
                            </page>
                        </notebook>
                    </sheet>
                </form>
            </field>
        </record>
    
        <!-- asrama.room minimal form view -->
        <record id="asrama_room_view_form_minimal" model="ir.ui.view">
            <field name="name">Asrama Room Minimal</field>
            <field name="model">asrama.room</field>
            <field name="arch" type="xml">
                <form>
                    <group>
                        <field name="name" />   
                        <field name="room_no" />
                        <field name="hostel_id" required="1" />
                        <field name="floor_no" />
                        <field name="student_per_room" />
                        <field name="asrama_amenities_ids" widget="many2many_tags" 
                            options="{'color_field': 'color','no_create_edit': True}" />
                    </group>
                </form>
            </field>
        </record>
    
        <!-- asrama.room kanban view -->
        <record id="asrama_room_view_kanban" model="ir.ui.view">
            <field name="name">Asrama Room Kanban</field>
            <field name="model">asrama.room</field>
            <field name="arch" type="xml">
                <kanban default_group_by="stage_id" 
                    on_create="quick_create" 
                    quick_create_view="asramaku.asrama_room_view_form_minimal">
                    <field name="stage_id" />
                    <field name="color" />
                    <templates>
                        <t t-name="kanban-menu" groups="base.group_user">
                            <a t-if="widget.editable" role="menuitem" type="edit" class="dropdown-item">Edit</a>
                            <a t-if="widget.deletable" role="menuitem" type="delete" class="dropdown-item">Delete</a>
                            <ul class="oe_kanban_colorpicker" data-field="color" />
                            <div role="separator" class="dropdown-divider"></div>
                        </t><!--kanban-menu-->
                        <t t-name="kanban-box">
                            <div t-attf-class="{{'oe_kanban_color_' + kanban_getcolor(record.color.raw_value) }} oe_kanban_card oe_kanban_global_click oe_semantic_html_overide" >
                                <div class="oe_kanban_card oe_kanban_global_click">
                                    <strong class="o_kanban_record_title">
                                        <field name="name" />
                                    </strong>
                                    <div class="text-muted">
                                        <i class="fa fa-building" />
                                        <field name="hostel_id" />
                                    </div>
                                    <span class="oe_kanban_list_many2many">
                                        <field name="asrama_amenities_ids" widget="many2many_tags" options="{'color_field':'color'}" />
                                    </span>
                                    <div>
                                        <field name="popularity" widget='priority' />
                                    </div>
                                </div><!--oe_kanban_card-->
                            </div><!--oe_kanban_color-->
                        </t><!--kanban-box-->
                    </templates>
                </kanban>
            </field>
        </record>
    
        <record id="asrama_room_action_kanban" model="ir.actions.act_window.view">
            <field name="act_window_id" ref="asrama_room_action"/>
            <field name="view_id" ref="asrama_room_view_kanban"/>
            <field name="view_mode">kanban</field>
            <field name="sequence" eval="1"/>
        </record>
    
        <record id="asrama_room_action_tree" model="ir.actions.act_window.view">
            <field name="act_window_id" ref="asrama_room_action"/>
            <field name="view_id" ref="asrama_room_view_tree"/>
            <field name="view_mode">tree</field>
            <field name="sequence" eval="2"/>
        </record>
    
        <record id="asrama_room_action_form" model="ir.actions.act_window.view">
            <field name="act_window_id" ref="asrama_room_action"/>
            <field name="view_id" ref="asrama_room_view_form"/>
            <field name="view_mode">form</field>
            <field name="sequence" eval="3"/>
        </record>
    
        <!-- asrama.room action window -->
        <record id="asrama_room_action" model="ir.actions.act_window">
            <field name="name">Room</field>
            <field name="type">ir.actions.act_window</field>
            <field name="res_model">asrama.room</field>
            <field name="view_mode">kanban,tree,form</field>
            <field name="domain">[]</field>
            <field name="context">{}</field>
            <field name="help" type="html">
                <p class="oe_view_nocontent_create">
                    Tambah kamar
                </p>
            </field>
        </record>
    
        <!-- This Menu Item must have a parent and an action -->
        <menuitem id="asrama_room_menu" name="Room Asrama" parent="hostel_main_menu" action="asrama_room_action" sequence="2"/>
    
    </odoo>
    
  8. File manifest

    {
        'name': 'Manajemen Asrama',
        "data":[
            "security/hostel_security.xml",
            "security/ir.model.access.csv",
            "data/data.xml",
            "data/data_stage.xml",
            "views/hostel.xml",
            "views/asrama_room_stage.xml",
            "views/asrama_amenities.xml",
            "views/asrama_room.xml",
            "views/asrama_student.xml",
            "views/asrama_room_availability_view.xml",
            "views/res_config_settings.xml",
            "wizard/assign_room_student.xml",
        ],
        'pre_init_hook' : 'pre_init_hook_hostel',
        'post_init_hook' : 'add_room_hook',
        'uninstall_hook' : 'uninstall_hook_user'
    }
    
  9. Start Odoo dengan upgrade modul

  10. Ke sub menu Asrama Amenities
  11. Tampilan asrama amenities
  12. Tampilan kanban dengan warna dan prioritas
  13. Tampilan many2many pada amenities

Kunjungi https://www.proweb.co.id/implementasi-odoo/ untuk menambah wawasan anda mengenai Odoo ERP.