[MERGE] Forward-port of branch 13.0 to 14.0
@@ -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<5" decoration-danger="duration>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 {}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
After Width: | Height: | Size: 110 KiB |
@@ -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/
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 724 B |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
@@ -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):
|
||||
|
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 > 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>
|
||||
|
After Width: | Height: | Size: 823 B |
|
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')
|
||||
@@ -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"/>
|
||||