[MERGE] Forward-port of branch 13.0 to 14.0

This commit is contained in:
Antoine Vandevenne (anv)
2021-05-04 16:51:07 +02:00
2984 changed files with 41996 additions and 59708 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,40 @@
# HG changeset patch
# Parent 303a5f4f011822dcb42b5833d579eabd3f03f4bf
Index: addons/openacademy/__manifest__.py
===================================================================
--- addons.orig/openacademy/__manifest__.py 2014-08-26 17:26:18.143783102 +0200
+++ addons/openacademy/__manifest__.py 2014-08-26 17:26:18.135783102 +0200
@@ -25,7 +25,8 @@
# always loaded
'data': [
- # 'security/ir.model.access.csv',
+ 'security/security.xml',
+ 'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
'views/partner.xml',
Index: addons/openacademy/security/ir.model.access.csv
===================================================================
--- addons.orig/openacademy/security/ir.model.access.csv 2014-08-26 17:26:18.143783102 +0200
+++ addons/openacademy/security/ir.model.access.csv 2014-08-26 17:26:18.135783102 +0200
@@ -1,2 +1,5 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
-access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0
+course_manager,course manager,model_openacademy_course,group_manager,1,1,1,1
+session_manager,session manager,model_openacademy_session,group_manager,1,1,1,1
+course_read_all,course all,model_openacademy_course,,1,0,0,0
+session_read_all,session all,model_openacademy_session,,1,0,0,0
Index: addons/openacademy/security/security.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/security/security.xml 2014-08-26 17:26:18.135783102 +0200
@@ -0,0 +1,7 @@
+<odoo>
+
+ <record id="group_manager" model="res.groups">
+ <field name="name">OpenAcademy / Manager</field>
+ </record>
+
+</odoo>
@@ -0,0 +1,27 @@
# HG changeset patch
# Parent 0602022dc2a428f9995c886df33b699b6d3bcb69
Index: addons/openacademy/security/security.xml
===================================================================
--- addons.orig/openacademy/security/security.xml 2014-08-26 17:26:18.971783090 +0200
+++ addons/openacademy/security/security.xml 2014-08-26 17:26:18.967783090 +0200
@@ -3,5 +3,19 @@
<record id="group_manager" model="res.groups">
<field name="name">OpenAcademy / Manager</field>
</record>
+
+ <record id="only_responsible_can_modify" model="ir.rule">
+ <field name="name">Only Responsible can modify Course</field>
+ <field name="model_id" ref="model_openacademy_course"/>
+ <field name="groups" eval="[(4, ref('openacademy.group_manager'))]"/>
+ <field name="perm_read" eval="0"/>
+ <field name="perm_write" eval="1"/>
+ <field name="perm_create" eval="0"/>
+ <field name="perm_unlink" eval="1"/>
+ <field name="domain_force">
+ ['|', ('responsible_id','=',False),
+ ('responsible_id','=',user.id)]
+ </field>
+ </record>
</odoo>
@@ -0,0 +1,19 @@
# HG changeset patch
# Parent f8d2422e87b3ff566dc947ad582608db3b15e077
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-26 17:26:09.283783234 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-26 17:26:09.279783234 +0200
@@ -115,9 +115,10 @@
<field name="name">session.tree</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
- <tree string="Session Tree">
+ <tree string="Session Tree" decoration-info="duration&lt;5" decoration-danger="duration&gt;15">
<field name="name"/>
<field name="course_id"/>
+ <field name="duration" invisible="1"/>
<field name="taken_seats" widget="progressbar"/>
</tree>
</field>
@@ -0,0 +1,53 @@
# HG changeset patch
# Parent 16e4cb131d9f7f3a72a8a1b0bc46c2ce9ac76435
Index: addons/openacademy/__manifest__.py
===================================================================
--- addons.orig/openacademy/__manifest__.py 2014-08-26 17:25:53.519783468 +0200
+++ addons/openacademy/__manifest__.py 2014-08-26 17:25:53.511783468 +0200
@@ -27,6 +27,7 @@
'data': [
# 'security/ir.model.access.csv',
'templates.xml',
+ 'views/openacademy.xml',
],
# only loaded in demonstration mode
'demo': [
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/views/openacademy.xml 2014-08-26 17:25:53.511783468 +0200
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+
+ <!-- window action -->
+ <!--
+ The following tag is an action definition for a "window action",
+ that is an action opening a view or a set of views
+ -->
+ <record model="ir.actions.act_window" id="course_list_action">
+ <field name="name">Courses</field>
+ <field name="res_model">openacademy.course</field>
+ <field name="view_mode">tree,form</field>
+ <field name="help" type="html">
+ <p class="o_view_nocontent_smiling_face">Create the first course
+ </p>
+ </field>
+ </record>
+
+ <!-- top level menu: no parent -->
+ <menuitem id="main_openacademy_menu" name="Open Academy"/>
+ <!-- A first level in the left side menu is needed
+ before using action= attribute -->
+ <menuitem id="openacademy_menu" name="Open Academy"
+ parent="main_openacademy_menu"/>
+ <!-- the following menuitem should appear *after*
+ its parent openacademy_menu and *after* its
+ action course_list_action -->
+ <menuitem id="courses_menu" name="Courses" parent="openacademy_menu"
+ action="course_list_action"/>
+ <!-- Full id location:
+ action="openacademy.course_list_action"
+ It is not required when it is the same module -->
+
+</odoo>
@@ -0,0 +1,77 @@
# HG changeset patch
# Parent 85a8d7317b9e13480f39ad739955442d15144451
# Parent 16fcdc4c6462a7872636f3c19550c16879af5281
diff --git a/openacademy/models.py b/openacademy/models.py
--- a/openacademy/models.py
+++ b/openacademy/models.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
+from datetime import timedelta
from odoo import models, fields, api, exceptions
class Course(models.Model):
@@ -57,6 +58,8 @@ class Session(models.Model):
attendee_ids = fields.Many2many('res.partner', string="Attendees")
taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')
+ end_date = fields.Date(string="End Date", store=True,
+ compute='_get_end_date', inverse='_set_end_date')
@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
@@ -83,6 +86,27 @@ class Session(models.Model):
},
}
+ @api.depends('start_date', 'duration')
+ def _get_end_date(self):
+ for r in self:
+ if not (r.start_date and r.duration):
+ r.end_date = r.start_date
+ continue
+
+ # Add duration to start_date, but: Monday + 5 days = Saturday, so
+ # subtract one second to get on Friday instead
+ duration = timedelta(days=r.duration, seconds=-1)
+ r.end_date = r.start_date + duration
+
+ def _set_end_date(self):
+ for r in self:
+ if not (r.start_date and r.end_date):
+ continue
+
+ # Compute the difference between dates, but: Friday - Monday = 4 days,
+ # so add one day to get 5 days instead
+ r.duration = (r.end_date - r.start_date).days + 1
+
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:
diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml
--- a/openacademy/views/openacademy.xml
+++ b/openacademy/views/openacademy.xml
@@ -124,10 +124,21 @@
</field>
</record>
+ <!-- calendar view -->
+ <record model="ir.ui.view" id="session_calendar_view">
+ <field name="name">session.calendar</field>
+ <field name="model">openacademy.session</field>
+ <field name="arch" type="xml">
+ <calendar string="Session Calendar" date_start="start_date" date_stop="end_date" color="instructor_id">
+ <field name="name"/>
+ </calendar>
+ </field>
+ </record>
+
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
- <field name="view_mode">tree,form</field>
+ <field name="view_mode">tree,form,calendar</field>
</record>
<menuitem id="session_menu" name="Sessions"
@@ -0,0 +1,40 @@
# HG changeset patch
# Parent a358be0a577b0569831958a8ec1302825c645dee
# Parent 59107edbe5f81bab5e7db172bf2bffa504ce399a
diff --git a/openacademy/models.py b/openacademy/models.py
--- a/openacademy/models.py
+++ b/openacademy/models.py
@@ -30,3 +30,13 @@ class Session(models.Model):
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
+
+ taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')
+
+ @api.depends('seats', 'attendee_ids')
+ def _taken_seats(self):
+ for r in self:
+ if not r.seats:
+ r.taken_seats = 0.0
+ else:
+ r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml
--- a/openacademy/views/openacademy.xml
+++ b/openacademy/views/openacademy.xml
@@ -99,6 +99,7 @@
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
+ <field name="taken_seats" widget="progressbar"/>
</group>
</group>
<label for="attendee_ids"/>
@@ -116,6 +117,7 @@
<tree string="Session Tree">
<field name="name"/>
<field name="course_id"/>
+ <field name="taken_seats" widget="progressbar"/>
</tree>
</field>
</record>
@@ -0,0 +1,25 @@
# HG changeset patch
# Parent 7a7d003fe38426a405ce0657a627a139133ec4dd
# Parent 52f54b46487c8224a5aade4b921be77360ed3eae
diff --git a/openacademy/models.py b/openacademy/models.py
--- a/openacademy/models.py
+++ b/openacademy/models.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from odoo import models, fields, api
+from odoo import models, fields, api, exceptions
class Course(models.Model):
_name = 'openacademy.course'
@@ -58,3 +58,9 @@ class Session(models.Model):
'message': "Increase seats or remove excess attendees",
},
}
+
+ @api.constrains('instructor_id', 'attendee_ids')
+ def _check_instructor_not_in_attendees(self):
+ for r in self:
+ if r.instructor_id and r.instructor_id in r.attendee_ids:
+ raise exceptions.ValidationError("A session's instructor can't be an attendee")
@@ -0,0 +1,24 @@
# HG changeset patch
# Parent 121bbfe120be3007f5e04611dbc27038abafcce8
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -14,6 +14,16 @@
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")
+ _sql_constraints = [
+ ('name_description_check',
+ 'CHECK(name != description)',
+ "The title of the course should not be the description"),
+
+ ('name_unique',
+ 'UNIQUE(name)',
+ "The course title must be unique"),
+ ]
+
class Session(models.Model):
_name = 'openacademy.session'
@@ -0,0 +1,27 @@
# HG changeset patch
# Parent 7d14b75cdfd4c7a272a13572947de5d47f3e851f
# Parent f400352a70963801f0b4732d33a0183e4f6800ff
diff --git a/openacademy/models.py b/openacademy/models.py
--- a/openacademy/models.py
+++ b/openacademy/models.py
@@ -14,6 +14,19 @@ class Course(models.Model):
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")
+ def copy(self, default=None):
+ default = dict(default or {})
+
+ copied_count = self.search_count(
+ [('name', '=like', u"Copy of {}%".format(self.name))])
+ if not copied_count:
+ new_name = u"Copy of {}".format(self.name)
+ else:
+ new_name = u"Copy of {} ({})".format(self.name, copied_count)
+
+ default['name'] = new_name
+ return super(Course, self).copy(default)
+
_sql_constraints = [
('name_description_check',
'CHECK(name != description)',
@@ -0,0 +1,152 @@
# HG changeset patch
# Parent 0000000000000000000000000000000000000000
Index: addons/openacademy/__manifest__.py
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/__manifest__.py 2014-08-26 17:25:49.787783523 +0200
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "Open Academy",
+
+ 'summary': """Manage trainings""",
+
+ 'description': """
+ Open Academy module for managing trainings:
+ - training courses
+ - training sessions
+ - attendees registration
+ """,
+
+ '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/14.0/odoo/addons/base/data/ir_module_category_data.xml
+ # for the full list
+ 'category': 'Test',
+ 'version': '0.1',
+
+ # any module necessary for this one to work correctly
+ 'depends': ['base'],
+
+ # always loaded
+ 'data': [
+ # 'security/ir.model.access.csv',
+ 'templates.xml',
+ ],
+ # only loaded in demonstration mode
+ 'demo': [
+ 'demo.xml',
+ ],
+}
Index: addons/openacademy/__init__.py
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/__init__.py 2014-08-26 17:25:49.791783523 +0200
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+from . import controllers
+from . import models
Index: addons/openacademy/controllers.py
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/controllers.py 2014-08-26 17:25:49.791783523 +0200
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from odoo import http
+
+# class Openacademy(http.Controller):
+# @http.route('/openacademy/openacademy/', auth='public')
+# def index(self, **kw):
+# return "Hello, world"
+
+# @http.route('/openacademy/openacademy/objects/', auth='public')
+# def list(self, **kw):
+# return http.request.render('openacademy.listing', {
+# 'root': '/openacademy/openacademy',
+# 'objects': http.request.env['openacademy.openacademy'].search([]),
+# })
+
+# @http.route('/openacademy/openacademy/objects/<model("openacademy.openacademy"):obj>/', auth='public')
+# def object(self, obj, **kw):
+# return http.request.render('openacademy.object', {
+# 'object': obj
+# })
Index: addons/openacademy/demo.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/demo.xml 2014-08-26 17:25:49.791783523 +0200
@@ -0,0 +1,25 @@
+<odoo>
+
+ <!-- -->
+ <!-- <record id="object0" model="openacademy.openacademy"> -->
+ <!-- <field name="name">Object 0</field> -->
+ <!-- </record> -->
+ <!-- -->
+ <!-- <record id="object1" model="openacademy.openacademy"> -->
+ <!-- <field name="name">Object 1</field> -->
+ <!-- </record> -->
+ <!-- -->
+ <!-- <record id="object2" model="openacademy.openacademy"> -->
+ <!-- <field name="name">Object 2</field> -->
+ <!-- </record> -->
+ <!-- -->
+ <!-- <record id="object3" model="openacademy.openacademy"> -->
+ <!-- <field name="name">Object 3</field> -->
+ <!-- </record> -->
+ <!-- -->
+ <!-- <record id="object4" model="openacademy.openacademy"> -->
+ <!-- <field name="name">Object 4</field> -->
+ <!-- </record> -->
+ <!-- -->
+
+</odoo>
Index: addons/openacademy/models.py
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/models.py 2014-08-26 17:25:49.791783523 +0200
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+
+# class openacademy(models.Model):
+# _name = 'openacademy.openacademy'
+
+# name = fields.Char()
Index: addons/openacademy/security/ir.model.access.csv
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/security/ir.model.access.csv 2014-08-26 17:25:49.791783523 +0200
@@ -0,0 +1,2 @@
+id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
+access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0
Index: addons/openacademy/templates.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/templates.xml 2014-08-26 17:25:49.791783523 +0200
@@ -0,0 +1,22 @@
+<odoo>
+
+ <!-- <template id="listing"> -->
+ <!-- <ul> -->
+ <!-- <li t-foreach="objects" t-as="object"> -->
+ <!-- <a t-attf-href="{{ root }}/objects/{{ object.id }}"> -->
+ <!-- <t t-esc="object.display_name"/> -->
+ <!-- </a> -->
+ <!-- </li> -->
+ <!-- </ul> -->
+ <!-- </template> -->
+ <!-- <template id="object"> -->
+ <!-- <h1><t t-esc="object.display_name"/></h1> -->
+ <!-- <dl> -->
+ <!-- <t t-foreach="object._fields" t-as="field"> -->
+ <!-- <dt><t t-esc="field"/></dt> -->
+ <!-- <dd><t t-esc="object[field]"/></dd> -->
+ <!-- </t> -->
+ <!-- </dl> -->
+ <!-- </template> -->
+
+</odoo>
@@ -0,0 +1,91 @@
# HG changeset patch
# Parent 643813940cbea07bec792f9e1c60022a9292fa90
Index: addons/openacademy/__manifest__.py
===================================================================
--- addons.orig/openacademy/__manifest__.py 2014-08-26 17:26:21.535783052 +0200
+++ addons/openacademy/__manifest__.py 2014-08-26 17:26:21.531783052 +0200
@@ -21,7 +21,7 @@
'version': '0.1',
# any module necessary for this one to work correctly
- 'depends': ['base'],
+ 'depends': ['base', 'board'],
# always loaded
'data': [
@@ -30,6 +30,7 @@
'templates.xml',
'views/openacademy.xml',
'views/partner.xml',
+ 'views/session_board.xml',
'reports.xml',
],
# only loaded in demonstration mode
Index: addons/openacademy/views/session_board.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/views/session_board.xml 2014-08-26 17:26:21.531783052 +0200
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<odoo>
+
+ <record model="ir.actions.act_window" id="act_session_graph">
+ <field name="name">Attendees by course</field>
+ <field name="res_model">openacademy.session</field>
+ <field name="view_mode">graph</field>
+ <field name="view_id"
+ ref="openacademy.openacademy_session_graph_view"/>
+ </record>
+ <record model="ir.actions.act_window" id="act_session_calendar">
+ <field name="name">Sessions</field>
+ <field name="res_model">openacademy.session</field>
+ <field name="view_mode">calendar</field>
+ <field name="view_id" ref="openacademy.session_calendar_view"/>
+ </record>
+ <record model="ir.actions.act_window" id="act_course_list">
+ <field name="name">Courses</field>
+ <field name="res_model">openacademy.course</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <record model="ir.ui.view" id="board_session_form">
+ <field name="name">Session Dashboard Form</field>
+ <field name="model">board.board</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Session Dashboard">
+ <board style="2-1">
+ <column>
+ <action
+ string="Attendees by course"
+ name="%(act_session_graph)d"
+ height="150"
+ width="510"/>
+ <action
+ string="Sessions"
+ name="%(act_session_calendar)d"/>
+ </column>
+ <column>
+ <action
+ string="Courses"
+ name="%(act_course_list)d"/>
+ </column>
+ </board>
+ </form>
+ </field>
+ </record>
+ <record model="ir.actions.act_window" id="open_board_session">
+ <field name="name">Session Dashboard</field>
+ <field name="res_model">board.board</field>
+ <field name="view_mode">form</field>
+ <field name="usage">menu</field>
+ <field name="view_id" ref="board_session_form"/>
+ </record>
+
+ <menuitem
+ name="Session Dashboard" parent="base.menu_reporting_dashboard"
+ action="open_board_session"
+ sequence="1"
+ id="menu_board_session"/>
+
+</odoo>
@@ -0,0 +1,28 @@
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -20,9 +20,10 @@
_description = "OpenAcademy Sessions"
name = fields.Char(required=True)
- start_date = fields.Date()
+ start_date = fields.Date(default=fields.Date.today)
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
+ active = fields.Boolean(default=True)
instructor_id = fields.Many2one('res.partner', string="Instructor",
domain=['|', ('instructor', '=', True),
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml
+++ addons/openacademy/views/openacademy.xml
@@ -94,6 +94,7 @@
<field name="course_id"/>
<field name="name"/>
<field name="instructor_id"/>
+ <field name="active"/>
</group>
<group string="Schedule">
<field name="start_date"/>
@@ -0,0 +1,48 @@
# HG changeset patch
# Parent 84e2b0b43fc61fd0bcbb44c1929755d44ee58ae5
Index: addons/openacademy/demo.xml
===================================================================
--- addons.orig/openacademy/demo.xml 2014-08-26 17:25:52.683783480 +0200
+++ addons/openacademy/demo.xml 2014-08-26 17:25:52.679783480 +0200
@@ -1,25 +1,19 @@
<odoo>
- <!-- -->
- <!-- <record id="object0" model="openacademy.openacademy"> -->
- <!-- <field name="name">Object 0</field> -->
- <!-- </record> -->
- <!-- -->
- <!-- <record id="object1" model="openacademy.openacademy"> -->
- <!-- <field name="name">Object 1</field> -->
- <!-- </record> -->
- <!-- -->
- <!-- <record id="object2" model="openacademy.openacademy"> -->
- <!-- <field name="name">Object 2</field> -->
- <!-- </record> -->
- <!-- -->
- <!-- <record id="object3" model="openacademy.openacademy"> -->
- <!-- <field name="name">Object 3</field> -->
- <!-- </record> -->
- <!-- -->
- <!-- <record id="object4" model="openacademy.openacademy"> -->
- <!-- <field name="name">Object 4</field> -->
- <!-- </record> -->
- <!-- -->
+ <record model="openacademy.course" id="course0">
+ <field name="name">Course 0</field>
+ <field name="description">Course 0's description
+
+Can have multiple lines
+ </field>
+ </record>
+ <record model="openacademy.course" id="course1">
+ <field name="name">Course 1</field>
+ <!-- no description for this one -->
+ </record>
+ <record model="openacademy.course" id="course2">
+ <field name="name">Course 2</field>
+ <field name="description">Course 2's description</field>
+ </record>
</odoo>
@@ -0,0 +1,42 @@
# HG changeset patch
# Parent 69d1f2d359eb8ef304a9d99f17790c78b35eda1a
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -25,7 +25,8 @@
seats = fields.Integer(string="Number of seats")
instructor_id = fields.Many2one('res.partner', string="Instructor",
- domain=[('instructor', '=', True)])
+ domain=['|', ('instructor', '=', True),
+ ('category_id.name', 'ilike', "Teacher")])
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
Index: addons/openacademy/views/partner.xml
===================================================================
--- addons.orig/openacademy/views/partner.xml
+++ addons/openacademy/views/partner.xml
@@ -29,4 +29,20 @@
parent="configuration_menu"
action="contact_list_action"/>
+ <record model="ir.actions.act_window" id="contact_cat_list_action">
+ <field name="name">Contact Tags</field>
+ <field name="res_model">res.partner.category</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem id="contact_cat_menu" name="Contact Tags"
+ parent="configuration_menu"
+ action="contact_cat_list_action"/>
+
+ <record model="res.partner.category" id="teacher1">
+ <field name="name">Teacher / Level 1</field>
+ </record>
+ <record model="res.partner.category" id="teacher2">
+ <field name="name">Teacher / Level 2</field>
+ </record>
+
</odoo>
@@ -0,0 +1,17 @@
# HG changeset patch
# Parent 142c5065ff1b7266d944d4ef5239e814ae22f0df
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -24,7 +24,8 @@
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
- instructor_id = fields.Many2one('res.partner', string="Instructor")
+ instructor_id = fields.Many2one('res.partner', string="Instructor",
+ domain=[('instructor', '=', True)])
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
@@ -0,0 +1,29 @@
# HG changeset patch
# Parent 4a0db1d29257764f4df5cb1ee0be7e59e8c8d0d8
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-26 17:25:54.291783456 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-26 17:25:54.283783457 +0200
@@ -1,6 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
+ <record model="ir.ui.view" id="course_form_view">
+ <field name="name">course.form</field>
+ <field name="model">openacademy.course</field>
+ <field name="arch" type="xml">
+ <form string="Course Form">
+ <sheet>
+ <group>
+ <field name="name"/>
+ <field name="description"/>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
@@ -0,0 +1,24 @@
# HG changeset patch
# Parent 5508a5440faa7b607d057c4e4ae70af6b6f7cac9
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-26 17:25:55.023783446 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-26 17:25:55.015783446 +0200
@@ -9,8 +9,15 @@
<sheet>
<group>
<field name="name"/>
- <field name="description"/>
</group>
+ <notebook>
+ <page string="Description">
+ <field name="description"/>
+ </page>
+ <page string="About">
+ This is an example of notebooks
+ </page>
+ </notebook>
</sheet>
</form>
</field>
@@ -0,0 +1,30 @@
# HG changeset patch
# Parent dba00a105dd2a82490394b8dec5fea5f1d8847e1
# Parent f4374b6e2e661e0782e396b24c57c1eb97d13288
diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml
--- a/openacademy/views/openacademy.xml
+++ b/openacademy/views/openacademy.xml
@@ -142,10 +142,21 @@
</field>
</record>
+ <record model="ir.ui.view" id="session_gantt_view">
+ <field name="name">session.gantt</field>
+ <field name="model">openacademy.session</field>
+ <field name="arch" type="xml">
+ <gantt string="Session Gantt"
+ date_start="start_date" date_stop="end_date"
+ default_group_by='instructor_id'>
+ </gantt>
+ </field>
+ </record>
+
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
- <field name="view_mode">tree,form,calendar</field>
+ <field name="view_mode">tree,form,calendar,gantt</field>
</record>
<menuitem id="session_menu" name="Sessions"
@@ -0,0 +1,55 @@
# HG changeset patch
# Parent a6fe4d3923db1f8f5dff2c39a711a814b0a0f549
# Parent 0687e07f570f363bf5005c6337e0c565a63a6bac
diff --git a/openacademy/models.py b/openacademy/models.py
--- a/openacademy/models.py
+++ b/openacademy/models.py
@@ -60,6 +60,9 @@ class Session(models.Model):
end_date = fields.Date(string="End Date", store=True,
compute='_get_end_date', inverse='_set_end_date')
+ attendees_count = fields.Integer(
+ string="Attendees count", compute='_get_attendees_count', store=True)
+
@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
for r in self:
@@ -106,6 +109,11 @@ class Session(models.Model):
# so add one day to get 5 days instead
r.duration = (r.end_date - r.start_date).days + 1
+ @api.depends('attendee_ids')
+ def _get_attendees_count(self):
+ for r in self:
+ r.attendees_count = len(r.attendee_ids)
+
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:
diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml
--- a/openacademy/views/openacademy.xml
+++ b/openacademy/views/openacademy.xml
@@ -153,10 +153,21 @@
</field>
</record>
+ <record model="ir.ui.view" id="openacademy_session_graph_view">
+ <field name="name">openacademy.session.graph</field>
+ <field name="model">openacademy.session</field>
+ <field name="arch" type="xml">
+ <graph string="Participations by Courses">
+ <field name="course_id"/>
+ <field name="attendees_count" type="measure"/>
+ </graph>
+ </field>
+ </record>
+
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
- <field name="view_mode">tree,form,calendar,gantt</field>
+ <field name="view_mode">tree,form,calendar,gantt,graph</field>
</record>
<menuitem id="session_menu" name="Sessions"
@@ -0,0 +1,77 @@
# HG changeset patch
# Parent 8d66f7620781558d4520f97e4cebc14ed180683e
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -49,6 +49,7 @@
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
active = fields.Boolean(default=True)
+ color = fields.Integer()
instructor_id = fields.Many2one('res.partner', string="Instructor",
domain=['|', ('instructor', '=', True),
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml
+++ addons/openacademy/views/openacademy.xml
@@ -165,10 +165,56 @@
</field>
</record>
+ <record model="ir.ui.view" id="view_openacad_session_kanban">
+ <field name="name">openacademy.session.kanban</field>
+ <field name="model">openacademy.session</field>
+ <field name="arch" type="xml">
+ <kanban default_group_by="course_id">
+ <field name="color"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div
+ t-attf-class="oe_kanban_color_{{kanban_getcolor(record.color.raw_value)}}
+ oe_kanban_global_click_edit oe_semantic_html_override
+ oe_kanban_card {{record.group_fancy==1 ? 'oe_kanban_card_fancy' : ''}}">
+ <div class="oe_dropdown_kanban">
+ <!-- dropdown menu -->
+ <div class="oe_dropdown_toggle">
+ <i class="fa fa-bars fa-lg" title="Manage" aria-label="Manage"/>
+ <ul class="oe_dropdown_menu">
+ <li>
+ <a type="delete">Delete</a>
+ </li>
+ <li>
+ <ul class="oe_kanban_colorpicker"
+ data-field="color"/>
+ </li>
+ </ul>
+ </div>
+ <div class="oe_clear"></div>
+ </div>
+ <div t-attf-class="oe_kanban_content">
+ <!-- title -->
+ Session name:
+ <field name="name"/>
+ <br/>
+ Start date:
+ <field name="start_date"/>
+ <br/>
+ duration:
+ <field name="duration"/>
+ </div>
+ </div>
+ </t>
+ </templates>
+ </kanban>
+ </field>
+ </record>
+
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
- <field name="view_mode">tree,form,calendar,gantt,graph</field>
+ <field name="view_mode">tree,form,calendar,gantt,graph,kanban</field>
</record>
<menuitem id="session_menu" name="Sessions"
@@ -0,0 +1,22 @@
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -27,3 +27,4 @@
instructor_id = fields.Many2one('res.partner', string="Instructor")
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
+ attendee_ids = fields.Many2many('res.partner', string="Attendees")
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml
+++ addons/openacademy/views/openacademy.xml
@@ -101,6 +101,8 @@
<field name="seats"/>
</group>
</group>
+ <label for="attendee_ids"/>
+ <field name="attendee_ids"/>
</sheet>
</form>
</field>
@@ -0,0 +1,95 @@
# HG changeset patch
# Parent a6e217b1fbbc64111581c269629b1c25c23abb99
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -9,6 +9,9 @@
name = fields.Char(string="Title", required=True)
description = fields.Text()
+ responsible_id = fields.Many2one('res.users',
+ ondelete='set null', string="Responsible", index=True)
+
class Session(models.Model):
_name = 'openacademy.session'
@@ -18,3 +21,7 @@
start_date = fields.Date()
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
+
+ instructor_id = fields.Many2one('res.partner', string="Instructor")
+ course_id = fields.Many2one('openacademy.course',
+ ondelete='cascade', string="Course", required=True)
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml
+++ addons/openacademy/views/openacademy.xml
@@ -9,6 +9,7 @@
<sheet>
<group>
<field name="name"/>
+ <field name="responsible_id"/>
</group>
<notebook>
<page string="Description">
@@ -34,6 +35,18 @@
</field>
</record>
+ <!-- override the automatically generated list view for courses -->
+ <record model="ir.ui.view" id="course_tree_view">
+ <field name="name">course.tree</field>
+ <field name="model">openacademy.course</field>
+ <field name="arch" type="xml">
+ <tree string="Course Tree">
+ <field name="name"/>
+ <field name="responsible_id"/>
+ </tree>
+ </field>
+ </record>
+
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
@@ -72,16 +85,34 @@
<form string="Session Form">
<sheet>
<group>
- <field name="name"/>
- <field name="start_date"/>
- <field name="duration"/>
- <field name="seats"/>
+ <group string="General">
+ <field name="course_id"/>
+ <field name="name"/>
+ <field name="instructor_id"/>
+ </group>
+ <group string="Schedule">
+ <field name="start_date"/>
+ <field name="duration"/>
+ <field name="seats"/>
+ </group>
</group>
</sheet>
</form>
</field>
</record>
+ <!-- session tree/list view -->
+ <record model="ir.ui.view" id="session_tree_view">
+ <field name="name">session.tree</field>
+ <field name="model">openacademy.session</field>
+ <field name="arch" type="xml">
+ <tree string="Session Tree">
+ <field name="name"/>
+ <field name="course_id"/>
+ </tree>
+ </field>
+ </record>
+
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
@@ -0,0 +1,19 @@
# HG changeset patch
# Parent e3bb12713a6d38c28f50d46e8c1bab74ac40c1be
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -2,7 +2,9 @@
from odoo import models, fields, api
-# class openacademy(models.Model):
-# _name = 'openacademy.openacademy'
+class Course(models.Model):
+ _name = 'openacademy.course'
+ _description = "OpenAcademy Courses"
-# name = fields.Char()
+ name = fields.Char(string="Title", required=True)
+ description = fields.Text()
@@ -0,0 +1,78 @@
# HG changeset patch
# Parent d903c828fb10f2b38e5f43e9ceaeae0a9db7f858
Index: addons/openacademy/__init__.py
===================================================================
--- addons.orig/openacademy/__init__.py 2014-08-26 17:26:01.227783353 +0200
+++ addons/openacademy/__init__.py 2014-08-26 17:26:01.219783354 +0200
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
+from . import partner
Index: addons/openacademy/__manifest__.py
===================================================================
--- addons.orig/openacademy/__manifest__.py 2014-08-26 17:26:01.227783353 +0200
+++ addons/openacademy/__manifest__.py 2014-08-26 17:26:01.223783354 +0200
@@ -28,6 +28,7 @@
# 'security/ir.model.access.csv',
'templates.xml',
'views/openacademy.xml',
+ 'views/partner.xml',
],
# only loaded in demonstration mode
'demo': [
Index: addons/openacademy/partner.py
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/partner.py 2014-08-26 17:26:01.223783354 +0200
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+from odoo import fields, models
+
+class Partner(models.Model):
+ _inherit = 'res.partner'
+
+ # Add a new column to the res.partner model, by default partners are not
+ # instructors
+ instructor = fields.Boolean("Instructor", default=False)
+
+ session_ids = fields.Many2many('openacademy.session',
+ string="Attended Sessions", readonly=True)
Index: addons/openacademy/views/partner.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/views/partner.xml 2014-08-26 17:26:01.223783354 +0200
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+ <odoo>
+
+ <!-- Add instructor field to existing view -->
+ <record model="ir.ui.view" id="partner_instructor_form_view">
+ <field name="name">partner.instructor</field>
+ <field name="model">res.partner</field>
+ <field name="inherit_id" ref="base.view_partner_form"/>
+ <field name="arch" type="xml">
+ <notebook position="inside">
+ <page string="Sessions">
+ <group>
+ <field name="instructor"/>
+ <field name="session_ids"/>
+ </group>
+ </page>
+ </notebook>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="contact_list_action">
+ <field name="name">Contacts</field>
+ <field name="res_model">res.partner</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem id="configuration_menu" name="Configuration"
+ parent="main_openacademy_menu"/>
+ <menuitem id="contact_menu" name="Contacts"
+ parent="configuration_menu"
+ action="contact_list_action"/>
+
+</odoo>
@@ -0,0 +1,28 @@
# HG changeset patch
# Parent 8d5573b704b2867788dd6895503f1871c2976a29
# Parent 9eb163e5da677a0d09e01a354ba56697b576a4bc
diff --git a/openacademy/models.py b/openacademy/models.py
--- a/openacademy/models.py
+++ b/openacademy/models.py
@@ -41,3 +41,20 @@ class Session(models.Model):
r.taken_seats = 0.0
else:
r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
+
+ @api.onchange('seats', 'attendee_ids')
+ def _verify_valid_seats(self):
+ if self.seats < 0:
+ return {
+ 'warning': {
+ 'title': "Incorrect 'seats' value",
+ 'message': "The number of available seats may not be negative",
+ },
+ }
+ if self.seats < len(self.attendee_ids):
+ return {
+ 'warning': {
+ 'title': "Too many attendees",
+ 'message': "Increase seats or remove excess attendees",
+ },
+ }
@@ -0,0 +1,36 @@
# HG changeset patch
# Parent cb05882d4fe73e97b9d34a69190ced14d1a50c24
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -11,6 +11,8 @@
responsible_id = fields.Many2one('res.users',
ondelete='set null', string="Responsible", index=True)
+ session_ids = fields.One2many(
+ 'openacademy.session', 'course_id', string="Sessions")
class Session(models.Model):
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml
+++ addons/openacademy/views/openacademy.xml
@@ -15,8 +15,13 @@
<page string="Description">
<field name="description"/>
</page>
- <page string="About">
- This is an example of notebooks
+ <page string="Sessions">
+ <field name="session_ids">
+ <tree string="Registered sessions">
+ <field name="name"/>
+ <field name="instructor_id"/>
+ </tree>
+ </field>
</page>
</notebook>
</sheet>
@@ -0,0 +1,52 @@
# HG changeset patch
# Parent c140f0a861a08881d8737bca0ffb83904a2059a3
Index: addons/openacademy/__manifest__.py
===================================================================
--- addons.orig/openacademy/__manifest__.py 2014-08-29 08:39:43.975536806 +0200
+++ addons/openacademy/__manifest__.py 2014-08-29 08:39:52.000000000 +0200
@@ -30,6 +30,7 @@
'templates.xml',
'views/openacademy.xml',
'views/partner.xml',
+ 'reports.xml',
],
# only loaded in demonstration mode
'demo': [
Index: foo/openacademy/reports.xml
===================================================================
--- /dev/null
+++ foo/openacademy/reports.xml
@@ -0,0 +1,32 @@
+<odoo>
+
+ <record id="report_session" model="ir.actions.report">
+ <field name="name">Session Report</field>
+ <field name="model">openacademy.session</field>
+ <field name="report_type">qweb-pdf</field>
+ <field name="report_name">openacademy.report_session_view</field>
+ <field name="report_file">openacademy.report_session</field>
+ <field name="binding_model_id" ref="model_openacademy_session"/>
+ <field name="binding_type">report</field>
+ </record>
+
+ <template id="report_session_view">
+ <t t-call="web.html_container">
+ <t t-foreach="docs" t-as="doc">
+ <t t-call="web.external_layout">
+ <div class="page">
+ <h2 t-field="doc.name"/>
+ <p>From <span t-field="doc.start_date"/> to <span t-field="doc.end_date"/></p>
+ <h3>Attendees:</h3>
+ <ul>
+ <t t-foreach="doc.attendee_ids" t-as="attendee">
+ <li><span t-field="attendee.name"/></li>
+ </t>
+ </ul>
+ </div>
+ </t>
+ </t>
+ </t>
+ </template>
+
+</odoo>
@@ -0,0 +1,28 @@
# HG changeset patch
# Parent 93a45ab8dd0a76c131cb5eeca6e44b71dca9f100
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:01:45.299033618 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-28 14:18:58.847018275 +0200
@@ -36,6 +36,12 @@
<search>
<field name="name"/>
<field name="description"/>
+ <filter name="my_courses" string="My Courses"
+ domain="[('responsible_id', '=', uid)]"/>
+ <group string="Group By">
+ <filter name="by_responsible" string="Responsible"
+ context="{'group_by': 'responsible_id'}"/>
+ </group>
</search>
</field>
</record>
@@ -61,6 +67,7 @@
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_mode">tree,form</field>
+ <field name="context" eval="{'search_default_my_courses': 1}"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">Create the first course
</p>
@@ -0,0 +1,24 @@
# HG changeset patch
# Parent b9bfc8929e0ffc3eb153641e14952fe5d99eb908
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-26 17:25:55.807783434 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-26 17:25:55.799783434 +0200
@@ -23,6 +23,17 @@
</field>
</record>
+ <record model="ir.ui.view" id="course_search_view">
+ <field name="name">course.search</field>
+ <field name="model">openacademy.course</field>
+ <field name="arch" type="xml">
+ <search>
+ <field name="name"/>
+ <field name="description"/>
+ </search>
+ </field>
+ </record>
+
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
@@ -0,0 +1,57 @@
# HG changeset patch
# Parent 22f8d180a7f9ad209d7e98cf7d1bd0fee1f05350
Index: addons/openacademy/models.py
===================================================================
--- addons.orig/openacademy/models.py
+++ addons/openacademy/models.py
@@ -8,3 +8,13 @@
name = fields.Char(string="Title", required=True)
description = fields.Text()
+
+
+class Session(models.Model):
+ _name = 'openacademy.session'
+ _description = "OpenAcademy Sessions"
+
+ name = fields.Char(required=True)
+ start_date = fields.Date()
+ duration = fields.Float(digits=(6, 2), help="Duration in days")
+ seats = fields.Integer(string="Number of seats")
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml
+++ addons/openacademy/views/openacademy.xml
@@ -64,4 +64,32 @@
action="openacademy.course_list_action"
It is not required when it is the same module -->
+ <!-- session form view -->
+ <record model="ir.ui.view" id="session_form_view">
+ <field name="name">session.form</field>
+ <field name="model">openacademy.session</field>
+ <field name="arch" type="xml">
+ <form string="Session Form">
+ <sheet>
+ <group>
+ <field name="name"/>
+ <field name="start_date"/>
+ <field name="duration"/>
+ <field name="seats"/>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="session_list_action">
+ <field name="name">Sessions</field>
+ <field name="res_model">openacademy.session</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem id="session_menu" name="Sessions"
+ parent="openacademy_menu"
+ action="session_list_action"/>
+
</odoo>
@@ -0,0 +1,56 @@
# HG changeset patch
# Parent 7c95aad3b60e4c2006c5f706bd157e8e05318bfa
diff --git a/openacademy/models.py b/openacademy/models.py
--- a/openacademy/models.py
+++ b/openacademy/models.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from datetime import timedelta
-from odoo import models, fields, api, exceptions
+from odoo import models, fields, api, exceptions, _
class Course(models.Model):
_name = 'openacademy.course'
@@ -20,11 +20,11 @@ class Course(models.Model):
default = dict(default or {})
copied_count = self.search_count(
- [('name', '=like', u"Copy of {}%".format(self.name))])
+ [('name', '=like', _(u"Copy of {}%").format(self.name))])
if not copied_count:
- new_name = u"Copy of {}".format(self.name)
+ new_name = _(u"Copy of {}").format(self.name)
else:
- new_name = u"Copy of {} ({})".format(self.name, copied_count)
+ new_name = _(u"Copy of {} ({})").format(self.name, copied_count)
default['name'] = new_name
return super(Course, self).copy(default)
@@ -81,15 +81,15 @@ class Session(models.Model):
if self.seats < 0:
return {
'warning': {
- 'title': "Incorrect 'seats' value",
- 'message': "The number of available seats may not be negative",
+ 'title': _("Incorrect 'seats' value"),
+ 'message': _("The number of available seats may not be negative"),
},
}
if self.seats < len(self.attendee_ids):
return {
'warning': {
- 'title': "Too many attendees",
- 'message': "Increase seats or remove excess attendees",
+ 'title': _("Too many attendees"),
+ 'message': _("Increase seats or remove excess attendees"),
},
}
@@ -114,4 +114,4 @@ class Session(models.Model):
def _check_instructor_not_in_attendees(self):
for r in self:
if r.instructor_id and r.instructor_id in r.attendee_ids:
- raise exceptions.ValidationError("A session's instructor can't be an attendee")
+ raise exceptions.ValidationError(_("A session's instructor can't be an attendee"))
@@ -0,0 +1,36 @@
Index: addons/openacademy/__init__.py
===================================================================
--- addons.orig/openacademy/__init__.py
+++ addons/openacademy/__init__.py
@@ -2,3 +2,4 @@
from . import controllers
from . import models
from . import partner
+from . import wizard
Index: addons/openacademy/wizard.py
===================================================================
--- /dev/null
+++ addons/openacademy/wizard.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+
+class Wizard(models.TransientModel):
+ _name = 'openacademy.wizard'
+ _description = "Wizard: Quick Registration of Attendees to Sessions"
+
+ session_id = fields.Many2one('openacademy.session',
+ string="Session", required=True)
+ attendee_ids = fields.Many2many('res.partner', string="Attendees")
Index: foo/openacademy/security/ir.model.access.csv
===================================================================
--- foo.orig/openacademy/security/ir.model.access.csv
+++ foo/openacademy/security/ir.model.access.csv
@@ -1,5 +1,6 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
course_manager,course manager,model_openacademy_course,group_manager,1,1,1,1
session_manager,session manager,model_openacademy_session,group_manager,1,1,1,1
+wizard_manager,wizard session manager,model_openacademy_wizard,group_manager,1,1,1,1
course_read_all,course all,model_openacademy_course,,1,0,0,0
session_read_all,session all,model_openacademy_session,,1,0,0,0
@@ -0,0 +1,29 @@
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml
+++ addons/openacademy/views/openacademy.xml
@@ -232,6 +232,12 @@
<field name="session_id"/>
<field name="attendee_ids"/>
</group>
+ <footer>
+ <button name="subscribe" type="object"
+ string="Subscribe" class="oe_highlight"/>
+ or
+ <button special="cancel" string="Cancel"/>
+ </footer>
</form>
</field>
</record>
Index: addons/openacademy/wizard.py
===================================================================
--- addons.orig/openacademy/wizard.py
+++ addons/openacademy/wizard.py
@@ -12,3 +12,7 @@
session_id = fields.Many2one('openacademy.session',
string="Session", required=True, default=_default_session)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
+
+ def subscribe(self):
+ self.session_id.attendee_ids |= self.attendee_ids
+ return {}
@@ -0,0 +1,45 @@
Index: addons/openacademy/wizard.py
===================================================================
--- addons.orig/openacademy/wizard.py
+++ addons/openacademy/wizard.py
@@ -6,6 +6,9 @@ class Wizard(models.TransientModel):
_name = 'openacademy.wizard'
_description = "Wizard: Quick Registration of Attendees to Sessions"
+ def _default_session(self):
+ return self.env['openacademy.session'].browse(self._context.get('active_id'))
+
session_id = fields.Many2one('openacademy.session',
- string="Session", required=True)
+ string="Session", required=True, default=_default_session)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
Index: foo/openacademy/views/openacademy.xml
===================================================================
--- foo.orig/openacademy/views/openacademy.xml
+++ foo/openacademy/views/openacademy.xml
@@ -220,4 +220,25 @@
parent="openacademy_menu"
action="session_list_action"/>
+ <record model="ir.ui.view" id="wizard_form_view">
+ <field name="name">wizard.form</field>
+ <field name="model">openacademy.wizard</field>
+ <field name="arch" type="xml">
+ <form string="Add Attendees">
+ <group>
+ <field name="session_id"/>
+ <field name="attendee_ids"/>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <record id="launch_session_wizard" model="ir.actions.act_window">
+ <field name="name">Add Attendees</field>
+ <field name="res_model">openacademy.wizard</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ <field name="binding_model_id" ref="model_openacademy_session"/>
+ </record>
+
</odoo>
@@ -0,0 +1,37 @@
Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml
+++ addons/openacademy/views/openacademy.xml
@@ -229,7 +229,7 @@
<field name="arch" type="xml">
<form string="Add Attendees">
<group>
- <field name="session_id"/>
+ <field name="session_ids"/>
<field name="attendee_ids"/>
</group>
<footer>
Index: addons/openacademy/wizard.py
===================================================================
--- addons.orig/openacademy/wizard.py
+++ addons/openacademy/wizard.py
@@ -6,13 +6,14 @@ class Wizard(models.TransientModel):
_name = 'openacademy.wizard'
_description = "Wizard: Quick Registration of Attendees to Sessions"
- def _default_session(self):
- return self.env['openacademy.session'].browse(self._context.get('active_id'))
+ def _default_sessions(self):
+ return self.env['openacademy.session'].browse(self._context.get('active_ids'))
- session_id = fields.Many2one('openacademy.session',
- string="Session", required=True, default=_default_session)
+ session_ids = fields.Many2many('openacademy.session',
+ string="Sessions", required=True, default=_default_sessions)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
def subscribe(self):
- self.session_id.attendee_ids |= self.attendee_ids
+ for session in self.session_ids:
+ session.attendee_ids |= self.attendee_ids
return {}
+35
View File
@@ -0,0 +1,35 @@
exercise-creation
exercise-model
exercise-demo
exercise-basic-action
exercise-formview
exercise-formview-notebooks
exercise-searchview-basic
exercise-session
exercise-many2one
exercise-one2many
exercise-many2many
exercise-model-inheritance
exercise-domain-basic
exercise-domain-advanced
exercise-computed
exercise-defaults
exercise-onchange
exercise-constraint-python
exercise-constraint-sql
exercise-copy-override
exercise-advanced-treeview
exercise-calendar
exercise-searchview
exercise-gantt
exercise-graph
exercise-kanban
exercise-access-rights
exercise-access-rules
exercise-wizard
exercise-wizard-launch
exercise-wizard-action
exercise-wizard-multi
exercise-translations
exercise-report
exercise-dashboard
+229
View File
@@ -0,0 +1,229 @@
.. _reference/howtos/company:
========================
Multi-company Guidelines
========================
.. warning::
This tutorial requires good knowledge of Odoo.
Please refer to the :ref:`basic tutorial <howto/base>` first if needed.
As of version 13.0, a user can be logged in multiple companies at once. This allows the user to
access information from multiple companies but also to create/edit records in a multi-company
environment.
If not handled correctly, it may be the source of a lot of inconsistent multi-company behaviors.
For instance, a user logged in both companies A and B could create a sales order in company A and
add products belonging to company B to it. It is only when the user will log out from company B that
access errors will occur for the sales order.
To correctly manage multi-company behaviors, Odoo's ORM provides multiple features:
- :ref:`Company-dependent fields <howto/company/company_dependent>`
- :ref:`Multi-company consistency <howto/company/check_company>`
- :ref:`Default company <howto/company/default_company>`
- :ref:`Views <howto/company/views>`
- :ref:`Security rules <howto/company/security>`
.. _howto/company/company_dependent:
Company-dependent fields
------------------------
When a record is available from multiple companies, we must expect that different values will be
assigned to a given field depending on the company from which the value is set.
For the field of a same record to support several values, it must be defined with the attribute
`company_dependent` set to `True`.
.. code-block:: python
from odoo import api, fields, models
class Record(models.Model):
_name = 'record.public'
info = fields.Text()
company_info = fields.Text(company_dependent=True)
display_info = fields.Text(string='Infos', compute='_compute_display_info')
@api.depends_context('company')
def _compute_display_info(self):
for record in self:
record.display_info = record.info + record.company_info
.. note:: The `_compute_display_info` method is decorated with `depends_context('company')`
(see :attr:`~odoo.api.depends_context`) to ensure that the computed field is recomputed
depending on the current company (`self.env.company`).
When a company-dependent field is read, the current company is used to retrieve its value. In other
words, if a user is logged in companies A and B with A as main company and creates a record for
company B, the values of company-dependent fields will be that of company A.
To read the values of company-dependent fields set from another company than the current one, we need
to ensure the company we are using is the correct one. This can be done with :meth:`~odoo.models.Model.with_company`,
which updates the current company.
.. code-block:: python
# Accessed as main company (self.env.company)
val = record.company_dependent_field
# Accessed as desired company (company_B)
val = record.with_company(company_B).company_dependent_field
# record.with_company(company_B).env.company == company_B
.. warning::
Whenever you are computing/creating/... things that may behave differently
in different companies, you should make sure whatever you are doing is done
in the right company. It doesn't cost much to always use `with_company` to
avoid problems later on.
.. code-block:: python
@api.onchange('field_name')
def _onchange_field_name(self):
self = self.with_company(self.company_id)
...
@api.depends('field_2')
def _compute_field_3(self):
for record in self:
record = record.with_company(record.company_id)
...
.. _howto/company/check_company:
Multi-company consistency
-------------------------
When a record is made shareable between several companies by the mean of a `company_id` field, we
must take care that it cannot be linked to the record of another company through a relational field.
For instance, we do not want to have a sales order and its invoice belonging to different companies.
To ensure this multi-company consistency, you must:
* Set the class attribute `_check_company_auto` to `True`.
* Define relational fields with the attribute `check_company` set to `True` if their model has a
`company_id` field.
On each :meth:`~odoo.models.Model.create` and :meth:`~odoo.models.Model.write`, automatic checks
will be triggered to ensure the multi-company consistency of the record.
.. code-block:: python
from odoo import fields, models
class Record(models.Model):
_name = 'record.shareable'
_check_company_auto = True
company_id = fields.Many2one('res.company')
other_record_id = fields.Many2one('other.record', check_company=True)
.. note:: The field `company_id` must not be defined with `check_company=True`.
.. currentmodule:: odoo.models
.. automethod:: Model._check_company
.. warning:: The `check_company` feature performs a strict check ! It means that if a record has no
`company_id` (i.e. the field is not required), it cannot be linked to a record whose
`company_id` is set.
.. note::
When no domain is defined on the field and `check_company` is set to `True`, a default domain is
added: `['|', '('company_id', '=', False), ('company_id', '=', company_id)]`
.. _howto/company/default_company:
Default company
---------------
When the field `company_id` is made required on a model, a good practice is to set a default
company. It eases the setup flow for the user or even guarantees its validity when the company is
hidden from the view. Indeed, the company is usually hidden if the user does not have access to
multiple companies (i.e. when the user does not have the group `base.group_multi_company`).
.. code-block:: python
from odoo import api, fields, models
class Record(models.Model):
_name = 'record.restricted'
_check_company_auto = True
company_id = fields.Many2one(
'res.company', required=True, default=lambda self: self.env.company
)
other_record_id = fields.Many2one('other.record', check_company=True)
.. _howto/company/views:
Views
-----
As stated in :ref:`above <howto/company/default_company>`, the company is usually hidden
from the view if the user does not have access to multiple companies. This is tested with the group
`base.group_multi_company`.
.. code-block:: xml
<record model="ir.ui.view" id="record_form_view">
<field name="name">record.restricted.form</field>
<field name="model">record.restricted</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="company_id" groups="base.group_multi_company"/>
<field name="other_record_id"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
.. _howto/company/security:
Security rules
--------------
When working with records shared across companies or restricted to a single company, we must take
care that a user does not have access to records belonging to other companies.
This is achieved with security rules based on `company_ids`, which contains the current companies of
the user (the companies the user checked in the multi-company widget).
.. code-block:: xml
<!-- Shareable Records -->
<record model="ir.rule" id="record_shared_company_rule">
<field name="name">Shared Record: multi-company</field>
<field name="model_id" ref="model_record_shared"/>
<field name="global" eval="True"/>
<field name="domain_force">
['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]
</field>
</record>
.. code-block:: xml
<!-- Company-restricted Records -->
<record model="ir.rule" id="record_restricted_company_rule">
<field name="name">Restricted Record: multi-company</field>
<field name="model_id" ref="model_record_restricted"/>
<field name="global" eval="True"/>
<field name="domain_force">
[('company_id', 'in', company_ids)]
</field>
</record>
.. todo:: check_company on company_dependent fields.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 110 KiB

+131
View File
@@ -0,0 +1,131 @@
===================
Profiling Odoo code
===================
.. warning::
This tutorial requires :ref:`having installed Odoo <setup/install>`
and :doc:`writing Odoo code <backend>`
Graph a method
==============
Odoo embeds a profiler of code. This embedded profiler output can be used to
generate a graph of calls triggered by the method, number of queries, percentage
of time taken in the method itself as well as the time that the method took and
its sub-called methods.
.. code:: python
from odoo.tools.misc import profile
[...]
@profile('/temp/prof.profile')
def mymethod(...)
This produces a file called /temp/prof.profile
A tool called *gprof2dot* will produce a graph with this result:
.. code:: bash
gprof2dot -f pstats -o /temp/prof.xdot /temp/prof.profile
A tool called *xdot* will display the resulting graph:
.. code:: bash
xdot /temp/prof.xdot
Log a method
============
Another profiler can be used to log statistics on a method:
.. code:: python
from odoo.tools.profiler import profile
[...]
@profile
@api.model
def mymethod(...):
The statistics will be displayed into the logs once the method to be analysed is
completely reviewed.
.. code:: bash
2018-03-28 06:18:23,196 22878 INFO openerp odoo.tools.profiler:
calls queries ms
project.task ------------------------ /home/odoo/src/odoo/addons/project/models/project.py, 638
1 0 0.02 @profile
@api.model
def create(self, vals):
# context: no_log, because subtype already handle this
1 0 0.01 context = dict(self.env.context, mail_create_nolog=True)
# for default stage
1 0 0.01 if vals.get('project_id') and not context.get('default_project_id'):
context['default_project_id'] = vals.get('project_id')
# user_id change: update date_assign
1 0 0.01 if vals.get('user_id'):
vals['date_assign'] = fields.Datetime.now()
# Stage change: Update date_end if folded stage
1 0 0.0 if vals.get('stage_id'):
vals.update(self.update_date_end(vals['stage_id']))
1 108 631.8 task = super(Task, self.with_context(context)).create(vals)
1 0 0.01 return task
Total:
1 108 631.85
Dump stack
==========
Sending the SIGQUIT signal to an Odoo process (only available on POSIX) makes
this process output the current stack trace to log, with info level. When an
odoo process seems stucked, sending this signal to the process permit to know
what the process is doing, and letting the process continue his job.
Tracing code execution
======================
Instead of sending the SIGQUIT signal to an Odoo process often enough, to check
where the processes are performing worse than expected, we can use the `py-spy`_ tool to
do it for us.
Install py-spy
--------------
.. code:: bash
python3 -m pip install py-spy
Record executed code
--------------------
As py-spy is installed, we now record the executed code lines.
This tool will record, multiple times a second, the stacktrace of the process.
.. code:: bash
# record to raw file
py-spy record -o profile.json -f speedscope --pid <PID>
# OR record directly to svg
py-spy record -o profile.svg --pid <PID>
where <PID> is the process ID of the odoo process you want to graph.
To open profile.json you can use online tool `speedscope.app`_.
To open profile.svg you should use browser, because other viewer may not
support interactive part.
.. image:: profile/flamegraph.svg
.. _py-spy: https://github.com/benfred/py-spy
.. _speedscope.app: https://www.speedscope.app/
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

+512
View File
@@ -0,0 +1,512 @@
.. queue:: website/series
==================
Building a Website
==================
.. warning::
* This guide assumes `basic knowledge of Python
<http://docs.python.org/2/tutorial/>`_
* This guide assumes :ref:`an installed Odoo <setup/install>`
Creating a basic module
=======================
In Odoo, tasks are performed by creating modules.
Modules customize the behavior of an Odoo installation, either by adding new
behaviors or by altering existing ones (including behaviors added by other
modules).
:ref:`Odoo's scaffolding <reference/cmdline/scaffold>` can setup a basic
module. To quickly get started simply invoke:
.. code-block:: console
$ ./odoo-bin scaffold Academy my-modules
This will automatically create a ``my-modules`` *module directory* with an
``academy`` module inside. The directory can be an existing module directory
if you want, but the module name must be unique within the directory.
.. patch::
:hidden:
A demonstration module
======================
We have a "complete" module ready for installation.
Although it does absolutely nothing we can install it:
* start the Odoo server
.. code-block:: console
$ ./odoo-bin --addons-path addons,my-modules
* go to http://localhost:8069
* create a new database including demonstration data
* to go :menuselection:`Settings --> Modules --> Modules`
* in the top-right corner remove the *Installed* filter and search for
*academy*
* click the :guilabel:`Install` button for the *Academy* module
To the browser
==============
:ref:`Controllers <reference/controllers>` interpret browser requests and
send data back.
Add a simple controller and ensure it is imported by ``__init__.py`` (so
Odoo can find it):
.. patch::
Shut down your server (:kbd:`^C`) then restart it:
.. code-block:: console
$ ./odoo-bin --addons-path addons,my-modules
and open a page to http://localhost:8069/academy/academy/, you should see your
"page" appear:
.. figure:: website/helloworld.png
Templates
=========
Generating HTML in Python isn't very pleasant.
The usual solution is templates_, pseudo-documents with placeholders and
display logic. Odoo allows any Python templating system, but provides its
own :ref:`QWeb <reference/qweb>` templating system which integrates with other
features.
Create a template and ensure the template file is registered in the
``__manifest__.py`` manifest, and alter the controller to use our template:
.. patch::
The templates iterates (``t-foreach``) on all the teachers (passed through the
*template context*), and prints each teacher in its own paragraph.
Finally restart Odoo and update the module's data (to install the template)
by going to :menuselection:`Settings --> Modules --> Modules -->
Academy` and clicking :guilabel:`Upgrade`.
.. tip::
Alternatively, Odoo can be restarted :option:`and update modules at
the same time<odoo-bin -u>`:
.. code-block:: console
$ odoo-bin --addons-path addons,my-modules -d academy -u academy
Going to http://localhost:8069/academy/academy/ should now result in:
.. image:: website/basic-list.png
Storing data in Odoo
====================
:ref:`Odoo models <reference/orm/model>` map to database tables.
In the previous section we just displayed a list of string entered statically
in the Python code. This doesn't allow modifications or persistent storage
so we'll now move our data to the database.
Defining the data model
-----------------------
Define a teacher model, and ensure it is imported from ``__init__.py`` so it
is correctly loaded:
.. patch::
Then setup :ref:`basic access control <reference/security/acl>` for the model
and add them to the manifest:
.. patch::
this simply gives read access (``perm_read``) to all users (``group_id:id``
left empty).
.. note::
:ref:`Data files <reference/data>` (XML or CSV) must be added to the
module manifest, Python files (models or controllers) don't but have to
be imported from ``__init__.py`` (directly or indirectly)
.. warning::
the administrator user bypasses access control, they have access to all
models even if not given access
Demonstration data
------------------
The second step is to add some demonstration data to the system so it's
possible to test it easily. This is done by adding a ``demo``
:ref:`data file <reference/data>`, which must be linked from the manifest:
.. patch::
.. tip::
:ref:`Data files <reference/data>` can be used for demo and non-demo data.
Demo data are only loaded in "demonstration mode" and can be used for flow
testing and demonstration, non-demo data are always loaded and used as
initial system setup.
In this case we're using demonstration data because an actual user of the
system would want to input or import their own teachers list, this list
is only useful for testing.
Accessing the data
------------------
The last step is to alter model and template to use our demonstration data:
#. fetch the records from the database instead of having a static list
#. Because :meth:`~odoo.models.Model.search` returns a set of records
matching the filter ("all records" here), alter the template to print each
teacher's ``name``
.. patch::
Restart the server and update the module (in order to update the manifest
and templates and load the demo file) then navigate to
http://localhost:8069/academy/academy/. The page should look slightly
different: names should simply be prefixed by a number (the database
identifier for the teacher).
Website support
===============
Odoo bundles a module dedicated to building websites.
So far we've used controllers fairly directly, but Odoo 8 added deeper
integration and a few other services (e.g. default styling, theming) via the
``website`` module.
#. first, add ``website`` as a dependency to ``academy``
#. then add the ``website=True`` flag on the controller, this sets up a few
new variables on :ref:`the request object <reference/http/request>` and
allows using the website layout in our template
#. use the website layout in the template
.. patch::
After restarting the server while updating the module (in order to update the
manifest and template) access http://localhost:8069/academy/academy/ should
yield a nicer looking page with branding and a number of built-in page
elements (top-level menu, footer, …)
.. image:: website/layout.png
The website layout also provides support for editing tools: click
:guilabel:`Sign In` (in the top-right), fill the credentials in (``admin`` /
``admin`` by default) then click :guilabel:`Log In`.
You're now in Odoo "proper": the administrative interface. For now click on
the :guilabel:`Website` menu item (top-left corner.
We're back in the website but as an administrator, with access to advanced
editing features provided by the *website* support:
* a template code editor (:menuselection:`Customize --> HTML Editor`) where
you can see and edit all templates used for the current page
* the :guilabel:`Edit` button in the top-left switches to "editing mode" where
blocks (snippets) and rich text editing are available
* a number of other features such as mobile preview or :abbr:`SEO (Search
Engine Optimization)`
URLs and routing
================
Controller methods are associated with *routes* via the
:func:`~odoo.http.route` decorator which takes a routing string and a
number of attributes to customise its behavior or security.
We've seen a "literal" routing string, which matches a URL section exactly,
but routing strings can also use `converter patterns`_ which match bits
of URLs and make those available as local variables. For instance we can
create a new controller method which takes a bit of URL and prints it out:
.. patch::
restart Odoo, access http://localhost:8069/academy/Alice/ and
http://localhost:8069/academy/Bob/ and see the difference.
As the name indicates, `converter patterns`_ don't just do extraction, they
also do *validation* and *conversion*, so we can change the new controller
to only accept integers:
.. patch::
Restart Odoo, access http://localhost:8069/academy/2, note how the old value
was a string, but the new one was converted to an integers. Try accessing
http://localhost:8069/academy/Carol/ and note that the page was not found:
since "Carol" is not an integer, the route was ignored and no route could be
found.
Odoo provides an additional converter called ``model`` which provides records
directly when given their id. Let's use this to create a generic page for
teacher biographies:
.. patch::
then change the list of model to link to our new controller:
.. patch::
Restart Odoo and upgrade the module, then you can visit each teacher's page.
As an exercise, try adding blocks to a teacher's page to write a biography,
then go to another teacher's page and so forth. You will discover, that your
biography is shared between all teachers, because blocks are added to the
*template*, and the *biography* template is shared between all teachers, when
one page is edited they're all edited at the same time.
Field editing
=============
Data which is specific to a record should be saved on that record, so let us
add a new biography field to our teachers:
.. patch::
Restart Odoo and update the views, reload the teacher's page and… the field
is invisible since it contains nothing.
.. todo:: the view has been set to noupdate because modified previously,
force via ``-i`` or do something else?
For record fields, templates can use a special ``t-field`` directive which
allows editing the field content from the website using field-specific
interfaces. Change the *person* template to use ``t-field``:
.. patch::
Restart Odoo and upgrade the module, there is now a placeholder under the
teacher's name and a new zone for blocks in :guilabel:`Edit` mode. Content
dropped there is stored in the corresponding teacher's ``biography`` field, and
thus specific to that teacher.
The teacher's name is also editable, and when saved the change is visible on
the index page.
``t-field`` can also take formatting options which depend on the exact field.
For instance if we display the modification date for a teacher's record:
.. patch::
it is displayed in a very "computery" manner and hard to read, but we could
ask for a human-readable version:
.. patch::
or a relative display:
.. patch::
Administration and ERP integration
==================================
A brief and incomplete introduction to the Odoo administration
--------------------------------------------------------------
The Odoo administration was briefly seen during the `website support`_ section.
We can go back to it using :menuselection:`Administrator --> Administrator` in
the menu (or :guilabel:`Sign In` if you're signed out).
The conceptual structure of the Odoo backend is simple:
#. first are menus, a tree (menus can have sub-menus) of records. Menus
without children map to…
#. actions. Actions have various types: links, reports, code which Odoo should
execute or data display. Data display actions are called *window actions*,
and tell Odoo to display a given *model* according to a set of views…
#. a view has a type, a broad category to which it corresponds (a list,
a graph, a calendar) and an *architecture* which customises the way the
model is displayed inside the view.
Editing in the Odoo administration
----------------------------------
By default, an Odoo model is essentially invisible to a user. To make it
visible it must be available through an action, which itself needs to be
reachable, generally through a menu.
Let's create a menu for our model:
.. patch::
then accessing http://localhost:8069/web/ in the top left should be a menu
:guilabel:`Academy`, which is selected by default, as it is the first menu,
and having opened a listing of teachers. From the listing it is possible to
:guilabel:`Create` new teacher records, and to switch to the "form" by-record
view.
If there is no definition of how to present records (a
:ref:`view <reference/views>`) Odoo will automatically create a basic one
on-the-fly. In our case it works for the "list" view for now (only displays
the teacher's name) but in the "form" view the HTML ``biography`` field is
displayed side-by-side with the ``name`` field and not given enough space.
Let's define a custom form view to make viewing and editing teacher records
a better experience:
.. patch::
Relations between models
------------------------
We have seen a pair of "basic" fields stored directly in the record. There are
:ref:`a number of basic fields <reference/fields/basic>`. The second
broad categories of fields are :ref:`relational
<reference/fields/relational>` and used to link records to one another
(within a model or across models).
For demonstration, let's create a *courses* model. Each course should have a
``teacher`` field, linking to a single teacher record, but each teacher can
teach many courses:
.. patch::
let's also add views so we can see and edit a course's teacher:
.. patch::
It should also be possible to create new courses directly from a teacher's
page, or to see all the courses they teach, so add
:class:`the inverse relationship <odoo.fields.One2many>` to the *teachers*
model:
.. patch::
Discussions and notifications
-----------------------------
Odoo provides technical models, which don't directly fulfill business needs
but which add capabilities to business objects without having to build
them by hand.
One of these is the *Chatter* system, part of Odoo's email and messaging
system, which can add notifications and discussion threads to any model.
The model simply has to :attr:`~odoo.models.Model._inherit`
``mail.thread``, and add the ``message_ids`` field to its form view to display
the discussion thread. Discussion threads are per-record.
For our academy, it makes sense to allow discussing courses to handle e.g.
scheduling changes or discussions between teachers and assistants:
.. patch::
At the bottom of each course form, there is now a discussion thread and the
possibility for users of the system to leave messages and follow or unfollow
discussions linked to specific courses.
Selling courses
---------------
Odoo also provides business models which allow using or opting in business
needs more directly. For instance the ``website_sale`` module sets up an
e-commerce site based on the products in the Odoo system. We can easily make
course subscriptions sellable by making our courses specific kinds of
products.
Rather than the previous classical inheritance, this means replacing our
*course* model by the *product* model, and extending products in-place (to
add anything we need to it).
First of all we need to add a dependency on ``website_sale`` so we get both
products (via ``sale``) and the ecommerce interface:
.. patch::
restart Odoo, update your module, there is now a :guilabel:`Shop` section in
the website, listing a number of pre-filled (via demonstration data) products.
The second step is to replace the *courses* model by ``product.template``,
and add a new category of product for courses:
.. patch::
With this installed, a few courses are now available in the :guilabel:`Shop`,
though they may have to be looked for.
.. note::
* to extend a model in-place, it's :attr:`inherited
<odoo.models.Model._inherit>` without giving it a new
:attr:`~odoo.models.Model._name`
* ``product.template`` already uses the discussions system, so we can
remove it from our extension model
* we're creating our courses as *published* by default so they can be
seen without having to log in
Altering existing views
-----------------------
So far, we have briefly seen:
* the creation of new models
* the creation of new views
* the creation of new records
* the alteration of existing models
We're left with the alteration of existing records and the alteration of
existing views. We'll do both on the :guilabel:`Shop` pages.
View alteration is done by creating *extension* views, which are applied on
top of the original view and alter it. These alteration views can be added or
removed without modifying the original, making it easier to try things out and
roll changes back.
Since our courses are free, there is no reason to display their price on the
shop page, so we're going to alter the view and hide the price if it's 0. The
first task is finding out which view displays the price, this can be done via
:menuselection:`Customize --> HTML Editor` which lets us read the various
templates involved in rendering a page. Going through a few of them, "Product
item" looks a likely culprit.
Altering view architectures is done in 3 steps:
#. Create a new view
#. Extend the view to modify by setting the new view's ``inherit_id`` to the
modified view's external id
#. In the architecture, use the ``xpath`` tag to select and alter elements
from the modified view
.. patch::
The second thing we will change is making the product categories sidebar
visible by default: :menuselection:`Customize --> Product Categories` lets
you toggle a tree of product categories (used to filter the main display) on
and off.
This is done via the ``customize_show`` and ``active`` fields of extension
templates: an extension template (such as the one we've just created) can be
*customize_show=True*. This choice will display the view in the :guilabel:`Customize`
menu with a check box, allowing administrators to activate or disable them
(and easily customize their website pages).
We simply need to modify the *Product Categories* record and set its default
to *active="True"*:
.. patch::
With this, the *Product Categories* sidebar will automatically be enabled when
the *Academy* module is installed.
.. _templates: http://en.wikipedia.org/wiki/Web_template
.. _postgres:
.. _postgresql:
http://www.postgresql.org
.. _converter pattern:
.. _converter patterns:
http://werkzeug.pocoo.org/docs/routing/#rule-format
@@ -0,0 +1,23 @@
# HG changeset patch
# Parent 91c0cc5b319e7bf240d359817b8bd044769a4f5c
# Parent 0a9ec16d98785205f25868bc23485569a1444cf3
diff --git a/academy/__manifest__.py b/academy/__manifest__.py
--- a/academy/__manifest__.py
+++ b/academy/__manifest__.py
@@ -24,7 +24,7 @@
# always loaded
'data': [
- # 'security/ir.model.access.csv',
+ 'security/ir.model.access.csv',
'templates.xml',
],
# only loaded in demonstration mode
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
--- a/academy/security/ir.model.access.csv
+++ b/academy/security/ir.model.access.csv
@@ -1,2 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_academy_academy,academy.academy,model_academy_academy,,1,0,0,0
+access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
@@ -0,0 +1,22 @@
# HG changeset patch
# Parent 78b4476f35cbef86ee4f858daf843e3b932fb9fa
# Parent 0a4f5e3206a0738201a40d2d1a88f41fdbbc98bf
diff --git a/academy/controllers.py b/academy/controllers.py
--- a/academy/controllers.py
+++ b/academy/controllers.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
from odoo import http
-# class Academy(http.Controller):
-# @http.route('/academy/academy/', auth='public')
-# def index(self, **kw):
-# return "Hello, world"
+class Academy(http.Controller):
+ @http.route('/academy/academy/', auth='public')
+ def index(self, **kw):
+ return "Hello, world"
# @http.route('/academy/academy/objects/', auth='public')
# def list(self, **kw):
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
# HG changeset patch
# Parent 3e800beb7db0f9f05bfd4db275c86e8903d4f127
# Parent 4082801768b28c2871920e41d23b7879763e763e
diff --git a/academy/models.py b/academy/models.py
--- a/academy/models.py
+++ b/academy/models.py
@@ -2,7 +2,7 @@
from odoo import models, fields, api
-# class academy(models.Model):
-# _name = 'academy.academy'
+class Teachers(models.Model):
+ _name = 'academy.teachers'
-# name = fields.Char()
+ name = fields.Char()
@@ -0,0 +1,23 @@
# HG changeset patch
# Parent 4814709fe1c52515f5108623c2c8f0bce57ffac0
# Parent 926b3fdd1bba025d9543bb85ac40159b392ba13c
diff --git a/academy/models.py b/academy/models.py
--- a/academy/models.py
+++ b/academy/models.py
@@ -6,3 +6,4 @@ class Teachers(models.Model):
_name = 'academy.teachers'
name = fields.Char()
+ biography = fields.Html()
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -21,6 +21,7 @@
<div class="oe_structure">
<div class="container">
<h3><t t-esc="person.name"/></h3>
+ <div><t t-esc="person.biography"/></div>
</div>
</div>
<div class="oe_structure"/>
@@ -0,0 +1,18 @@
# HG changeset patch
# Parent 216904cc7636679adbf1a5a211070e739c59c3a8
# Parent edeaf1ebff99c9b3d7cbe1925ca3d2ece519ba52
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -20,8 +20,8 @@
<div class="oe_structure"/>
<div class="oe_structure">
<div class="container">
- <h3><t t-esc="person.name"/></h3>
- <div><t t-esc="person.biography"/></div>
+ <h3 t-field="person.name"/>
+ <div t-field="person.biography"/>
</div>
</div>
<div class="oe_structure"/>
@@ -0,0 +1,29 @@
# HG changeset patch
# Parent 957395d27f63dfdb2ea0dacdbe72bc35e724ddcc
# Parent 7156498d3cffb6128e73da2e351f2b5af5f7be63
diff --git a/academy/models.py b/academy/models.py
--- a/academy/models.py
+++ b/academy/models.py
@@ -12,6 +12,7 @@ class Teachers(models.Model):
class Courses(models.Model):
_name = 'academy.courses'
+ _inherit = 'mail.thread'
name = fields.Char()
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
diff --git a/academy/views.xml b/academy/views.xml
--- a/academy/views.xml
+++ b/academy/views.xml
@@ -61,6 +61,10 @@
<label for="teacher_id"/>
<field name="teacher_id"/>
</sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
</form>
</field>
</record>
@@ -0,0 +1,44 @@
# HG changeset patch
# Parent 122340f4360287bbc53f54e53ac609a8e7237796
# Parent 1f9194514fa81c49b9bc7922a010513d816a0c2e
diff --git a/academy/controllers.py b/academy/controllers.py
--- a/academy/controllers.py
+++ b/academy/controllers.py
@@ -9,9 +9,11 @@ class Academy(http.Controller):
'teachers': Teachers.search([])
})
- @http.route('/academy/<int:id>/', auth='public', website=True)
- def teacher(self, id):
- return '<h1>{} ({})</h1>'.format(id, type(id).__name__)
+ @http.route('/academy/<model("academy.teachers"):teacher>/', auth='public', website=True)
+ def teacher(self, teacher):
+ return http.request.render('academy.biography', {
+ 'person': teacher
+ })
# @http.route('/academy/academy/objects/', auth='public')
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -12,6 +12,18 @@
</div>
</t>
</template>
+ <template id="biography">
+ <t t-call="website.layout">
+ <t t-set="title">Academy</t>
+ <div class="oe_structure"/>
+ <div class="oe_structure">
+ <div class="container">
+ <p><t t-esc="person.id"/> <t t-esc="person.name"/></p>
+ </div>
+ </div>
+ <div class="oe_structure"/>
+ </t>
+ </template>
<!-- <template id="object"> -->
<!-- <h1><t t-esc="object.display_name"/></h1> -->
<!-- <dl> -->
@@ -0,0 +1,24 @@
# HG changeset patch
# Parent c3705f93fcea86a279f2c31204cc0c2914e784d8
# Parent 8f2d190d879cdf6464bd61f7994b8b582befcafd
diff --git a/academy/models.py b/academy/models.py
--- a/academy/models.py
+++ b/academy/models.py
@@ -7,3 +7,9 @@ class Teachers(models.Model):
name = fields.Char()
biography = fields.Html()
+
+class Courses(models.Model):
+ _name = 'academy.courses'
+
+ name = fields.Char()
+ teacher_id = fields.Many2one('academy.teachers', string="Teacher")
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
--- a/academy/security/ir.model.access.csv
+++ b/academy/security/ir.model.access.csv
@@ -1,2 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
+access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
@@ -0,0 +1,21 @@
# HG changeset patch
# Parent 1708c59c9731207e06ca84529c839c571e622481
# Parent ac472417df8770ce9b6922968aa86779a86be35c
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -28,6 +28,13 @@
<div class="oe_structure"/>
</t>
</template>
+
+ <template id="product_item_hide_no_price" inherit_id="website_sale.products_item">
+ <xpath expr="//div[hasclass('product_price')]/b" position="attributes">
+ <attribute name="t-if">product.price &gt; 0</attribute>
+ </xpath>
+ </template>
+
<!-- <template id="object"> -->
<!-- <h1><t t-esc="object.display_name"/></h1> -->
<!-- <dl> -->
@@ -0,0 +1,33 @@
# HG changeset patch
# Parent c3619e60d819e746dcaae49a99d7ca1f4c58593c
diff -r c3619e60d819 -r 957395d27f63 academy/models.py
--- a/academy/models.py Wed Aug 13 14:29:45 2014 +0200
+++ b/academy/models.py Wed Aug 13 15:06:30 2014 +0200
@@ -8,6 +8,8 @@ class Teachers(models.Model):
name = fields.Char()
biography = fields.Html()
+ course_ids = fields.One2many('academy.courses', 'teacher_id', string="Courses")
+
class Courses(models.Model):
_name = 'academy.courses'
diff -r c3619e60d819 -r 957395d27f63 academy/views.xml
--- a/academy/views.xml Wed Aug 13 14:29:45 2014 +0200
+++ b/academy/views.xml Wed Aug 13 15:06:30 2014 +0200
@@ -12,8 +12,15 @@
<form>
<sheet>
<label for="name"/> <field name="name"/>
+
<label for="biography"/>
<field name="biography"/>
+
+ <field name="course_ids">
+ <tree string="Courses" editable="bottom">
+ <field name="name"/>
+ </tree>
+ </field>
</sheet>
</form>
</field>
@@ -0,0 +1,142 @@
# HG changeset patch
# Parent 2ee7212e5de4702dff08d9b5e4268e7dc261d038
# Parent 4ca7b848bb60c3573de7ed41a28b4b65186a5725
diff --git a/academy/__manifest__.py b/academy/__manifest__.py
--- a/academy/__manifest__.py
+++ b/academy/__manifest__.py
@@ -27,6 +27,7 @@
'security/ir.model.access.csv',
'templates.xml',
'views.xml',
+ 'data.xml',
],
# only loaded in demonstration mode
'demo': [
diff --git a/academy/data.xml b/academy/data.xml
new file mode 100644
--- /dev/null
+++ b/academy/data.xml
@@ -0,0 +1,6 @@
+<odoo>
+ <record model="product.public.category" id="category_courses">
+ <field name="name">Courses</field>
+ <field name="parent_id" ref="website_sale.categ_others"/>
+ </record>
+</odoo>
diff --git a/academy/demo.xml b/academy/demo.xml
--- a/academy/demo.xml
+++ b/academy/demo.xml
@@ -10,4 +10,29 @@
<field name="name">Lester Vaughn</field>
</record>
+ <record id="course0" model="product.template">
+ <field name="name">Course 0</field>
+ <field name="teacher_id" ref="padilla"/>
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
+ <field name="is_published">True</field>
+ <field name="list_price" type="float">0</field>
+ <field name="type">service</field>
+ </record>
+ <record id="course1" model="product.template">
+ <field name="name">Course 1</field>
+ <field name="teacher_id" ref="padilla"/>
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
+ <field name="is_published">True</field>
+ <field name="list_price" type="float">0</field>
+ <field name="type">service</field>
+ </record>
+ <record id="course2" model="product.template">
+ <field name="name">Course 2</field>
+ <field name="teacher_id" ref="vaughn"/>
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
+ <field name="is_published">True</field>
+ <field name="list_price" type="float">0</field>
+ <field name="type">service</field>
+ </record>
+
</odoo>
diff --git a/academy/models.py b/academy/models.py
--- a/academy/models.py
+++ b/academy/models.py
@@ -8,11 +8,9 @@ class Teachers(models.Model):
name = fields.Char()
biography = fields.Html()
- course_ids = fields.One2many('academy.courses', 'teacher_id', string="Courses")
+ course_ids = fields.One2many('product.template', 'teacher_id', string="Courses")
class Courses(models.Model):
- _name = 'academy.courses'
- _inherit = 'mail.thread'
+ _inherit = 'product.template'
- name = fields.Char()
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
--- a/academy/security/ir.model.access.csv
+++ b/academy/security/ir.model.access.csv
@@ -1,3 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
-access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
diff --git a/academy/views.xml b/academy/views.xml
--- a/academy/views.xml
+++ b/academy/views.xml
@@ -26,55 +26,9 @@
</field>
</record>
- <record id="action_academy_courses" model="ir.actions.act_window">
- <field name="name">Academy courses</field>
- <field name="res_model">academy.courses</field>
- </record>
- <record id="academy_course_search" model="ir.ui.view">
- <field name="name">Academy courses: search</field>
- <field name="model">academy.courses</field>
- <field name="arch" type="xml">
- <search>
- <field name="name"/>
- <field name="teacher_id"/>
- </search>
- </field>
- </record>
- <record id="academy_course_list" model="ir.ui.view">
- <field name="name">Academy courses: list</field>
- <field name="model">academy.courses</field>
- <field name="arch" type="xml">
- <tree string="Courses">
- <field name="name"/>
- <field name="teacher_id"/>
- </tree>
- </field>
- </record>
- <record id="academy_course_form" model="ir.ui.view">
- <field name="name">Academy courses: form</field>
- <field name="model">academy.courses</field>
- <field name="arch" type="xml">
- <form>
- <sheet>
- <label for="name"/>
- <field name="name"/>
- <label for="teacher_id"/>
- <field name="teacher_id"/>
- </sheet>
- <div class="oe_chatter">
- <field name="message_follower_ids" widget="mail_followers"/>
- <field name="message_ids" widget="mail_thread"/>
- </div>
- </form>
- </field>
- </record>
-
<menuitem sequence="0" id="menu_academy" name="Academy"/>
<menuitem id="menu_academy_content" parent="menu_academy"
name="Academy Content"/>
- <menuitem id="menu_academy_content_courses"
- parent="menu_academy_content"
- action="action_academy_courses"/>
<menuitem id="menu_academy_content_teachers"
parent="menu_academy_content"
action="action_academy_teachers"/>
@@ -0,0 +1,18 @@
# HG changeset patch
# Parent c706c2ec28328a65a8aee6a7ba7d06896468c1e3
# Parent 053262442acdbe3c803c29ce35002ec69452c5fd
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -35,6 +35,10 @@
</xpath>
</template>
+ <record id="website_sale.products_categories" model="ir.ui.view">
+ <field name="active" eval="True"/>
+ </record>
+
<!-- <template id="object"> -->
<!-- <h1><t t-esc="object.display_name"/></h1> -->
<!-- <dl> -->
@@ -0,0 +1,57 @@
# HG changeset patch
# Parent c687289c105fe855f799a1e5b498382edbd80cb3
diff -r c687289c105f -r 1012f1736bb6 academy/views.xml
--- a/academy/views.xml Thu Aug 14 12:58:50 2014 +0200
+++ b/academy/views.xml Thu Aug 14 13:00:19 2014 +0200
@@ -19,9 +19,51 @@
</field>
</record>
+ <record id="action_academy_courses" model="ir.actions.act_window">
+ <field name="name">Academy courses</field>
+ <field name="res_model">academy.courses</field>
+ </record>
+ <record id="academy_course_search" model="ir.ui.view">
+ <field name="name">Academy courses: search</field>
+ <field name="model">academy.courses</field>
+ <field name="arch" type="xml">
+ <search>
+ <field name="name"/>
+ <field name="teacher_id"/>
+ </search>
+ </field>
+ </record>
+ <record id="academy_course_list" model="ir.ui.view">
+ <field name="name">Academy courses: list</field>
+ <field name="model">academy.courses</field>
+ <field name="arch" type="xml">
+ <tree string="Courses">
+ <field name="name"/>
+ <field name="teacher_id"/>
+ </tree>
+ </field>
+ </record>
+ <record id="academy_course_form" model="ir.ui.view">
+ <field name="name">Academy courses: form</field>
+ <field name="model">academy.courses</field>
+ <field name="arch" type="xml">
+ <form>
+ <sheet>
+ <label for="name"/>
+ <field name="name"/>
+ <label for="teacher_id"/>
+ <field name="teacher_id"/>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
<menuitem sequence="0" id="menu_academy" name="Academy"/>
<menuitem id="menu_academy_content" parent="menu_academy"
name="Academy Content"/>
+ <menuitem id="menu_academy_content_courses"
+ parent="menu_academy_content"
+ action="action_academy_courses"/>
<menuitem id="menu_academy_content_teachers"
parent="menu_academy_content"
action="action_academy_teachers"/>
@@ -0,0 +1,30 @@
# HG changeset patch
# Parent f0a3629abb17ca9f4dddd8885321d9ca5de14b55
# Parent 4c1c3d77ba8ad35dc759405a0db39a6eae943b0f
diff --git a/academy/controllers.py b/academy/controllers.py
--- a/academy/controllers.py
+++ b/academy/controllers.py
@@ -4,8 +4,9 @@ from odoo import http
class Academy(http.Controller):
@http.route('/academy/academy/', auth='public')
def index(self, **kw):
+ Teachers = http.request.env['academy.teachers']
return http.request.render('academy.index', {
- 'teachers': ["Diana Padilla", "Jody Caroll", "Lester Vaughn"],
+ 'teachers': Teachers.search([])
})
# @http.route('/academy/academy/objects/', auth='public')
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -3,7 +3,7 @@
<template id="index">
<title>Academy</title>
<t t-foreach="teachers" t-as="teacher">
- <p><t t-esc="teacher"/></p>
+ <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
</t>
</template>
<!-- <template id="object"> -->
@@ -0,0 +1,42 @@
# HG changeset patch
# Parent c77aa2c3341b9ab3a4e9ed05874e8a278fe69453
# Parent f416ab6c60f9cb3a803d30ff4dbff880eaa44a65
diff --git a/academy/demo.xml b/academy/demo.xml
--- a/academy/demo.xml
+++ b/academy/demo.xml
@@ -1,25 +1,13 @@
<odoo>
- <!-- -->
- <!-- <record id="object0" model="academy.academy"> -->
- <!-- <field name="name">Object 0</field> -->
- <!-- </record> -->
- <!-- -->
- <!-- <record id="object1" model="academy.academy"> -->
- <!-- <field name="name">Object 1</field> -->
- <!-- </record> -->
- <!-- -->
- <!-- <record id="object2" model="academy.academy"> -->
- <!-- <field name="name">Object 2</field> -->
- <!-- </record> -->
- <!-- -->
- <!-- <record id="object3" model="academy.academy"> -->
- <!-- <field name="name">Object 3</field> -->
- <!-- </record> -->
- <!-- -->
- <!-- <record id="object4" model="academy.academy"> -->
- <!-- <field name="name">Object 4</field> -->
- <!-- </record> -->
- <!-- -->
+ <record id="padilla" model="academy.teachers">
+ <field name="name">Diana Padilla</field>
+ </record>
+ <record id="carroll" model="academy.teachers">
+ <field name="name">Jody Carroll</field>
+ </record>
+ <record id="vaughn" model="academy.teachers">
+ <field name="name">Lester Vaughn</field>
+ </record>
</odoo>
@@ -0,0 +1,16 @@
# HG changeset patch
# Parent f3ad3f919b49bc70c87e1500ec0d39f741243b3d
# Parent 07a68419287382d7dd9d600c8b813c3c37a88864
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -21,7 +21,7 @@
<div class="oe_structure">
<div class="container">
<h3 t-field="person.name"/>
- <p>Last modified: <i t-field="person.write_date"/></p>
+ <p>Last modified: <i t-field="person.write_date" t-options='{"format": "long"}'/></p>
<div t-field="person.biography"/>
</div>
</div>
@@ -0,0 +1,15 @@
# HG changeset patch
# Parent d5b8a2de3e35c26c29f85ce500d41ccd2373c508
# Parent 65d27383542ef9214ff9e2c308aa54b042cff2d9
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -21,6 +21,7 @@
<div class="oe_structure">
<div class="container">
<h3 t-field="person.name"/>
+ <p>Last modified: <i t-field="person.write_date"/></p>
<div t-field="person.biography"/>
</div>
</div>
@@ -0,0 +1,16 @@
# HG changeset patch
# Parent c3d04f578b762612422e09777e0ac5b3863d6ea6
# Parent 0bc39fe6579c4e48b61e77bc970da2e1784f1a67
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -21,7 +21,7 @@
<div class="oe_structure">
<div class="container">
<h3 t-field="person.name"/>
- <p>Last modified: <i t-field="person.write_date" t-options='{"format": "long"}'/></p>
+ <p>Last modified: <i t-field="person.write_date" t-options='{"widget": "relative"}'/></p>
<div t-field="person.biography"/>
</div>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

@@ -0,0 +1,153 @@
# HG changeset patch
# Parent 0000000000000000000000000000000000000000
# Parent 0000000000000000000000000000000000000000
diff --git a/academy/__init__.py b/academy/__init__.py
new file mode 100644
--- /dev/null
+++ b/academy/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import controllers
+import models
diff --git a/academy/__manifest__.py b/academy/__manifest__.py
new file mode 100644
--- /dev/null
+++ b/academy/__manifest__.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "Academy",
+
+ 'summary': """
+ Short (1 phrase/line) summary of the module's purpose, used as
+ subtitle on modules listing or apps.odoo.com""",
+
+ 'description': """
+ Long description of module's purpose
+ """,
+
+ 'author': "My Company",
+ 'website': "https://www.yourcompany.com",
+
+ # Categories can be used to filter modules in modules listing
+ # Check https://github.com/odoo/odoo/blob/14.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': ['base'],
+
+ # always loaded
+ 'data': [
+ # 'security/ir.model.access.csv',
+ 'templates.xml',
+ ],
+ # only loaded in demonstration mode
+ 'demo': [
+ 'demo.xml',
+ ],
+}
diff --git a/academy/controllers.py b/academy/controllers.py
new file mode 100644
--- /dev/null
+++ b/academy/controllers.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from odoo import http
+
+# class Academy(http.Controller):
+# @http.route('/academy/academy/', auth='public')
+# def index(self, **kw):
+# return "Hello, world"
+
+# @http.route('/academy/academy/objects/', auth='public')
+# def list(self, **kw):
+# return http.request.render('academy.listing', {
+# 'root': '/academy/academy',
+# 'objects': http.request.env['academy.academy'].search([]),
+# })
+
+# @http.route('/academy/academy/objects/<model("academy.academy"):obj>/', auth='public')
+# def object(self, obj, **kw):
+# return http.request.render('academy.object', {
+# 'object': obj
+# })
diff --git a/academy/demo.xml b/academy/demo.xml
new file mode 100644
--- /dev/null
+++ b/academy/demo.xml
@@ -0,0 +1,25 @@
+<odoo>
+
+ <!-- -->
+ <!-- <record id="object0" model="academy.academy"> -->
+ <!-- <field name="name">Object 0</field> -->
+ <!-- </record> -->
+ <!-- -->
+ <!-- <record id="object1" model="academy.academy"> -->
+ <!-- <field name="name">Object 1</field> -->
+ <!-- </record> -->
+ <!-- -->
+ <!-- <record id="object2" model="academy.academy"> -->
+ <!-- <field name="name">Object 2</field> -->
+ <!-- </record> -->
+ <!-- -->
+ <!-- <record id="object3" model="academy.academy"> -->
+ <!-- <field name="name">Object 3</field> -->
+ <!-- </record> -->
+ <!-- -->
+ <!-- <record id="object4" model="academy.academy"> -->
+ <!-- <field name="name">Object 4</field> -->
+ <!-- </record> -->
+ <!-- -->
+
+</odoo>
diff --git a/academy/models.py b/academy/models.py
new file mode 100644
--- /dev/null
+++ b/academy/models.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+
+# class academy(models.Model):
+# _name = 'academy.academy'
+
+# name = fields.Char()
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
new file mode 100644
--- /dev/null
+++ b/academy/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_academy_academy,academy.academy,model_academy_academy,,1,0,0,0
diff --git a/academy/templates.xml b/academy/templates.xml
new file mode 100644
--- /dev/null
+++ b/academy/templates.xml
@@ -0,0 +1,22 @@
+<odoo>
+
+ <!-- <template id="listing"> -->
+ <!-- <ul> -->
+ <!-- <li t-foreach="objects" t-as="object"> -->
+ <!-- <a t-attf-href="#{ root }/objects/#{ object.id }"> -->
+ <!-- <t t-esc="object.display_name"/> -->
+ <!-- </a> -->
+ <!-- </li> -->
+ <!-- </ul> -->
+ <!-- </template> -->
+ <!-- <template id="object"> -->
+ <!-- <h1><t t-esc="object.display_name"/></h1> -->
+ <!-- <dl> -->
+ <!-- <t t-foreach="object._fields" t-as="field"> -->
+ <!-- <dt><t t-esc="field"/></dt> -->
+ <!-- <dd><t t-esc="object[field]"/></dd> -->
+ <!-- </t> -->
+ <!-- </dl> -->
+ <!-- </template> -->
+
+</odoo>
@@ -0,0 +1,16 @@
# HG changeset patch
# Parent 6106208621f01d5aea67727f09adf5c5b2a38642
# Parent ae97cfb61fd0e1a47727a9d8ed4a702116254eff
diff --git a/academy/__manifest__.py b/academy/__manifest__.py
--- a/academy/__manifest__.py
+++ b/academy/__manifest__.py
@@ -20,7 +20,7 @@
'version': '0.1',
# any module necessary for this one to work correctly
- 'depends': ['website'],
+ 'depends': ['website_sale'],
# always loaded
'data': [
@@ -0,0 +1,19 @@
# HG changeset patch
# Parent 0795247fa6cbb63238941f3dd26a8b6144fa71ee
# Parent f66ff336f8ed20e022660bd74a43af1b44a1f275
diff --git a/academy/controllers.py b/academy/controllers.py
--- a/academy/controllers.py
+++ b/academy/controllers.py
@@ -9,6 +9,11 @@ class Academy(http.Controller):
'teachers': Teachers.search([])
})
+ @http.route('/academy/<name>/', auth='public', website=True)
+ def teacher(self, name):
+ return '<h1>{}</h1>'.format(name)
+
+
# @http.route('/academy/academy/objects/', auth='public')
# def list(self, **kw):
# return http.request.render('academy.listing', {
@@ -0,0 +1,20 @@
# HG changeset patch
# Parent e9d9740fa1852c80541c6b5b94280c8ec74cc4bb
# Parent cf501446e2f8d7a059ef7bd16866ed03c8f2a04f
diff --git a/academy/controllers.py b/academy/controllers.py
--- a/academy/controllers.py
+++ b/academy/controllers.py
@@ -9,9 +9,9 @@ class Academy(http.Controller):
'teachers': Teachers.search([])
})
- @http.route('/academy/<name>/', auth='public', website=True)
- def teacher(self, name):
- return '<h1>{}</h1>'.format(name)
+ @http.route('/academy/<int:id>/', auth='public', website=True)
+ def teacher(self, id):
+ return '<h1>{} ({})</h1>'.format(id, type(id).__name__)
# @http.route('/academy/academy/objects/', auth='public')
+27
View File
@@ -0,0 +1,27 @@
module-empty
basic-controller
template
basic-model
basic-acl
demo-data
data-retrieval-orm
website-support
routing-basic
routing-converter
converter-model
teacher-links
biography-esc
biography-field
field-raw
field-format
field-widget
teachers-menu
teachers-formview
course-m2o
course-views
course-o2m
chatter-backend
product-dependency
course-product
course-no-price
course-products-sidebar
@@ -0,0 +1,27 @@
# HG changeset patch
# Parent 91ffd360f3ffd44acb605c09753498b3d5f3f210
# Parent 976a92eff9104d355cb86a0b41e883a19e75e923
diff --git a/academy/templates.xml b/academy/templates.xml
--- a/academy/templates.xml
+++ b/academy/templates.xml
@@ -6,7 +6,9 @@
<div class="oe_structure">
<div class="container">
<t t-foreach="teachers" t-as="teacher">
- <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
+ <p><a t-attf-href="/academy/{{ slug(teacher) }}">
+ <t t-esc="teacher.name"/></a>
+ </p>
</t>
</div>
</div>
@@ -18,7 +20,7 @@
<div class="oe_structure"/>
<div class="oe_structure">
<div class="container">
- <p><t t-esc="person.id"/> <t t-esc="person.name"/></p>
+ <h3><t t-esc="person.name"/></h3>
</div>
</div>
<div class="oe_structure"/>
@@ -0,0 +1,26 @@
# HG changeset patch
# Parent 0201053e995dff675aac7499eaa88eaf68bd2882
diff -r 0201053e995d -r c3705f93fcea academy/views.xml
--- a/academy/views.xml Wed Aug 13 11:04:49 2014 +0200
+++ b/academy/views.xml Wed Aug 13 11:04:55 2014 +0200
@@ -5,6 +5,20 @@
<field name="res_model">academy.teachers</field>
</record>
+ <record id="academy_teacher_form" model="ir.ui.view">
+ <field name="name">Academy teachers: form</field>
+ <field name="model">academy.teachers</field>
+ <field name="arch" type="xml">
+ <form>
+ <sheet>
+ <label for="name"/> <field name="name"/>
+ <label for="biography"/>
+ <field name="biography"/>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
<menuitem sequence="0" id="menu_academy" name="Academy"/>
<menuitem id="menu_academy_content" parent="menu_academy"
name="Academy Content"/>

Some files were not shown because too many files have changed in this diff Show More