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:
fromodooimportapi, fields, modelsclassHospitalPatient(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> <menuitemid="menu_hospital_main"name="Hospital"action="" /> <menuitemid="menu_patient_main"name="Patient Details"action=""parent="menu_hospital_main" /> <menuitemid="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> <recordid="action_hospital_patient"model="ir.actions.act_window"> <fieldname="name">Patients</field> <fieldname="type">ir.actions.act_window</field> <fieldname="res_model">hospital.patient</field> <fieldname="view_mode">tree,form</field> <fieldname="context"eval="{}" /> <fieldname="help"type='html'> <pclass='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 :
<menuitemid="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_unlinkhospital.access_hospital_patient,access_hospital_patient,hospital.model_hospital_patient,base.group_user,1,1,1,1add 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>
<menuitemid="menu_hospital_main"name="Hospital"sequence="0"web_icon="hospital,static\description\icon.png" />
<menuitemid="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>
<recordmodel="ir.ui.view"id="view_hospital_patient_tree">
<fieldname="name">hospital patient</field>
<fieldname="model">hospital.patient</field>
<fieldname="arch"type="xml">
<tree>
<fieldname="name"string="Patient Name" />
<fieldname="ref" />
<fieldname="age" />
<fieldname="gender" />
</tree>
</field>
</record>
<recordmodel="ir.ui.view"id="view_hospital_patient_form">
<fieldname="name">hospital patient</field>
<fieldname="model">hospital.patient</field>
<fieldname="arch"type="xml">
<form>
<sheet>
<group>
<group>
<fieldname="name" />
<fieldname="age" />
</group>
<group>
<fieldname="gender" />
<fieldname="ref" />
</group>
</group>
</sheet>
</form>
</field>
</record>
in custom_addons\hospital\views\patient_view.xml add this after form view
<recordmodel="ir.ui.view"id="view_hospital_patient_search">
<fieldname="name">hospital patient</field>
<fieldname="model">hospital.patient</field>
<fieldname="arch"type="xml">
<searchstring='test'>
<fieldname="name"string="Patient Name"filter_domain="['|',('name', 'ilike', self),('ref', 'ilike', self)]" />
<fieldname="age" />
<fieldname="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,'<'.
<recordmodel="ir.ui.view"id="view_hospital_patient_search">
<fieldname="name">hospital patient</field>
<fieldname="model">hospital.patient</field>
<fieldname="arch"type="xml">
<searchstring='test'>
<fieldname="name"string="Patient Name"filter_domain="['|',('name', 'ilike', self),('ref', 'ilike', self)]" />
<fieldname="age" />
<fieldname="gender" />
<filterstring="Male"name="filter_male"domain="[('gender','=','male')]" />
<filterstring="Female"name="filter_female"domain="[('gender','=','female')]" />
<separator />
<filterstring="Kids"name="filter_kids"domain="[('age','<=','5')]" />
<groupexpand="0"string="Group By">
<filterstring="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>
<fieldname="gender" />
<fieldname="ref" />
<fieldname="active"invisible="1" />
</group>in search record
<separator />
<filterstring="Kids"name="filter_kids"domain="[('age','<=','5')]" />
<separator />
<filterstring="Archive"name="filter_archive"domain="[('active','=',False)]" />model will be from patient.hospital
view:
<?xml version="1.0" encoding="utf-8"?> <odoo> <recordid="action_hospital_female_patient"model="ir.actions.act_window"> <fieldname="name">Female Patients</field> <fieldname="type">ir.actions.act_window</field> <fieldname="res_model">hospital.patient</field> <fieldname="view_mode">tree,form</field> <fieldname="context">{}</field> <fieldname="domain">[('gender','=','female')]</field> <fieldname="help"type='html'> <pclass='o_view_nocontent_smiling_face'> Create your first patient! </p> </field> </record> <menuitemid="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
<fieldname="context">{"default_gender":"female","default_age":"20"}</field>rather than keeping it empty
in view, custom_addons\hospital\views\patient_view.xml
<fieldname="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>
<divclass="oe_chatter">
<fieldname="message_follower_ids"widget="mail_followers" />
<fieldname="activity_ids"widget="mail_activity" />
<fieldname="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>
<fieldname="gender"icon="fa-user"enable_conter="1" />
</searchpanel>create model file, custom_addons\hospital\models\appointment.py
fromodooimportmodels, fields, apiclassHospitalAppointment(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 . importmodelsfrom . importpatientfrom . importappointment
crete view (where we add the many2one field), custom_addons\hospital\views\appointment_view.xml
<?xml version="1.0" encoding="utf-8"?> <odoo> <recordmodel="ir.ui.view"id="view_hospital_appointment_tree"> <fieldname="name">hospital appointment</field> <fieldname="model">hospital.appointment</field> <fieldname="arch"type="xml"> <tree> <fieldname="patient_id" /> </tree> </field> </record> <recordmodel="ir.ui.view"id="view_hospital_appointment_form"> <fieldname="name">hospital appointment</field> <fieldname="model">hospital.appointment</field> <fieldname="arch"type="xml"> <form> <sheet> <group> <group> <fieldname="patient_id" /> </group> <group></group> </group> </sheet> <divclass="oe_chatter"> <fieldname="message_follower_ids"widget="mail_followers" /> <fieldname="activity_ids"widget="mail_activity" /> <fieldname="message_ids"widget="mail_thread" /> </div> </form> </field> </record> <recordid="action_hospital_appointment"model="ir.actions.act_window"> <fieldname="name">appointments</field> <fieldname="type">ir.actions.act_window</field> <fieldname="res_model">hospital.appointment</field> <fieldname="view_mode">tree,form</field> <fieldname="context">{}</field> <fieldname="help"type='html'> <pclass='o_view_nocontent_smiling_face'> Create your first appointment! </p> </field> </record> <menuitemid="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_unlinkhospital.access_hospital_patient,access_hospital_patient,hospital.model_hospital_patient,base.group_user,1,1,1,1hospital.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 -->
<fieldname="appointment_time" />
<fieldname="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 -->
<fieldname="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)
forrecinself:
today=date.today()
ifrec.dob:
rec.age=today.year-rec.dob.yearelse:
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 -->
<fieldname="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')defonchange_patient_id(self):
self.ref=self.patient_id.refin custom_addons\hospital\views\patient_view.xml
<fieldname="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
<fieldname="appointment_id"/>at the end of the sheet tag add <notebook><page><group><field/>
in custom_addons\hospital\views\appointment_view.xml
<!-- notebook -->
<notebook>
<pagestring="Prescription"name="prescription">
<group>
<fieldname="booking_date"/>
</group>
</page>
<pagestring="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
<fieldname="prescription"placeholder="Enter your prescription"/>in from or tree tag add crete='0'
example:
<formcreate="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-->
<divclass="oe_title">
<h1>
<fieldname="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>
<fieldname="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
<buttonname="action_test"string="Object Button"type="object"class="oe_highlight"confirm='confirm massage' />
<buttonname="%(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
defaction_test(self):
print('test!!!!!!!!!!!!!!!!!!!!')in custom_addons\hospital\models\patient.py
@api.depends('dob')def_compute_age(self):
print("self................", self)
forrecinself:
today=datetime.date.today()
ifrec.dob:
rec.age=today.year-rec.dob.yearelse:
rec.age=1in custom_addons\hospital\models\appointment.py
defaction_test(self):
print('test!!!!!!!!!!!!!!!!!!!!')
return {
'effect': {
'fadeout': 'slow',
'message': 'Printed',
'type': 'rainbow_man',
}
}in the tree view custom_addons\hospital\views\appointment_view.xml
<treedecoration-success="state =='done'"decoration-info="state =='draft'"decoration-danger="state=='in_consultation'">
<fieldname="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
<fieldname="activity_ids"widget='list_activity' />
in tree view custom_addons\hospital\views\appointment_view.xml
<fieldname="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 -->
<fieldname="doctor_id"widget="many2one_avatar"/>
<!-- will just show the avatar and chat when you click -->
<fieldname="doctor_id"widget="many2one_avatar_user"/>
<!-- in form view -->
<fieldname="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
<fieldname="booking_date"default_focus="1" />in tree field add sample='1'
in custom_addons\hospital\views\appointment_view.xml
<treedecoration-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'
<treedecoration-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>
<buttonname="action_in_consultation"string="In Consultation"type="object"states="draft"class="oe_highlight"/>
<buttonname="action_done"string="Done"type="object"states="in_consultation"class="oe_highlight"/>
<buttonname="action_cancel"string="Cancel"type="object"states="draft,in_consultation" />
<buttonname="action_draft"string="Draft"type="object"states="cancel"class="oe_highlight"/>defaction_draft(self):
forrecinself:
rec.state='draft'defaction_in_consultation(self):
forrecinself:
rec.state='in_consultation'defaction_done(self):
forrecinself:
rec.state='done'defaction_cancel(self):
forrecinself:
rec.state='cancel'add data-hotkey='i', any letter available
<buttonname="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
classAppointmentPharmacyLines(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 -->
<pagestring='Pharmacy'>
<fieldname="pharmacy_lines_id">
<treeeditable='bottom'>
<fieldname="product_id" />
<fieldname="price_unit" />
<fieldname="qty" />
</tree>
<form>
<group>
<fieldname="product_id" />
<fieldname="price_unit" />
<fieldname="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 <pagestring='Pharmacy'>
<fieldname="pharmacy_lines_id">
<treeeditable='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 -->
<fieldname="hide_sales_price"groups="base.group_no_one"/><!-- Pharmacy page -->
<pagestring='Pharmacy'>
<fieldname="pharmacy_lines_id">
<treeeditable='bottom'>
<fieldname="product_id"/>
<fieldname="price_unit"attrs="{ 'column_invisible':[('parent.hide_sales_price','=',True)] }"/>
<fieldname="qty"/>
</tree>
<form>
<group>
<fieldname="product_id"/>
<fieldname="price_unit"attrs="{ 'column_invisible':[('parent.hide_sales_price','=',True)] }"/>
<fieldname="qty"/>
</group>
</form>
</field>
</page>add groups="base.group_no_one" to the field u wanna hide
<fieldname="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>
<fieldname="image"widget='image'class="oe_avatar" />
<group>
<group>
<fieldname="name" />
<fieldname="dob" />
<fieldname="age" />
</group>patient_tag
to create a model, will deal with 6 files:
MyModel.py, patient_tag.py
fromemail.policyimportdefaultfromodooimportapi, fields, modelsclassPatientTag(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> <recordmodel="ir.ui.view"id="view_patient_tag_tree"> <fieldname="name">Patient Tags</field> <fieldname="model">patient.tag</field> <fieldname="arch"type="xml"> <treesample='1'> <fieldname="name"string="Tag Name" /> </tree> </field> </record> <recordmodel="ir.ui.view"id="view_patient_tag_form"> <fieldname="name">Patient Tags</field> <fieldname="model">patient.tag</field> <fieldname="arch"type="xml"> <form> <sheet> <group> <group> <fieldname="name" /> </group> <group> <fieldname="active"widget="boolean_toggle" /> </group> </group> </sheet> </form> </field> </record> <recordid="action_patient_tag"model="ir.actions.act_window"> <fieldname="name">Tags</field> <fieldname="type">ir.actions.act_window</field> <fieldname="res_model">patient.tag</field> <fieldname="view_mode">tree,form</field> <fieldname="context"></field> <fieldname="help"type='html'> <pclass='o_view_nocontent_smiling_face'> Create your first tag! </p> </field> </record> <menuitemid="menu_patient_tag"name="Tags"action="action_patient_tag"parent="menu_configuration_main" /> </odoo>
menu.py
<menuitemid="menu_configuration_main"name="Configuration"sequence="20"parent="menu_hospital_main" />
__init__.py
from . importmodelsfrom . importpatientfrom . importappointmentfrom . importpatient_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
<fieldname="color"widget="color_picker"groups="base.group_no_one"/>
<fieldname="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
<fieldname="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
<recordmodel="ir.ui.view"id="view_hospital_appointment_activity">
<fieldname="name">hospital.appointment.activity</field>
<fieldname="model">hospital.appointment</field>
<fieldname="arch"type="xml">
<activitystring='Appointment'>
<fieldname="patient_id" />
<fieldname="ref" />
<templates>
<divt-name="activity-box">
<imgt-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>
<fieldname="ref" />
</div>
</div>
</templates>
</activity>
</field>
</record><!-- action -->
<fieldname="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
<fieldname="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 . importcancel_appointmentcustom_addons\hospital_init_.py
from . importcontrollersfrom . importmodelsfrom . importwizardwizards 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>
<menuitemid="menu_hospital_main"name="Hospital"sequence="0"web_icon="hospital,static\description\icon.png" />
<menuitemid="menu_patient_main"name="Patient Details"sequence="0"parent="menu_hospital_main" />
<menuitemid="menu_appointment_main"name="Appointment Details"sequence="20"parent="menu_hospital_main" />
<menuitemid="menu_configuration_main"name="Configuration"sequence="20"parent="menu_hospital_main" />
<menuitemid="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_unlinkhospital.access_hospital_patient,access_hospital_patient,hospital.model_hospital_patient,base.group_user,1,1,1,1hospital.access_hospital_appointment,access_hospital_appointment,hospital.model_hospital_appointment,base.group_user,1,1,1,1hospital.access_appointment_pharmacy_lines,access_appointment_pharmacy_lines,hospital.model_appointment_pharmacy_lines,base.group_user,1,1,1,1hospital.access_patient_tag,access_patient_tag,hospital.model_patient_tag,base.group_user,1,1,1,1hospital.access_cancel_appointment_wizard,access_cancel_appointment_wizard,hospital.model_cancel_appointment_wizard,base.group_user,1,1,1,1fromodooimportapi, fields, modelsclassCancelAppointmentWizard(models.TransientModel):
_name='cancel.appointment.wizard'_description='Cancel Appointment Wizard'appointment_id=fields.Many2one(
string='Appointment',
comodel_name='hospital.appointment'
)
defaction_cancel(self):
return<?xml version='1.0' encoding='utf-8'?>
<odoo>
<recordmodel="ir.ui.view"id="view_cancel_appointment_form">
<fieldname="name">cancel.appointment.wizard.form</field>
<fieldname="model">cancel.appointment.wizard</field>
<fieldname="arch"type="xml">
<form>
<group>
<group>
<fieldname="appointment_id" />
</group>
<group></group>
</group>
<footer>
<buttonstring="Cancel Appointment"type="object"class="btn-primary"name="action_cancel"data-hotkey="q" />
<buttonstring="Discard"special="cancel"class="btn-secondary"data-hotkey="z" />
</footer>
</form>
</field>
</record>
<recordid="action_cancel_appointment_wizard"model="ir.actions.act_window">
<fieldname="name">Cancel Appointment</field>
<fieldname="type">ir.actions.act_window</field>
<fieldname="res_model">cancel.appointment.wizard</field>
<fieldname="view_mode">form</field>
<fieldname="context"></field>
<fieldname="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>
<fieldname="appointment_id" />
<fieldname="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 -->
<buttontype='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'defaction_cancel(self):
action=self.env.ref(
'hospital.action_cancel_appointment_wizard').read()[0]
returnaction- 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>
<recordid="patient_tag_vip"model="patient.tag">
<fieldname="name">VIP</field>
</record>
<recordid="patient_tag_kid"model="patient.tag">
<fieldname="name">KID</field>
</record>
<recordid="patient_tag_kid"model="patient.tag">
<fieldname="name">Master</field>
<fieldname="active"eval="False" />
</record>
</odoo>in custom_addons\hospital\data\patient.tag.csv
id,name,activepatient_tag_ASAP,ASAP,Truepatient_tag_NASAP,NASAP,Falseto 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:
fromodooimportmodels, fields, apiclassSaleOrder(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 . importsale_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>
<recordid="view_order_form_inherit"model="ir.ui.view">
<fieldname="name">sale.order.inherit</field>
<fieldname="model">sale.order</field>
<!-- get ref from External ID of the view you wanna add to it -->
<fieldname="inherit_id"ref="sale.view_order_form" />
<fieldname="arch"type="xml">
<xpathexpr="//field[@name='payment_term_id']"position="after">
<fieldname="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:
defaction_inherit(self,arg1,arg2):
# to_do before the execution of the inherited methodsuper(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
defaction_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.modeldefcreate(self, vals_list):
print("Odoo Metes are the best", vals_list)
vals_list['ref'] ='REFERENCE'returnsuper(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>
<recordmodel="ir.sequence"id="seq_hospital_patient">
<fieldname="name">Hospital Patient</field>
<fieldname="code">hospital.patient</field>
<fieldname="prefix">HP</field>
<fieldname="padding">5</field>
<fieldname="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.modeldefcreate(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')
returnsuper(HospitalPatient, self).create(vals_list)or to avoid losing one of sequence
@api.modeldefcreate(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_seqreturnsuper(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.
defwrite(self, vals):
print('write trigger when we edit', vals)
ifnotself.refandnotvals.get('ref'):
self.ref=self.env['ir.sequence'].next_by_code(
'hospital.patient')
returnsuper(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.modeldefdefault_get(self, fields):
res=super(CancelAppointmentWizard, self).default_get(fields)
res['date_cancel'] =datetime.date.today()
returnresdate_cancel=fields.Date(string='Cancellation Date')custom_addons\hospital\wizard\cancel_appointment_wizard.xml
<form>
<group>
<group>
<fieldname="appointment_id" />
<fieldname="reason" />
</group>
<group>
<fieldname="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
defname_get(self):
# result = []# for record in self:# name = '[' + record.ref + '] ' + record.name# result.append((record.id, name))# return resultreturn [(record.id, "[%s] %s"% (record.ref, record.name)) forrecordinself]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
<menuitemid="menu_technical"name="Technical"parent='menu_hospital_main'sequence="110">
<menuitemid="menu_playground"name="Play Ground"action='action_playground'sequence="110"/>
</menuitem>and we can add it as a main model like CRM or Sales
<menuitemid="menu_technical"name="Technical"sequence="110">
<menuitemid="menu_playground"name="Play Ground"sequence="10">
<menuitemid="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
fromodooimportmodels, api, fieldsfromodoo.tools.safe_evalimportsafe_evalclassOdooPlayGround(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',
)
defaction_execute(self):
try:
ifself.model_id:
model=self.env[self.model_id.model]
else:
model=selfself.result=safe_eval(self.code.strip(), {'self': model})
exceptExceptionase:
self.result=str(e)in init.py
from . importmodels, patient, appointment, patient_tag, odoo_playgroundin odoo_playground_view.xml
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<recordid="odoo_playground_view_form"model="ir.ui.view">
<fieldname="name">odoo.playground.view.form</field>
<fieldname="model">odoo.playgroundg</field>
<fieldname="arch"type="xml">
<formstring="">
<sheet>
<group>
<fieldname="model_id"/>
<fieldname="code"widget="ace"options="{'mode': 'python'}"/>
</group>
<group>
<buttonname="action_execute"string="Execute"type="object"class="btn-primary"/>
</group>
<group>
<fieldname="result"/>
</group>
</sheet>
</form>
</field>
</record>
<recordid="action_playground"model="ir.actions.act_window">
<fieldname="name">Play Ground</field>
<fieldname="res_model">odoo.playgroundg</field>
<fieldname="view_mode">form</field>
<fieldname="target">inline</field>
<fieldname="type">ir.actions.act_window</field>
</record>
<menuitemid="menu_technical"name="Technical"parent='menu_hospital_main'sequence="110">
<menuitemid="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
<recordid="action_playground"model="ir.actions.act_window">
<fieldname="name">Play Ground</field>
<fieldname="res_model">odoo.playgroundg</field>
<fieldname="view_mode">form</field>
<!-- <field name="target">inline</field> --><!-- <field name="target">new</field> -->
<fieldname="target">current</field>
<fieldname="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> <divclass="oe_title"> <h1> <fieldname="name" /> </h1> </div> <div> <h3> <fieldname='priority'widget='priority' /> </h3>
sequence_data.xml, custom_addons\hospital\data\sequence_data.xml
<recordmodel="ir.sequence"id="seq_hospital_appointment"> <fieldname="name">Hospital Appointment</field> <fieldname="code">hospital.appointment</field> <fieldname="prefix">OP</field> <fieldname="padding">5</field> <fieldname="company_id"eval="False" /> </record>
appointment.py, edit the create method, custom_addons\hospital\models\appointment.py
@api.modeldefcreate(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') returnsuper(HospitalAppointment, self).create(vals_list)
custom_addons\hospital\views\appointment_view.xml
<!-- button type action -->
<buttontype='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.modeldefdefault_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')
returnresNote 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
<buttontype='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
<fieldname="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
<fieldname="context">{"default_gender":"female","default_age":20,"hide_gender":1}</field>invisible attribute, custom_addons\hospital\views\patient_view.xml
<fieldname="gender"invisible="context.get('hide_gender')" />_ is for translation purpose
fromodooimportapi, fields, models, _fromodoo.exceptionsimportValidationError# ...some code heredefaction_cancel(self):
ifself.appointment_id.booking_date==fields.Date.today():
raiseValidationError(
_("Sorry, cancellation is not allowed on the same day of booking !"))
returnin custom_addons\hospital\wizard\cancel_appointment_wizard.xml
<fieldname="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))]
)