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 odoo
the database nameselect * from hospital_patient
the 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 = 1
notice: 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.ref
in 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 = 1
in 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
attrs
attribute to the field will be hidden,parent
means 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_appointment
custom_addons\hospital_init_.py
from . import controllers
from . import models
from . import wizard
wizards 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,1
from 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_order
manifest 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==False
the value in the DB is empty,vals.get('ref')==False
the value of the filed passed to the DB is empty.vals
in 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 odoo
sudo 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_playground
in 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 res
Note 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 !"))
return
in 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))]
)