[MERGE] Forward-port of branch 13.0 to 12.0
This commit is contained in:
commit
6aea496f26
5
conf.py
5
conf.py
@ -102,9 +102,6 @@ extensions = [
|
||||
|
||||
'exercise_admonition',
|
||||
|
||||
# Build code from git patches
|
||||
'patchqueue',
|
||||
|
||||
# Redirection generator
|
||||
'redirects',
|
||||
|
||||
@ -308,7 +305,7 @@ def _generate_alternate_urls(app, pagename, templatename, context, doctree):
|
||||
|
||||
def _build_url(_version=None, _lang=None):
|
||||
if app.config.is_remote_build:
|
||||
# Project root like https://odoo.com/documentation/14.0/fr
|
||||
# Project root like https://www.odoo.com/documentation
|
||||
_root = app.config.project_root
|
||||
else:
|
||||
# Project root like .../documentation/_build/html/14.0/fr
|
||||
|
@ -10,3 +10,4 @@ Devices
|
||||
devices/camera
|
||||
devices/footswitch
|
||||
devices/printer
|
||||
devices/scale
|
||||
|
55
content/applications/productivity/iot/devices/scale.rst
Normal file
55
content/applications/productivity/iot/devices/scale.rst
Normal file
@ -0,0 +1,55 @@
|
||||
===============
|
||||
Connect a Scale
|
||||
===============
|
||||
|
||||
When using your **IoT Box** in Odoo, you could need to use a scale. Doing so is easy and convenient
|
||||
as it can be done in a few steps. Then, you can use it in your **Point of Sale app** to weigh your
|
||||
products, which is helpful if their price are based on it.
|
||||
|
||||
Connection
|
||||
==========
|
||||
|
||||
To link the scale to the **IoT Box**, connect them with a cable.
|
||||
|
||||
.. note::
|
||||
In some cases, a serial to USB adapter may be needed.
|
||||
|
||||
If your scale is `compatibale with Odoo IoT Box <https://www.odoo.com/page/iot-hardware>`_, there
|
||||
is no need to set up anything because it will be automatically detected as soon as it is connected.
|
||||
|
||||
.. image:: scale/iot-choice.png
|
||||
:align: center
|
||||
:alt: IOT box auto detection.
|
||||
|
||||
You may need to restart the box and download your scales’ drivers from the box in some cases. To do
|
||||
so, go to the *IoT Box Home Page* and click on *drivers list*. Then, click on load drivers.
|
||||
|
||||
.. image:: scale/driver-list.png
|
||||
:align: center
|
||||
:alt: View of the IoT box settings and driver list.
|
||||
|
||||
Use a Scale in Point of Sale
|
||||
============================
|
||||
|
||||
To use the scale in your *Point of Sale* app, go to :menuselection:`Point of Sale --> Configuration
|
||||
--> Point of Sale`, open the one you want to configure, then click on *Edit* and enable the *IoT
|
||||
Box* feature.
|
||||
|
||||
.. image:: scale/iot-box-pos.png
|
||||
:align: center
|
||||
:alt: View of the IoT box feature inside of the PoS settings.
|
||||
|
||||
Now, choose the *IoT Box* in the dropdown menu and check the *Electronic Scale* option. Then, you
|
||||
hit save.
|
||||
|
||||
.. image:: scale/electronic-scale-feature.png
|
||||
:align: center
|
||||
:alt: List of the external tools that can be used with PoS and the IoT box.
|
||||
|
||||
The scale is now available in all your *PoS* sessions. Then, if a product has a price per weight
|
||||
set, clicking on it on the *PoS* screen opens the scale screen, where the cashier can weigh the
|
||||
product and add the correct price to the cart.
|
||||
|
||||
.. image:: scale/scale-view.png
|
||||
:align: center
|
||||
:alt: Electronic Scale dashboard view when no items are being weighed.
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
@ -6,7 +6,6 @@ eCommerce
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
ecommerce/overview
|
||||
ecommerce/getting_started
|
||||
ecommerce/managing_products
|
||||
ecommerce/taxes
|
||||
|
@ -1,9 +0,0 @@
|
||||
=================
|
||||
Overview
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
overview/introduction
|
||||
|
@ -1,24 +0,0 @@
|
||||
==============================
|
||||
Introduction to Odoo eCommerce
|
||||
==============================
|
||||
|
||||
.. youtube:: tR0xandHlhU
|
||||
:align: right
|
||||
:width: 700
|
||||
:height: 394
|
||||
|
||||
The documentation will help you go live with your eCommerce website in no time.
|
||||
The topics follow the buying process:
|
||||
|
||||
* Product Page
|
||||
* Shop Page
|
||||
* Pricing
|
||||
* Taxes
|
||||
* Checkout process
|
||||
* Upselling & cross-selling
|
||||
* Payment
|
||||
* Shipping & Tracking
|
||||
|
||||
.. seealso::
|
||||
|
||||
* :doc:`../../website/publish/domain_name`
|
@ -1,6 +1,4 @@
|
||||
|
||||
.. queue:: backend/series
|
||||
|
||||
.. _howto/base:
|
||||
.. _howto/module:
|
||||
|
||||
@ -114,14 +112,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
|
||||
-------------------------
|
||||
|
||||
@ -216,12 +206,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
|
||||
----------
|
||||
|
||||
@ -260,12 +244,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.
|
||||
|
||||
@ -315,14 +293,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
|
||||
===========
|
||||
|
||||
@ -417,22 +387,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
|
||||
@ -480,10 +440,6 @@ searching on the ``name`` field.
|
||||
|
||||
Allow searching for courses based on their title or their description.
|
||||
|
||||
.. only:: solutions
|
||||
|
||||
.. patch::
|
||||
|
||||
Relations between models
|
||||
========================
|
||||
|
||||
@ -500,18 +456,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
|
||||
-----------------
|
||||
|
||||
@ -562,25 +506,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
|
||||
@ -588,13 +518,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
|
||||
===========
|
||||
|
||||
@ -694,21 +617,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
|
||||
#######
|
||||
|
||||
@ -741,31 +649,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
|
||||
==================================
|
||||
|
||||
@ -834,13 +723,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
|
||||
--------------
|
||||
|
||||
@ -871,15 +753,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 records with an ``active`` field set
|
||||
to ``False`` invisible.
|
||||
|
||||
Onchange
|
||||
========
|
||||
|
||||
@ -925,10 +798,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
|
||||
=================
|
||||
|
||||
@ -956,10 +825,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
|
||||
@ -974,10 +839,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
|
||||
@ -987,10 +848,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
|
||||
==============
|
||||
|
||||
@ -1038,12 +895,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
|
||||
---------
|
||||
|
||||
@ -1073,19 +924,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
|
||||
------------
|
||||
|
||||
@ -1135,10 +973,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
|
||||
-----
|
||||
|
||||
@ -1163,13 +997,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
|
||||
|
||||
#. Add the gantt view's definition, and add the gantt view to the
|
||||
*Session* model's action
|
||||
|
||||
.. patch::
|
||||
|
||||
Graph views
|
||||
-----------
|
||||
|
||||
@ -1221,13 +1048,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
|
||||
------
|
||||
|
||||
@ -1248,13 +1068,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
|
||||
========
|
||||
|
||||
@ -1291,16 +1104,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,
|
||||
@ -1309,17 +1112,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
|
||||
------------
|
||||
|
||||
@ -1353,14 +1145,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::
|
||||
|
||||
.. _howto/module/wizard:
|
||||
|
||||
Wizards
|
||||
=======
|
||||
|
||||
@ -1386,12 +1170,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
|
||||
-----------------
|
||||
|
||||
@ -1430,28 +1208,16 @@ the action is "bound" to.
|
||||
#. 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
|
||||
====================
|
||||
|
||||
@ -1491,39 +1257,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 --> Languages`)
|
||||
#. 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
|
||||
=========
|
||||
|
||||
@ -1623,10 +1356,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
|
||||
----------
|
||||
|
||||
@ -1638,21 +1367,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
|
||||
@@ -115,9 +115,10 @@
|
||||
<field name="name">session.tree</field>
|
||||
<field name="model">openacademy.session</field>
|
||||
<field name="arch" type="xml">
|
||||
- <tree string="Session Tree">
|
||||
+ <tree string="Session Tree" decoration-info="duration<5" decoration-danger="duration>15">
|
||||
<field name="name"/>
|
||||
<field name="course_id"/>
|
||||
+ <field name="duration" invisible="1"/>
|
||||
<field name="taken_seats" widget="progressbar"/>
|
||||
</tree>
|
||||
</field>
|
@ -1,53 +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,34 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<odoo>
|
||||
+
|
||||
+ <!-- window action -->
|
||||
+ <!--
|
||||
+ The following tag is an action definition for a "window action",
|
||||
+ that is an action opening a view or a set of views
|
||||
+ -->
|
||||
+ <record model="ir.actions.act_window" id="course_list_action">
|
||||
+ <field name="name">Courses</field>
|
||||
+ <field name="res_model">openacademy.course</field>
|
||||
+ <field name="view_mode">tree,form</field>
|
||||
+ <field name="help" type="html">
|
||||
+ <p class="o_view_nocontent_smiling_face">Create the first course
|
||||
+ </p>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+
|
||||
+ <!-- top level menu: no parent -->
|
||||
+ <menuitem id="main_openacademy_menu" name="Open Academy"/>
|
||||
+ <!-- A first level in the left side menu is needed
|
||||
+ before using action= attribute -->
|
||||
+ <menuitem id="openacademy_menu" name="Open Academy"
|
||||
+ parent="main_openacademy_menu"/>
|
||||
+ <!-- the following menuitem should appear *after*
|
||||
+ its parent openacademy_menu and *after* its
|
||||
+ action course_list_action -->
|
||||
+ <menuitem id="courses_menu" name="Courses" parent="openacademy_menu"
|
||||
+ action="course_list_action"/>
|
||||
+ <!-- Full id location:
|
||||
+ action="openacademy.course_list_action"
|
||||
+ It is not required when it is the same module -->
|
||||
+
|
||||
+</odoo>
|
@ -1,77 +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
|
||||
@@ -124,10 +124,21 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
+ <!-- calendar view -->
|
||||
+ <record model="ir.ui.view" id="session_calendar_view">
|
||||
+ <field name="name">session.calendar</field>
|
||||
+ <field name="model">openacademy.session</field>
|
||||
+ <field name="arch" type="xml">
|
||||
+ <calendar string="Session Calendar" date_start="start_date" date_stop="end_date" color="instructor_id">
|
||||
+ <field name="name"/>
|
||||
+ </calendar>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+
|
||||
<record model="ir.actions.act_window" id="session_list_action">
|
||||
<field name="name">Sessions</field>
|
||||
<field name="res_model">openacademy.session</field>
|
||||
- <field name="view_mode">tree,form</field>
|
||||
+ <field name="view_mode">tree,form,calendar</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="session_menu" name="Sessions"
|
@ -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
|
||||
@@ -99,6 +99,7 @@
|
||||
<field name="start_date"/>
|
||||
<field name="duration"/>
|
||||
<field name="seats"/>
|
||||
+ <field name="taken_seats" widget="progressbar"/>
|
||||
</group>
|
||||
</group>
|
||||
<label for="attendee_ids"/>
|
||||
@@ -116,6 +117,7 @@
|
||||
<tree string="Session Tree">
|
||||
<field name="name"/>
|
||||
<field name="course_id"/>
|
||||
+ <field name="taken_seats" widget="progressbar"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
@ -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,27 +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,19 @@ class Course(models.Model):
|
||||
session_ids = fields.One2many(
|
||||
'openacademy.session', 'course_id', string="Sessions")
|
||||
|
||||
+ def copy(self, default=None):
|
||||
+ default = dict(default or {})
|
||||
+
|
||||
+ copied_count = self.search_count(
|
||||
+ [('name', '=like', u"Copy of {}%".format(self.name))])
|
||||
+ if not copied_count:
|
||||
+ new_name = u"Copy of {}".format(self.name)
|
||||
+ else:
|
||||
+ new_name = u"Copy of {} ({})".format(self.name, copied_count)
|
||||
+
|
||||
+ default['name'] = new_name
|
||||
+ return super(Course, self).copy(default)
|
||||
+
|
||||
_sql_constraints = [
|
||||
('name_description_check',
|
||||
'CHECK(name != description)',
|
@ -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/14.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
+ # for the full list
|
||||
+ 'category': 'Test',
|
||||
+ 'version': '0.1',
|
||||
+
|
||||
+ # any module necessary for this one to work correctly
|
||||
+ 'depends': ['base'],
|
||||
+
|
||||
+ # always loaded
|
||||
+ 'data': [
|
||||
+ # 'security/ir.model.access.csv',
|
||||
+ 'templates.xml',
|
||||
+ ],
|
||||
+ # only loaded in demonstration mode
|
||||
+ 'demo': [
|
||||
+ 'demo.xml',
|
||||
+ ],
|
||||
+}
|
||||
Index: addons/openacademy/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
||||
+++ addons/openacademy/__init__.py 2014-08-26 17:25:49.791783523 +0200
|
||||
@@ -0,0 +1,3 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+from . import controllers
|
||||
+from . import models
|
||||
Index: addons/openacademy/controllers.py
|
||||
===================================================================
|
||||
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
||||
+++ addons/openacademy/controllers.py 2014-08-26 17:25:49.791783523 +0200
|
||||
@@ -0,0 +1,20 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+from odoo import http
|
||||
+
|
||||
+# class Openacademy(http.Controller):
|
||||
+# @http.route('/openacademy/openacademy/', auth='public')
|
||||
+# def index(self, **kw):
|
||||
+# return "Hello, world"
|
||||
+
|
||||
+# @http.route('/openacademy/openacademy/objects/', auth='public')
|
||||
+# def list(self, **kw):
|
||||
+# return http.request.render('openacademy.listing', {
|
||||
+# 'root': '/openacademy/openacademy',
|
||||
+# 'objects': http.request.env['openacademy.openacademy'].search([]),
|
||||
+# })
|
||||
+
|
||||
+# @http.route('/openacademy/openacademy/objects/<model("openacademy.openacademy"):obj>/', auth='public')
|
||||
+# def object(self, obj, **kw):
|
||||
+# return http.request.render('openacademy.object', {
|
||||
+# 'object': obj
|
||||
+# })
|
||||
Index: addons/openacademy/demo.xml
|
||||
===================================================================
|
||||
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
||||
+++ addons/openacademy/demo.xml 2014-08-26 17:25:49.791783523 +0200
|
||||
@@ -0,0 +1,25 @@
|
||||
+<odoo>
|
||||
+
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object0" model="openacademy.openacademy"> -->
|
||||
+ <!-- <field name="name">Object 0</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object1" model="openacademy.openacademy"> -->
|
||||
+ <!-- <field name="name">Object 1</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object2" model="openacademy.openacademy"> -->
|
||||
+ <!-- <field name="name">Object 2</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object3" model="openacademy.openacademy"> -->
|
||||
+ <!-- <field name="name">Object 3</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object4" model="openacademy.openacademy"> -->
|
||||
+ <!-- <field name="name">Object 4</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+
|
||||
+</odoo>
|
||||
Index: addons/openacademy/models.py
|
||||
===================================================================
|
||||
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
||||
+++ addons/openacademy/models.py 2014-08-26 17:25:49.791783523 +0200
|
||||
@@ -0,0 +1,8 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+
|
||||
+from odoo import models, fields, api
|
||||
+
|
||||
+# class openacademy(models.Model):
|
||||
+# _name = 'openacademy.openacademy'
|
||||
+
|
||||
+# name = fields.Char()
|
||||
Index: addons/openacademy/security/ir.model.access.csv
|
||||
===================================================================
|
||||
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
||||
+++ addons/openacademy/security/ir.model.access.csv 2014-08-26 17:25:49.791783523 +0200
|
||||
@@ -0,0 +1,2 @@
|
||||
+id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
|
||||
+access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0
|
||||
Index: addons/openacademy/templates.xml
|
||||
===================================================================
|
||||
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
||||
+++ addons/openacademy/templates.xml 2014-08-26 17:25:49.791783523 +0200
|
||||
@@ -0,0 +1,22 @@
|
||||
+<odoo>
|
||||
+
|
||||
+ <!-- <template id="listing"> -->
|
||||
+ <!-- <ul> -->
|
||||
+ <!-- <li t-foreach="objects" t-as="object"> -->
|
||||
+ <!-- <a t-attf-href="{{ root }}/objects/{{ object.id }}"> -->
|
||||
+ <!-- <t t-esc="object.display_name"/> -->
|
||||
+ <!-- </a> -->
|
||||
+ <!-- </li> -->
|
||||
+ <!-- </ul> -->
|
||||
+ <!-- </template> -->
|
||||
+ <!-- <template id="object"> -->
|
||||
+ <!-- <h1><t t-esc="object.display_name"/></h1> -->
|
||||
+ <!-- <dl> -->
|
||||
+ <!-- <t t-foreach="object._fields" t-as="field"> -->
|
||||
+ <!-- <dt><t t-esc="field"/></dt> -->
|
||||
+ <!-- <dd><t t-esc="object[field]"/></dd> -->
|
||||
+ <!-- </t> -->
|
||||
+ <!-- </dl> -->
|
||||
+ <!-- </template> -->
|
||||
+
|
||||
+</odoo>
|
@ -1,91 +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,62 @@
|
||||
+<?xml version="1.0"?>
|
||||
+<odoo>
|
||||
+
|
||||
+ <record model="ir.actions.act_window" id="act_session_graph">
|
||||
+ <field name="name">Attendees by course</field>
|
||||
+ <field name="res_model">openacademy.session</field>
|
||||
+ <field name="view_mode">graph</field>
|
||||
+ <field name="view_id"
|
||||
+ ref="openacademy.openacademy_session_graph_view"/>
|
||||
+ </record>
|
||||
+ <record model="ir.actions.act_window" id="act_session_calendar">
|
||||
+ <field name="name">Sessions</field>
|
||||
+ <field name="res_model">openacademy.session</field>
|
||||
+ <field name="view_mode">calendar</field>
|
||||
+ <field name="view_id" ref="openacademy.session_calendar_view"/>
|
||||
+ </record>
|
||||
+ <record model="ir.actions.act_window" id="act_course_list">
|
||||
+ <field name="name">Courses</field>
|
||||
+ <field name="res_model">openacademy.course</field>
|
||||
+ <field name="view_mode">tree,form</field>
|
||||
+ </record>
|
||||
+ <record model="ir.ui.view" id="board_session_form">
|
||||
+ <field name="name">Session Dashboard Form</field>
|
||||
+ <field name="model">board.board</field>
|
||||
+ <field name="type">form</field>
|
||||
+ <field name="arch" type="xml">
|
||||
+ <form string="Session Dashboard">
|
||||
+ <board style="2-1">
|
||||
+ <column>
|
||||
+ <action
|
||||
+ string="Attendees by course"
|
||||
+ name="%(act_session_graph)d"
|
||||
+ height="150"
|
||||
+ width="510"/>
|
||||
+ <action
|
||||
+ string="Sessions"
|
||||
+ name="%(act_session_calendar)d"/>
|
||||
+ </column>
|
||||
+ <column>
|
||||
+ <action
|
||||
+ string="Courses"
|
||||
+ name="%(act_course_list)d"/>
|
||||
+ </column>
|
||||
+ </board>
|
||||
+ </form>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+ <record model="ir.actions.act_window" id="open_board_session">
|
||||
+ <field name="name">Session Dashboard</field>
|
||||
+ <field name="res_model">board.board</field>
|
||||
+ <field name="view_mode">form</field>
|
||||
+ <field name="usage">menu</field>
|
||||
+ <field name="view_id" ref="board_session_form"/>
|
||||
+ </record>
|
||||
+
|
||||
+ <menuitem
|
||||
+ name="Session Dashboard" parent="base.menu_reporting_dashboard"
|
||||
+ action="open_board_session"
|
||||
+ sequence="1"
|
||||
+ id="menu_board_session"/>
|
||||
+
|
||||
+</odoo>
|
@ -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
|
||||
@@ -94,6 +94,7 @@
|
||||
<field name="course_id"/>
|
||||
<field name="name"/>
|
||||
<field name="instructor_id"/>
|
||||
+ <field name="active"/>
|
||||
</group>
|
||||
<group string="Schedule">
|
||||
<field name="start_date"/>
|
@ -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,30 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent dba00a105dd2a82490394b8dec5fea5f1d8847e1
|
||||
# Parent f4374b6e2e661e0782e396b24c57c1eb97d13288
|
||||
|
||||
diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml
|
||||
--- a/openacademy/views/openacademy.xml
|
||||
+++ b/openacademy/views/openacademy.xml
|
||||
@@ -142,10 +142,21 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
+ <record model="ir.ui.view" id="session_gantt_view">
|
||||
+ <field name="name">session.gantt</field>
|
||||
+ <field name="model">openacademy.session</field>
|
||||
+ <field name="arch" type="xml">
|
||||
+ <gantt string="Session Gantt"
|
||||
+ date_start="start_date" date_stop="end_date"
|
||||
+ default_group_by='instructor_id'>
|
||||
+ </gantt>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+
|
||||
<record model="ir.actions.act_window" id="session_list_action">
|
||||
<field name="name">Sessions</field>
|
||||
<field name="res_model">openacademy.session</field>
|
||||
- <field name="view_mode">tree,form,calendar</field>
|
||||
+ <field name="view_mode">tree,form,calendar,gantt</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="session_menu" name="Sessions"
|
@ -1,55 +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
|
||||
@@ -60,6 +60,9 @@ class Session(models.Model):
|
||||
end_date = fields.Date(string="End Date", store=True,
|
||||
compute='_get_end_date', inverse='_set_end_date')
|
||||
|
||||
+ attendees_count = fields.Integer(
|
||||
+ string="Attendees count", compute='_get_attendees_count', store=True)
|
||||
+
|
||||
@api.depends('seats', 'attendee_ids')
|
||||
def _taken_seats(self):
|
||||
for r in self:
|
||||
@@ -106,6 +109,11 @@ class Session(models.Model):
|
||||
# so add one day to get 5 days instead
|
||||
r.duration = (r.end_date - r.start_date).days + 1
|
||||
|
||||
+ @api.depends('attendee_ids')
|
||||
+ def _get_attendees_count(self):
|
||||
+ for r in self:
|
||||
+ r.attendees_count = len(r.attendee_ids)
|
||||
+
|
||||
@api.constrains('instructor_id', 'attendee_ids')
|
||||
def _check_instructor_not_in_attendees(self):
|
||||
for r in self:
|
||||
diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml
|
||||
--- a/openacademy/views/openacademy.xml
|
||||
+++ b/openacademy/views/openacademy.xml
|
||||
@@ -153,10 +153,21 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
+ <record model="ir.ui.view" id="openacademy_session_graph_view">
|
||||
+ <field name="name">openacademy.session.graph</field>
|
||||
+ <field name="model">openacademy.session</field>
|
||||
+ <field name="arch" type="xml">
|
||||
+ <graph string="Participations by Courses">
|
||||
+ <field name="course_id"/>
|
||||
+ <field name="attendees_count" type="measure"/>
|
||||
+ </graph>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+
|
||||
<record model="ir.actions.act_window" id="session_list_action">
|
||||
<field name="name">Sessions</field>
|
||||
<field name="res_model">openacademy.session</field>
|
||||
- <field name="view_mode">tree,form,calendar,gantt</field>
|
||||
+ <field name="view_mode">tree,form,calendar,gantt,graph</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="session_menu" name="Sessions"
|
@ -1,77 +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
|
||||
@@ -165,10 +165,56 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
+ <record model="ir.ui.view" id="view_openacad_session_kanban">
|
||||
+ <field name="name">openacademy.session.kanban</field>
|
||||
+ <field name="model">openacademy.session</field>
|
||||
+ <field name="arch" type="xml">
|
||||
+ <kanban default_group_by="course_id">
|
||||
+ <field name="color"/>
|
||||
+ <templates>
|
||||
+ <t t-name="kanban-box">
|
||||
+ <div
|
||||
+ t-attf-class="oe_kanban_color_{{kanban_getcolor(record.color.raw_value)}}
|
||||
+ oe_kanban_global_click_edit oe_semantic_html_override
|
||||
+ oe_kanban_card {{record.group_fancy==1 ? 'oe_kanban_card_fancy' : ''}}">
|
||||
+ <div class="oe_dropdown_kanban">
|
||||
+ <!-- dropdown menu -->
|
||||
+ <div class="oe_dropdown_toggle">
|
||||
+ <i class="fa fa-bars fa-lg" title="Manage" aria-label="Manage"/>
|
||||
+ <ul class="oe_dropdown_menu">
|
||||
+ <li>
|
||||
+ <a type="delete">Delete</a>
|
||||
+ </li>
|
||||
+ <li>
|
||||
+ <ul class="oe_kanban_colorpicker"
|
||||
+ data-field="color"/>
|
||||
+ </li>
|
||||
+ </ul>
|
||||
+ </div>
|
||||
+ <div class="oe_clear"></div>
|
||||
+ </div>
|
||||
+ <div t-attf-class="oe_kanban_content">
|
||||
+ <!-- title -->
|
||||
+ Session name:
|
||||
+ <field name="name"/>
|
||||
+ <br/>
|
||||
+ Start date:
|
||||
+ <field name="start_date"/>
|
||||
+ <br/>
|
||||
+ duration:
|
||||
+ <field name="duration"/>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ </t>
|
||||
+ </templates>
|
||||
+ </kanban>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+
|
||||
<record model="ir.actions.act_window" id="session_list_action">
|
||||
<field name="name">Sessions</field>
|
||||
<field name="res_model">openacademy.session</field>
|
||||
- <field name="view_mode">tree,form,calendar,gantt,graph</field>
|
||||
+ <field name="view_mode">tree,form,calendar,gantt,graph,kanban</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="session_menu" name="Sessions"
|
@ -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
|
||||
@@ -101,6 +101,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",
|
||||
@@ -72,16 +85,34 @@
|
||||
<form string="Session Form">
|
||||
<sheet>
|
||||
<group>
|
||||
- <field name="name"/>
|
||||
- <field name="start_date"/>
|
||||
- <field name="duration"/>
|
||||
- <field name="seats"/>
|
||||
+ <group string="General">
|
||||
+ <field name="course_id"/>
|
||||
+ <field name="name"/>
|
||||
+ <field name="instructor_id"/>
|
||||
+ </group>
|
||||
+ <group string="Schedule">
|
||||
+ <field name="start_date"/>
|
||||
+ <field name="duration"/>
|
||||
+ <field name="seats"/>
|
||||
+ </group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
+ <!-- session tree/list view -->
|
||||
+ <record model="ir.ui.view" id="session_tree_view">
|
||||
+ <field name="name">session.tree</field>
|
||||
+ <field name="model">openacademy.session</field>
|
||||
+ <field name="arch" type="xml">
|
||||
+ <tree string="Session Tree">
|
||||
+ <field name="name"/>
|
||||
+ <field name="course_id"/>
|
||||
+ </tree>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+
|
||||
<record model="ir.actions.act_window" id="session_list_action">
|
||||
<field name="name">Sessions</field>
|
||||
<field name="res_model">openacademy.session</field>
|
@ -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,52 +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: foo/openacademy/reports.xml
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ foo/openacademy/reports.xml
|
||||
@@ -0,0 +1,32 @@
|
||||
+<odoo>
|
||||
+
|
||||
+ <record id="report_session" model="ir.actions.report">
|
||||
+ <field name="name">Session Report</field>
|
||||
+ <field name="model">openacademy.session</field>
|
||||
+ <field name="report_type">qweb-pdf</field>
|
||||
+ <field name="report_name">openacademy.report_session_view</field>
|
||||
+ <field name="report_file">openacademy.report_session</field>
|
||||
+ <field name="binding_model_id" ref="model_openacademy_session"/>
|
||||
+ <field name="binding_type">report</field>
|
||||
+ </record>
|
||||
+
|
||||
+ <template id="report_session_view">
|
||||
+ <t t-call="web.html_container">
|
||||
+ <t t-foreach="docs" t-as="doc">
|
||||
+ <t t-call="web.external_layout">
|
||||
+ <div class="page">
|
||||
+ <h2 t-field="doc.name"/>
|
||||
+ <p>From <span t-field="doc.start_date"/> to <span t-field="doc.end_date"/></p>
|
||||
+ <h3>Attendees:</h3>
|
||||
+ <ul>
|
||||
+ <t t-foreach="doc.attendee_ids" t-as="attendee">
|
||||
+ <li><span t-field="attendee.name"/></li>
|
||||
+ </t>
|
||||
+ </ul>
|
||||
+ </div>
|
||||
+ </t>
|
||||
+ </t>
|
||||
+ </t>
|
||||
+ </template>
|
||||
+
|
||||
+</odoo>
|
@ -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>
|
||||
@@ -61,6 +67,7 @@
|
||||
<field name="name">Courses</field>
|
||||
<field name="res_model">openacademy.course</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
+ <field name="context" eval="{'search_default_my_courses': 1}"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">Create the first course
|
||||
</p>
|
@ -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,57 +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
|
||||
@@ -64,4 +64,32 @@
|
||||
action="openacademy.course_list_action"
|
||||
It is not required when it is the same module -->
|
||||
|
||||
+ <!-- session form view -->
|
||||
+ <record model="ir.ui.view" id="session_form_view">
|
||||
+ <field name="name">session.form</field>
|
||||
+ <field name="model">openacademy.session</field>
|
||||
+ <field name="arch" type="xml">
|
||||
+ <form string="Session Form">
|
||||
+ <sheet>
|
||||
+ <group>
|
||||
+ <field name="name"/>
|
||||
+ <field name="start_date"/>
|
||||
+ <field name="duration"/>
|
||||
+ <field name="seats"/>
|
||||
+ </group>
|
||||
+ </sheet>
|
||||
+ </form>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+
|
||||
+ <record model="ir.actions.act_window" id="session_list_action">
|
||||
+ <field name="name">Sessions</field>
|
||||
+ <field name="res_model">openacademy.session</field>
|
||||
+ <field name="view_mode">tree,form</field>
|
||||
+ </record>
|
||||
+
|
||||
+ <menuitem id="session_menu" name="Sessions"
|
||||
+ parent="openacademy_menu"
|
||||
+ action="session_list_action"/>
|
||||
+
|
||||
</odoo>
|
@ -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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -114,4 +114,4 @@ class Session(models.Model):
|
||||
def _check_instructor_not_in_attendees(self):
|
||||
for r in self:
|
||||
if r.instructor_id and r.instructor_id in r.attendee_ids:
|
||||
- raise exceptions.ValidationError("A session's instructor can't be an attendee")
|
||||
+ raise exceptions.ValidationError(_("A session's instructor can't be an attendee"))
|
@ -1,36 +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")
|
||||
Index: foo/openacademy/security/ir.model.access.csv
|
||||
===================================================================
|
||||
--- foo.orig/openacademy/security/ir.model.access.csv
|
||||
+++ foo/openacademy/security/ir.model.access.csv
|
||||
@@ -1,5 +1,6 @@
|
||||
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
|
||||
course_manager,course manager,model_openacademy_course,group_manager,1,1,1,1
|
||||
session_manager,session manager,model_openacademy_session,group_manager,1,1,1,1
|
||||
+wizard_manager,wizard session manager,model_openacademy_wizard,group_manager,1,1,1,1
|
||||
course_read_all,course all,model_openacademy_course,,1,0,0,0
|
||||
session_read_all,session all,model_openacademy_session,,1,0,0,0
|
@ -1,29 +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,7 @@
|
||||
session_id = fields.Many2one('openacademy.session',
|
||||
string="Session", required=True, default=_default_session)
|
||||
attendee_ids = fields.Many2many('res.partner', string="Attendees")
|
||||
+
|
||||
+ def subscribe(self):
|
||||
+ self.session_id.attendee_ids |= self.attendee_ids
|
||||
+ return {}
|
@ -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: foo/openacademy/views/openacademy.xml
|
||||
===================================================================
|
||||
--- foo.orig/openacademy/views/openacademy.xml
|
||||
+++ foo/openacademy/views/openacademy.xml
|
||||
@@ -220,4 +220,25 @@
|
||||
parent="openacademy_menu"
|
||||
action="session_list_action"/>
|
||||
|
||||
+ <record model="ir.ui.view" id="wizard_form_view">
|
||||
+ <field name="name">wizard.form</field>
|
||||
+ <field name="model">openacademy.wizard</field>
|
||||
+ <field name="arch" type="xml">
|
||||
+ <form string="Add Attendees">
|
||||
+ <group>
|
||||
+ <field name="session_id"/>
|
||||
+ <field name="attendee_ids"/>
|
||||
+ </group>
|
||||
+ </form>
|
||||
+ </field>
|
||||
+ </record>
|
||||
+
|
||||
+ <record id="launch_session_wizard" model="ir.actions.act_window">
|
||||
+ <field name="name">Add Attendees</field>
|
||||
+ <field name="res_model">openacademy.wizard</field>
|
||||
+ <field name="view_mode">form</field>
|
||||
+ <field name="target">new</field>
|
||||
+ <field name="binding_model_id" ref="model_openacademy_session"/>
|
||||
+ </record>
|
||||
+
|
||||
</odoo>
|
@ -1,37 +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,13 +6,14 @@ class Wizard(models.TransientModel):
|
||||
_name = 'openacademy.wizard'
|
||||
_description = "Wizard: Quick Registration of Attendees to Sessions"
|
||||
|
||||
- def _default_session(self):
|
||||
- return self.env['openacademy.session'].browse(self._context.get('active_id'))
|
||||
+ def _default_sessions(self):
|
||||
+ return self.env['openacademy.session'].browse(self._context.get('active_ids'))
|
||||
|
||||
- session_id = fields.Many2one('openacademy.session',
|
||||
- string="Session", required=True, default=_default_session)
|
||||
+ session_ids = fields.Many2many('openacademy.session',
|
||||
+ string="Sessions", required=True, default=_default_sessions)
|
||||
attendee_ids = fields.Many2many('res.partner', string="Attendees")
|
||||
|
||||
def subscribe(self):
|
||||
- self.session_id.attendee_ids |= self.attendee_ids
|
||||
+ for session in self.session_ids:
|
||||
+ session.attendee_ids |= self.attendee_ids
|
||||
return {}
|
@ -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="is_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
+ <record id="course1" model="product.template">
|
||||
+ <field name="name">Course 1</field>
|
||||
+ <field name="teacher_id" ref="padilla"/>
|
||||
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
|
||||
+ <field name="is_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
+ <record id="course2" model="product.template">
|
||||
+ <field name="name">Course 2</field>
|
||||
+ <field name="teacher_id" ref="vaughn"/>
|
||||
+ <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
|
||||
+ <field name="is_published">True</field>
|
||||
+ <field name="list_price" type="float">0</field>
|
||||
+ <field name="type">service</field>
|
||||
+ </record>
|
||||
+
|
||||
</odoo>
|
||||
diff --git a/academy/models.py b/academy/models.py
|
||||
--- a/academy/models.py
|
||||
+++ b/academy/models.py
|
||||
@@ -8,11 +8,9 @@ class Teachers(models.Model):
|
||||
name = fields.Char()
|
||||
biography = fields.Html()
|
||||
|
||||
- course_ids = fields.One2many('academy.courses', 'teacher_id', string="Courses")
|
||||
+ course_ids = fields.One2many('product.template', 'teacher_id', string="Courses")
|
||||
|
||||
class Courses(models.Model):
|
||||
- _name = 'academy.courses'
|
||||
- _inherit = 'mail.thread'
|
||||
+ _inherit = 'product.template'
|
||||
|
||||
- name = fields.Char()
|
||||
teacher_id = fields.Many2one('academy.teachers', string="Teacher")
|
||||
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
|
||||
--- a/academy/security/ir.model.access.csv
|
||||
+++ b/academy/security/ir.model.access.csv
|
||||
@@ -1,3 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
-access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
|
||||
diff --git a/academy/views.xml b/academy/views.xml
|
||||
--- a/academy/views.xml
|
||||
+++ b/academy/views.xml
|
||||
@@ -26,55 +26,9 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
- <record id="action_academy_courses" model="ir.actions.act_window">
|
||||
- <field name="name">Academy courses</field>
|
||||
- <field name="res_model">academy.courses</field>
|
||||
- </record>
|
||||
- <record id="academy_course_search" model="ir.ui.view">
|
||||
- <field name="name">Academy courses: search</field>
|
||||
- <field name="model">academy.courses</field>
|
||||
- <field name="arch" type="xml">
|
||||
- <search>
|
||||
- <field name="name"/>
|
||||
- <field name="teacher_id"/>
|
||||
- </search>
|
||||
- </field>
|
||||
- </record>
|
||||
- <record id="academy_course_list" model="ir.ui.view">
|
||||
- <field name="name">Academy courses: list</field>
|
||||
- <field name="model">academy.courses</field>
|
||||
- <field name="arch" type="xml">
|
||||
- <tree string="Courses">
|
||||
- <field name="name"/>
|
||||
- <field name="teacher_id"/>
|
||||
- </tree>
|
||||
- </field>
|
||||
- </record>
|
||||
- <record id="academy_course_form" model="ir.ui.view">
|
||||
- <field name="name">Academy courses: form</field>
|
||||
- <field name="model">academy.courses</field>
|
||||
- <field name="arch" type="xml">
|
||||
- <form>
|
||||
- <sheet>
|
||||
- <label for="name"/>
|
||||
- <field name="name"/>
|
||||
- <label for="teacher_id"/>
|
||||
- <field name="teacher_id"/>
|
||||
- </sheet>
|
||||
- <div class="oe_chatter">
|
||||
- <field name="message_follower_ids" widget="mail_followers"/>
|
||||
- <field name="message_ids" widget="mail_thread"/>
|
||||
- </div>
|
||||
- </form>
|
||||
- </field>
|
||||
- </record>
|
||||
-
|
||||
<menuitem sequence="0" id="menu_academy" name="Academy"/>
|
||||
<menuitem id="menu_academy_content" parent="menu_academy"
|
||||
name="Academy Content"/>
|
||||
- <menuitem id="menu_academy_content_courses"
|
||||
- parent="menu_academy_content"
|
||||
- action="action_academy_courses"/>
|
||||
<menuitem id="menu_academy_content_teachers"
|
||||
parent="menu_academy_content"
|
||||
action="action_academy_teachers"/>
|
@ -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/14.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
+ # for the full list
|
||||
+ 'category': 'Uncategorized',
|
||||
+ 'version': '0.1',
|
||||
+
|
||||
+ # any module necessary for this one to work correctly
|
||||
+ 'depends': ['base'],
|
||||
+
|
||||
+ # always loaded
|
||||
+ 'data': [
|
||||
+ # 'security/ir.model.access.csv',
|
||||
+ 'templates.xml',
|
||||
+ ],
|
||||
+ # only loaded in demonstration mode
|
||||
+ 'demo': [
|
||||
+ 'demo.xml',
|
||||
+ ],
|
||||
+}
|
||||
diff --git a/academy/controllers.py b/academy/controllers.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/controllers.py
|
||||
@@ -0,0 +1,20 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+from odoo import http
|
||||
+
|
||||
+# class Academy(http.Controller):
|
||||
+# @http.route('/academy/academy/', auth='public')
|
||||
+# def index(self, **kw):
|
||||
+# return "Hello, world"
|
||||
+
|
||||
+# @http.route('/academy/academy/objects/', auth='public')
|
||||
+# def list(self, **kw):
|
||||
+# return http.request.render('academy.listing', {
|
||||
+# 'root': '/academy/academy',
|
||||
+# 'objects': http.request.env['academy.academy'].search([]),
|
||||
+# })
|
||||
+
|
||||
+# @http.route('/academy/academy/objects/<model("academy.academy"):obj>/', auth='public')
|
||||
+# def object(self, obj, **kw):
|
||||
+# return http.request.render('academy.object', {
|
||||
+# 'object': obj
|
||||
+# })
|
||||
diff --git a/academy/demo.xml b/academy/demo.xml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/demo.xml
|
||||
@@ -0,0 +1,25 @@
|
||||
+<odoo>
|
||||
+
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object0" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 0</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object1" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 1</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object2" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 2</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object3" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 3</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+ <!-- <record id="object4" model="academy.academy"> -->
|
||||
+ <!-- <field name="name">Object 4</field> -->
|
||||
+ <!-- </record> -->
|
||||
+ <!-- -->
|
||||
+
|
||||
+</odoo>
|
||||
diff --git a/academy/models.py b/academy/models.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/models.py
|
||||
@@ -0,0 +1,8 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+
|
||||
+from odoo import models, fields, api
|
||||
+
|
||||
+# class academy(models.Model):
|
||||
+# _name = 'academy.academy'
|
||||
+
|
||||
+# name = fields.Char()
|
||||
diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.access.csv
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/security/ir.model.access.csv
|
||||
@@ -0,0 +1,2 @@
|
||||
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
+access_academy_academy,academy.academy,model_academy_academy,,1,0,0,0
|
||||
diff --git a/academy/templates.xml b/academy/templates.xml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/academy/templates.xml
|
||||
@@ -0,0 +1,22 @@
|
||||
+<odoo>
|
||||
+
|
||||
+ <!-- <template id="listing"> -->
|
||||
+ <!-- <ul> -->
|
||||
+ <!-- <li t-foreach="objects" t-as="object"> -->
|
||||
+ <!-- <a t-attf-href="#{ root }/objects/#{ object.id }"> -->
|
||||
+ <!-- <t t-esc="object.display_name"/> -->
|
||||
+ <!-- </a> -->
|
||||
+ <!-- </li> -->
|
||||
+ <!-- </ul> -->
|
||||
+ <!-- </template> -->
|
||||
+ <!-- <template id="object"> -->
|
||||
+ <!-- <h1><t t-esc="object.display_name"/></h1> -->
|
||||
+ <!-- <dl> -->
|
||||
+ <!-- <t t-foreach="object._fields" t-as="field"> -->
|
||||
+ <!-- <dt><t t-esc="field"/></dt> -->
|
||||
+ <!-- <dd><t t-esc="object[field]"/></dd> -->
|
||||
+ <!-- </t> -->
|
||||
+ <!-- </dl> -->
|
||||
+ <!-- </template> -->
|
||||
+
|
||||
+</odoo>
|
@ -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"> -->
|
@ -1,40 +1,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
|
||||
===============
|
||||
@ -152,8 +118,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'
|
||||
|
||||
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.tools.iap_tools.iap_jsonrpc`) and a service helper
|
||||
for transactions (:class:`~odoo.addons.iap.tools.iap_tools.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.tools.iap_tools.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,39 +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,21 @@
|
||||
+# -*- 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'
|
||||
+ 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
|
@ -31,7 +31,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_enterprise_en| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Enterprise Agreement
|
||||
:target: odoo_enterprise_agreement.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_enterprise_agreement.pdf
|
||||
|
||||
.. |view_partnership_en| image:: legal/img/txt.svg
|
||||
:alt: View Odoo Partnership Agreement
|
||||
@ -39,7 +39,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_partnership_en| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Partnership Agreement
|
||||
:target: odoo_partnership_agreement.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_partnership_agreement.pdf
|
||||
|
||||
.. |view_terms_of_sale_en| image:: legal/img/txt.svg
|
||||
:alt: View Terms of Sale
|
||||
@ -51,7 +51,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_enterprise_fr| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Enterprise Agreement (FR)
|
||||
:target: odoo_enterprise_agreement_fr.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_enterprise_agreement_fr.pdf
|
||||
|
||||
.. |view_enterprise_fr| image:: legal/img/txt.svg
|
||||
:alt: View Odoo Enterprise Agreement (FR)
|
||||
@ -59,7 +59,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_partnership_fr| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Partnership Agreement (FR)
|
||||
:target: odoo_partnership_agreement_fr.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_partnership_agreement_fr.pdf
|
||||
|
||||
.. |view_enterprise_nl| image:: legal/img/txt.svg
|
||||
:alt: View Odoo Enterprise Agreement (NL)
|
||||
@ -71,7 +71,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_terms_of_sale_fr| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Terms of Sale (FR)
|
||||
:target: terms_of_sale_fr.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/terms_of_sale_fr.pdf
|
||||
|
||||
.. |view_terms_of_sale_fr| image:: legal/img/txt.svg
|
||||
:alt: View Terms of Sale (FR)
|
||||
@ -79,7 +79,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_enterprise_nl| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Enterprise Agreement (NL)
|
||||
:target: odoo_enterprise_agreement_nl.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_enterprise_agreement_nl.pdf
|
||||
|
||||
.. |download_partnership_nl| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Partnership Agreement (NL)
|
||||
@ -87,7 +87,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_enterprise_de| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Enterprise Agreement (DE)
|
||||
:target: odoo_enterprise_agreement_de.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_enterprise_agreement_de.pdf
|
||||
|
||||
.. |view_enterprise_de| image:: legal/img/txt.svg
|
||||
:alt: View Odoo Enterprise Agreement (DE)
|
||||
@ -95,7 +95,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_partnership_de| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Partnership Agreement (DE)
|
||||
:target: odoo_partnership_agreement_de.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_partnership_agreement_de.pdf
|
||||
|
||||
.. |view_partnership_de| image:: legal/img/txt.svg
|
||||
:alt: View Odoo Partnership Agreement (DE)
|
||||
@ -103,7 +103,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_enterprise_es| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Enterprise Agreement (ES)
|
||||
:target: odoo_enterprise_agreement_es.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_enterprise_agreement_es.pdf
|
||||
|
||||
.. |view_enterprise_es| image:: legal/img/txt.svg
|
||||
:alt: View Odoo Partnership Agreement (ES)
|
||||
@ -111,7 +111,7 @@ Terms and Conditions
|
||||
|
||||
.. |download_partnership_es| image:: legal/img/pdf.svg
|
||||
:alt: Download Odoo Partnership Agreement (ES)
|
||||
:target: odoo_partnership_agreement_es.pdf
|
||||
:target: https://www.odoo.com/documentation/12.0/odoo_partnership_agreement_es.pdf
|
||||
|
||||
.. |view_partnership_es| image:: legal/img/txt.svg
|
||||
:alt: View Odoo Partnership Agreement (ES)
|
||||
|
@ -402,7 +402,6 @@ header.o_main_header{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
p, h5 {
|
||||
color: $gray;
|
||||
}
|
||||
@ -451,30 +450,9 @@ header.o_main_header{
|
||||
}
|
||||
}
|
||||
|
||||
// "in between" pages (Applications / Contributing)
|
||||
// pages with full width: Legal
|
||||
|
||||
&.o_fullwidth_page {
|
||||
.toctree-wrapper {
|
||||
display: none;
|
||||
/* margin-top: 1.5rem;
|
||||
> ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> li.toctree-l1 {
|
||||
@include media-breakpoint-up(lg) {
|
||||
width: 33.3%;
|
||||
}
|
||||
padding-bottom: 3rem;
|
||||
padding-right: 3rem;
|
||||
}
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left:0;
|
||||
} */
|
||||
}
|
||||
|
||||
.toctree-l1 > a {
|
||||
display: block;
|
||||
color: $gray-darker;
|
||||
@ -485,6 +463,8 @@ header.o_main_header{
|
||||
}
|
||||
}
|
||||
|
||||
// pages with column for code on the right
|
||||
|
||||
&.o_has_code_column {
|
||||
|
||||
article.doc-body {
|
||||
@ -569,6 +549,11 @@ header.o_main_header{
|
||||
article.doc-body {
|
||||
position: relative;
|
||||
|
||||
// hide ugly toctree on "in between" pages (e.g.: /applications.html, /administration.html, ...)
|
||||
.toctree-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_git_link {
|
||||
@include font-size($font-size-secondary);
|
||||
@include o-position-absolute($top: 0px, $right: 1rem);
|
||||
|
@ -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