[REM] *: patchqueue extension
Remove the patchqueue extension from the doc requirements since it isn't maintained anymore (raising warnings for recent sphinx versions) and the patches to specify code blocks aren't easy to maintain. Remove hidden code patches, and replaces shown patches by code block / literalincludes to keep the useful content.
This commit is contained in:
parent
73806971a2
commit
ac99ad7abd
3
conf.py
3
conf.py
@ -102,9 +102,6 @@ extensions = [
|
||||
|
||||
'exercise_admonition',
|
||||
|
||||
# Build code from git patches
|
||||
'patchqueue',
|
||||
|
||||
# Redirection generator
|
||||
'redirects',
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
|
||||
.. queue:: backend/series
|
||||
|
||||
=================
|
||||
Building a Module
|
||||
=================
|
||||
@ -111,14 +109,6 @@ or XML. The usage of most of those files will be explained along this tutorial.
|
||||
Use the command line above to create an empty module Open Academy, and
|
||||
install it in Odoo.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Invoke the command ``odoo-bin scaffold openacademy addons``.
|
||||
#. Adapt the manifest file to your module.
|
||||
#. Don't bother about the other files.
|
||||
|
||||
.. patch::
|
||||
|
||||
Object-Relational Mapping
|
||||
-------------------------
|
||||
|
||||
@ -213,12 +203,6 @@ overridden by setting :attr:`~odoo.models.Model._rec_name`.
|
||||
Define a new data model *Course* in the *openacademy* module. A course
|
||||
has a title and a description. Courses must have a title.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
Edit the file ``openacademy/models/models.py`` to include a *Course* class.
|
||||
|
||||
.. patch::
|
||||
|
||||
Data files
|
||||
----------
|
||||
|
||||
@ -257,12 +241,6 @@ be declared in the ``'data'`` list (always loaded) or in the ``'demo'`` list
|
||||
Create demonstration data filling the *Courses* model with a few
|
||||
demonstration courses.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
Edit the file ``openacademy/demo/demo.xml`` to include some data.
|
||||
|
||||
.. patch::
|
||||
|
||||
.. tip:: The content of the data files is only loaded when a module is
|
||||
installed or updated.
|
||||
|
||||
@ -310,14 +288,6 @@ action more easily.
|
||||
- display a list of all the courses
|
||||
- create/modify courses
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Create ``openacademy/views/openacademy.xml`` with an action and
|
||||
the menus triggering the action
|
||||
#. Add it to the ``data`` list of ``openacademy/__manifest__.py``
|
||||
|
||||
.. patch::
|
||||
|
||||
Basic views
|
||||
===========
|
||||
|
||||
@ -410,22 +380,12 @@ elements (groups, notebooks) and interactive elements (buttons and fields):
|
||||
Create your own form view for the Course object. Data displayed should be:
|
||||
the name and the description of the course.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
.. exercise:: Notebooks
|
||||
|
||||
In the Course form view, put the description field under a tab, such that
|
||||
it will be easier to add other tabs later, containing additional
|
||||
information.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
Modify the Course form view as follows:
|
||||
|
||||
.. patch::
|
||||
|
||||
Form views can also use plain HTML for more flexible layouts:
|
||||
|
||||
.. code-block:: xml
|
||||
@ -473,10 +433,6 @@ searching on the ``name`` field.
|
||||
|
||||
Allow searching for courses based on their title or their description.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
Relations between models
|
||||
========================
|
||||
|
||||
@ -493,18 +449,6 @@ client data; it is also related to its sale order line records.
|
||||
duration and a number of seats. Add an action and a menu item to display
|
||||
them. Make the new model visible via a menu item.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Create the class *Session* in ``openacademy/models/models.py``.
|
||||
#. Add access to the session object in ``openacademy/view/openacademy.xml``.
|
||||
|
||||
.. patch::
|
||||
|
||||
.. note:: ``digits=(6, 2)`` specifies the precision of a float number:
|
||||
6 is the total number of digits, while 2 is the number of
|
||||
digits after the comma. Note that it results in the number
|
||||
digits before the comma is a maximum 4
|
||||
|
||||
Relational fields
|
||||
-----------------
|
||||
|
||||
@ -555,25 +499,11 @@ Relational field types are:
|
||||
of the model ``openacademy.course`` and is required.
|
||||
- Adapt the views.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Add the relevant ``Many2one`` fields to the models, and
|
||||
#. add them in the views.
|
||||
|
||||
.. patch::
|
||||
|
||||
.. exercise:: Inverse one2many relations
|
||||
|
||||
Using the inverse relational field one2many, modify the models to reflect
|
||||
the relation between courses and sessions.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Modify the ``Course`` class, and
|
||||
#. add the field in the course form view.
|
||||
|
||||
.. patch::
|
||||
|
||||
.. exercise:: Multiple many2many relations
|
||||
|
||||
Using the relational field many2many, modify the *Session* model to relate
|
||||
@ -581,13 +511,6 @@ Relational field types are:
|
||||
partner records, so we will relate to the built-in model ``res.partner``.
|
||||
Adapt the views accordingly.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Modify the ``Session`` class, and
|
||||
#. add the field in the form view.
|
||||
|
||||
.. patch::
|
||||
|
||||
Inheritance
|
||||
===========
|
||||
|
||||
@ -687,21 +610,6 @@ instead of a single view its ``arch`` field is composed of any number of
|
||||
the session-partner relation
|
||||
* Using view inheritance, display this fields in the partner form view
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. note::
|
||||
|
||||
This is the opportunity to introduce the developer mode to
|
||||
inspect the view, find its external ID and the place to put the
|
||||
new field.
|
||||
|
||||
#. Create a file ``openacademy/models/partner.py`` and import it in
|
||||
``__init__.py``
|
||||
#. Create a file ``openacademy/views/partner.xml`` and add it to
|
||||
``__manifest__.py``
|
||||
|
||||
.. patch::
|
||||
|
||||
Domains
|
||||
#######
|
||||
|
||||
@ -734,31 +642,12 @@ records for the relation when trying to select records in the client interface.
|
||||
When selecting the instructor for a *Session*, only instructors (partners
|
||||
with ``instructor`` set to ``True``) should be visible.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
.. note::
|
||||
|
||||
A domain declared as a literal list is evaluated server-side and
|
||||
can't refer to dynamic values on the right-hand side, a domain
|
||||
declared as a string is evaluated client-side and allows
|
||||
field names on the right-hand side
|
||||
|
||||
.. exercise:: More complex domains
|
||||
|
||||
Create new partner categories *Teacher / Level 1* and *Teacher / Level 2*.
|
||||
The instructor for a session can be either an instructor or a teacher
|
||||
(of any level).
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Modify the *Session* model's domain
|
||||
#. Modify ``openacademy/view/partner.xml`` to get access to
|
||||
*Partner categories*:
|
||||
|
||||
.. patch::
|
||||
|
||||
Computed fields and default values
|
||||
==================================
|
||||
|
||||
@ -828,13 +717,6 @@ field whenever some of its dependencies have been modified::
|
||||
* Display that field in the tree and form views
|
||||
* Display the field as a progress bar
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Add a computed field to *Session*
|
||||
#. Show the field in the *Session* view:
|
||||
|
||||
.. patch::
|
||||
|
||||
Default values
|
||||
--------------
|
||||
|
||||
@ -865,15 +747,6 @@ float, string), or a function taking a recordset and returning a value::
|
||||
* Add a field ``active`` in the class Session, and set sessions as active by
|
||||
default.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
.. note::
|
||||
|
||||
Odoo has built-in rules making fields with an ``active`` field set
|
||||
to ``False`` invisible.
|
||||
|
||||
Onchange
|
||||
========
|
||||
|
||||
@ -919,10 +792,6 @@ the ``taken_seats`` progressbar is automatically updated.
|
||||
Add an explicit onchange to warn about invalid values, like a negative
|
||||
number of seats, or more participants than seats.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
Model constraints
|
||||
=================
|
||||
|
||||
@ -950,10 +819,6 @@ raise an exception if its invariant is not satisfied::
|
||||
Add a constraint that checks that the instructor is not present in the
|
||||
attendees of his/her own session.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
SQL constraints are defined through the model attribute
|
||||
:attr:`~odoo.models.Model._sql_constraints`. The latter is assigned to a list
|
||||
of triples of strings ``(name, sql_definition, message)``, where ``name`` is a
|
||||
@ -968,10 +833,6 @@ and ``message`` is the error message.
|
||||
#. CHECK that the course description and the course title are different
|
||||
#. Make the Course's name UNIQUE
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
.. exercise:: Exercise 6 - Add a duplicate option
|
||||
|
||||
Since we added a constraint for the Course name uniqueness, it is not
|
||||
@ -981,10 +842,6 @@ and ``message`` is the error message.
|
||||
Re-implement your own "copy" method which allows to duplicate the Course
|
||||
object, changing the original name into "Copy of [original name]".
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
Advanced Views
|
||||
==============
|
||||
|
||||
@ -1028,12 +885,6 @@ behavior:
|
||||
5 days are colored blue, and the ones lasting more than 15 days are
|
||||
colored red.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
Modify the session tree view:
|
||||
|
||||
.. patch::
|
||||
|
||||
Calendars
|
||||
---------
|
||||
|
||||
@ -1063,19 +914,6 @@ their most common attributes are:
|
||||
Add a Calendar view to the *Session* model enabling the user to view the
|
||||
events associated to the Open Academy.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Add an ``end_date`` field computed from ``start_date`` and
|
||||
``duration``
|
||||
|
||||
.. tip:: the inverse function makes the field writable, and allows
|
||||
moving the sessions (via drag and drop) in the calendar view
|
||||
|
||||
#. Add a calendar view to the *Session* model
|
||||
#. And add the calendar view to the *Session* model's actions
|
||||
|
||||
.. patch::
|
||||
|
||||
Search views
|
||||
------------
|
||||
|
||||
@ -1125,10 +963,6 @@ default and behave as booleans (they can only be enabled by default).
|
||||
responsible in the course search view. Make it selected by default.
|
||||
#. Add a button to group courses by responsible user.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
Gantt
|
||||
-----
|
||||
|
||||
@ -1153,14 +987,6 @@ their root element is ``<gantt>``.
|
||||
Add a Gantt Chart enabling the user to view the sessions scheduling linked
|
||||
to the Open Academy module. The sessions should be grouped by instructor.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Create a computed field expressing the session's duration in hours
|
||||
#. Add the gantt view's definition, and add the gantt view to the
|
||||
*Session* model's action
|
||||
|
||||
.. patch::
|
||||
|
||||
Graph views
|
||||
-----------
|
||||
|
||||
@ -1212,13 +1038,6 @@ the values:
|
||||
Add a Graph view in the Session object that displays, for each course, the
|
||||
number of attendees under the form of a bar chart.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Add the number of attendees as a stored computed field
|
||||
#. Then add the relevant view
|
||||
|
||||
.. patch::
|
||||
|
||||
Kanban
|
||||
------
|
||||
|
||||
@ -1239,13 +1058,6 @@ Kanban views define the structure of each card as a mix of form elements
|
||||
Add a Kanban view that displays sessions grouped by course (columns are
|
||||
thus courses).
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Add an integer ``color`` field to the *Session* model
|
||||
#. Add the kanban view and update the action
|
||||
|
||||
.. patch::
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
@ -1282,16 +1094,6 @@ rights are usually created by a CSV file named after its model:
|
||||
Create a new user "John Smith". Then create a group
|
||||
"OpenAcademy / Session Read" with read access to the *Session* model.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Create a new user *John Smith* through
|
||||
:menuselection:`Settings --> Users --> Users`
|
||||
#. Create a new group ``session_read`` through
|
||||
:menuselection:`Settings --> Users --> Groups`, it should have
|
||||
read access on the *Session* model
|
||||
#. Edit *John Smith* to make them a member of ``session_read``
|
||||
#. Log in as *John Smith* to check the access rights are correct
|
||||
|
||||
.. exercise:: Add access control through data files in your module
|
||||
|
||||
Using data files,
|
||||
@ -1300,17 +1102,6 @@ rights are usually created by a CSV file named after its model:
|
||||
OpenAcademy models
|
||||
* Make *Session* and *Course* readable by all users
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Create a new file ``openacademy/security/security.xml`` to
|
||||
hold the OpenAcademy Manager group
|
||||
#. Edit the file ``openacademy/security/ir.model.access.csv`` with
|
||||
the access rights to the models
|
||||
#. Finally update ``openacademy/__manifest__.py`` to add the new data
|
||||
files to it
|
||||
|
||||
.. patch::
|
||||
|
||||
Record rules
|
||||
------------
|
||||
|
||||
@ -1344,12 +1135,6 @@ the same convention as the method :meth:`~odoo.models.Model.write` of the ORM.
|
||||
to the responsible of a course. If a course has no responsible, all users
|
||||
of the group must be able to modify it.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
Create a new rule in ``openacademy/security/security.xml``:
|
||||
|
||||
.. patch::
|
||||
|
||||
Wizards
|
||||
=======
|
||||
|
||||
@ -1377,12 +1162,6 @@ session, or for a list of sessions at once.
|
||||
Create a wizard model with a many2one relationship with the *Session*
|
||||
model and a many2many relationship with the *Partner* model.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
Add a new file ``openacademy/wizard.py``:
|
||||
|
||||
.. patch::
|
||||
|
||||
Launching wizards
|
||||
-----------------
|
||||
|
||||
@ -1416,28 +1195,16 @@ Wizards use regular views and their buttons may use the attribute
|
||||
#. Define a default value for the session field in the wizard; use the
|
||||
context parameter ``self._context`` to retrieve the current session.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
.. exercise:: Register attendees
|
||||
|
||||
Add buttons to the wizard, and implement the corresponding method for adding
|
||||
the attendees to the given session.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
.. exercise:: Register attendees to multiple sessions
|
||||
|
||||
Modify the wizard model so that attendees can be registered to multiple
|
||||
sessions.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
Internationalization
|
||||
====================
|
||||
|
||||
@ -1477,40 +1244,6 @@ for editing and merging PO/POT files.
|
||||
Choose a second language for your Odoo installation. Translate your
|
||||
module using the facilities provided by Odoo.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Create a directory ``openacademy/i18n/``
|
||||
#. You will need to activate the developer mode
|
||||
to access the menus mentioned below (
|
||||
:menuselection:`Settings --> Activate the developer mode`
|
||||
)
|
||||
#. Install whichever language you want (
|
||||
:menuselection:`Settings --> Translations --> Load a
|
||||
Translation`)
|
||||
#. Generate the missing terms (:menuselection:`Settings -->
|
||||
Translations --> Application Terms --> Generate Missing Terms`)
|
||||
#. Create a template translation file by exporting (
|
||||
:menuselection:`Settings --> Translations -> Import/Export
|
||||
--> Export Translation`) without specifying a language, save in
|
||||
``openacademy/i18n/``
|
||||
#. Create a translation file by exporting (
|
||||
:menuselection:`Settings --> Translations --> Import/Export
|
||||
--> Export Translation`) and specifying a language. Save it in
|
||||
``openacademy/i18n/``
|
||||
#. Open the exported translation file (with a basic text editor or a
|
||||
dedicated PO-file editor e.g. POEdit_ and translate the missing
|
||||
terms
|
||||
|
||||
#. In ``models.py``, add an import statement for the function
|
||||
``odoo._`` and mark missing strings as translatable
|
||||
|
||||
#. Repeat steps 3-6
|
||||
|
||||
.. patch::
|
||||
|
||||
.. todo:: do we never reload translations?
|
||||
|
||||
|
||||
Reporting
|
||||
=========
|
||||
|
||||
@ -1599,10 +1332,6 @@ http://localhost:8069/report/pdf/account.report_invoice/1.
|
||||
For each session, it should display session's name, its start and end,
|
||||
and list the session's attendees.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
Dashboards
|
||||
----------
|
||||
|
||||
@ -1614,21 +1343,6 @@ Dashboards
|
||||
and automatically displayed in the web client when the OpenAcademy main
|
||||
menu is selected.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
#. Create a file ``openacademy/views/session_board.xml``. It should contain
|
||||
the board view, the actions referenced in that view, an action to
|
||||
open the dashboard and a re-definition of the main menu item to add
|
||||
the dashboard action
|
||||
|
||||
.. note:: Available dashboard styles are ``1``, ``1-1``, ``1-2``,
|
||||
``2-1`` and ``1-1-1``
|
||||
|
||||
#. Update ``openacademy/__manifest__.py`` to reference the new data
|
||||
file
|
||||
|
||||
.. patch::
|
||||
|
||||
WebServices
|
||||
===========
|
||||
|
||||
|
@ -1,40 +0,0 @@
|
||||
# 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>
|
@ -1,27 +0,0 @@
|
||||
# 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>
|
@ -1,19 +0,0 @@
|
||||
# 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
|
||||
@@ -116,9 +116,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>
|
@ -1,54 +0,0 @@
|
||||
# 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,35 @@
|
||||
+<?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_type">form</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>
|
@ -1,78 +0,0 @@
|
||||
# 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
|
||||
@@ -125,11 +125,22 @@
|
||||
</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_type">form</field>
|
||||
- <field name="view_mode">tree,form</field>
|
||||
+ <field name="view_mode">tree,form,calendar</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="session_menu" name="Sessions"
|
@ -1,40 +0,0 @@
|
||||
# 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
|
||||
@@ -100,6 +100,7 @@
|
||||
<field name="start_date"/>
|
||||
<field name="duration"/>
|
||||
<field name="seats"/>
|
||||
+ <field name="taken_seats" widget="progressbar"/>
|
||||
</group>
|
||||
</group>
|
||||
<label for="attendee_ids"/>
|
||||
@@ -117,6 +118,7 @@
|
||||
<tree string="Session Tree">
|
||||
<field name="name"/>
|
||||
<field name="course_id"/>
|
||||
+ <field name="taken_seats" widget="progressbar"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
@ -1,25 +0,0 @@
|
||||
# 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")
|
@ -1,24 +0,0 @@
|
||||
# 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'
|
@ -1,28 +0,0 @@
|
||||
# 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,20 @@ class Course(models.Model):
|
||||
session_ids = fields.One2many(
|
||||
'openacademy.session', 'course_id', string="Sessions")
|
||||
|
||||
+ @api.multi
|
||||
+ 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)',
|
@ -1,152 +0,0 @@
|
||||
# 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/12.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>
|
@ -1,95 +0,0 @@
|
||||
# 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,66 @@
|
||||
+<?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_type">form</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_type">form</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_type">form</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_type">form</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>
|
@ -1,28 +0,0 @@
|
||||
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
|
||||
@@ -95,6 +95,7 @@
|
||||
<field name="course_id"/>
|
||||
<field name="name"/>
|
||||
<field name="instructor_id"/>
|
||||
+ <field name="active"/>
|
||||
</group>
|
||||
<group string="Schedule">
|
||||
<field name="start_date"/>
|
@ -1,48 +0,0 @@
|
||||
# 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>
|
@ -1,42 +0,0 @@
|
||||
# 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>
|
@ -1,17 +0,0 @@
|
||||
# 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")
|
@ -1,29 +0,0 @@
|
||||
# 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",
|
@ -1,24 +0,0 @@
|
||||
# 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>
|
@ -1,61 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent dba00a105dd2a82490394b8dec5fea5f1d8847e1
|
||||
# Parent f4374b6e2e661e0782e396b24c57c1eb97d13288
|
||||
|
||||
diff --git a/openacademy/models.py b/openacademy/models.py
|
||||
--- a/openacademy/models.py
|
||||
+++ b/openacademy/models.py
|
||||
@@ -61,6 +61,9 @@ class Session(models.Model):
|
||||
end_date = fields.Date(string="End Date", store=True,
|
||||
compute='_get_end_date', inverse='_set_end_date')
|
||||
|
||||
+ hours = fields.Float(string="Duration in hours",
|
||||
+ compute='_get_hours', inverse='_set_hours')
|
||||
+
|
||||
@api.depends('seats', 'attendee_ids')
|
||||
def _taken_seats(self):
|
||||
for r in self:
|
||||
@@ -107,6 +110,15 @@ 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('duration')
|
||||
+ def _get_hours(self):
|
||||
+ for r in self:
|
||||
+ r.hours = r.duration * 24
|
||||
+
|
||||
+ def _set_hours(self):
|
||||
+ for r in self:
|
||||
+ r.duration = r.hours / 24
|
||||
+
|
||||
@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
|
||||
@@ -143,11 +143,23 @@
|
||||
</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_delay="hours"
|
||||
+ default_group_by='instructor_id'>
|
||||
+ <!-- <field name="name"/> this is not required after Odoo 10.0 -->
|
||||
+ </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_type">form</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"
|
@ -1,56 +0,0 @@
|
||||
# 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
|
||||
@@ -64,6 +64,9 @@ class Session(models.Model):
|
||||
hours = fields.Float(string="Duration in hours",
|
||||
compute='_get_hours', inverse='_set_hours')
|
||||
|
||||
+ 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:
|
||||
@@ -119,6 +122,11 @@ class Session(models.Model):
|
||||
for r in self:
|
||||
r.duration = r.hours / 24
|
||||
|
||||
+ @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
|
||||
@@ -155,11 +155,22 @@
|
||||
</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_type">form</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"
|
@ -1,78 +0,0 @@
|
||||
# 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
|
||||
@@ -166,11 +166,57 @@
|
||||
</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_type">form</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"
|
@ -1,22 +0,0 @@
|
||||
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
|
||||
@@ -102,6 +102,8 @@
|
||||
<field name="seats"/>
|
||||
</group>
|
||||
</group>
|
||||
+ <label for="attendee_ids"/>
|
||||
+ <field name="attendee_ids"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
@ -1,95 +0,0 @@
|
||||
# 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",
|
||||
@@ -73,16 +86,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>
|
@ -1,19 +0,0 @@
|
||||
# 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()
|
@ -1,78 +0,0 @@
|
||||
# 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>
|
@ -1,28 +0,0 @@
|
||||
# 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",
|
||||
+ },
|
||||
+ }
|
@ -1,36 +0,0 @@
|
||||
# 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>
|
@ -1,50 +0,0 @@
|
||||
# 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: addons/openacademy/reports.xml
|
||||
===================================================================
|
||||
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
||||
+++ addons/openacademy/reports.xml 2014-08-29 08:41:12.663540061 +0200
|
||||
@@ -0,0 +1,30 @@
|
||||
+<odoo>
|
||||
+
|
||||
+ <report
|
||||
+ id="report_session"
|
||||
+ model="openacademy.session"
|
||||
+ string="Session Report"
|
||||
+ name="openacademy.report_session_view"
|
||||
+ file="openacademy.report_session"
|
||||
+ report_type="qweb-pdf" />
|
||||
+
|
||||
+ <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>
|
@ -1,28 +0,0 @@
|
||||
# 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>
|
||||
@@ -62,6 +68,7 @@
|
||||
<field name="res_model">openacademy.course</field>
|
||||
<field name="view_type">form</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>
|
@ -1,24 +0,0 @@
|
||||
# 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",
|
@ -1,58 +0,0 @@
|
||||
# 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
|
||||
@@ -65,4 +65,33 @@
|
||||
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_type">form</field>
|
||||
+ <field name="view_mode">tree,form</field>
|
||||
+ </record>
|
||||
+
|
||||
+ <menuitem id="session_menu" name="Sessions"
|
||||
+ parent="openacademy_menu"
|
||||
+ action="session_list_action"/>
|
||||
+
|
||||
</odoo>
|
@ -1,56 +0,0 @@
|
||||
# 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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -132,4 +132,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"))
|
@ -1,25 +0,0 @@
|
||||
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")
|
@ -1,30 +0,0 @@
|
||||
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,8 @@
|
||||
session_id = fields.Many2one('openacademy.session',
|
||||
string="Session", required=True, default=_default_session)
|
||||
attendee_ids = fields.Many2many('res.partner', string="Attendees")
|
||||
+
|
||||
+ @api.multi
|
||||
+ def subscribe(self):
|
||||
+ self.session_id.attendee_ids |= self.attendee_ids
|
||||
+ return {}
|
@ -1,45 +0,0 @@
|
||||
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: addons/openacademy/views/openacademy.xml
|
||||
===================================================================
|
||||
--- addons.orig/openacademy/views/openacademy.xml
|
||||
+++ addons/openacademy/views/openacademy.xml
|
||||
@@ -223,4 +223,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>
|
||||
+
|
||||
+ <act_window id="launch_session_wizard"
|
||||
+ name="Add Attendees"
|
||||
+ src_model="openacademy.session"
|
||||
+ res_model="openacademy.wizard"
|
||||
+ view_mode="form"
|
||||
+ target="new"
|
||||
+ key2="client_action_multi"/>
|
||||
+
|
||||
</odoo>
|
@ -1,38 +0,0 @@
|
||||
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,14 +6,15 @@ 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")
|
||||
|
||||
@api.multi
|
||||
def subscribe(self):
|
||||
- self.session_id.attendee_ids |= self.attendee_ids
|
||||
+ for session in self.session_ids:
|
||||
+ session.attendee_ids |= self.attendee_ids
|
||||
return {}
|
@ -1,35 +0,0 @@
|
||||
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
|
@ -1,6 +1,3 @@
|
||||
|
||||
.. queue:: website/series
|
||||
|
||||
==================
|
||||
Building a Website
|
||||
==================
|
||||
@ -31,9 +28,6 @@ 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
|
||||
======================
|
||||
|
||||
@ -63,7 +57,17 @@ send data back.
|
||||
Add a simple controller and ensure it is imported by ``__init__.py`` (so
|
||||
Odoo can find it):
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/controllers.py``
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import http
|
||||
|
||||
class Academy(http.Controller):
|
||||
|
||||
@http.route('/academy/academy/', auth='public')
|
||||
def index(self, **kw):
|
||||
return "Hello, world"
|
||||
|
||||
Shut down your server (:kbd:`^C`) then restart it:
|
||||
|
||||
@ -89,7 +93,30 @@ 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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/controllers.py``
|
||||
|
||||
class Academy(http.Controller):
|
||||
|
||||
@http.route('/academy/academy/', auth='public')
|
||||
def index(self, **kw):
|
||||
return http.request.render('academy.index', {
|
||||
'teachers': ["Diana Padilla", "Jody Caroll", "Lester Vaughn"],
|
||||
})
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<odoo>
|
||||
|
||||
<template id="index">
|
||||
<title>Academy</title>
|
||||
<t t-foreach="teachers" t-as="teacher">
|
||||
<p><t t-esc="teacher"/></p>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
The templates iterates (``t-foreach``) on all the teachers (passed through the
|
||||
*template context*), and prints each teacher in its own paragraph.
|
||||
@ -126,12 +153,33 @@ Defining the data model
|
||||
Define a teacher model, and ensure it is imported from ``__init__.py`` so it
|
||||
is correctly loaded:
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/models.py``
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
class Teachers(models.Model):
|
||||
_name = 'academy.teachers'
|
||||
|
||||
name = fields.Char()
|
||||
|
||||
Then setup :ref:`basic access control <reference/security/acl>` for the model
|
||||
and add them to the manifest:
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/__manifest__.py``
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'templates.xml',
|
||||
],
|
||||
|
||||
.. code-block:: csv
|
||||
:caption: ``academy/security/ir.model.access.csv``
|
||||
|
||||
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
|
||||
|
||||
this simply gives read access (``perm_read``) to all users (``group_id:id``
|
||||
left empty).
|
||||
@ -154,7 +202,22 @@ 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::
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/demo.xml``
|
||||
|
||||
<odoo>
|
||||
|
||||
<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>
|
||||
|
||||
.. tip::
|
||||
|
||||
@ -177,7 +240,31 @@ The last step is to alter model and template to use our demonstration data:
|
||||
matching the filter ("all records" here), alter the template to print each
|
||||
teacher's ``name``
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/controllers.py``
|
||||
|
||||
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': Teachers.search([])
|
||||
})
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<odoo>
|
||||
|
||||
<template id="index">
|
||||
<title>Academy</title>
|
||||
<t t-foreach="teachers" t-as="teacher">
|
||||
<p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
Restart the server and update the module (in order to update the manifest
|
||||
and templates and load the demo file) then navigate to
|
||||
@ -200,7 +287,48 @@ integration and a few other services (e.g. default styling, theming) via the
|
||||
allows using the website layout in our template
|
||||
#. use the website layout in the template
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/__manifest__.py``
|
||||
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['website'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``academy/controllers.py``
|
||||
|
||||
class Academy(http.Controller):
|
||||
|
||||
@http.route('/academy/academy/', auth='public', website=True)
|
||||
def index(self, **kw):
|
||||
Teachers = http.request.env['academy.teachers']
|
||||
return http.request.render('academy.index', {
|
||||
'teachers': Teachers.search([])
|
||||
})
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<odoo>
|
||||
|
||||
<template id="index">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="title">Academy</t>
|
||||
<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>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
After restarting the server while updating the module (in order to update the
|
||||
manifest and template) access http://localhost:8069/academy/academy/ should
|
||||
@ -238,7 +366,13 @@ 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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/controllers.py``
|
||||
|
||||
# New route
|
||||
@http.route('/academy/<name>/', auth='public', website=True)
|
||||
def teacher(self, name):
|
||||
return '<h1>{}</h1>'.format(name)
|
||||
|
||||
restart Odoo, access http://localhost:8069/academy/Alice/ and
|
||||
http://localhost:8069/academy/Bob/ and see the difference.
|
||||
@ -247,7 +381,12 @@ 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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/controllers.py``
|
||||
|
||||
@http.route('/academy/<int:id>/', auth='public', website=True)
|
||||
def teacher(self, id):
|
||||
return '<h1>{} ({})</h1>'.format(id, type(id).__name__)
|
||||
|
||||
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
|
||||
@ -259,11 +398,52 @@ 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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/controllers.py``
|
||||
|
||||
@http.route('/academy/<model("academy.teachers"):teacher>/', auth='public', website=True)
|
||||
def teacher(self, teacher):
|
||||
return http.request.render('academy.biography', {
|
||||
'person': teacher
|
||||
})
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<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">
|
||||
<h3><t t-esc="person.name"/></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
then change the list of model to link to our new controller:
|
||||
|
||||
.. patch::
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<template id="index">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="title">Academy</t>
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<t t-foreach="teachers" t-as="teacher">
|
||||
<p>
|
||||
<a t-attf-href="/academy/{{ slug(teacher) }}">
|
||||
<t t-esc="teacher.name"/></a>
|
||||
</p>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
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,
|
||||
@ -278,7 +458,31 @@ 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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/models.py``
|
||||
|
||||
class Teachers(models.Model):
|
||||
_name = 'academy.teachers'
|
||||
|
||||
name = fields.Char()
|
||||
biography = fields.Html()
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<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">
|
||||
<h3><t t-esc="person.name"/></h3>
|
||||
<div><t t-esc="person.biography"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
Restart Odoo and update the views, reload the teacher's page and… the field
|
||||
is invisible since it contains nothing.
|
||||
@ -290,7 +494,15 @@ 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::
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3 t-field="person.name"/>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
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
|
||||
@ -303,16 +515,43 @@ 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::
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<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>
|
||||
|
||||
it is displayed in a very "computery" manner and hard to read, but we could
|
||||
ask for a human-readable version:
|
||||
|
||||
.. patch::
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<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>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
or a relative display:
|
||||
|
||||
.. patch::
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<div class="oe_structure">
|
||||
<div class="container">
|
||||
<h3 t-field="person.name"/>
|
||||
<p>Last modified: <i t-field="person.write_date" t-options='{"widget": "relative"}'/></p>
|
||||
<div t-field="person.biography"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Administration and ERP integration
|
||||
==================================
|
||||
@ -344,7 +583,32 @@ reachable, generally through a menu.
|
||||
|
||||
Let's create a menu for our model:
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/__manifest__.py``
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'templates.xml',
|
||||
'views.xml',
|
||||
],
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/views.xml``
|
||||
|
||||
<odoo>
|
||||
<record id="action_academy_teachers" model="ir.actions.act_window">
|
||||
<field name="name">Academy teachers</field>
|
||||
<field name="res_model">academy.teachers</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_teachers"
|
||||
parent="menu_academy_content"
|
||||
action="action_academy_teachers"/>
|
||||
</odoo>
|
||||
|
||||
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,
|
||||
@ -360,7 +624,21 @@ 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::
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/views.xml``
|
||||
|
||||
<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>
|
||||
<field name="name"/>
|
||||
<field name="biography"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Relations between models
|
||||
------------------------
|
||||
@ -375,18 +653,117 @@ 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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/models.py``
|
||||
|
||||
class Courses(models.Model):
|
||||
_name = 'academy.courses'
|
||||
|
||||
name = fields.Char()
|
||||
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
|
||||
|
||||
.. code-block:: csv
|
||||
:caption: ``academy/security/ir.model.access.csv``
|
||||
|
||||
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
|
||||
|
||||
let's also add views so we can see and edit a course's teacher:
|
||||
|
||||
.. patch::
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/views.xml``
|
||||
|
||||
<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>
|
||||
<field name="name"/>
|
||||
<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"/>
|
||||
|
||||
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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/models.py``
|
||||
|
||||
class Teachers(models.Model):
|
||||
_name = 'academy.teachers'
|
||||
|
||||
name = fields.Char()
|
||||
biography = fields.Html()
|
||||
|
||||
course_ids = fields.One2many('academy.courses', 'teacher_id', string="Courses")
|
||||
|
||||
class Courses(models.Model):
|
||||
_name = 'academy.courses'
|
||||
|
||||
name = fields.Char()
|
||||
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/views.xml``
|
||||
|
||||
<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>
|
||||
<field name="name"/>
|
||||
<field name="biography"/>
|
||||
<field name="course_ids">
|
||||
<tree Sstring="Courses" editable="bottom">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Discussions and notifications
|
||||
-----------------------------
|
||||
@ -404,7 +781,47 @@ 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::
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``academy/__manifest__.py``
|
||||
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['website', 'mail'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``academy/models.py``
|
||||
|
||||
class Courses(models.Model):
|
||||
_name = 'academy.courses'
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
name = fields.Char()
|
||||
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/views.xml``
|
||||
|
||||
<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>
|
||||
<field name="name"/>
|
||||
<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>
|
||||
|
||||
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
|
||||
@ -426,7 +843,16 @@ 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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/__manifest__.py``
|
||||
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['mail', 'website_sale'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
|
||||
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.
|
||||
@ -434,7 +860,65 @@ 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::
|
||||
.. code-block:: python
|
||||
:caption: ``academy/__manifest__.py``
|
||||
|
||||
'security/ir.model.access.csv',
|
||||
'templates.xml',
|
||||
'views.xml',
|
||||
'data.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/data.xml``
|
||||
|
||||
<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>
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/demo.xml``
|
||||
|
||||
<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="website_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="website_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="website_published">True</field>
|
||||
<field name="list_price" type="float">0</field>
|
||||
<field name="type">service</field>
|
||||
</record>
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``academy/models.py``
|
||||
|
||||
class Courses(models.Model):
|
||||
_name = 'academy.courses'
|
||||
_inherit = ['mail.thread', 'product.template']
|
||||
|
||||
name = fields.Char()
|
||||
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
|
||||
|
||||
With this installed, a few courses are now available in the :guilabel:`Shop`,
|
||||
though they may have to be looked for.
|
||||
@ -482,7 +966,14 @@ Altering view architectures is done in 3 steps:
|
||||
#. In the architecture, use the ``xpath`` tag to select and alter elements
|
||||
from the modified view
|
||||
|
||||
.. patch::
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<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>
|
||||
|
||||
The second thing we will change is making the product categories sidebar
|
||||
visible by default: :menuselection:`Customize --> Product Categories` lets
|
||||
@ -498,7 +989,12 @@ menu with a check box, allowing administrators to activate or disable them
|
||||
We simply need to modify the *Product Categories* record and set its default
|
||||
to *active="True"*:
|
||||
|
||||
.. patch::
|
||||
.. code-block:: xml
|
||||
:caption: ``academy/templates.xml``
|
||||
|
||||
<record id="website_sale.products_categories" model="ir.ui.view">
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
|
||||
With this, the *Product Categories* sidebar will automatically be enabled when
|
||||
the *Academy* module is installed.
|
||||
|
@ -1,23 +0,0 @@
|
||||
# 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
|
@ -1,22 +0,0 @@
|
||||
# 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):
|
@ -1,18 +0,0 @@
|
||||
# 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()
|
@ -1,23 +0,0 @@
|
||||
# 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"/>
|
@ -1,18 +0,0 @@
|
||||
# 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"/>
|
@ -1,29 +0,0 @@
|
||||
# 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>
|
@ -1,44 +0,0 @@
|
||||
# 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> -->
|
@ -1,24 +0,0 @@
|
||||
# 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
|
@ -1,21 +0,0 @@
|
||||
# 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> -->
|
@ -1,33 +0,0 @@
|
||||
# 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>
|
@ -1,142 +0,0 @@
|
||||
# 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="website_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="website_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="website_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"/>
|
@ -1,18 +0,0 @@
|
||||
# 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> -->
|
@ -1,57 +0,0 @@
|
||||
# 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"/>
|
@ -1,30 +0,0 @@
|
||||
# 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"> -->
|
@ -1,42 +0,0 @@
|
||||
# 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>
|
@ -1,16 +0,0 @@
|
||||
# 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>
|
@ -1,15 +0,0 @@
|
||||
# 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>
|
@ -1,16 +0,0 @@
|
||||
# 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>
|
@ -1,153 +0,0 @@
|
||||
# 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/12.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>
|
@ -1,16 +0,0 @@
|
||||
# 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': [
|
@ -1,19 +0,0 @@
|
||||
# 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', {
|
@ -1,20 +0,0 @@
|
||||
# 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')
|
@ -1,27 +0,0 @@
|
||||
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
|
@ -1,27 +0,0 @@
|
||||
# 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"/>
|
@ -1,26 +0,0 @@
|
||||
# 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"/>
|
@ -1,35 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent c8cc9e66a2701dad6589ff1d950e1cb7738d0854
|
||||
# Parent 44a09b8865d418cda8b7c46dfb70fadc86e22184
|
||||
|
||||
diff --git a/academy/__manifest__.py b/academy/__manifest__.py
|
||||
--- a/academy/__manifest__.py
|
||||
+++ b/academy/__manifest__.py
|
||||
@@ -26,6 +26,7 @@
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'templates.xml',
|
||||
+ 'views.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
diff --git a/academy/views.xml b/academy/views.xml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/views.xml
|
||||
@@ -0,0 +1,13 @@
|
||||
+<odoo>
|
||||
+
|
||||
+ <record id="action_academy_teachers" model="ir.actions.act_window">
|
||||
+ <field name="name">Academy teachers</field>
|
||||
+ <field name="res_model">academy.teachers</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_teachers"
|
||||
+ parent="menu_academy_content"
|
||||
+ action="action_academy_teachers"/>
|
||||
+
|
||||
+</odoo>
|
@ -1,42 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent d5ad2ad84a5db0668e4e08fefb6e3f628c2e14d1
|
||||
# Parent e65c9826721cb2421131399bc9223e03b482d38f
|
||||
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -4,7 +4,9 @@ from odoo import http
|
||||
class Academy(http.Controller):
|
||||
@http.route('/academy/academy/', auth='public')
|
||||
def index(self, **kw):
|
||||
- return "Hello, world"
|
||||
+ return http.request.render('academy.index', {
|
||||
+ 'teachers': ["Diana Padilla", "Jody Caroll", "Lester Vaughn"],
|
||||
+ })
|
||||
|
||||
# @http.route('/academy/academy/objects/', auth='public')
|
||||
# def list(self, **kw):
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -1,14 +1,11 @@
|
||||
<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="index">
|
||||
+ <title>Academy</title>
|
||||
+ <t t-foreach="teachers" t-as="teacher">
|
||||
+ <p><t t-esc="teacher"/></p>
|
||||
+ </t>
|
||||
+ </template>
|
||||
<!-- <template id="object"> -->
|
||||
<!-- <h1><t t-esc="object.display_name"/></h1> -->
|
||||
<!-- <dl> -->
|
@ -1,50 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 7ae0db4c2ddf5d0f7f58db2af8976fcec905cc4e
|
||||
# Parent 06f959831b14dd5d2e0a57d3e14bf919e18f8e3a
|
||||
|
||||
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': ['base'],
|
||||
+ 'depends': ['website'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
--- a/academy/controllers.py
|
||||
+++ b/academy/controllers.py
|
||||
@@ -2,7 +2,7 @@
|
||||
from odoo import http
|
||||
|
||||
class Academy(http.Controller):
|
||||
- @http.route('/academy/academy/', auth='public')
|
||||
+ @http.route('/academy/academy/', auth='public', website=True)
|
||||
def index(self, **kw):
|
||||
Teachers = http.request.env['academy.teachers']
|
||||
return http.request.render('academy.index', {
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
--- a/academy/templates.xml
|
||||
+++ b/academy/templates.xml
|
||||
@@ -1,9 +1,15 @@
|
||||
<odoo>
|
||||
|
||||
<template id="index">
|
||||
- <title>Academy</title>
|
||||
- <t t-foreach="teachers" t-as="teacher">
|
||||
- <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
|
||||
+ <t t-call="website.layout">
|
||||
+ <t t-set="title">Academy</t>
|
||||
+ <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>
|
||||
+ </t>
|
||||
+ </div>
|
||||
+ </div>
|
||||
</t>
|
||||
</template>
|
||||
<!-- <template id="object"> -->
|
@ -2,40 +2,6 @@
|
||||
|
||||
.. _webservices/iap:
|
||||
|
||||
.. using sphinx-patchqueue:
|
||||
* the "queue" directive selects a *series* file which lists the patches in
|
||||
the patch queue, in order of application (from top to bottom). The
|
||||
corresponding patch files should be in the same directory.
|
||||
* the "patch" directive steps to the next patch in the queue, applies it
|
||||
and reifies its content (depending on the extension's configuration, by
|
||||
default it shows the changed files post-diff application, slicing to
|
||||
only display sections affecte by the file)
|
||||
|
||||
.. while it's technically possible to apply and update patches by hand, it's
|
||||
finnicky work and easy to break.
|
||||
|
||||
.. the easiest way is to install quilt (http://savannah.nongnu.org/projects/quilt),
|
||||
go to the directory where you want to reify the addon, then create a
|
||||
"patches" symlink to the patches directory (the iap/ folder next to this
|
||||
file) or set QUILT_PATCHES to that folder.
|
||||
|
||||
.. at that point you have a "primed" queue with no patch applied, and you can
|
||||
move within the queue with "quilt push" and "quilt pop".
|
||||
* "quilt new" creates a new empty patch at the top of the stack
|
||||
* "quilt add" tells quilt to start tracking the file, quilt add *works per
|
||||
patch*, it must be called *every time you want to alter a file within a
|
||||
patch*: quilt is not a full VCS (since it's intended to sit on top of
|
||||
an existing source) and does not do permanent tracking of files
|
||||
* "quilt edit" is a shorthand to "quilt add" then open the file in your
|
||||
editor, I suggest you use that rather than open the edited module
|
||||
normally, it avoids forgetting to "quilt add" before doing your
|
||||
modifications (at which point your modifications are untracked,
|
||||
invisible and depending on your editor may be a PITA to revert & redo)
|
||||
* "quilt refresh" updates the current patch to include pending changes
|
||||
|
||||
.. see "man quilt" for the rest of the subcommands. FWIW I could not get
|
||||
"quilt setup" to do anything useful.
|
||||
|
||||
===============
|
||||
In-App Purchase
|
||||
===============
|
||||
@ -153,8 +119,6 @@ For this example, the service we will provide is ~~mining dogecoins~~ burning
|
||||
Register the service on Odoo
|
||||
----------------------------
|
||||
|
||||
.. queue:: iap_service/series
|
||||
|
||||
.. todo:: complete this part with screenshots
|
||||
|
||||
The first step is to register your service on the IAP endpoint (production
|
||||
@ -258,8 +222,6 @@ A credit pack is essentially a product with five characteristics:
|
||||
Odoo App
|
||||
--------
|
||||
|
||||
.. queue:: iap/series
|
||||
|
||||
.. todo:: does this actually require apps?
|
||||
|
||||
The second step is to develop an `Odoo App`_ which clients can install in their
|
||||
@ -271,13 +233,55 @@ First, we will create an *odoo module* depending on ``iap``. IAP is a standard
|
||||
V11 module and the dependency ensures a local account is properly set up and
|
||||
we will have access to some necessary views and useful helpers.
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-5
|
||||
:caption: `coalroller/__manifest__.py`
|
||||
|
||||
{
|
||||
'name': "Coal Roller",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
}
|
||||
|
||||
Second, the "local" side of the integration. Here we will only be adding an
|
||||
action button to the partners view, but you can of course provide significant
|
||||
local value via your application and additional parts via a remote service.
|
||||
|
||||
.. patch::
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5-7
|
||||
:caption: `coalroller/__manifest__.py`
|
||||
|
||||
{
|
||||
'name': "Coal Roller",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
'data': [
|
||||
'views/res_partner_views.xml',
|
||||
],
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
:emphasize-lines: 1-17
|
||||
:caption: `coalroller/views/res_partner_views.xml`
|
||||
|
||||
<odoo>
|
||||
<record model="ir.ui.view" id="partner_form_coalroll">
|
||||
<field name="name">partner.form.coalroll</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']">
|
||||
<button type="object" name="action_partner_coalroll"
|
||||
class="oe_stat_button" icon="fa-gears">
|
||||
<div class="o_form_field o_stat_info">
|
||||
<span class="o_stat_text">Roll Coal</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
.. image:: images/button.png
|
||||
:align: center
|
||||
@ -301,7 +305,31 @@ In that call, we will need to provide:
|
||||
where :class:`service_name <ServiceName>` is the name of the service registered
|
||||
on IAP endpoint.
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-21
|
||||
:caption: `coalroller/models/res_partner.py`
|
||||
|
||||
from odoo import api, models
|
||||
from odoo.addons.iap import jsonrpc, InsufficientCreditError
|
||||
|
||||
# whichever URL you deploy the service at, here we will run the remote
|
||||
# service in a local Odoo bound to the port 8070
|
||||
DEFAULT_ENDPOINT = 'http://localhost:8070'
|
||||
class Partner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
@api.multi
|
||||
def action_partner_coalroll(self):
|
||||
# fetch the user's token for our service
|
||||
user_token = self.env['iap.account'].get('coalroller')
|
||||
params = {
|
||||
# we don't have any parameter to provide
|
||||
'account_token': user_token.account_token
|
||||
}
|
||||
# ir.config_parameter allows locally overriding the endpoint
|
||||
# for testing & al
|
||||
endpoint = self.env['ir.config_parameter'].sudo().get_param('coalroller.endpoint', DEFAULT_ENDPOINT)
|
||||
jsonrpc(endpoint + '/roll', params=params)
|
||||
return True
|
||||
|
||||
.. note::
|
||||
|
||||
@ -325,20 +353,56 @@ In that call, we will need to provide:
|
||||
Service
|
||||
-------
|
||||
|
||||
.. queue:: iap_service/series
|
||||
|
||||
Though that is not *required*, since ``iap`` provides both a client helper
|
||||
for JSON-RPC2_ calls (:func:`~odoo.addons.iap.jsonrpc`) and a service helper
|
||||
for transactions (:class:`~odoo.addons.iap.models.iap.charge`) we will also be
|
||||
implementing the service side as an Odoo module:
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-5
|
||||
:caption: `coalroller_service/__manifest__.py`
|
||||
|
||||
{
|
||||
'name': "Coal Roller Service",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
}
|
||||
|
||||
Since the query from the client comes as JSON-RPC2_ we will need the
|
||||
corresponding controller which can call :class:`~odoo.addons.iap.models.iap.charge` and
|
||||
perform the service within:
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-27
|
||||
:caption: `coalroller_service/controllers/main.py`
|
||||
|
||||
from passlib import pwd, hash
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.iap import charge
|
||||
|
||||
class CoalBurnerController(http.Controller):
|
||||
@http.route('/roll', type='json', auth='none', csrf='false')
|
||||
def roll(self, account_token):
|
||||
# the service key *is a secret*, it should not be committed in
|
||||
# the source
|
||||
service_key = http.request.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
|
||||
|
||||
# we charge 1 credit for 10 seconds of CPU
|
||||
cost = 1
|
||||
# TODO: allow the user to specify how many (tens of seconds) of CPU they want to use
|
||||
with charge(http.request.env, service_key, account_token, cost):
|
||||
|
||||
# 10 seconds of CPU per credit
|
||||
end = time.time() (10 * cost)
|
||||
while time.time() < end:
|
||||
# we will use CPU doing useful things: generating and
|
||||
# hashing passphrases
|
||||
p = pwd.genphrase()
|
||||
h = hash.pbkdf2_sha512.hash(p)
|
||||
# here we don't have anything useful to the client, an error
|
||||
# will be raised & transmitted in case of issue, if no error
|
||||
# is raised we did the job
|
||||
|
||||
.. todo:: for the actual IAP will the "portal" page be on odoo.com or iap.odoo.com?
|
||||
|
||||
@ -382,8 +446,66 @@ parameters we can use to make things clearer to the end-user.
|
||||
service provider is requesting, its purpose is to tell your users why
|
||||
they should be interested in your IAP offers.
|
||||
|
||||
.. patch::
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5-7
|
||||
:caption: `coalroller_service/__manifest__.py`
|
||||
|
||||
{
|
||||
'name': "Coal Roller Service",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
'data': [
|
||||
'views/no-credit.xml',
|
||||
],
|
||||
}
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10-12
|
||||
:caption: `coalroller_service/controllers/main.py`
|
||||
|
||||
@http.route('/roll', type='json', auth='none', csrf='false')
|
||||
def roll(self, account_token):
|
||||
# the service key *is a secret*, it should not be committed in
|
||||
# the source
|
||||
service_key = http.request.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
|
||||
|
||||
# we charge 1 credit for 10 seconds of CPU
|
||||
cost = 1
|
||||
# TODO: allow the user to specify how many (tens of seconds) of CPU they want to use
|
||||
with charge(http.request.env, service_key, account_token, cost,
|
||||
description="We're just obeying orders",
|
||||
credit_template='coalroller_service.no_credit'):
|
||||
|
||||
# 10 seconds of CPU per credit
|
||||
end = time.time() (10 * cost)
|
||||
while time.time() < end:
|
||||
# we will use CPU doing useful things: generating and
|
||||
# hashing passphrases
|
||||
p = pwd.genphrase()
|
||||
h = hash.pbkdf2_sha512.hash(p)
|
||||
|
||||
.. code-block:: xml
|
||||
:emphasize-lines: 1-18
|
||||
:caption: `coalroller_service/views/no-credit.xml`
|
||||
|
||||
<odoo>
|
||||
<template id="no_credit" name="No credit warning">
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-7 offset-lg-1 mt32 mb32">
|
||||
<h2>Consume electricity doing nothing useful!</h2>
|
||||
<ul>
|
||||
<li>Heat our state of the art data center for no reason</li>
|
||||
<li>Use multiple watts for only 0.1€</li>
|
||||
<li>Roll coal without going outside</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
.. TODO:: how do you test your service?
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
Index: coalroller/coalroller/__manifest__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/__manifest__.py
|
||||
@@ -0,0 +1,5 @@
|
||||
+{
|
||||
+ 'name': "Coal Roller",
|
||||
+ 'category': 'Tools',
|
||||
+ 'depends': ['iap'],
|
||||
+}
|
||||
Index: coalroller/coalroller/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/__init__.py
|
||||
@@ -0,0 +1 @@
|
||||
+# -*- coding: utf-8 -*-
|
@ -1,34 +0,0 @@
|
||||
Index: coalroller/coalroller/__manifest__.py
|
||||
===================================================================
|
||||
--- coalroller.orig/coalroller/__manifest__.py
|
||||
+++ coalroller/coalroller/__manifest__.py
|
||||
@@ -2,4 +2,7 @@
|
||||
'name': "Coal Roller",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
+ 'data': [
|
||||
+ 'views/views.xml',
|
||||
+ ],
|
||||
}
|
||||
Index: coalroller/coalroller/views/views.xml
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/views/views.xml
|
||||
@@ -0,0 +1,17 @@
|
||||
+<odoo>
|
||||
+ <record model="ir.ui.view" id="partner_form_coalroll">
|
||||
+ <field name="name">partner.form.coalroll</field>
|
||||
+ <field name="model">res.partner</field>
|
||||
+ <field name="inherit_id" ref="base.view_partner_form" />
|
||||
+ <field name="arch" type="xml">
|
||||
+ <xpath expr="//div[@name='button_box']">
|
||||
+ <button type="object" name="action_partner_coalroll"
|
||||
+ class="oe_stat_button" icon="fa-gears">
|
||||
+ <div class="o_form_field o_stat_info">
|
||||
+ <span class="o_stat_text">Roll Coal</span>
|
||||
+ </div>
|
||||
+ </button>
|
||||
+ </xpath>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+</odoo>
|
@ -1,40 +0,0 @@
|
||||
Index: coalroller/coalroller/__init__.py
|
||||
===================================================================
|
||||
--- coalroller.orig/coalroller/__init__.py
|
||||
+++ coalroller/coalroller/__init__.py
|
||||
@@ -1 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
+from . import models
|
||||
Index: coalroller/coalroller/models/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/models/__init__.py
|
||||
@@ -0,0 +1 @@
|
||||
+from . import res_partner
|
||||
Index: coalroller/coalroller/models/res_partner.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/models/res_partner.py
|
||||
@@ -0,0 +1,22 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+from odoo import api, models
|
||||
+from odoo.addons.iap import jsonrpc, InsufficientCreditError
|
||||
+
|
||||
+# whichever URL you deploy the service at, here we will run the remote
|
||||
+# service in a local Odoo bound to the port 8070
|
||||
+DEFAULT_ENDPOINT = 'http://localhost:8070'
|
||||
+class Partner(models.Model):
|
||||
+ _inherit = 'res.partner'
|
||||
+ @api.multi
|
||||
+ def action_partner_coalroll(self):
|
||||
+ # fetch the user's token for our service
|
||||
+ user_token = self.env['iap.account'].get('coalroller')
|
||||
+ params = {
|
||||
+ # we don't have any parameter to provide
|
||||
+ 'account_token': user_token.account_token
|
||||
+ }
|
||||
+ # ir.config_parameter allows locally overriding the endpoint
|
||||
+ # for testing & al
|
||||
+ endpoint = self.env['ir.config_parameter'].sudo().get_param('coalroller.endpoint', DEFAULT_ENDPOINT)
|
||||
+ jsonrpc(endpoint + '/roll', params=params)
|
||||
+ return True
|
@ -1,3 +0,0 @@
|
||||
01-init
|
||||
02-button
|
||||
03-callback
|
@ -1,16 +0,0 @@
|
||||
Index: coalroller_service/coalroller_service/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/__init__.py
|
||||
@@ -0,0 +1 @@
|
||||
+# -*- encoding: utf-8 -*-
|
||||
Index: coalroller_service/coalroller_service/__manifest__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/__manifest__.py
|
||||
@@ -0,0 +1,5 @@
|
||||
+{
|
||||
+ 'name': "Coal Roller Service",
|
||||
+ 'category': 'Tools',
|
||||
+ 'depends': ['iap'],
|
||||
+}
|
@ -1,48 +0,0 @@
|
||||
Index: coalroller_service/coalroller_service/controllers/main.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/controllers/main.py
|
||||
@@ -0,0 +1,29 @@
|
||||
+import time
|
||||
+
|
||||
+from passlib import pwd, hash
|
||||
+
|
||||
+from odoo import http
|
||||
+from odoo.addons.iap import charge
|
||||
+
|
||||
+class CoalBurnerController(http.Controller):
|
||||
+ @http.route('/roll', type='json', auth='none', csrf='false')
|
||||
+ def roll(self, account_token):
|
||||
+ # the service key *is a secret*, it should not be committed in
|
||||
+ # the source
|
||||
+ service_key = self.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
|
||||
+
|
||||
+ # we charge 1 credit for 10 seconds of CPU
|
||||
+ cost = 1
|
||||
+ # TODO: allow the user to specify how many (tens of seconds) of CPU they want to use
|
||||
+ with charge(http.request.env, service_key, account_token, cost):
|
||||
+
|
||||
+ # 10 seconds of CPU per credit
|
||||
+ end = time.time() + (10 * cost)
|
||||
+ while time.time() < end:
|
||||
+ # we will use CPU doing useful things: generating and
|
||||
+ # hashing passphrases
|
||||
+ p = pwd.genphrase()
|
||||
+ h = hash.pbkdf2_sha512.hash(p)
|
||||
+ # here we don't have anything useful to the client, an error
|
||||
+ # will be raised & transmitted in case of issue, if no error
|
||||
+ # is raised we did the job
|
||||
Index: coalroller_service/coalroller_service/controllers/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/controllers/__init__.py
|
||||
@@ -0,0 +1,2 @@
|
||||
+# -*- encoding: utf-8 -*-
|
||||
+from . import main
|
||||
Index: coalroller_service/coalroller_service/__init__.py
|
||||
===================================================================
|
||||
--- coalroller_service.orig/coalroller_service/__init__.py
|
||||
+++ coalroller_service/coalroller_service/__init__.py
|
||||
@@ -1 +1,2 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
+from . import controllers
|
@ -1,56 +0,0 @@
|
||||
Index: coalroller_service/coalroller_service/controllers/main.py
|
||||
===================================================================
|
||||
--- coalroller_service.orig/coalroller_service/controllers/main.py
|
||||
+++ coalroller_service/coalroller_service/controllers/main.py
|
||||
@@ -10,12 +10,14 @@ class CoalBurnerController(http.Controll
|
||||
def roll(self, account_token):
|
||||
# the service key *is a secret*, it should not be committed in
|
||||
# the source
|
||||
- service_key = self.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
|
||||
+ service_key = http.request.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
|
||||
|
||||
# we charge 1 credit for 10 seconds of CPU
|
||||
cost = 1
|
||||
# TODO: allow the user to specify how many (tens of seconds) of CPU they want to use
|
||||
- with charge(http.request.env, service_key, account_token, cost):
|
||||
+ with charge(http.request.env, service_key, account_token, cost,
|
||||
+ description="We're just obeying orders",
|
||||
+ credit_template='coalroller_service.no_credit'):
|
||||
|
||||
# 10 seconds of CPU per credit
|
||||
end = time.time() + (10 * cost)
|
||||
Index: coalroller_service/coalroller_service/views/no-credit.xml
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/views/no-credit.xml
|
||||
@@ -0,0 +1,18 @@
|
||||
+<odoo>
|
||||
+ <template id="no_credit" name="No credit warning">
|
||||
+ <div>
|
||||
+ <div class="container-fluid">
|
||||
+ <div class="row">
|
||||
+ <div class="col-md-7 offset-lg-1 mt32 mb32">
|
||||
+ <h2>Consume electricity doing nothing useful!</h2>
|
||||
+ <ul>
|
||||
+ <li>Heat our state of the art data center for no reason</li>
|
||||
+ <li>Use multiple watts for only 0.1€</li>
|
||||
+ <li>Roll coal without going outside</li>
|
||||
+ </ul>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ </template>
|
||||
+</odoo>
|
||||
Index: coalroller_service/coalroller_service/__manifest__.py
|
||||
===================================================================
|
||||
--- coalroller_service.orig/coalroller_service/__manifest__.py
|
||||
+++ coalroller_service/coalroller_service/__manifest__.py
|
||||
@@ -2,4 +2,7 @@
|
||||
'name': "Coal Roller Service",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
+ 'data': [
|
||||
+ 'views/no-credit.xml',
|
||||
+ ],
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
01-init
|
||||
02-tnx
|
||||
03-template
|
@ -4,4 +4,3 @@ pygments-csv-lexer~=0.1
|
||||
pysass~=0.1.0
|
||||
sphinx~=3.0
|
||||
werkzeug==0.14.1
|
||||
sphinx-patchqueue>=1.0
|
||||
|
Loading…
Reference in New Issue
Block a user