The videos:
- could to try it
python odoo-bin -d odoo -r odoo -w odoo
python odoo-bin scaffold om_hospital addons\
git commit -am "git commit -a --allow-empty-message -m ''"
Notes from the playlist
"application": True,
"sequence": -100,- create a directory "static"
- create a directory "description"
- add the image with name "icon" the image will automatically added
each model is a table in the database contain whatever data, customer, sales history...etc
- create a directory "models"
- create the "__init__.py" file
- create a file "patient.py"
- in the main "__init__.py" add
from . import models - in the models/__init__.py add
from . import patient
In the patient.py:
from odoo import api, fields, models
class HospitalPatient(models.Model):
# that will create a table with name "hospital_patient"
_name = "hospital.patient"
_description = "Hospital Patient"
name = fields.Char(string="Name")
age = fields.Integer(string="Age")
# list with tubules (key,value)
gender = fields.Selection([('male','Male'),('female', 'Female')], string="Gender")Too see the changes:
- in Apps, install/upgrade the model
- in settings -> technical -> database structure -> modules
- search for the table name
Another way
psql –U postgres\c odoothe database nameselect * from hospital_patientthe model name
Note: add a space at the begging of select statement
-
create dir. views
-
inside views dir. create menu.xml file
<?xml version="1.0" encoding="utf-8"?> <odoo> <menuitem id="menu_hospital_main" name="Hospital" action="" /> <menuitem id="menu_patient_main" name="Patient Details" action="" parent="menu_hospital_main" /> <menuitem id="menu_patient_details" name="Patients" action="" parent="menu_patient_main" /> </odoo>
-
import the menu file in the manifest file by
'data' :['views/menu.xml']
Too see the changes:
- upgrade the model
- more info about the model
- installed features/ created menus
-
create a patient.xml file
<?xml version="1.0" encoding="utf-8"?> <odoo> <record id="action_hospital_patient" model="ir.actions.act_window"> <field name="name">Patients</field> <field name="type">ir.actions.act_window</field> <field name="res_model">hospital.patient</field> <field name="view_mode">tree,form</field> <field name="context" eval="{}" /> <field name="help" type='html'> <p class='o_view_nocontent_smiling_face'> Create your first patient! </p> </field> </record> </odoo>
-
import it in the manifest file
'data' :['views/menu.xml','views/patient.xml']
Too see the changes:
- upgrade the model
- settings>technical>Window Actions
- search for Name of your action
-
edit the menu to be :
<menuitem id="menu_patient_details" name="Patients" action="action_hospital_patient" parent="menu_patient_main" />
-
from the debugging icon, became a superuser
-
now u can see the hospital in the main menu
NOTE: to get an existing action go for it works and from the debugging icon, edit action
Important: must create any menuitem that uses an action, after their action. So the Patients menuitem will be moved to patient.xml, because the execution start in manifest in the data object, executing them one by one, so when it executes the menu.xml will find a menuitem that has an action that still didn't created, causing an error.
- search in the logs for the access rules in custom_addons\hospital\security\ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
hospital.access_hospital_patient,access_hospital_patient,hospital.model_hospital_patient,base.group_user,1,1,1,1
add access path to the manifest file
install web_responsive and add int to custom_addons
in custom_addons\hospital\views\menu.xml edited to be
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_hospital_main" name="Hospital" sequence="0" web_icon="hospital,static\description\icon.png" />
<menuitem id="menu_patient_main" name="Patient Details" sequence="0" parent="menu_hospital_main" />
</odoo>then uninstall the model in the app list not the custom_addons
NOTICE that here we will add here a filed called ref in the patient model, copy name filed and change its name to ref with string value Reference. (in custom_addons\hospital\models\patient.py)
ref = fields.Char(string="Recreance")in custom_addons\hospital\views\patient_view.xml add this after <odoo>
<record model="ir.ui.view" id="view_hospital_patient_tree">
<field name="name">hospital patient</field>
<field name="model">hospital.patient</field>
<field name="arch" type="xml">
<tree>
<field name="name" string="Patient Name" />
<field name="ref" />
<field name="age" />
<field name="gender" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_hospital_patient_form">
<field name="name">hospital patient</field>
<field name="model">hospital.patient</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="name" />
<field name="age" />
</group>
<group>
<field name="gender" />
<field name="ref" />
</group>
</group>
</sheet>
</form>
</field>
</record>
in custom_addons\hospital\views\patient_view.xml add this after form view
<record model="ir.ui.view" id="view_hospital_patient_search">
<field name="name">hospital patient</field>
<field name="model">hospital.patient</field>
<field name="arch" type="xml">
<search string='test'>
<field name="name" string="Patient Name" filter_domain="['|',('name', 'ilike', self),('ref', 'ilike', self)]" />
<field name="age" />
<field name="gender" />
</search>
</field>
</record>NOTES:
- in filter_domain to have more than oen or condition,'|', add another '|' and add the condition
- filter_domain="['|','|',('name', 'ilike', self),('anotherValue', 'ilike', self),('ref', 'ilike', self)]"
NOTES:
- separator to not allow the or operation, when u select more than one filter
<means less than,'<'.
<record model="ir.ui.view" id="view_hospital_patient_search">
<field name="name">hospital patient</field>
<field name="model">hospital.patient</field>
<field name="arch" type="xml">
<search string='test'>
<field name="name" string="Patient Name" filter_domain="['|',('name', 'ilike', self),('ref', 'ilike', self)]" />
<field name="age" />
<field name="gender" />
<filter string="Male" name="filter_male" domain="[('gender','=','male')]" />
<filter string="Female" name="filter_female" domain="[('gender','=','female')]" />
<separator />
<filter string="Kids" name="filter_kids" domain="[('age','<=','5')]" />
<group expand="0" string="Group By">
<filter string="Gender" name="group_by_gender" context="{'group_by':'gender'}" />
</group>
</search>
</field>
</record>in custom_addons\hospital\models\patient.py add active filed
active = fields.Boolean(string="Active", default=True)
age = fields.Integer(string="Age")
# list with tubules (key,value)
gender = fields.Selection(
[('male', 'Male'), ('female', 'Female')], string="Gender")
name = fields.Char(string="Name")in custom_addons\hospital\views\patient_view.xml add active filed
in form record
<group>
<field name="gender" />
<field name="ref" />
<field name="active" invisible="1" />
</group>in search record
<separator />
<filter string="Kids" name="filter_kids" domain="[('age','<=','5')]" />
<separator />
<filter string="Archive" name="filter_archive" domain="[('active','=',False)]" />-
model will be from patient.hospital
-
view:
<?xml version="1.0" encoding="utf-8"?> <odoo> <record id="action_hospital_female_patient" model="ir.actions.act_window"> <field name="name">Female Patients</field> <field name="type">ir.actions.act_window</field> <field name="res_model">hospital.patient</field> <field name="view_mode">tree,form</field> <field name="context">{}</field> <field name="domain">[('gender','=','female')]</field> <field name="help" type='html'> <p class='o_view_nocontent_smiling_face'> Create your first patient! </p> </field> </record> <menuitem id="menu_female_patient_details" name="Female Patients" action="action_hospital_female_patient" parent="menu_patient_main" /> </odoo>
-
add it to the custom_addons\hospital_manifest_.py
'data': [
'security/ir.model.access.csv',
'views/menu.xml',
'views/patient_view.xml',
'views/female_patient_view.xml',
],in view, custom_addons\hospital\views\female_patient_view.xml
<field name="context">{"default_gender":"female","default_age":"20"}</field>rather than keeping it empty
in view, custom_addons\hospital\views\patient_view.xml
<field name="context">{'search_default_filter_male':1,'search_default_group_by_gender':1}</field>-
in custom_addons\hospital_manifest_.py
'depends': ['mail'],
-
in custom_addons\hospital\models\patient.py
_name = "hospital.patient" _inherit = ['mail.thread', 'mail.activity.mixin'] _description = "Hospital Patient"
-
in custom_addons\hospital\views\patient_view.xml in form view under the sheet tag
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" />
</div>in custom_addons\hospital\models\patient.py add tracking attribute
active = fields.Boolean(string="Active", default=True)
age = fields.Integer(string="Age", tracking=True)
# list with tubules (key,value)
gender = fields.Selection(
[('male', 'Male'), ('female', 'Female')], string="Gender", tracking=True)
name = fields.Char(string="Name", tracking=True)in search tag in patient_view.xml in search view
<searchpanel>
<field name="gender" icon="fa-user" enable_conter="1" />
</searchpanel>-
create model file, custom_addons\hospital\models\appointment.py
from odoo import models, fields, api class HospitalAppointment(models.Model): # that will create a table with name "hospital_appointment" _name = "hospital.appointment" _inherit = ['mail.thread', 'mail.activity.mixin'] _description = "Hospital Appointment" patient_id = fields.Many2one('hospital.patient', 'Patient')
-
add it to init in model dir, custom_addons\hospital\models_init_.py
from . import models from . import patient from . import appointment
-
crete view (where we add the many2one field), custom_addons\hospital\views\appointment_view.xml
<?xml version="1.0" encoding="utf-8"?> <odoo> <record model="ir.ui.view" id="view_hospital_appointment_tree"> <field name="name">hospital appointment</field> <field name="model">hospital.appointment</field> <field name="arch" type="xml"> <tree> <field name="patient_id" /> </tree> </field> </record> <record model="ir.ui.view" id="view_hospital_appointment_form"> <field name="name">hospital appointment</field> <field name="model">hospital.appointment</field> <field name="arch" type="xml"> <form> <sheet> <group> <group> <field name="patient_id" /> </group> <group></group> </group> </sheet> <div class="oe_chatter"> <field name="message_follower_ids" widget="mail_followers" /> <field name="activity_ids" widget="mail_activity" /> <field name="message_ids" widget="mail_thread" /> </div> </form> </field> </record> <record id="action_hospital_appointment" model="ir.actions.act_window"> <field name="name">appointments</field> <field name="type">ir.actions.act_window</field> <field name="res_model">hospital.appointment</field> <field name="view_mode">tree,form</field> <field name="context">{}</field> <field name="help" type='html'> <p class='o_view_nocontent_smiling_face'> Create your first appointment! </p> </field> </record> <menuitem id="menu_appointment_details" name="Appointments" action="action_hospital_appointment" parent="menu_hospital_main" /> </odoo>
-
add it to manifest, custom_addons\hospital_manifest_.py
# -*- coding: utf-8 -*- { 'name': "hospital", "application": True, "sequence": -101, 'summary': """ Short (1 phrase/line) summary of the module's purpose, used as subtitle on modules listing or apps.openerp.com""", 'description': """ Long description of module's purpose """, 'author': "My Company", 'website': "http://www.yourcompany.com", # Categories can be used to filter modules in modules listing # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml # for the full list 'category': 'Uncategorized', 'version': '0.1', # any module necessary for this one to work correctly 'depends': ['mail'], # always loaded 'data': [ 'security/ir.model.access.csv', 'views/menu.xml', 'views/patient_view.xml', 'views/appointment_view.xml', 'views/female_patient_view.xml', ], # only loaded in demonstration mode 'demo': [ 'demo/demo.xml', ], }
-
add it to the security access
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink hospital.access_hospital_patient,access_hospital_patient,hospital.model_hospital_patient,base.group_user,1,1,1,1 hospital.access_hospital_appointment,access_hospital_appointment,hospital.model_hospital_appointment,base.group_user,1,1,1,1
carful: Date and Datetime not date and datetime, Case sensitive
in custom_addons\hospital\models\appointment.py
appointment_time = fields.Datetime(
string="Appointment Time", default=fields.Datetime.now)
booking_date = fields.Date(
string="Booking Date", default=fields.Date.context_today)in custom_addons\hospital\views\appointment_view.xml under the patient_id
<!-- form view -->
<field name="appointment_time" />
<field name="booking_date" />in custom_addons\hospital\models\appointment.py
gender = fields.Selection(related="patient_id.gender", readonly=True)notice: readonly if False will allow to make change to the value and it is by default True in the related field
in custom_addons\hospital\views\appointment_view.xml under the patient_id
<!-- form view -->
<field name="gender" />- edit the age field
- add date of birth field(dob)
- crete the compute function
- comment the age filter, because the computed field does not store in the DB
in custom_addons\hospital\models\patient.py
dob = fields.Date(string="Date of Birth")
age = fields.Integer(string="Age", tracking=True, compute='_compute_age')
# age = fields.Integer(string="Age", tracking=True, compute='_compute_age', store=True)
@api.depends('dob')
def _compute_age(self):
print("self................", self)
for rec in self:
today = date.today()
if rec.dob:
rec.age = today.year - rec.dob.year
else:
rec.age = 1notice: we can store the age by adding the store attribute like the commented one. we can not track a field that is not stored in the database
in custom_addons\hospital\views\patient_view.xml
<!-- form view -->
<field name="dob"/>
<!-- search view -->
<!-- <separator /> -->
<!-- <filter string="Kids" name="filter_kids" domain="[('age','<=','5')]" /> -->- add field that will show the change, in model file
- create the onchange function, in model file
- add it in view
in custom_addons\hospital\models\patient.py
ref = fields.Char(string="Reference")
@api.onchange('patient_id')
def onchange_patient_id(self):
self.ref = self.patient_id.refin custom_addons\hospital\views\patient_view.xml
<field name="ref"/>From NOW on KEEP appointment_view.xml and appointment.py opens
- it is what we see in the navigation bar!!
- what will be seen if we used this value in many2one field
by default it is _rec_name = 'name'
in custom_addons\hospital\models\appointment.py
_rec_name = "ref"in custom_addons\hospital\models\patient.py
appointment_id = fields.Many2one('hospital.appointmentg', 'Appointment', tracking=True)in custom_addons\hospital\views\patient_view.xml
<field name="appointment_id"/>at the end of the sheet tag add <notebook><page><group><field/>
in custom_addons\hospital\views\appointment_view.xml
<!-- notebook -->
<notebook>
<page string="Prescription" name="prescription">
<group>
<field name="booking_date"/>
</group>
</page>
<page string="Pharmacy" name="pharmacy">
</page>
</notebook>
</sheet>notice: the name attribute is important because we will need it when we do an inhabitance
- add it in the model
- add it in the view
- add placeholder attribute
in custom_addons\hospital\models\appointment.py
prescription = fields.Html(string="Prescription")in custom_addons\hospital\views\appointment_view.xml
<field name="prescription" placeholder="Enter your prescription"/>in from or tree tag add crete='0'
example:
<form
create="0"
edit="0"
delete="0"
copy="0"
>MyExperience: this what i needed after editing the access rights of the employee to deny him from creating a quotation
- add new selection field in model
- add it at the beginning of form view sheet
in custom_addons\hospital\models\appointment.py
priority = fields.Selection([
('0', 'Normal'),
('1', 'Low'),
('2', 'High'),
('3', 'Very High')
], string='Priority')in custom_addons\hospital\views\appointment_view.xml
<sheet>
<!--starts priority widget-->
<div class="oe_title">
<h1>
<field name="priority" widget="priority" calss="mr-3"/>
</h1>
</div>- add new selection field in model
- add it to the view
in custom_addons\hospital\models\appointment.py
state = fields.Selection([
('draft', 'Draft'),
('in_consultation', 'In Consultation'),
('done', 'Done'),
('cancel', 'Cancel')
], string='Status')in custom_addons\hospital\views\appointment_view.xml
<form>
<!--status bar-->
<header>
<field name="state"
widget="statusbar"
nolabel="1"
options="{'clickable':'1}"
statusbar_visable="draft,in_consultation,done"
/>
</header>
<sheet>- action: to do a window action
- object: to run a python code
in custom_addons\hospital\views\appointment_view.xml
<button name="action_test" string="Object Button" type="object" class="oe_highlight" confirm='confirm massage' />
<button name="%(hospital.action_hospital_patient)d" string="Action Button" type="action" class="oe_highlight" help='this will redirect u to patient' />in custom_addons\hospital\models\appointment.py
def action_test(self):
print('test!!!!!!!!!!!!!!!!!!!!')in custom_addons\hospital\models\patient.py
@api.depends('dob')
def _compute_age(self):
print("self................", self)
for rec in self:
today = datetime.date.today()
if rec.dob:
rec.age = today.year - rec.dob.year
else:
rec.age = 1in custom_addons\hospital\models\appointment.py
def action_test(self):
print('test!!!!!!!!!!!!!!!!!!!!')
return {
'effect': {
'fadeout': 'slow',
'message': 'Printed',
'type': 'rainbow_man',
}
}in the tree view custom_addons\hospital\views\appointment_view.xml
<tree decoration-success="state =='done'" decoration-info="state =='draft'" decoration-danger="state=='in_consultation'">
<field name="state" widget='badge' decoration-success="state == 'done'" decoration-info="state =='draft'" decoration-danger="state=='in_consultation'" />- decoration-muted, gray
- decoration-info, blue
- decoration-success, green
- decoration-warning, yellow
- decoration-danger, red
-
activity is in the models when we inherited the mail model
-
just add it to the tree and activate the widget, in
<field name="activity_ids" widget='list_activity' />
-
in tree view custom_addons\hospital\views\appointment_view.xml
<field name="appointment_time" optional="show" />
the one with user open the chat, the other not
- create field based on users of odoo domain
- add it to the view with
widget='many2one_avatar_user'
in custom_addons\hospital\models\appointment.py
doctor_id = fields.Many2one('res.users', string='Doctor')in custom_addons\hospital\views\appointment_view.xml
<!-- in tree view -->
<!-- will just show the avatar no chat -->
<field name="doctor_id" widget="many2one_avatar"/>
<!-- will just show the avatar and chat when you click -->
<field name="doctor_id" widget="many2one_avatar_user"/>
<!-- in form view -->
<field name="doctor_id" widget="many2one_avatar"/>collaborative: more than one can write to the field at the same time resizable: can change the size of the field
in html field in the appointment model, add options="{'collaborative': true, 'resizable': true}"
select the field that you want to be focused when the user start edit the record and add default_focus='1', underscore
in custom_addons\hospital\views\appointment_view.xml
<field name="booking_date" default_focus="1" />in tree field add sample='1'
in custom_addons\hospital\views\appointment_view.xml
<tree decoration-success="state =='done'" decoration-info="state =='draft'" decoration-danger="state=='in_consultation'" sample='1'>after selecting the record in the tree view we can edit it
add to the tree view multi_edit='1'
<tree decoration-success="state =='done'" decoration-info="state =='draft'" decoration-danger="state=='in_consultation'" sample='1' multi_edit='1'>- u could search in odoo for, action_done, action_cancel... etc
- add buttons in the view with type object
- create their object actions/methods
- set their states attribute, when they will be shown
<!-- form view -->
<header>
<button name="action_in_consultation" string="In Consultation" type="object" states="draft" class="oe_highlight"/>
<button name="action_done" string="Done" type="object" states="in_consultation" class="oe_highlight"/>
<button name="action_cancel" string="Cancel" type="object" states="draft,in_consultation" />
<button name="action_draft" string="Draft" type="object" states="cancel" class="oe_highlight"/> def action_draft(self):
for rec in self:
rec.state = 'draft'
def action_in_consultation(self):
for rec in self:
rec.state = 'in_consultation'
def action_done(self):
for rec in self:
rec.state = 'done'
def action_cancel(self):
for rec in self:
rec.state = 'cancel'add data-hotkey='i', any letter available
<button name="action_in_consultation" string="In Consultation" type="object" states="draft" class="oe_highlight" data-hotkey='i'/>One2many results in many records connected to one record, like a bag containing many items
Many2one results in one record selected form many record, like an item form a bag
One2many in appointment from pharmacy
One -> appointment
Many -> Pharmacy
- create the model, we want its many
- in pharmacy, add a Many2one field with the appointment
- in appointment, add One2many field and pass field from step 1
- add the pharmacy One2many field to the view
- create the tree and form view from one2 many field
- in this case the product model have been used so we need to add it to the depends in the manifest file
- add security access
class AppointmentPharmacyLines(models.Model):
_name = 'appointment.pharmacy.lines'
_description = 'Appointment Pharmacy Lines'
product_id = fields.Many2one('product.product', required=True)
price_unit = fields.Float(related='product_id.list_price')
qty = fields.Integer(string='Quantity', default=1)
appointment_id = fields.Many2one(
'hospital.appointment', string='Appointment')pharmacy_lines_id = fields.One2many('appointment.pharmacy.lines', 'appointment_id', string='Pharmacy Lines')<notebook>
<!-- Prescription page -->
<page string='Pharmacy'>
<field name="pharmacy_lines_id">
<tree editable='bottom'>
<field name="product_id" />
<field name="price_unit" />
<field name="qty" />
</tree>
<form>
<group>
<field name="product_id" />
<field name="price_unit" />
<field name="qty" />
</group>
</form>
</field>
</page>
</notebook>'depends': ['mail', 'product'],om_hospital_g.access_appointmentg_pharmacy_lines,access_appointmentg_pharmacy_lines,om_hospital_g.model_appointmentg_pharmacy_lines,base.group_user,1,1,1,1
<page string='Pharmacy'>
<field name="pharmacy_lines_id">
<tree editable='bottom'>bottom, will fill the data at the bottom of the list/treetop, will fill the data at the top of the list/tree- without the editable, when adding a new line will show a pop up of the form
- add boolean field
- add
attrsattribute to the field will be hidden,parentmeans the appointment model - add the condition of hide
in appointment model
hide_sales_price = fields.Boolean(string='Hide Sales Price')<!-- First group -->
<field name="hide_sales_price" groups="base.group_no_one"/><!-- Pharmacy page -->
<page string='Pharmacy'>
<field name="pharmacy_lines_id">
<tree editable='bottom'>
<field name="product_id"/>
<field name="price_unit"
attrs="{ 'column_invisible':[('parent.hide_sales_price','=',True)] }"/>
<field name="qty"/>
</tree>
<form>
<group>
<field name="product_id"/>
<field name="price_unit"
attrs="{ 'column_invisible':[('parent.hide_sales_price','=',True)] }"/>
<field name="qty"/>
</group>
</form>
</field>
</page>add groups="base.group_no_one" to the field u wanna hide
<field name="hide_sales_price" groups="base.group_no_one"/>Back to the patient model
in custom_addons\hospital\models\patient.py
image = fields.Image(string='Image')in custom_addons\hospital\views\patient_view.xml in Form view
<sheet>
<field name="image" widget='image' class="oe_avatar" />
<group>
<group>
<field name="name" />
<field name="dob" />
<field name="age" />
</group>patient_tag
to create a model, will deal with 6 files:
-
MyModel.py, patient_tag.py
from email.policy import default from odoo import api, fields, models class PatientTag(models.Model): _name = 'patient.tag' _description = 'Patient Tag' name = fields.Char( string='Name', required=True ) active = fields.Boolean( string='Active', default=True )
-
my_view.xml, patient_tag_view.xml
<?xml version='1.0' encoding='utf-8'?> <odoo> <record model="ir.ui.view" id="view_patient_tag_tree"> <field name="name">Patient Tags</field> <field name="model">patient.tag</field> <field name="arch" type="xml"> <tree sample='1'> <field name="name" string="Tag Name" /> </tree> </field> </record> <record model="ir.ui.view" id="view_patient_tag_form"> <field name="name">Patient Tags</field> <field name="model">patient.tag</field> <field name="arch" type="xml"> <form> <sheet> <group> <group> <field name="name" /> </group> <group> <field name="active" widget="boolean_toggle" /> </group> </group> </sheet> </form> </field> </record> <record id="action_patient_tag" model="ir.actions.act_window"> <field name="name">Tags</field> <field name="type">ir.actions.act_window</field> <field name="res_model">patient.tag</field> <field name="view_mode">tree,form</field> <field name="context"></field> <field name="help" type='html'> <p class='o_view_nocontent_smiling_face'> Create your first tag! </p> </field> </record> <menuitem id="menu_patient_tag" name="Tags" action="action_patient_tag" parent="menu_configuration_main" /> </odoo>
-
menu.py
<menuitem id="menu_configuration_main" name="Configuration" sequence="20" parent="menu_hospital_main" />
-
__init__.py
from . import models from . import patient from . import appointment from . import patient_tag
-
__manifest__.py
'data': [ 'security/ir.model.access.csv', 'views/menu.xml', 'views/patient_view.xml', 'views/appointment_view.xml', 'views/female_patient_view.xml', 'views/patient_tag_view.xml', ],
-
..access.csv
hospital.access_patient_tag,access_patient_tag,hospital.model_patient_tag,base.group_user,1,1,1,1
- create color field, two types:
- char: more options
- integer: some given colors
- add the fields to the view
- in patient.py add many2many field, will crete a separate table
- in patient view add many2many field, with options attribute and many2many_tag widget
in custom_addons\hospital\models\patient_tag.py
color = fields.Integer(
string='Color',
)
color2 = fields.Char(
string='Color2',
)in custom_addons\hospital\views\patient_tag_view.xml in form view
<field name="color" widget="color_picker" groups="base.group_no_one"/>
<field name="color2" widget="color"/>in custom_addons\hospital\models\patient.py
tag_ids = fields.Many2many(
string='Tag',
comodel_name='patient.tag'
)in tree and form view custom_addons\hospital\views\patient_view.xml
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" />Notice: options="{'color_field': 'color'}" allows you to see the color of the tag. And sadly, if you tried color2 will not work!!
appointment
- create the view
- don't for get to add it to the
view_mode
<record model="ir.ui.view" id="view_hospital_appointment_activity">
<field name="name">hospital.appointment.activity</field>
<field name="model">hospital.appointment</field>
<field name="arch" type="xml">
<activity string='Appointment'>
<field name="patient_id" />
<field name="ref" />
<templates>
<div t-name="activity-box">
<img t-att-src="activity_image('hospital.patient','image',record.patient_id.raw_value)" t-att-title="record.patient_id.value" t-att-alt="record.patient_id.value" />
<div>
<field name="ref" />
</div>
</div>
</templates>
</activity>
</field>
</record><!-- action -->
<field name="view_mode">tree,form,activity</field>add codeview':true at the beginning of the options attribute, because when I add it at the end, it work but for me
in prescription field in custom_addons\hospital\views\appointment_view.xml
<field name="prescription" placeholder="Enter your prescription"
options="{'codeview': true, 'collaborative': true, 'resizable': true}"/>cancel_appointment files
- for temporarily data usage
- create dir. wizard
- import wizard in main __init__.py file
- create its own __init__.py file
- create the my_model.py/cancel_appointment.py
- define it in the ..access.csv
- create my_model_wizard.xml/cancel_appointment_wizard.xml, will keep
- create the the menu window action, and keep it in menu.xml, because step 8
- add the relative path to __manifest__.py, wizards added before the views and after security
- make the form as a pop up,
target - editing the default footer
custom_addons\hospital\wizard_init_.py
from . import cancel_appointmentcustom_addons\hospital_init_.py
from . import controllers
from . import models
from . import wizardwizards added before the views and after security
'data': [
'security/ir.model.access.csv',
'wizard/cancel_appointment_wizard.xml',
'views/menu.xml',
'views/patient_view.xml',
'views/appointment_view.xml',
'views/female_patient_view.xml',
'views/patient_tag_view.xml',
]we will add two items
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_hospital_main" name="Hospital" sequence="0" web_icon="hospital,static\description\icon.png" />
<menuitem id="menu_patient_main" name="Patient Details" sequence="0" parent="menu_hospital_main" />
<menuitem id="menu_appointment_main" name="Appointment Details" sequence="20" parent="menu_hospital_main" />
<menuitem id="menu_configuration_main" name="Configuration" sequence="20" parent="menu_hospital_main" />
<menuitem id="menu_cancel_appointment" action='action_cancel_appointment_wizard' name="Cancellation" sequence="10" parent="menu_appointment_main" />
</odoo> id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
hospital.access_hospital_patient,access_hospital_patient,hospital.model_hospital_patient,base.group_user,1,1,1,1
hospital.access_hospital_appointment,access_hospital_appointment,hospital.model_hospital_appointment,base.group_user,1,1,1,1
hospital.access_appointment_pharmacy_lines,access_appointment_pharmacy_lines,hospital.model_appointment_pharmacy_lines,base.group_user,1,1,1,1
hospital.access_patient_tag,access_patient_tag,hospital.model_patient_tag,base.group_user,1,1,1,1
hospital.access_cancel_appointment_wizard,access_cancel_appointment_wizard,hospital.model_cancel_appointment_wizard,base.group_user,1,1,1,1from odoo import api, fields, models
class CancelAppointmentWizard(models.TransientModel):
_name = 'cancel.appointment.wizard'
_description = 'Cancel Appointment Wizard'
appointment_id = fields.Many2one(
string='Appointment',
comodel_name='hospital.appointment'
)
def action_cancel(self):
return<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record model="ir.ui.view" id="view_cancel_appointment_form">
<field name="name">cancel.appointment.wizard.form</field>
<field name="model">cancel.appointment.wizard</field>
<field name="arch" type="xml">
<form>
<group>
<group>
<field name="appointment_id" />
</group>
<group></group>
</group>
<footer>
<button string="Cancel Appointment" type="object" class="btn-primary" name="action_cancel" data-hotkey="q" />
<button string="Discard" special="cancel" class="btn-secondary" data-hotkey="z" />
</footer>
</form>
</field>
</record>
<record id="action_cancel_appointment_wizard" model="ir.actions.act_window">
<field name="name">Cancel Appointment</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">cancel.appointment.wizard</field>
<field name="view_mode">form</field>
<field name="context"></field>
<field name="target">new</field>
</record>
</odoo>in custom_addons\hospital\wizard\cancel_appointment.py
reason = fields.Text(
string='Reason',
)in custom_addons\hospital\wizard\cancel_appointment_wizard.xml
<group>
<field name="appointment_id" />
<field name="reason" />
</group>in custom_addons\hospital\views\appointment_view.xml
<!-- button type action -->
<!-- <button type='action' data-hotkey='n' name="%(action_cancel_appointment_wizard)d" string='Cancel' states='in_consultation,draft' /> -->
<!-- button type object -->
<button type='object' data-hotkey='n' name="action_cancel" string='Cancel' states='in_consultation,draft' />in custom_addons\hospital\models\appointment.py
# def action_cancel(self):
# for rec in self:
# rec.state = 'cancel'
def action_cancel(self):
action = self.env.ref(
'hospital.action_cancel_appointment_wizard').read()[0]
return action- create 'data' dir.
- create my_model_data.xml/patient_tag_data.xml file
- create my.model.csv/patient.tag.csv file
- add it to manifest file after security
notice: the naming is important, patient_tag.csv causes an error, it can not find the model, while patient.tag.csv not
in custom_addons\hospital_manifest_.py
'data': [
'security/ir.model.access.csv',
'data/patient_tag_data.xml',
'data/patient.tag.csv',
'wizard/cancel_appointment_wizard.xml',
'views/menu.xml',in custom_addons\hospital\data\patient_tag_data.xml
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id="patient_tag_vip" model="patient.tag">
<field name="name">VIP</field>
</record>
<record id="patient_tag_kid" model="patient.tag">
<field name="name">KID</field>
</record>
<record id="patient_tag_kid" model="patient.tag">
<field name="name">Master</field>
<field name="active" eval="False" />
</record>
</odoo>in custom_addons\hospital\data\patient.tag.csv
id,name,active
patient_tag_ASAP,ASAP,True
patient_tag_NASAP,NASAP,False
to create odoo model using command line
where u can find the odoo-bin file use this command python odoo-bin scaffold :my_model :my_path
python odoo-bin scaffold om_inheritance C:\Users\bashr\odoo\odoo\custom_addons\
- crate the model, my_inherited_model.py
- init file
- manifest file
- add a field to an inherited view, this is the tricky part: since we will need to connect our field with an existing on on the desired view.
- if we was able to find the field in the view, our new field will be added successfully
- if not, we will have to look at the inherited views, and based on your luck and experience, u guess which one that might have this field 😅
- download the model from the app store
my_inherited_model.py, in custom_addons\om_inheritance\models\sale_order.py:
from odoo import models, fields, api
class SaleOrder(models.Model):
_inherit = 'sale.order'
confirmed_user_id = fields.Many2one(
string='Confirmed User',
comodel_name='res.users',
)init file, custom_addons\om_inheritance\models_init_.py
from . import sale_ordermanifest file, custom_addons\om_inheritance_manifest_.py
# any module necessary for this one to work correctly
'depends': ['sale'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
'views/sale_order_view.xml',
'views/templates.xml',
],my_inherited_view, custom_addons\om_inheritance\views\sale_order_view.xml
<odoo>
<data>
<record id="view_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.inherit</field>
<field name="model">sale.order</field>
<!-- get ref from External ID of the view you wanna add to it -->
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='payment_term_id']" position="after">
<field name="confirmed_user_id" />
</xpath>
</field>
</record>
</data>
</odoo>- add inherited method to my_inherited_model.py,
- REMEMBER where u place the super will be affected by the execution sequence of python
inherit method syntax:
def action_inherit(self,arg1,arg2):
# to_do before the execution of the inherited method
super(MyModel,self).action_inherit(arg1,arg2)
# to_do after the execution of the inherited method
# if u dint place the super line, this method (action_inherit) will get executed not the inherited one!!!!in custom_addons\om_inheritance\models\sale_order.py
def action_confirm(self):
super(SaleOrder, self).action_confirm()
print('Hi, I am working!! ..................')
self.confirmed_user_id = self.env.user.id- create, 71+72
- write, 73
- default_get, 74
in custom_addons\hospital\models\patient.py
@api.model
def create(self, vals_list):
print("Odoo Metes are the best", vals_list)
vals_list['ref'] = 'REFERENCE'
return super(HospitalPatient, self).create(vals_list)- crete sequence_data.xml in data rep.
- add it to manifest file
- use sequence value
sequence_data.xml, custom_addons\hospital\data\sequence_data.xml
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record model="ir.sequence" id="seq_hospital_patient">
<field name="name">Hospital Patient</field>
<field name="code">hospital.patient</field>
<field name="prefix">HP</field>
<field name="padding">5</field>
<field name="company_id" eval="False" />
</record>
</odoo>manifest file, custom_addons\hospital_manifest_.py
'data': [
'security/ir.model.access.csv',
'data/patient_tag_data.xml',
'data/patient.tag.csv',
'data/sequence_data.xml',Using the sequence, patient.py, custom_addons\hospital\models\patient.py
@api.model
def create(self, vals_list):
print("Odoo Metes are the best",
self.env['ir.sequence'].next_by_code('hospital.patient'))
vals_list['ref'] = self.env['ir.sequence'].next_by_code('hospital.patient')
return super(HospitalPatient, self).create(vals_list)or to avoid losing one of sequence
@api.model
def create(self, vals_list):
curr_seq = self.env['ir.sequence'].next_by_code('hospital.patient')
print("Odoo Metes are the best", curr_seq)
vals_list['ref'] = curr_seq
return super(HospitalPatient, self).create(vals_list)WHAT WE WILL BE DOING: if a record does not have a ref value and got updated, give it a ref value
NOTICE: vals.get('ref') to allow entering a value to ref filed.
self.ref==Falsethe value in the DB is empty,vals.get('ref')==Falsethe value of the filed passed to the DB is empty.valsin write method differ form create method, which it contain only the changed fields.
def write(self, vals):
print('write trigger when we edit', vals)
if not self.ref and not vals.get('ref'):
self.ref = self.env['ir.sequence'].next_by_code(
'hospital.patient')
return super(HospitalPatient, self).write(vals)WHAT WE WILL BE DOING: Showing the current date in the date_cancel field, by overriding default_get method
custom_addons\hospital\wizard\cancel_appointment.py
@api.model
def default_get(self, fields):
res = super(CancelAppointmentWizard, self).default_get(fields)
res['date_cancel'] = datetime.date.today()
return res
date_cancel = fields.Date(string='Cancellation Date')custom_addons\hospital\wizard\cancel_appointment_wizard.xml
<form>
<group>
<group>
<field name="appointment_id" />
<field name="reason" />
</group>
<group>
<field name="date_cancel" />
</group>
</group>how the model display its fields in many2one, the changes will done in patient.py and shown in appointment model, the model that uses the patient.py as many2one field
in custom_addons\hospital\models\patient.py
def name_get(self):
# result = []
# for record in self:
# name = '[' + record.ref + '] ' + record.name
# result.append((record.id, name))
# return result
return [(record.id, "[%s] %s" % (record.ref, record.name)) for record in self]python_path odoo-bin_path -c odoo.conf_path
C:\python\python.exe C:\Users\bashr\odoo\odoo\odoo-bin -c C:\Users\bashr\odoo\odoo\odoo.conf
so what is odoo.conf?
C:\python\python.exe C:\Users\bashr\odoo\odoo\odoo-bin -h
will see a list of values you can assign, so what odoo.conf does is assigning those values, but we can do this inline we could try
- --addons-path=C:\Users\bashr\odoo\odoo\odoo\addons,C:\Users\bashr\odoo\odoo\addons,C:\Users\bashr\odoo\odoo\custom_addons
- -r odoo, database user
- -w odoo, database password
- --db_host=localhost
- --db_port=5432
- -p 8055, UI port
C:\python\python.exe C:\Users\bashr\odoo\odoo\odoo-bin --addons-path=C:\Users\bashr\odoo\odoo\odoo\addons,C:\Users\bashr\odoo\odoo\addons,C:\Users\bashr\odoo\odoo\custom_addons -r odoo -w odoo --db_host=localhost --db_port=5432 -p 8055
will add
- --stop-after-init, stop after initialization
- -s, save
- -c :configuration_file_path, where it will be saved
C:\python\python.exe C:\Users\bashr\odoo\odoo\odoo-bin --addons-path=C:\Users\bashr\odoo\odoo\odoo\addons,C:\Users\bashr\odoo\odoo\addons,C:\Users\bashr\odoo\odoo\custom_addons -r odoo -w odoo --db_host=localhost --db_port=5432 -p 8055 --stop-after-init -s -c C:\Users\bashr\odoo\odoo\conf\testconf.conf
will add
- -d, database name
- -i :model_name, install
C:\python\python.exe C:\Users\bashr\odoo\odoo\odoo-bin -c C:\Users\bashr\odoo\odoo\odoo.conf -d odoo -i crm
that means that u have a process running on this port:
- just change port number.
- kill the process that running on this port
second solution (only Ubuntu/Linux):
ps aux||grep odoosudo kill -9 :process_id
will add
- -d :database_name, to access this DB
- -u :model_name, to upgrade this model
C:\python\python.exe C:\Users\bashr\odoo\odoo\odoo-bin -c C:\Users\bashr\odoo\odoo\odoo.conf -d odoo -u hospital
will add
- -d :database_name, to access this DB
C:\python\python.exe C:\Users\bashr\odoo\odoo\odoo-bin -c C:\Users\bashr\odoo\odoo\odoo.conf -d odoo_test
for some reason when i tried to run odoo on the default port, 8069, did not work.
shows something like that: web.assets_common.min.js:4451 Uncaught (in promise) Error: QWeb2: Template
changing the port solved the issue, I changed it to 80 instead of 8069.
we can add to the root menu like this
<menuitem id="menu_technical" name="Technical" parent='menu_hospital_main' sequence="110">
<menuitem id="menu_playground" name="Play Ground" action='action_playground' sequence="110"/>
</menuitem>and we can add it as a main model like CRM or Sales
<menuitem id="menu_technical" name="Technical" sequence="110">
<menuitem id="menu_playground" name="Play Ground" sequence="10">
<menuitem id="menu_playground2" name="Play Ground" action='action_playground' sequence="10">
</menuitem>
</menuitem>
</menuitem>- my_model.py
- init file
- my_model_view.xml
- manifest file
- security
in odoo_playground.py
from odoo import models, api, fields
from odoo.tools.safe_eval import safe_eval
class OdooPlayGround(models.Model):
_name = 'odoo.playground'
_description = 'Odoo PlayGround'
DEFAULT_ENV_VARIABLE = """ #WHATEVER WERE THERE.. \n\n"""
model_id = fields.Many2one(
comodel_name='ir.model',
string='Model',
)
code = fields.Text(
string='Code',
default=DEFAULT_ENV_VARIABLE
)
result = fields.Text(
string='Result',
)
def action_execute(self):
try:
if self.model_id:
model = self.env[self.model_id.model]
else:
model = self
self.result = safe_eval(self.code.strip(), {'self': model})
except Exception as e:
self.result = str(e)in init.py
from . import models, patient, appointment, patient_tag, odoo_playgroundin odoo_playground_view.xml
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id="odoo_playground_view_form" model="ir.ui.view">
<field name="name">odoo.playground.view.form</field>
<field name="model">odoo.playgroundg</field>
<field name="arch" type="xml">
<form string="">
<sheet>
<group>
<field name="model_id"/>
<field name="code" widget="ace" options="{'mode': 'python'}"/>
</group>
<group>
<button name="action_execute" string="Execute" type="object" class="btn-primary"/>
</group>
<group>
<field name="result"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_playground" model="ir.actions.act_window">
<field name="name">Play Ground</field>
<field name="res_model">odoo.playgroundg</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="type">ir.actions.act_window</field>
</record>
<menuitem id="menu_technical" name="Technical" parent='menu_hospital_main' sequence="110">
<menuitem id="menu_playground" name="Play Ground" action='action_playground' sequence="110"/>
</menuitem>
<!-- <menuitem id="menu_technical" name="Technical" sequence="110">-->
<!-- <menuitem id="menu_playground" name="Play Ground" sequence="10">-->
<!-- <menuitem id="menu_playground2" name="Play Ground" action='action_playground' sequence="10">-->
<!-- </menuitem>-->
<!-- </menuitem>-->
<!-- </menuitem>-->
</odoo>in manifest.py
# always loaded
'data': [
'security/ir.model.access.csv',
'data/patient_tag_data.xml',
'data/patient.tag.csv',
'data/sequence_data.xml',
'views/views.xml',
'wizard/cancel_appointment_wizard.xml',
'views/menu.xml',
'views/patient_view.xml',
'views/female_view.xml',
'views/appointment_view.xml',
'views/templates.xml',
'views/patient_tag_view.xml',
'views/odoo_playground_view.xml'
],DO NOT FORGET copying the security access
- current: like the patient model
- new: like wizards, the cancel appointment model
- inline: like the playground model
- inline vs current: Current window, will have the discard and save buttons, etc. But, this inline will not
in odoo_playground_view.xml
<record id="action_playground" model="ir.actions.act_window">
<field name="name">Play Ground</field>
<field name="res_model">odoo.playgroundg</field>
<field name="view_mode">form</field>
<!-- <field name="target">inline</field> -->
<!-- <field name="target">new</field> -->
<field name="target">current</field>
<field name="type">ir.actions.act_window</field>
</record>try and get it back to inline
self is a record set, record set is an object we can access its inner values.
self.env['hospital.appointment'].browse(6).patient_id.name self.env['hospital.appointment'].browse(6).action_done()
self.env['my.model'].browse(cids/id).patient_id.name
to get the record set of whatever inside
self.env.ref('hospital.patient_tag_vip').name self.env.ref('XML ID').name
XML ID, can be found when you open the view metadata from the debugging icon, in the needed view
Adding the appointment sequence:
- appointment.py, adding field
- appointment view, show it in the view
- sequence_data.xml, adding the sequence
- appointment.py, edit the create method
Using the active_id:
- from view
- form model
-
appointment.py, adding field, custom_addons\hospital\models\appointment.py
Note that we will commit the rec_name, since we wanna display the name field when we uses the active_id
# _rec_name = 'ref' name = fields.Char( string='name', readonly=True )
-
appointment view, custom_addons\hospital\views\appointment_view.xml
</header> <sheet> <div class="oe_title"> <h1> <field name="name" /> </h1> </div> <div> <h3> <field name='priority' widget='priority' /> </h3>
-
sequence_data.xml, custom_addons\hospital\data\sequence_data.xml
<record model="ir.sequence" id="seq_hospital_appointment"> <field name="name">Hospital Appointment</field> <field name="code">hospital.appointment</field> <field name="prefix">OP</field> <field name="padding">5</field> <field name="company_id" eval="False" /> </record>
-
appointment.py, edit the create method, custom_addons\hospital\models\appointment.py
@api.model def create(self, vals_list): print("Odoo Metes are the best", self.env['ir.sequence'].next_by_code('hospital.appointment')) vals_list['name'] = self.env['ir.sequence'].next_by_code( 'hospital.appointment') return super(HospitalAppointment, self).create(vals_list)
custom_addons\hospital\views\appointment_view.xml
<!-- button type action -->
<button type='action' data-hotkey='n' name="%(action_cancel_appointment_wizard)d" string='Cancel' states='in_consultation,draft' context="{'default_appointment_id':active_id}" />edit the cancel_appointment.py wizard, custom_addons\hospital\wizard\cancel_appointment.py
@api.model
def default_get(self, fields):
res = super(CancelAppointmentWizard, self).default_get(fields)
res['date_cancel'] = datetime.date.today()
print(".................context", self.env.context)
res['appointment_id'] = self.env.context.get('active_id')
return resNote that we need two separate view files to apply this, ex:
- appointment_view.xml and cancel_appointment_view.xml
- female_patient_view.xml and patient_view.xml
steps:
- add a marker to the context attribute to the record you wanna hide
- add invisible attribute and get the value of that marker, in form view
Case 1: appointment_view.xml and cancel_appointment_view.xml
adding the marker, custom_addons\hospital\views\appointment_view.xml
<button type='action' data-hotkey='n' name="%(action_cancel_appointment_wizard)d" string='Cancel' states='in_consultation,draft' context="{'hide_appointment': 1}" />invisible attribute, custom_addons\hospital\wizard\cancel_appointment_wizard.xml
<field name="appointment_id" invisible="context.get('hide_appointment')" />Case 2: female_patient_view.xml and patient_view.xml, hide gender
adding the marker, custom_addons\hospital\views\female_patient_view.xml
<field name="context">{"default_gender":"female","default_age":20,"hide_gender":1}</field>invisible attribute, custom_addons\hospital\views\patient_view.xml
<field name="gender" invisible="context.get('hide_gender')" />_ is for translation purpose
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
# ...some code here
def action_cancel(self):
if self.appointment_id.booking_date == fields.Date.today():
raise ValidationError(
_("Sorry, cancellation is not allowed on the same day of booking !"))
returnin custom_addons\hospital\wizard\cancel_appointment_wizard.xml
<field name="appointment_id" invisible="context.get('hide_appointment')" domain="[('state', '=', 'draft')]" />in custom_addons\hospital\wizard\cancel_appointment.py
appointment_id = fields.Many2one(
string='Appointment',
comodel_name='hospital.appointment',
domain=[('state', '=', 'draft'), ('priority', 'in', ('0', '1', False))]
)