Compare commits
5 Commits
18.0
...
17.0-revam
Author | SHA1 | Date | |
---|---|---|---|
![]() |
61b62525ca | ||
![]() |
5fcfde48a4 | ||
![]() |
df1e582b93 | ||
![]() |
81929ceb1c | ||
![]() |
32cba451c1 |
@ -44,8 +44,8 @@ Git
|
||||
basic knowledge of Git commands to proceed.
|
||||
|
||||
To clone a Git repository, choose between cloning with HTTPS or SSH. In most cases, the best option
|
||||
is HTTPS. However, choose SSH to contribute to Odoo source code or when following the :doc:`Getting
|
||||
Started developer tutorial </developer/tutorials/server_framework_101>`.
|
||||
is HTTPS. However, choose SSH to contribute to Odoo source code or when following the
|
||||
:doc:`/developer/tutorials/server_framework_101` tutorial.
|
||||
|
||||
.. tabs::
|
||||
|
||||
|
@ -161,7 +161,10 @@ navigate to the directory where you installed Odoo from sources and follow the g
|
||||
#. Select **<your_github_account>/odoo** or **<your_github_account>/enterprise** for the head
|
||||
repository. Replace `<your_github_account>` with the name of the GitHub account on which you
|
||||
created the fork or by **odoo-dev** if you work at Odoo.
|
||||
#. Review your changes and click on the :guilabel:`Create pull request` button.
|
||||
#. Click on :guilabel:`Create pull request` to create the :abbr:`PR (Pull Request)` and
|
||||
automatically request a review from the code maintainers. If you wish to double-check your
|
||||
changes first, or if you work at Odoo and follow an internal process for reviews, click on
|
||||
:guilabel:`Create draft pull request`.
|
||||
#. Tick the :guilabel:`Allow edits from maintainer` checkbox. Skip this step if you work at Odoo.
|
||||
#. Complete the description and click on the :guilabel:`Create pull request` button again.
|
||||
|
||||
|
@ -25,9 +25,13 @@ These guidelines should be applied to every new module and to all new developmen
|
||||
going under major changes. In that case first do a **move** commit then apply
|
||||
the changes related to the feature.
|
||||
|
||||
.. _contributing/coding_guidelines/module_structure:
|
||||
|
||||
Module structure
|
||||
================
|
||||
|
||||
.. _contributing/coding_guidelines/module_structure/directories:
|
||||
|
||||
Directories
|
||||
-----------
|
||||
|
||||
@ -46,6 +50,7 @@ Other optional directories compose the module.
|
||||
- *report/* : contains the printable reports and models based on SQL views. Python objects and XML views are included in this directory
|
||||
- *tests/* : contains the Python tests
|
||||
|
||||
.. _contributing/coding_guidelines/module_structure/file_naming:
|
||||
|
||||
File naming
|
||||
-----------
|
||||
@ -808,6 +813,8 @@ In general in Odoo, when manipulating strings, prefer ``%`` over ``.format()``
|
||||
of position (when multiple variables have to be replaced). This makes the
|
||||
translation easier for the community translators.
|
||||
|
||||
.. _contributing/coding_guidelines/model_members:
|
||||
|
||||
Symbols and Conventions
|
||||
-----------------------
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
Git guidelines
|
||||
==============
|
||||
|
||||
.. _contributing/git_guidelines/configure_git:
|
||||
|
||||
Configure your git
|
||||
------------------
|
||||
|
||||
@ -17,6 +19,8 @@ way towards making your commits more helpful:
|
||||
- Be sure to add your full name to your Github profile here. Please feel fancy
|
||||
and add your team, avatar, your favorite quote, and whatnot ;-)
|
||||
|
||||
.. _contributing/git_guidelines/message_structure:
|
||||
|
||||
Commit message structure
|
||||
------------------------
|
||||
|
||||
@ -42,6 +46,8 @@ description. Try to follow the preferred structure for your commit messages
|
||||
Closes #123 (close related PR on Github)
|
||||
opw-123 (related to ticket)
|
||||
|
||||
.. _contributing/git_guidelines/commit_tag_module:
|
||||
|
||||
Tag and module name
|
||||
-------------------
|
||||
|
||||
@ -73,6 +79,8 @@ various to tell it is cross-modules. Unless really required or easier avoid
|
||||
modifying code across several modules in the same commit. Understanding module
|
||||
history may become difficult.
|
||||
|
||||
.. _contributing/git_guidelines/commit_header:
|
||||
|
||||
Commit message header
|
||||
---------------------
|
||||
|
||||
@ -86,6 +94,8 @@ Commit message header should make a valid sentence once concatenated with
|
||||
archive users linked to active partners`` is correct as it makes a valid sentence
|
||||
``if applied, this commit will prevent users to archive...``.
|
||||
|
||||
.. _contributing/git_guidelines/commit_body:
|
||||
|
||||
Commit message full description
|
||||
-------------------------------
|
||||
|
||||
|
@ -76,6 +76,8 @@ following attributes:
|
||||
|
||||
Requires an :term:`external id`, defaults to ``True``.
|
||||
|
||||
.. _reference/data/field:
|
||||
|
||||
``field``
|
||||
---------
|
||||
|
||||
@ -203,6 +205,8 @@ Because some important structural models of Odoo are complex and involved,
|
||||
data files provide shorter alternatives to defining them using
|
||||
:ref:`record tags <reference/data/record>`:
|
||||
|
||||
.. _reference/data/shortcuts/menuitem:
|
||||
|
||||
``menuitem``
|
||||
------------
|
||||
|
||||
|
@ -213,12 +213,20 @@ These helpers are also available by importing `odoo.tools.date_utils`.
|
||||
Relational Fields
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. _reference/fields/many2one:
|
||||
|
||||
.. autoclass:: Many2one()
|
||||
|
||||
.. _reference/fields/one2many:
|
||||
|
||||
.. autoclass:: One2many()
|
||||
|
||||
.. _reference/fields/many2many:
|
||||
|
||||
.. autoclass:: Many2many()
|
||||
|
||||
.. _reference/fields/command:
|
||||
|
||||
.. autoclass:: Command()
|
||||
:members:
|
||||
:undoc-members:
|
||||
@ -510,6 +518,8 @@ behavior is desired:
|
||||
:class:`~odoo.fields.Many2one`
|
||||
:type: :class:`~odoo.addons.base.models.res_company`
|
||||
|
||||
.. _reference/orm/recordsets:
|
||||
|
||||
Recordsets
|
||||
==========
|
||||
|
||||
|
@ -516,8 +516,8 @@ Structural components
|
||||
Structural components provide structure or "visual" features with little logic. They are used as
|
||||
elements or sets of elements in form views.
|
||||
|
||||
Form views accept the following children structural components: :ref:`group
|
||||
<reference/view_architectures/form/group>`, :ref:`sheet <reference/view_architectures/form/sheet>`,
|
||||
Form views accept the following children structural components: :ref:`sheet
|
||||
<reference/view_architectures/form/sheet>`, :ref:`group <reference/view_architectures/form/group>`,
|
||||
:ref:`notebook <reference/view_architectures/form/notebook>`,
|
||||
:ref:`notebook <reference/view_architectures/form/notebook>`,
|
||||
:ref:`newline <reference/view_architectures/form/newline>`,
|
||||
@ -529,6 +529,24 @@ Form views accept the following children structural components: :ref:`group
|
||||
|
||||
Placeholders are denoted in all caps.
|
||||
|
||||
.. _reference/view_architectures/form/sheet:
|
||||
|
||||
`sheet`: make the layout responsive
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The `sheet` element can be used as a direct child of the :ref:`form
|
||||
<reference/view_architectures/form>` root element for a narrower and more responsive form layout
|
||||
(centered page, margin...). It usually contains :ref:`group
|
||||
<reference/view_architectures/form/group>` elements.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<form>
|
||||
<sheet>
|
||||
...
|
||||
</sheet>
|
||||
</form>
|
||||
|
||||
.. _reference/view_architectures/form/group:
|
||||
|
||||
`group`: define columns layouts
|
||||
@ -615,24 +633,6 @@ The `group` element can have the following attributes:
|
||||
</group>
|
||||
</group>
|
||||
|
||||
.. _reference/view_architectures/form/sheet:
|
||||
|
||||
`sheet`: make the layout responsive
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The `sheet` element can be used as a direct child of the :ref:`form
|
||||
<reference/view_architectures/form>` root element for a narrower and more responsive form layout
|
||||
(centered page, margin...). It usually contains :ref:`group
|
||||
<reference/view_architectures/form/group>` elements.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<form>
|
||||
<sheet>
|
||||
...
|
||||
</sheet>
|
||||
</form>
|
||||
|
||||
.. _reference/view_architectures/form/notebook:
|
||||
|
||||
`notebook` & `page`: add tabbed sections
|
||||
|
@ -191,7 +191,7 @@ When the data to create is more complex it can be useful, or even necessary, to
|
||||
Data Extension
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
During the Core Training, we saw in the :doc:`server_framework_101/12_inheritance` chapter we
|
||||
During the Core Training, we saw in the :doc:`server_framework_101_legacy/12_inheritance` chapter we
|
||||
could inherit (extend) an existing view. This was a special case of data extension: any data can be
|
||||
extended in a module.
|
||||
|
||||
|
@ -7,7 +7,7 @@ Build PDF Reports
|
||||
completed it and use the `estate` module you have built as a base for the exercises in this
|
||||
tutorial.
|
||||
|
||||
We were previously :doc:`introduced to QWeb <server_framework_101/14_qwebintro>`
|
||||
We were previously :doc:`introduced to QWeb <server_framework_101_legacy/14_qwebintro>`
|
||||
where it was used to build a kanban view. Now we will expand on one of QWeb's
|
||||
other main uses: creating PDF reports. A common business requirement is the ability to create documents
|
||||
to send to customers and to use internally. These reports can be used to summarize and display
|
||||
@ -178,7 +178,7 @@ Its contents are all explained in :ref:`the documentation <reference/actions/rep
|
||||
An ``ir.actions.report`` is primarily used via the Print menu of a model's view. In the practical
|
||||
example, the ``binding_model_id`` specifies which model's views the report should show, and Odoo
|
||||
will auto-magically add it for you. Another common use case of the report action is to link it to
|
||||
a button as we learned in :doc:`server_framework_101/09_actions`. This is handy for reports
|
||||
a button as we learned in :doc:`server_framework_101_legacy/09_actions`. This is handy for reports
|
||||
that only make sense under specific conditions. For example, if we wanted to make a "Final Sale"
|
||||
report, then we can link it to a "Print Sale Info" button that appears in the form view only when
|
||||
the property is "Sold".
|
||||
|
@ -15,7 +15,7 @@ currently,
|
||||
update or delete properties, property types, or property tags.
|
||||
* If ``estate_account`` is installed then only agents allowed to interact
|
||||
with invoicing can confirm sales as that's necessary to :ref:`create an
|
||||
invoice <tutorials/server_framework_101/13_other_module/create>`.
|
||||
invoice <tutorials/server_framework_101_legacy/13_other_module/create>`.
|
||||
|
||||
However:
|
||||
|
||||
@ -140,8 +140,6 @@ Access Rights
|
||||
real-estate application.
|
||||
- Real-estate agents will not be able to update the property types or tags.
|
||||
|
||||
Access rights were first introduced in :doc:`server_framework_101/04_securityintro`.
|
||||
|
||||
Access rights are a way to give users access to models *via* groups: associate
|
||||
an access right to a group, then all users with that group will have the access.
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
:show-content:
|
||||
:hide-page-toc:
|
||||
|
||||
====================
|
||||
Server framework 101
|
||||
@ -10,16 +11,23 @@ Server framework 101
|
||||
|
||||
server_framework_101/*
|
||||
|
||||
Welcome to the Server framework 101 tutorial! If you reached this page that means you are
|
||||
interested in the development of your own Odoo module. It might also mean that you recently
|
||||
joined the Odoo company for a rather technical position. In any case, your journey to the
|
||||
technical side of Odoo starts here.
|
||||
**Welcome to the Server framework 101 tutorial!**
|
||||
|
||||
The goal of this tutorial is for you to get an insight of the most important parts of the Odoo
|
||||
development framework while developing your own Odoo module to manage real estate assets. The
|
||||
chapters should be followed in their given order since they cover the development of a new Odoo
|
||||
application from scratch in an incremental way. In other words, each chapter depends on the previous
|
||||
one.
|
||||
Are you eager to learn about the server framework of Odoo? Perhaps you have recently joined Odoo in
|
||||
a technical role. Either way, this tutorial will level you up as an Odoo developer and show you the
|
||||
ropes of working with the server framework.
|
||||
|
||||
You will embark on a step-by-step journey, building a real estate asset management application as
|
||||
you explore the core concepts of the server framework. Each chapter of the tutorial builds upon the
|
||||
previous one, so make sure to follow them in order.
|
||||
|
||||
.. todo: insert picture of the final view
|
||||
|
||||
.. note::
|
||||
Throughout this tutorial, you will encounter exercises designed to reinforce your learning. There
|
||||
will often be multiple ways to approach these exercises, so feel free to explore your own ideas
|
||||
and solutions. This is the secret to secure your understanding and develop your problem-solving
|
||||
skills.
|
||||
|
||||
.. important::
|
||||
Before going further, make sure you have prepared your development environment with the
|
||||
@ -27,18 +35,13 @@ one.
|
||||
|
||||
Ready? Let's get started!
|
||||
|
||||
* :doc:`server_framework_101/01_architecture`
|
||||
* :doc:`server_framework_101/02_newapp`
|
||||
* :doc:`server_framework_101/03_basicmodel`
|
||||
* :doc:`server_framework_101/04_securityintro`
|
||||
* :doc:`server_framework_101/05_firstui`
|
||||
* :doc:`server_framework_101/06_basicviews`
|
||||
* :doc:`server_framework_101/07_relations`
|
||||
* :doc:`server_framework_101/08_compute_onchange`
|
||||
* :doc:`server_framework_101/09_actions`
|
||||
* :doc:`server_framework_101/10_constraints`
|
||||
* :doc:`server_framework_101/11_sprinkles`
|
||||
* :doc:`server_framework_101/12_inheritance`
|
||||
* :doc:`server_framework_101/13_other_module`
|
||||
* :doc:`server_framework_101/14_qwebintro`
|
||||
* :doc:`server_framework_101/15_final_word`
|
||||
- :doc:`server_framework_101/01_architecture_overview`
|
||||
- :doc:`server_framework_101/02_lay_the_foundations`
|
||||
- :doc:`server_framework_101/03_build_user_interface`
|
||||
- :doc:`server_framework_101/04_relational_fields`
|
||||
- :doc:`server_framework_101/05_connect_the_dots`
|
||||
- :doc:`server_framework_101/06_security`
|
||||
- :doc:`server_framework_101/07_advanced_views`
|
||||
- :doc:`server_framework_101/08_inheritance`
|
||||
- :doc:`server_framework_101/09_portal`
|
||||
- :doc:`server_framework_101/10_unit_testing`
|
||||
|
@ -0,0 +1,72 @@
|
||||
================================
|
||||
Chapter 1: Architecture overview
|
||||
================================
|
||||
|
||||
Before we start building our app, let's take a high level glance at the architecture of Odoo.
|
||||
|
||||
.. _tutorials/server_framework_101/multitier_app:
|
||||
|
||||
Multitier application
|
||||
=====================
|
||||
|
||||
Odoo leverages a `multitier architecture <https://en.wikipedia.org/wiki/Multitier_architecture>`_,
|
||||
meaning that the presentation, the business logic, and the data storage are separated. More
|
||||
specifically, it uses a three-tier architecture (image from Wikipedia):
|
||||
|
||||
.. image:: 01_architecture_overview/three-tier-architecture.svg
|
||||
:align: center
|
||||
:alt: Overview of a three-tier application
|
||||
|
||||
The presentation tier of Odoo is a combination of HTML5, JavaScript, and CSS. The logic tier is
|
||||
exclusively written in Python, while the data tier only supports PostgreSQL as an :abbr:`RDBMS
|
||||
(Relational Database Management System Software)`.
|
||||
|
||||
Depending on the scope of your Odoo development, it can be done in any of these tiers. Therefore,
|
||||
before going any further, it may be a good idea to refresh your memory if you don't have an
|
||||
intermediate level in these topics. In order to go through this tutorial, you will need a very basic
|
||||
knowledge of HTML and an intermediate level of Python. There are plenty of tutorials that are freely
|
||||
accessible, so we cannot recommend one over another since it depends on your background. For
|
||||
reference, this is the official `Python tutorial <https://docs.python.org/3/tutorial/>`_.
|
||||
|
||||
.. _tutorials/server_framework_101/modules:
|
||||
|
||||
Odoo modules
|
||||
============
|
||||
|
||||
Odoo relies on modular components called **modules** to extend its functionality. These modules are
|
||||
essentially self-contained packages of code and data that serve a specific purpose within the
|
||||
system. You can think of them as building blocks.
|
||||
|
||||
Modules offer two main ways to customize Odoo:
|
||||
|
||||
- Adding new functionality: You can create entirely new features with modules, such as a real-time
|
||||
bus fleet visualization module.
|
||||
- Extending existing functionality: Modules can also be used to modify or enhance existing Odoo
|
||||
features, like adding your country's accounting rules to the generic accounting support.
|
||||
|
||||
Terminology:
|
||||
|
||||
- Developers group their business features in Odoo *modules*.
|
||||
- The main user-facing modules are flagged and exposed as *Apps*, but a majority of the modules are
|
||||
not Apps.
|
||||
- *Modules* may also be referred to as *addons*.
|
||||
|
||||
In practice, modules are represented by directories. They are placed in a designated location called
|
||||
the **addons path**, which the server scans to discover available modules.
|
||||
|
||||
In the :doc:`setup guide <../setup_guide>`, we cloned the `odoo/tutorials` repository and included
|
||||
it in the `addons-path` argument when starting the server. The directories present in the repository
|
||||
are all modules that can be installed on your Odoo server.
|
||||
|
||||
.. exercise::
|
||||
In your file explorer, navigate to the `odoo/tutorials` repository and inspect the available
|
||||
modules. You should find the `real_estate` module in which we will build our real estate
|
||||
application throughout this tutorial.
|
||||
|
||||
You must have noticed that the module directories are not empty; they all contain at least two
|
||||
essential files: :file:`__init__.py` and :file:`__manifest__.py`. These files are what makes a
|
||||
simple directory an Odoo module. We'll get back to it in the next chapter.
|
||||
|
||||
----
|
||||
|
||||
Ready to start? Let's now :doc:`lay the foundations <02_lay_the_foundations>` of our first Odoo app!
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
@ -0,0 +1,574 @@
|
||||
==============================
|
||||
Chapter 2: Lay the foundations
|
||||
==============================
|
||||
|
||||
In this chapter, we'll focus on the fundamental building blocks of Odoo's data structure: models,
|
||||
fields, and records. We'll use them together to lay the foundations of our real estate application.
|
||||
|
||||
.. _tutorials/server_framework_101/install_app:
|
||||
|
||||
Install the app
|
||||
===============
|
||||
|
||||
If you followed the :doc:`setup guide <../setup_guide>` carefully, you should now have a running
|
||||
Odoo server with the `odoo/tutorials` repository in the `addons-path`, and be logged in as an
|
||||
administrator.
|
||||
|
||||
**Let's install our real estate app!** In your browser, open Odoo and navigate to
|
||||
:menuselection:`Apps`. Search for :guilabel:`Real Estate` and click :guilabel:`Activate`.
|
||||
|
||||
Nothing has changed? That's normal; the `real_estate` module you just installed is currently an
|
||||
empty shell. It doesn't even have a menu entry. Currently, it only contains three files:
|
||||
|
||||
- An empty :file:`__init__.py` file to make Python treat the :file:`real_estate` directory as a
|
||||
package.
|
||||
- The :file:`__manifest__.py` file that declares the :file:`real_estate` Python package as an Odoo
|
||||
module.
|
||||
- The optional :file:`static/description/icon.png` file that serves as the app icon.
|
||||
|
||||
The first two files are the minimum requirements for a directory to be considered an Odoo module.
|
||||
|
||||
.. exercise::
|
||||
Search for each key of the :file:`real_estate/__manifest__.py` file in the :ref:`reference
|
||||
documentation <reference/module/manifest>` and understand the base metadata that defines the
|
||||
`real_estate` module.
|
||||
|
||||
.. seealso::
|
||||
`The manifest file of the Sales app <{GITHUB_PATH}/addons/sale/__manifest__.py>`_
|
||||
|
||||
.. _tutorials/server_framework_101/create_first_model:
|
||||
|
||||
Create the first model
|
||||
======================
|
||||
|
||||
Now that our module is recognized by Odoo, it's time to build towards the business features. Data is
|
||||
essential for any real-world application, and our real estate application won't be an exception.
|
||||
|
||||
To store data effectively, we need two things: a way to define the structure of that data, and a
|
||||
system to store and manipulate it. Fortunately, the server framework of Odoo comes equipped with the
|
||||
perfect tool: the :abbr:`ORM (Object Relational Mapper)` layer. It's the ORM that takes care of
|
||||
preparing and executing SQL queries for you when you manipulate data in Python.
|
||||
|
||||
The ORM simplifies data access and manipulation by allowing you to define **models**. Models act as
|
||||
blueprints for your data; they define the structure and organization of information within your
|
||||
module. You can see them as templates that specify what kind of data your module will handle. For
|
||||
example, real estate properties, owners, or tenants. In Odoo, models are represented by Python
|
||||
classes that inherit from the `odoo.models.Model` class provided by the ORM, and they are identified
|
||||
by their `_name` attribute. In addition to `_name`, models also accept the `_description` attribute
|
||||
to define a human-readable description of the model.
|
||||
|
||||
Each model is made of smaller components called **fields**. You can see them as the individual
|
||||
characteristics that describe your data. Fields allow you to define the relevant data your
|
||||
application needs to capture and manage. In the case of real estate properties, some example fields
|
||||
could be the property name, the type (house, apartment, etc.), and the floor area. Within model
|
||||
classes, fields are defined as class attributes. Each field is an instance of a class from the
|
||||
`odoo.fields` package. For example, `Char`, `Float`, `Boolean`, each designed to handle different
|
||||
types of data. When defining a field, developers can pass various arguments to finely control how
|
||||
data is handled and presented in Odoo. For example, `string` defines the label for the field in the
|
||||
user interface, `help` provides a tooltip when hovering the field in the user interface, `required`
|
||||
makes filling in the field mandatory, and `default` provides a default field value.
|
||||
|
||||
Individual data entries are called **records**. They are based on the structure defined by models
|
||||
and contain the actual data for each field specified in the model. In Python, records are
|
||||
represented as instances of the model's class, allowing developers to interact with data using
|
||||
object-oriented programming techniques. For example, in a real estate application using a tenant
|
||||
model, each specific tenant (such as "Bafien Carpink") would be a separate record of that model.
|
||||
|
||||
.. example::
|
||||
Before we dive into creating our own models, let's take a look at a basic example of a model that
|
||||
represents storable products. It defines a `product` model with the `Product` class inheriting
|
||||
from `models.Model`. Within this class, several fields are defined to capture product data:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
_name = 'product'
|
||||
_description = "Storable Product"
|
||||
|
||||
name = fields.Char(string="Name", required=True)
|
||||
description = fields.Text(string="Description")
|
||||
price = fields.Float(string="Sales Price", required=True)
|
||||
category = fields.Selection(
|
||||
string="Category",
|
||||
help="The category of the product; if none are suitable, select 'Other'.",
|
||||
selection=[
|
||||
('apparel', "Clothing"),
|
||||
('electronics', "Electronics"),
|
||||
('home_decor', "Home Decor"),
|
||||
('other', "Other"),
|
||||
],
|
||||
required=True,
|
||||
default='apparel',
|
||||
)
|
||||
|
||||
.. note::
|
||||
- `name` is a `Char` field while `description` is a `Text` field; `Char` fields are typically
|
||||
used for short texts, whereas `Text` fields can hold longer content and multiple lines.
|
||||
- The label of the `price` field is arbitrary and doesn't have to be the upper-cased version
|
||||
of the attribute name.
|
||||
- `category` is a `Selection` field with predefined options, each defined by a technical code
|
||||
and a corresponding label. Since it is required, a default value is provided.
|
||||
|
||||
.. seealso::
|
||||
For the full list of fields and their attributes, see the :ref:`reference documentation
|
||||
<reference/orm/fields>`.
|
||||
|
||||
Building on these new concepts, let's now create the first model for our real estate app. We'll
|
||||
create a model with some fields to represent real estate properties and their characteristics.
|
||||
|
||||
.. exercise::
|
||||
#. Create a new :file:`real_estate_property.py` file at the root of the `real_estate` module.
|
||||
#. Update the :file:`real_estate/__init__.py` file to relatively import the
|
||||
:file:`real_estate_property.py` file, like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from . import real_estate_property
|
||||
|
||||
#. Define a new model with `real.estate.property` as `_name` and a short `_description`.
|
||||
#. Add fields to represent the following characteristics:
|
||||
|
||||
- :guilabel:`Name` (required)
|
||||
- :guilabel:`Description`
|
||||
- :guilabel:`Image` (max 600x400 pixels)
|
||||
- :guilabel:`Active` (whether the property listing is active; defaults to true)
|
||||
- :guilabel:`State` (:guilabel:`New`, :guilabel:`Offer Received`, :guilabel:`Under Option`, or
|
||||
:guilabel:`Sold`; required; defaults to :guilabel:`New`)
|
||||
- :guilabel:`Type` (:guilabel:`House`, :guilabel:`Apartment`, :guilabel:`Office Building`,
|
||||
:guilabel:`Retail Space`, or :guilabel:`Warehouse`; required; defaults to :guilabel:`House`)
|
||||
- :guilabel:`Selling Price` (without currency; with help text; required)
|
||||
- :guilabel:`Availability Date`
|
||||
- :guilabel:`Floor Area` (in square meters; with help text)
|
||||
- :guilabel:`Number of Bedrooms` (defaults to two)
|
||||
- :guilabel:`Garage` (whether there is a garage)
|
||||
- :guilabel:`Garden` (whether there is a garden)
|
||||
- :guilabel:`Garden Area` (in square meters; with help text)
|
||||
|
||||
.. tip::
|
||||
- The class name doesn't matter, but the convention is to use the model's upper-cased `_name`
|
||||
(without dots).
|
||||
- Refer to the documentation on :ref:`fields <reference/orm/fields>` to select the right class
|
||||
and attributes for each field.
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: python
|
||||
:caption: `__init__.py`
|
||||
|
||||
from . import real_estate_property
|
||||
|
||||
.. code-block:: python
|
||||
:caption: `real_estate_property.py`
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class RealEstateProperty(models.Model):
|
||||
_name = 'real.estate.property'
|
||||
_description = "Real Estate Property"
|
||||
|
||||
name = fields.Char(string="Name", required=True)
|
||||
description = fields.Text(string="Description")
|
||||
image = fields.Image(string="Image")
|
||||
active = fields.Boolean(string="Active", default=True)
|
||||
state = fields.Selection(
|
||||
string="State",
|
||||
selection=[
|
||||
('new', "New"),
|
||||
('offer_received', "Offer Received"),
|
||||
('under_option', "Under Option"),
|
||||
('sold', "Sold"),
|
||||
],
|
||||
required=True,
|
||||
default='new',
|
||||
)
|
||||
type = fields.Selection(
|
||||
string="Type",
|
||||
selection=[
|
||||
('house', "House"),
|
||||
('apartment', "Apartment"),
|
||||
('office', "Office Building"),
|
||||
('retail', "Retail Space"),
|
||||
('warehouse', "Warehouse"),
|
||||
],
|
||||
required=True,
|
||||
default='house',
|
||||
)
|
||||
selling_price = fields.Float(
|
||||
string="Selling Price", help="The selling price excluding taxes.", required=True
|
||||
)
|
||||
availability_date = fields.Date(string="Availability Date")
|
||||
floor_area = fields.Integer(
|
||||
string="Floor Area", help="The floor area in square meters excluding the garden."
|
||||
)
|
||||
bedrooms = fields.Integer(string="Number of bedrooms", default=2)
|
||||
has_garage = fields.Boolean(string="Garage")
|
||||
has_garden = fields.Boolean(string="Garden")
|
||||
garden_area = fields.Integer(
|
||||
string="Garden Area", help="The garden area excluding the building."
|
||||
)
|
||||
|
||||
Congrats, you have just defined the first model of our real estate app! However, the changes have
|
||||
not yet been applied to the database. To do so, you must add the `-u real_estate` argument to the
|
||||
server start-up command and restart the server. The :option:`-u <odoo-bin --update>` argument
|
||||
instructs the server to update the specified modules at start-up.
|
||||
|
||||
.. _tutorials/server_framework_101/inspect_sql_table:
|
||||
|
||||
Inspect the SQL table
|
||||
=====================
|
||||
|
||||
Earlier, we quickly introduced models as a convenient way to store and handle data in Odoo. In fact,
|
||||
these models not only define the structure and behavior of data in Python, but they also correspond
|
||||
to SQL tables in the database. The `_name` attribute of their model is taken (with dots replaced by
|
||||
underscores) as the name of the corresponding table. For example, the `real.estate.property` model
|
||||
is linked to the `real_estate_property` table.
|
||||
|
||||
The same goes for fields that become columns in the SQL table of their model. The name of the class
|
||||
attribute representing the field is taken as the column name while the field's class determines the
|
||||
column type.
|
||||
|
||||
Once the server is running again, let's take a look in the database and see how the model and fields
|
||||
you created translate into a new SQL table. We will use `psql`, the CLI
|
||||
:dfn:`command-line interface` allowing to browse and interact with PostgreSQL databases.
|
||||
|
||||
.. exercise::
|
||||
#. In your terminal, execute the command :command:`psql -d tutorials`.
|
||||
#. Enter the command :command:`\\d real_estate_property` to print the description of the
|
||||
`real_estate_property` table.
|
||||
#. For each field of the `real.estate.property` model, try to understand how the field's
|
||||
attributes alter the table.
|
||||
#. Enter the command :command:`exit` to exit `psql`.
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: text
|
||||
:caption: terminal
|
||||
|
||||
$ psql -d tutorials
|
||||
|
||||
tutorials=> \d real_estate_property
|
||||
Table "public.real_estate_property"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
-------------------+-----------------------------+-----------+----------+--------------------------------------------------
|
||||
id | integer | | not null | nextval('real_estate_property_id_seq'::regclass)
|
||||
floor_area | integer | | |
|
||||
bedrooms | integer | | |
|
||||
garden_area | integer | | |
|
||||
create_uid | integer | | |
|
||||
write_uid | integer | | |
|
||||
name | character varying | | not null |
|
||||
state | character varying | | not null |
|
||||
type | character varying | | not null |
|
||||
availability_date | date | | |
|
||||
description | text | | |
|
||||
active | boolean | | |
|
||||
has_garage | boolean | | |
|
||||
has_garden | boolean | | |
|
||||
create_date | timestamp without time zone | | |
|
||||
write_date | timestamp without time zone | | |
|
||||
selling_price | double precision | | not null |
|
||||
Indexes:
|
||||
"real_estate_property_pkey" PRIMARY KEY, btree (id)
|
||||
Foreign-key constraints:
|
||||
"real_estate_property_create_uid_fkey" FOREIGN KEY (create_uid) REFERENCES res_users(id) ON DELETE SET NULL
|
||||
"real_estate_property_write_uid_fkey" FOREIGN KEY (write_uid) REFERENCES res_users(id) ON DELETE SET NULL
|
||||
|
||||
exit
|
||||
|
||||
- Each field, except the image that is saved as an attachment, is represented in the
|
||||
`real_estate_property` SQL table by a column whose type is determined by the field's class:
|
||||
|
||||
+--------------------+----------------------+
|
||||
| Field class | Column type |
|
||||
+====================+======================+
|
||||
| `fields.Integer` | `integer` |
|
||||
+--------------------+----------------------+
|
||||
| `fields.Float` | `double precision` |
|
||||
+--------------------+----------------------+
|
||||
| `fields.Char` | `character varying` |
|
||||
+--------------------+----------------------+
|
||||
| `fields.Text` | `text` |
|
||||
+--------------------+----------------------+
|
||||
| `fields.Selection` | `character varying` |
|
||||
+--------------------+----------------------+
|
||||
| `fields.Boolean` | `boolean` |
|
||||
+--------------------+----------------------+
|
||||
| `fields.Date` | `date` |
|
||||
+--------------------+----------------------+
|
||||
|
||||
- The `required` attribute of a field prevents the corresponding column to be nullable.
|
||||
|
||||
- The `default` attribute of a field does *not* set a default value on the column; instead, it's
|
||||
the ORM that takes care of setting default values for newly created records.
|
||||
|
||||
You might be surprised to find that the generated SQL table contains more columns than just the
|
||||
fields you defined. That is because Odoo automatically adds several readonly :dfn:`you can read but
|
||||
not write` fields to each model for internal purposes. Here are some additional fields you'll
|
||||
typically find:
|
||||
|
||||
- `id`: A unique identifier that is automatically computed for each new record.
|
||||
- `create_date`: The timestamp of when the record was created.
|
||||
- `create_uid`: The ID of the user who created the record.
|
||||
- `write_date`: The timestamp of the last modification to the record.
|
||||
- `write_uid`: The ID of the user who last modified the record.
|
||||
|
||||
.. seealso::
|
||||
:ref:`Reference documentation on automatic fields <reference/fields/automatic>`
|
||||
|
||||
.. _tutorials/server_framework_101/load_data_files:
|
||||
|
||||
Load data files
|
||||
===============
|
||||
|
||||
Now that we have created our first model, let's consider an important question: What's missing from
|
||||
our database? The answer is simple: data!
|
||||
|
||||
While we could create new records directly from the user interface, this approach has some
|
||||
limitations. It would be quite tedious and time-consuming, especially for large amounts of data, and
|
||||
the changes would only affect the current database.
|
||||
|
||||
.. _tutorials/server_framework_101/xml_data_files:
|
||||
|
||||
XML data files
|
||||
--------------
|
||||
|
||||
Fortunately, the server framework allows for a different approach: describe data operations in XML
|
||||
format in so-called **data files** that the server automatically loads at start-up in sequential
|
||||
order. This automates the process of populating the database, saving time and effort, and allows
|
||||
developers to include default data or configurations directly in their modules.
|
||||
|
||||
The most common data operation is creating new records through the `record` and `field` XML
|
||||
elements, but other operations exist, such as `delete`, which deletes previously created records, or
|
||||
even `function`, which allows executing arbitrary code.
|
||||
|
||||
Some data operations require their data elements to be uniquely identified by the system. This is
|
||||
achieved by means of the `id` attribute, also known as the **XML ID** or **external identifier**. It
|
||||
provides a way for other elements to reference it with the `ref` attribute and links data elements
|
||||
to the records they create or update. XML IDs are automatically prefixed with their module name when
|
||||
created from a data file so that records can be referenced by their full XML ID `<module>.<id>`.
|
||||
|
||||
.. example::
|
||||
Let's again take the `product` model as an example and describe a few product records in a data
|
||||
file.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="coffee_table" model="product">
|
||||
<field name="name">Coffee table</field>
|
||||
<field name="description">A dark wood table easy to match with other furnishing.</field>
|
||||
<field name="price">275</field>
|
||||
<field name="category">home_decor</field>
|
||||
</record>
|
||||
|
||||
<record id="product.tshirt" model="product">
|
||||
<field name="name">T-shirt</field>
|
||||
<field name="price">29.99</field>
|
||||
<field name="shop_id" ref="product.tshirt_shop"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
.. note::
|
||||
As we can see, data files are rather straightforward:
|
||||
|
||||
- The root element must be `odoo`.
|
||||
- Multiple data operations can be described inside a single `odoo` element.
|
||||
- The `id` attribute can be written with the module prefix included for clarity.
|
||||
- Required fields must be provided a value if they don't have a default one.
|
||||
- Non-required fields can be omitted.
|
||||
- The `ref` attribute is used to reference other records by their XML ID and use their record
|
||||
ID as value.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Reference documentation on XML data files <../../reference/backend/data>`
|
||||
|
||||
Let's now load some default real estate properties in our database.
|
||||
|
||||
.. exercise::
|
||||
#. Create a new :file:`real_estate_property_data.xml` file at the root of the `real_estate`
|
||||
module.
|
||||
#. Update the manifest to let the server know that it should load our data file. To do so, have
|
||||
the `data` key list our data file name.
|
||||
#. Use the `record` and `field` data operation to describe at least three default properties
|
||||
records. Try to vary property types and set different values than the default ones. Add the
|
||||
image files for the various properties at the root of the `real_estate` module and assign them
|
||||
to the properties' Image field.
|
||||
#. Restart the server, again with the `-u real_estate` argument, to load the module data at
|
||||
server start-up.
|
||||
#. In the terminal, execute the command `psql -d tutorials` and enter the command
|
||||
`SELECT * FROM real_estate_property;` to verify that the records were loaded.
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: python
|
||||
:caption: `__manifest__.py`
|
||||
:emphasize-lines: 2
|
||||
|
||||
'data': [
|
||||
'real_estate_property_data.xml',
|
||||
],
|
||||
|
||||
.. code-block:: text
|
||||
:caption: `country_house.png`
|
||||
|
||||
<binary data>
|
||||
|
||||
.. code-block:: text
|
||||
:caption: `loft.png`
|
||||
|
||||
<binary data>
|
||||
|
||||
.. code-block:: text
|
||||
:caption: `mixed_use_commercial.png`
|
||||
|
||||
<binary data>
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `real_estate_property_data.xml`
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="real_estate.country_house" model="real.estate.property">
|
||||
<field name="name">Country house</field>
|
||||
<field name="description">In the charming village of Grand-Rosière-Hottomont, 5 minutes from all facilities (shops, schools, public transport, ...), we offer this superb newly renovated country house!</field>
|
||||
<field name="image" type="base64" file="real_estate/country_house.png"/>
|
||||
<field name="type">house</field>
|
||||
<field name="selling_price">745000</field>
|
||||
<field name="availability_date">2024-08-01</field>
|
||||
<field name="floor_area">416</field>
|
||||
<field name="bedrooms">5</field>
|
||||
<field name="has_garage">True</field>
|
||||
<field name="has_garden">True</field>
|
||||
<field name="garden_area">2100</field>
|
||||
</record>
|
||||
|
||||
<record id="real_estate.loft" model="real.estate.property">
|
||||
<field name="name">Loft</field>
|
||||
<field name="description">Located on the 1st floor of a small, fully renovated building, magnificent 195 m² three-bedroom apartment with parking space.</field>
|
||||
<field name="image" type="base64" file="real_estate/loft.png"/>
|
||||
<field name="type">apartment</field>
|
||||
<field name="selling_price">339000</field>
|
||||
<field name="availability_date">2025-01-01</field>
|
||||
<field name="floor_area">195</field>
|
||||
<field name="bedrooms">3</field>
|
||||
<field name="has_garage">True</field>
|
||||
<field name="has_garden">False</field>
|
||||
</record>
|
||||
|
||||
<record id="real_estate.mixed_use_commercial" model="real.estate.property">
|
||||
<field name="name">Mixed use commercial building</field>
|
||||
<field name="description">The property is a former bank agency which consists of a retail ground floor, a basement and 2 extra office floors.</field>
|
||||
<field name="image" type="base64" file="real_estate/mixed_use_commercial.png"/>
|
||||
<field name="type">retail</field>
|
||||
<field name="selling_price">335000</field>
|
||||
<field name="availability_date">2024-10-02</field>
|
||||
<field name="floor_area">370</field>
|
||||
<field name="bedrooms">0</field>
|
||||
<field name="has_garage">False</field>
|
||||
<field name="has_garden">False</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
.. _tutorials/server_framework_101/csv_data_files:
|
||||
|
||||
CSV data files
|
||||
--------------
|
||||
|
||||
In addition to XML data files, the server framework allows loading data files in CSV format. This
|
||||
format is often more convenient for describing records with simple field values belonging to the
|
||||
same model. It also loads faster, making it the go-to format when performance matters most.
|
||||
|
||||
.. example::
|
||||
See below for an example of how a subset of `country states can be loaded into Odoo
|
||||
<{GITHUB_PATH}/odoo/addons/base/data/res.country.state.csv>`_.
|
||||
|
||||
.. code-block:: csv
|
||||
:caption: `res.country.state.csv`
|
||||
|
||||
"id","country_id:id","name","code"
|
||||
state_ca_ab,ca,"Alberta","AB"
|
||||
state_ca_bc,ca,"British Columbia","BC"
|
||||
state_ca_mb,ca,"Manitoba","MB"
|
||||
state_ca_nb,ca,"New Brunswick","NB"
|
||||
state_ca_nl,ca,"Newfoundland and Labrador","NL"
|
||||
state_ca_nt,ca,"Northwest Territories","NT"
|
||||
state_ca_ns,ca,"Nova Scotia","NS"
|
||||
state_ca_nu,ca,"Nunavut","NU"
|
||||
state_ca_on,ca,"Ontario","ON"
|
||||
state_ca_pe,ca,"Prince Edward Island","PE"
|
||||
state_ca_qc,ca,"Quebec","QC"
|
||||
state_ca_sk,ca,"Saskatchewan","SK"
|
||||
state_ca_yt,ca,"Yukon","YT"
|
||||
|
||||
.. note::
|
||||
- The file name must match the model name.
|
||||
- The first line lists the model fields to populate.
|
||||
- XML IDs are specified via the special `id` field.
|
||||
- The `:id` suffix is used to reference other records by their XML ID and use their record ID
|
||||
as value.
|
||||
- Each subsequent line describes one new record.
|
||||
|
||||
.. seealso::
|
||||
:ref:`Reference documentation on CSV data files <reference/data/csvdatafiles>`
|
||||
|
||||
In business applications like Odoo, one of the first questions to consider is who can access the
|
||||
data. By default, access to newly created models is restricted until it is explicitly granted.
|
||||
Granting access rights is done by creating records of the `ir.model.access` model, which specifies
|
||||
who has access to which model.
|
||||
|
||||
The topic of security will be covered in detail in :doc:`../restrict_data_access`. For now, we'll
|
||||
just give ourselves access rights to the `real.estate.property` model to get rid of the warning that
|
||||
began being logged at server start-up after creating the model:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
WARNING tutorials odoo.modules.loading: The models ['real.estate.property'] have no access rules [...]
|
||||
|
||||
.. exercise::
|
||||
#. Create a new :file:`ir.model.access.csv` file at the root of the `real_estate` module.
|
||||
#. Declare it in the manifest as you did for the :file:`real_estate_property_data.xml` file.
|
||||
#. Grant access to the `real.estate.property` model to all administrators of the database by
|
||||
adding new access rights with the following specifications:
|
||||
|
||||
- XML ID: `real_estate_property_system`
|
||||
- `name`: `real.estate.property.system`
|
||||
- `model_id`: The record ID of `model_real_estate_property`
|
||||
- `group_id`: The record ID of `base.group_system`
|
||||
- `perm_read`, `perm_write`, `perm_create`, and `perm_unlink`: `1`
|
||||
|
||||
.. tip::
|
||||
In Odoo, modules and models are automatically given an XML ID computed by prefixing their name
|
||||
with `module_` and `model_` respectively.
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: python
|
||||
:caption: `__manifest__.py`
|
||||
:emphasize-lines: 2
|
||||
|
||||
'data': [
|
||||
'ir.model.access.csv',
|
||||
'real_estate_property_data.xml',
|
||||
],
|
||||
|
||||
.. code-block:: csv
|
||||
:caption: `ir.model.access.csv`
|
||||
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
real_estate_property_system,real.estate.property.system,model_real_estate_property,base.group_system,1,1,1,1
|
||||
|
||||
After restarting the server, the warning should no longer appear.
|
||||
|
||||
----
|
||||
|
||||
In the next chapter, we'll :doc:`create user interface elements <03_build_user_interface>` to
|
||||
interact with the property model.
|
@ -0,0 +1,712 @@
|
||||
===================================
|
||||
Chapter 3: Build the user interface
|
||||
===================================
|
||||
|
||||
In this chapter, we will bring our application to life by building a :abbr:`UI (User Interface)`
|
||||
that allows users to interact with the models. This will require defining menu items, actions, and
|
||||
views. By the end of this chapter, we will have a fully functional interface to manage real estate
|
||||
properties!
|
||||
|
||||
.. _tutorials/server_framework_101/menu_items:
|
||||
|
||||
Add menus items
|
||||
===============
|
||||
|
||||
**Menus items** are the first thing users see and interact with. They give users access to the
|
||||
different parts of Odoo and can be nested to form a hierarchical structure. This allows the
|
||||
functionalities of complex applications to be organized into categories and sub-categories and makes
|
||||
them easier to navigate. The top level of the menu structure typically contains the menu items for
|
||||
the main applications (like :guilabel:`Contacts`, :guilabel:`Sales`, and :guilabel:`Accounting`).
|
||||
These top-level menu items can also be visually enhanced with custom icons for better recognition.
|
||||
|
||||
Menu items can take on two distinct roles:
|
||||
|
||||
- **Category menu items**: They serve as organizational containers and simply expand to show their
|
||||
submenu when clicked.
|
||||
- **Action menu items**: They trigger a specific action in the UI when clicked. While menu items
|
||||
often trigger actions, they are separate entities. The same action can be triggered by different
|
||||
menu items, or even buttons.
|
||||
|
||||
In Odoo, menu items are actually records of the `ir.ui.menu` model whose key fields are:
|
||||
|
||||
.. rst-class:: o-definition-list
|
||||
|
||||
`name` (required)
|
||||
The title of the menu item.
|
||||
`sequence`
|
||||
Determines the ordering of same-level menu items.
|
||||
`parent_id`
|
||||
The ID of the parent menu item's record.
|
||||
`web_icon`
|
||||
The path to the icon file to use as a menu item icon.
|
||||
`action`
|
||||
The action to trigger when the menu item is clicked.
|
||||
|
||||
Like for any other model, one can automatically create records of the `ir.ui.menu` model by means of
|
||||
a data file. Let’s do just that and add menu items to our real estate app!
|
||||
|
||||
.. exercise::
|
||||
#. Create and declare a new :file:`menus.xml` file at the root of the `real_estate` module.
|
||||
#. Describe a new :guilabel:`Real Estate` menu item to serve as root menu for our real estate
|
||||
app.
|
||||
|
||||
- Leave the `parent_id` field empty to place the menu item in the top-level menu.
|
||||
- Use the `static/description/icon.png` file as `web_icon`, in the format
|
||||
`<module>,<icon_file_path>`.
|
||||
|
||||
#. Nest new :guilabel:`Properties` and :guilabel:`Settings` menu items under the root menu item.
|
||||
As we have not yet created an action to browse properties or open settings, reference the
|
||||
following existing actions instead:
|
||||
|
||||
- `base.open_module_tree` that opens the list of modules.
|
||||
- `base.action_client_base_menu` that opens the general settings.
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: python
|
||||
:caption: `__manifest__.py`
|
||||
:emphasize-lines: 3
|
||||
|
||||
'data': [
|
||||
'ir.model.access.csv',
|
||||
'menus.xml',
|
||||
'real_estate_property_data.xml',
|
||||
],
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `menus.xml`
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record id="real_estate.root_menu" model="ir.ui.menu">
|
||||
<field name="name">Real Estate</field>
|
||||
<field name="web_icon">real_estate,static/description/icon.png</field>
|
||||
</record>
|
||||
|
||||
<record id="real_estate.properties_menu" model="ir.ui.menu">
|
||||
<field name="name">Properties</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="parent_id" ref="real_estate.root_menu"/>
|
||||
<field name="action" ref="base.open_module_tree"/>
|
||||
</record>
|
||||
|
||||
<record id="real_estate.settings_menu" model="ir.ui.menu">
|
||||
<field name="name">Settings</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="parent_id" ref="real_estate.root_menu"/>
|
||||
<field name="action" ref="base.action_client_base_menu"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
If you go to the app switcher :dfn:`the top-level menu of Odoo`, you should now see a menu item for
|
||||
our real estate app! Click it to open the app and automatically trigger the first action in its
|
||||
sub-menu. If you referenced the `base.open_module_tree` action, you should now see a list of Odoo
|
||||
modules.
|
||||
|
||||
.. _tutorials/server_framework_101/menuitem_shortcut:
|
||||
|
||||
Use the `menuitem` shortcut
|
||||
---------------------------
|
||||
|
||||
As an application grows in size, so do its menus, and it becomes increasingly complicated to define
|
||||
and nest menu items. While defining menu items using the `record` data operation works perfectly
|
||||
fine, the server framework provides a shortcut that makes the process easier and more intuitive,
|
||||
especially for nesting menu items: the `menuitem` data operation.
|
||||
|
||||
The `menuitem` tag is a special XML element that is specifically designed for creating menu items;
|
||||
it simplifies the syntax and automatically handles some technical details for you.
|
||||
|
||||
.. example::
|
||||
Our fictional `product` module could define menu items as follows:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<menuitem id="product.root_menu" name="Product" web_icon="product,static/description/product.png">
|
||||
<menuitem id="product.all_products_menu" name="All Products" sequence="1" action="product.view_all_products_action"/>
|
||||
<menuitem id="product.new_products_menu" name="New Products" sequence="2" action="product.view_new_products_action"/>
|
||||
</menuitem>
|
||||
|
||||
.. note::
|
||||
- The outer `menuitem` data operation creates the top-level :guilabel:`Product` menu item.
|
||||
- The specifications (`name`, `web_icon`, `sequence`, `action`, ...) of menu items are set
|
||||
through attributes of the XML element.
|
||||
- The menu items hierarchy is defined by nesting their XML elements.
|
||||
|
||||
Why keep complex code when you can simplify it? It's already time for our first **code
|
||||
refactoring**!
|
||||
|
||||
.. exercise::
|
||||
Rewrite the description of the menu items of our real estate app using the `menuitem` data
|
||||
operation instead of `record`.
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `menus.xml`
|
||||
:emphasize-lines: 4-21
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<menuitem
|
||||
id="real_estate.root_menu"
|
||||
name="Real Estate"
|
||||
web_icon="real_estate,static/description/icon.png"
|
||||
>
|
||||
<menuitem
|
||||
id="real_estate.properties_menu"
|
||||
name="Properties"
|
||||
sequence="1"
|
||||
action="base.open_module_tree"
|
||||
/>
|
||||
<menuitem
|
||||
id="real_estate.settings_menu"
|
||||
name="Settings"
|
||||
sequence="2"
|
||||
action="base.action_client_base_menu"
|
||||
/>
|
||||
</menuitem>
|
||||
|
||||
</odoo>
|
||||
|
||||
.. _tutorials/server_framework_101/define_window_actions:
|
||||
|
||||
Define window actions
|
||||
=====================
|
||||
|
||||
**Actions** define what happens when a user interacts with the UI, such as clicking a menu item.
|
||||
They connect the user interface with the underlying business logic. There exist different types of
|
||||
actions in Odoo, the most common one being **window actions** (`ir.actions.act_window`), that
|
||||
display the records of a specific model in a view. Other types of actions allow for different
|
||||
behaviors, like **URL actions** that open URLs (`ir.actions.act_url`) or **server actions**
|
||||
(`ir.actions.server`) that execute custom code.
|
||||
|
||||
In Odoo, actions can be stored in the database as records or returned as Python dictionaries
|
||||
interpreted as action descriptors when business logic is executed. Window actions are described by
|
||||
the `ir.actions.act_window` model whose key fields include:
|
||||
|
||||
.. rst-class:: o-definition-list
|
||||
|
||||
`name` (required)
|
||||
The title of the action; is often used as the page title.
|
||||
`res_model` (required)
|
||||
The model on which the action operates.
|
||||
`view_mode`
|
||||
A comma-separated list of view types to enable for this action; for example, `list,form,kanban`.
|
||||
`help`
|
||||
An optional help text for the users when there are no records to display.
|
||||
|
||||
.. example::
|
||||
The example below defines an action to open existing products in either list or form view.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="product.view_products_action" model="ir.actions.act_window">
|
||||
<field name="name">Products</field>
|
||||
<field name="res_model">product</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new product.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
.. note::
|
||||
The content of the `help` field can be written in different formats thanks to the `type`
|
||||
attribute of the :ref:`field <reference/data/field>` data operation.
|
||||
|
||||
.. seealso::
|
||||
:ref:`Reference documentation on window actions <reference/actions/window>`
|
||||
|
||||
As promised, we'll finally get to interact with our real estate properties in the UI. All we need
|
||||
now is an action to assign to the menu item.
|
||||
|
||||
.. exercise::
|
||||
#. Create and declare a new :file:`actions.xml` file at the root of the `real_estate` module.
|
||||
#. Describe a new :guilabel:`Properties` window action that opens `real.estate.property` records
|
||||
in list and form views, and assign it to the :guilabel:`Properties` menu item. Be creative
|
||||
with the help text! For reference, the list of supported classes can be found in the
|
||||
`view.scss <{GITHUB_PATH}/addons/web/static/src/views/view.scss>`_ file.
|
||||
|
||||
.. tip::
|
||||
Pay attention to the declaration order of data files in the manifest; you might introduce a
|
||||
data operation that depends on another one.
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: python
|
||||
:caption: `__manifest__.py`
|
||||
:emphasize-lines: 2,4
|
||||
|
||||
'data': [
|
||||
'actions.xml',
|
||||
'ir.model.access.csv',
|
||||
'menus.xml', # Depends on `actions.xml`
|
||||
'real_estate_property_data.xml',
|
||||
],
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `actions.xml`
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record id="real_estate.view_properties_action" model="ir.actions.act_window">
|
||||
<field name="name">Properties</field>
|
||||
<field name="res_model">real.estate.property</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<!-- Turns out I didn't feel like being creative with the help text ¯\_(ツ)_/¯ -->
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new property.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `menus.xml`
|
||||
:emphasize-lines: 5
|
||||
|
||||
<menuitem
|
||||
id="real_estate.properties_menu"
|
||||
name="Properties"
|
||||
sequence="10"
|
||||
action="real_estate.view_properties_action"
|
||||
/>
|
||||
|
||||
Clicking the :guilabel:`Properties` menu item now displays a list view of the default properties we
|
||||
created earlier. As we specified in the action that both list and form views were allowed, you can
|
||||
click any property record to display its form view. Delete all three records to see the help text
|
||||
you created.
|
||||
|
||||
.. _tutorials/server_framework_101/create_custom_views:
|
||||
|
||||
Create custom views
|
||||
===================
|
||||
|
||||
**Views** are the UI's building blocks, defining how model data is displayed on screen. They are
|
||||
structures written in XML that describe the layout and behavior of various UI components.
|
||||
|
||||
Odoo supports different types of views, each serving a different purpose. The most common types
|
||||
include **list views** for listing multiple records in a table-like format, **form views** for
|
||||
displaying and editing individual records, **kanban views** for presenting records in a card layout,
|
||||
and **search views** for defining search and filtering options.
|
||||
|
||||
In Odoo, views are records of the `ir.ui.view` model. Each view is associated with a specific model,
|
||||
determining which data it displays and interacts with. Key fields include:
|
||||
|
||||
.. rst-class:: o-definition-list
|
||||
|
||||
`name` (required)
|
||||
A unique name for the view.
|
||||
`model` (required)
|
||||
The model the view is associated with.
|
||||
`arch` (required)
|
||||
The view architecture as an XML string.
|
||||
|
||||
The `arch` field holds the view's XML architecture, which is composed of a root element determining
|
||||
the type of the view, and various inner components that depend on the view type. The root element
|
||||
(e.g., `list`, `form`, `search`) defines the view type, while the inner components describe the
|
||||
structure and content of the view. These components can be structural (like `sheet` that makes the
|
||||
layout responsive, or `group` that defines column layouts) or semantic (like `field` that displays
|
||||
field labels and values).
|
||||
|
||||
.. example::
|
||||
The following examples demonstrate how to define simple list, form and search views for the
|
||||
`product` model.
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: A list view for `product`
|
||||
|
||||
<record id="product_list" model="ir.ui.view">
|
||||
<field name="name">Product List</field>
|
||||
<field name="model">product</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="price"/>
|
||||
<field name="category"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: A form view for `product`
|
||||
|
||||
<record id="product_form" model="ir.ui.view">
|
||||
<field name="name">Product Form</field>
|
||||
<field name="model">product</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
<field name="price"/>
|
||||
<field name="category"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: A search view for `product`
|
||||
|
||||
<record id="product_search" model="ir.ui.view">
|
||||
<field name="name">Product Search</field>
|
||||
<field name="model">product</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
.. note::
|
||||
- The XML structure differs between view types.
|
||||
- The `description` field is omitted from the list view because it wouldn't fit visually.
|
||||
|
||||
.. seealso::
|
||||
- :doc:`Reference documentation on view records <../../reference/user_interface/view_records>`
|
||||
- :doc:`Reference documentation on view architectures
|
||||
<../../reference/user_interface/view_architectures>`
|
||||
|
||||
In :ref:`the previous section <tutorials/server_framework_101/define_window_actions>`, we defined
|
||||
the `view_mode` of our action to display `real.estate.property` records in list and form view.
|
||||
Although we haven't created the corresponding views yet, the server framework had our back and
|
||||
automatically provided generic views. The generic list and form views were hard to miss, but a
|
||||
generic search view was also provided; when searching for properties, you are in fact searching on
|
||||
property names because it's the only field of the generic view.
|
||||
|
||||
However convenient, we should almost never rely on these generic views in business applications.
|
||||
They are incomplete, badly structured, and often use the wrong field widgets. Let's create our own
|
||||
custom views for a better :abbr:`UX (User experience)`.
|
||||
|
||||
.. _tutorials/server_framework_101/list_view:
|
||||
|
||||
List view
|
||||
---------
|
||||
|
||||
For a start, the list view could use more fields than just the name.
|
||||
|
||||
.. exercise::
|
||||
#. Create a new :file:`real_estate_property_views.xml` file at the root of the `real_estate`
|
||||
module.
|
||||
#. Create a custom list view to display the following fields of the `real.estate.property` model
|
||||
in the given order: :guilabel:`Name`, :guilabel:`State`, :guilabel:`Type`,
|
||||
:guilabel:`Selling Price`, :guilabel:`Availability Date`, :guilabel:`Floor Area`,
|
||||
:guilabel:`Number of Bedrooms`, :guilabel:`Garage`, :guilabel:`Garden`, and
|
||||
:guilabel:`Garden Area`.
|
||||
#. Make the visibility of :guilabel:`Floor Area` and all following fields optional so that only
|
||||
the floor area is visible by default, while the remaining fields are hidden by default and
|
||||
must be manually displayed by accessing the view's column selector
|
||||
(:icon:`oi-settings-adjust` button).
|
||||
#. After restarting the server to load the new data, refresh the browser to see the result.
|
||||
|
||||
.. tip::
|
||||
Refer to the documentation on :ref:`the field component in list views
|
||||
<reference/view_architectures/list/field>`.
|
||||
|
||||
The final result should look like this:
|
||||
|
||||
.. image:: 03_build_user_interface/custom-list-view.png
|
||||
:align: center
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: python
|
||||
:caption: `__manifest__.py`
|
||||
:emphasize-lines: 6
|
||||
|
||||
'data': [
|
||||
'actions.xml',
|
||||
'ir.model.access.csv',
|
||||
'menus.xml', # Depends on `actions.xml`
|
||||
'real_estate_property_data.xml',
|
||||
'real_estate_property_views.xml',
|
||||
],
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `real_estate_property_views.xml`
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="real_estate.property_list" model="ir.ui.view">
|
||||
<field name="name">Property List</field>
|
||||
<field name="model">real.estate.property</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
<field name="type"/>
|
||||
<field name="selling_price"/>
|
||||
<field name="availability_date"/>
|
||||
<field name="floor_area" optional="show"/>
|
||||
<field name="bedrooms" optional="hide"/>
|
||||
<field name="has_garage" optional="hide"/>
|
||||
<field name="has_garden" optional="hide"/>
|
||||
<field name="garden_area" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
.. _tutorials/server_framework_101/form_view:
|
||||
|
||||
Form view
|
||||
---------
|
||||
|
||||
.. exercise::
|
||||
In the :file:`real_estate_property_views.xml` file, create a custom form view to display all
|
||||
fields of the `real.estate.property` model in a well-structured manner:
|
||||
|
||||
- The state should be displayed as a status bar in the header and should be able to be updated
|
||||
with a click.
|
||||
- The form should have margins (hint: use the `sheet` component).
|
||||
- The name should be displayed as the title of the form, should have its label on top, and should
|
||||
have a placeholder.
|
||||
- The image should be displayed as a thumbnail on the right side of the form.
|
||||
- The fields should be grouped in two sections displayed next to each other:
|
||||
|
||||
- Listing Information: :guilabel:`Type`, :guilabel:`Selling Price`,
|
||||
:guilabel:`Availability Date`, :guilabel:`Active`
|
||||
- Building Specifications: :guilabel:`Floor Area`, :guilabel:`Number of Bedrooms`,
|
||||
:guilabel:`Garage`, :guilabel:`Garden`, :guilabel:`Garden Area`
|
||||
|
||||
- The description should be displayed at the bottom of the form in its own section, should have
|
||||
no label, should have a placeholder, and should take the full width.
|
||||
|
||||
.. tip::
|
||||
- Refer to the documentation on :ref:`structural components
|
||||
<reference/view_architectures/form/structural>` and :ref:`the field component
|
||||
<reference/view_architectures/form/field>` in form views.
|
||||
- Add the :option:`--dev xml <odoo-bin --dev>` argument to the server start-up command to
|
||||
instruct the server to load records defined in XML from your filesystem rather than from the
|
||||
database. This avoids restarting the server after modifying an XML file.
|
||||
|
||||
The final result should look like this:
|
||||
|
||||
.. image:: 03_build_user_interface/custom-form-view.png
|
||||
:align: center
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `real_estate_property_views.xml`
|
||||
|
||||
<record id="real_estate.property_form" model="ir.ui.view">
|
||||
<field name="name">Property Form</field>
|
||||
<field name="model">real.estate.property</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<header>
|
||||
<field name="state" widget="statusbar" options="{'clickable': True}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<field name="image" widget="image" class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" string="Property Name"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Tiny House"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Listing Information">
|
||||
<field name="type"/>
|
||||
<field name="selling_price"/>
|
||||
<field name="availability_date"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group string="Building Specifications">
|
||||
<field name="floor_area"/>
|
||||
<field name="bedrooms"/>
|
||||
<field name="has_garage"/>
|
||||
<field name="has_garden"/>
|
||||
<field name="garden_area"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="Description"/>
|
||||
<field
|
||||
name="description"
|
||||
nolabel="1"
|
||||
colspan="2"
|
||||
placeholder="Write a description about this property."
|
||||
/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
.. _tutorials/server_framework_101/search_view:
|
||||
|
||||
Search view
|
||||
-----------
|
||||
|
||||
The `name` and `active` fields we added earlier to the model are not ordinary fields; they're
|
||||
examples of **reserved fields**. When set on a model, these special fields enable specific
|
||||
pre-defined behaviors. For example, the `active` field enables **archiving** (`active = False`) and
|
||||
**unarchiving** (`active = True`) records through the :icon:`oi-archive` :guilabel:`Archive` and
|
||||
:icon:`oi-unarchive` :guilabel:`Unarchive` buttons in the action menu. Archived records are
|
||||
automatically excluded from searches. You can observe this behavior by deselecting the
|
||||
:guilabel:`Active` checkbox for one of your property records: you'll notice the record no longer
|
||||
appears upon returning to the list view.
|
||||
|
||||
To facilitate the browsing of archived properties, we need to create a search view. Unlike list and
|
||||
form views, search views are not used to display record data on screen. Instead, they define the
|
||||
search behavior and enable users to search on specific fields. They also provide pre-defined
|
||||
**filters** that allow for quickly searching with complex queries and grouping records by particular
|
||||
fields.
|
||||
|
||||
The most common way to set up filters is through **search domains**. Domains are used to select
|
||||
specific records of a model by defining a list of criteria. Each criterion is a triplet in the
|
||||
format :code:`(<field_name>, <operator>, <value>)`.
|
||||
|
||||
.. example::
|
||||
The example search domain below selects only products of the category "Home Decor" whose price is
|
||||
less than 1000.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[('category', '=', 'home_decor'), ('price', '<', 1000)]
|
||||
|
||||
By default, domain criteria are combined with an implicit logical `&` (AND) operator, meaning
|
||||
*every* criterion must be satisfied for a record to match a domain. Criteria can also be combined
|
||||
with the logical `|` (OR) and `!` (NOT) operators in prefix form :dfn:`the operator is inserted
|
||||
before its operands`.
|
||||
|
||||
.. example::
|
||||
The example search domain below selects only products that belong to the category "Electronics"
|
||||
*or* whose price is *not* between 1000 and 2000.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
['|', ('category', '=', 'electronics'), '!', '&', ('price', '>=', 1000), ('price', '<', 2000)]
|
||||
|
||||
.. seealso::
|
||||
- :ref:`Reference documentation on search views <reference/view_architectures/search>`
|
||||
- :ref:`Reference documentation on search domains <reference/orm/domains>`
|
||||
- :ref:`Reference documentation of the list of reserved field names
|
||||
<reference/orm/fields/reserved>`
|
||||
|
||||
All the generic search view only allows for is searching on property names; that's the bare minimum.
|
||||
Let's enhance the search capabilities.
|
||||
|
||||
.. exercise::
|
||||
#. Create a custom search view with the following features:
|
||||
|
||||
- Enable searching on the these fields:
|
||||
|
||||
- :guilabel:`Name`: Match records whose name contain the search value.
|
||||
- :guilabel:`Description`: Match records whose description *or* name contains the search
|
||||
value.
|
||||
- :guilabel:`Selling Price`: Match records with a price *less than or equal to* the search
|
||||
value.
|
||||
- :guilabel:`Floor Area`: Match records with a floor area *at least* the search value.
|
||||
- :guilabel:`Number of Bedrooms`: Match records with *at least* the given number of
|
||||
bedrooms.
|
||||
|
||||
- Implement these filters:
|
||||
|
||||
- :guilabel:`For Sale`: The state is :guilabel:`New` or :guilabel:`Offer Received`.
|
||||
- :guilabel:`Availability Date`: Display a list of pre-defined availability date values.
|
||||
- :guilabel:`Garage`: The property has a garage.
|
||||
- :guilabel:`Garden`: The property has a garden.
|
||||
- :guilabel:`Archived`: The property is archived.
|
||||
|
||||
- Combine selected filters with a logical AND, except for Garage and Garden, which should use
|
||||
OR when both are selected.
|
||||
- Enable grouping properties by state and type.
|
||||
|
||||
#. Modify the window action to display only properties available for sale by default.
|
||||
#. Make sure that everything works!
|
||||
|
||||
.. tip::
|
||||
|
||||
- Refer to the documentation on :ref:`search view components
|
||||
<reference/view_architectures/search/components>`, :ref:`search domains
|
||||
<reference/orm/domains>`, and :ref:`search defaults
|
||||
<reference/view_architectures/search/defaults>`.
|
||||
- In XML, use entity references to avoid parsing errors: `<` for `<`, `>` for `>`, and
|
||||
`&` for `&`.
|
||||
|
||||
The final result should look like this:
|
||||
|
||||
.. image:: 03_build_user_interface/custom-search-view-fields.png
|
||||
:align: center
|
||||
|
||||
.. image:: 03_build_user_interface/custom-search-view-filters.png
|
||||
:align: center
|
||||
|
||||
.. spoiler:: Solution
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `real_estate_property_views.xml`
|
||||
|
||||
<record id="real_estate.property_search" model="ir.ui.view">
|
||||
<field name="name">Property Search</field>
|
||||
<field name="model">real.estate.property</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<!-- Fields -->
|
||||
<field name="name"/>
|
||||
<field
|
||||
name="description"
|
||||
filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"
|
||||
/>
|
||||
<field name="selling_price" string="Maximum Price" operator="<="/>
|
||||
<field name="floor_area" string="Minimum Floor Area" operator=">="/>
|
||||
<field name="bedrooms" string="Minimum Bedrooms" operator=">="/>
|
||||
|
||||
<!-- Filters -->
|
||||
<filter
|
||||
name="filter_for_sale"
|
||||
string="For Sale"
|
||||
domain="[('state', 'in', ['new', 'offer_received'])]"
|
||||
/>
|
||||
<separator/>
|
||||
<filter name="filter_availability" date="availability_date"/>
|
||||
<separator/>
|
||||
<filter name="filter_garage" string="Garage" domain="[('has_garage', '=', True)]"/>
|
||||
<filter name="filter_garden" string="Garden" domain="[('has_garden', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter name="filter_inactive" string="Archived" domain="[('active', '=', False)]"/>
|
||||
|
||||
<!-- Group by -->
|
||||
<filter name="group_by_state" context="{'group_by': 'state'}"/>
|
||||
<filter name="group_by_type" context="{'group_by': 'type'}"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: `actions.xml`
|
||||
:emphasize-lines: 4
|
||||
|
||||
<record id="real_estate.view_properties_action" model="ir.actions.act_window">
|
||||
<field name="name">Properties</field>
|
||||
<field name="res_model">real.estate.property</field>
|
||||
<field name="context">{'search_default_filter_for_sale': True}</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<!-- Turns out I didn't feel like being creative with the help text ¯\_(ツ)_/¯ -->
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new property.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
----
|
||||
|
||||
We now have a shiny UI to manage real estate properties, but our information model is still quite
|
||||
basic. We have a limited set of property types and a few building specifications, but that's not
|
||||
enough for a good real estate application. In the next chapter, we'll :doc:`connect properties to
|
||||
new models <04_relational_fields>` to transform our basic real estate app into a feature-rich tool.
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 9.2 KiB |
@ -0,0 +1,12 @@
|
||||
===========================
|
||||
Chapter 6: Tighten security
|
||||
===========================
|
||||
|
||||
tmp
|
||||
|
||||
.. todo: restrict access through acl
|
||||
.. todo: rule to only see your properties or unassigned ones
|
||||
|
||||
----
|
||||
|
||||
.. todo: add incentive for next chapter
|
@ -0,0 +1,25 @@
|
||||
=========================
|
||||
Chapter 7: Advanced views
|
||||
=========================
|
||||
|
||||
tmp
|
||||
|
||||
.. todo: invisible, required, readonly modifiers
|
||||
.. todo: introduce bootstrap
|
||||
.. todo: widgets; eg, <widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/>
|
||||
<field name="is_priority" widget="boolean_favorite" nolabel="1" readonly="False"/> (/!\ requires to have product installed to have the correct font-size in form view)
|
||||
.. todo: add Gantt view of properties availability
|
||||
.. todo: add Kanban view of properties
|
||||
.. todo: wizards -> create a "receive offer wizard" to default the amount to the property's selling price
|
||||
.. todo: context active_test False on the category_id field of products to see archived categories
|
||||
.. todo: sequence widget on tags
|
||||
.. todo: compute display_name for offers in form view
|
||||
.. todo: multi edit offers state
|
||||
.. todo: hide the 'Cancelled' state from the statusbar widget unless selected
|
||||
.. todo: pills for the offer state in list view
|
||||
.. todo: adapt existing or add new stat button relying on a model method that returns a dictionary acting as an action descriptor
|
||||
|
||||
|
||||
----
|
||||
|
||||
.. todo: add incentive for next chapter
|
@ -0,0 +1,15 @@
|
||||
======================
|
||||
Chapter 8: Inheritance
|
||||
======================
|
||||
|
||||
tmp
|
||||
|
||||
.. todo: inherit from mail.tread mixin and add a chatter
|
||||
.. todo: self.env._context
|
||||
.. todo: inherit from account.move
|
||||
.. todo: ex: override of real.estate.property::create to set vals['address_id'] = (0, 0, {})
|
||||
.. todo: ex: create invoice with lines (6, 0, 0)
|
||||
|
||||
----
|
||||
|
||||
.. todo: add incentive for next chapter
|
@ -0,0 +1,9 @@
|
||||
=================
|
||||
Chapter 9: Portal
|
||||
=================
|
||||
|
||||
Controllers + QWeb
|
||||
|
||||
----
|
||||
|
||||
.. todo: add incentive for next chapter
|
@ -0,0 +1,9 @@
|
||||
========================
|
||||
Chapter 10: Unit testing
|
||||
========================
|
||||
|
||||
tmp
|
||||
|
||||
.. todo: run with coverage
|
||||
|
||||
----
|
48
content/developer/tutorials/server_framework_101_legacy.rst
Normal file
@ -0,0 +1,48 @@
|
||||
:show-content:
|
||||
:orphan:
|
||||
|
||||
=============================
|
||||
Server framework 101 (legacy)
|
||||
=============================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`server_framework_101` instead.
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:glob:
|
||||
|
||||
server_framework_101_legacy/*
|
||||
|
||||
Welcome to the Server framework 101 tutorial! If you reached this page that means you are
|
||||
interested in the development of your own Odoo module. It might also mean that you recently
|
||||
joined the Odoo company for a rather technical position. In any case, your journey to the
|
||||
technical side of Odoo starts here.
|
||||
|
||||
The goal of this tutorial is for you to get an insight of the most important parts of the Odoo
|
||||
development framework while developing your own Odoo module to manage real estate assets. The
|
||||
chapters should be followed in their given order since they cover the development of a new Odoo
|
||||
application from scratch in an incremental way. In other words, each chapter depends on the previous
|
||||
one.
|
||||
|
||||
.. important::
|
||||
Before going further, make sure you have prepared your development environment with the
|
||||
:doc:`setup guide <setup_guide>`.
|
||||
|
||||
Ready? Let's get started!
|
||||
|
||||
* :doc:`server_framework_101_legacy/01_architecture`
|
||||
* :doc:`server_framework_101_legacy/02_newapp`
|
||||
* :doc:`server_framework_101_legacy/03_basicmodel`
|
||||
* :doc:`server_framework_101_legacy/04_securityintro`
|
||||
* :doc:`server_framework_101_legacy/05_firstui`
|
||||
* :doc:`server_framework_101_legacy/06_basicviews`
|
||||
* :doc:`server_framework_101_legacy/07_relations`
|
||||
* :doc:`server_framework_101_legacy/08_compute_onchange`
|
||||
* :doc:`server_framework_101_legacy/09_actions`
|
||||
* :doc:`server_framework_101_legacy/10_constraints`
|
||||
* :doc:`server_framework_101_legacy/11_sprinkles`
|
||||
* :doc:`server_framework_101_legacy/12_inheritance`
|
||||
* :doc:`server_framework_101_legacy/13_other_module`
|
||||
* :doc:`server_framework_101_legacy/14_qwebintro`
|
||||
* :doc:`server_framework_101_legacy/15_final_word`
|
@ -1,9 +1,15 @@
|
||||
.. _tutorials/server_framework_101/01_architecture:
|
||||
.. _tutorials/server_framework_101_legacy/01_architecture:
|
||||
|
||||
================================
|
||||
Chapter 1: Architecture Overview
|
||||
================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
Multitier application
|
||||
=====================
|
||||
|
@ -0,0 +1,253 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="592.55548" height="530.32971" id="svg2" sodipodi:version="0.32" inkscape:version="0.46" sodipodi:docname="Overview_of_a_three-tier_application_vectorVersion.svg" inkscape:output_extension="org.inkscape.output.svg.inkscape" version="1.0">
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" gridtolerance="10000" guidetolerance="10" objecttolerance="10" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" inkscape:cx="274.60069" inkscape:cy="311.51845" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1680" inkscape:window-height="994" inkscape:window-x="12" inkscape:window-y="42" showguides="true" inkscape:guide-bbox="true"/>
|
||||
<defs id="defs4">
|
||||
<linearGradient id="linearGradient5132">
|
||||
<stop id="stop5134" offset="0" style="stop-color: rgb(168, 173, 129); stop-opacity: 0.327434;"/>
|
||||
<stop id="stop5136" offset="1" style="stop-color: rgb(0, 0, 0); stop-opacity: 0;"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient5120">
|
||||
<stop style="stop-color: rgb(168, 173, 129); stop-opacity: 0.610619;" offset="0" id="stop5122"/>
|
||||
<stop style="stop-color: rgb(0, 0, 0); stop-opacity: 0;" offset="1" id="stop5124"/>
|
||||
</linearGradient>
|
||||
<marker style="overflow: visible;" id="Arrow2Lend" refX="0" refY="0" orient="auto" inkscape:stockid="Arrow2Lend">
|
||||
<path transform="matrix(-1.1, 0, 0, -1.1, -1.1, 0)" d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.97309,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z" style="font-size: 12px; fill-rule: evenodd; stroke-width: 0.625; stroke-linejoin: round;" id="path5691"/>
|
||||
</marker>
|
||||
<marker style="overflow: visible;" id="Arrow1Lend" refX="0" refY="0" orient="auto" inkscape:stockid="Arrow1Lend">
|
||||
<path transform="matrix(-0.8, 0, 0, -0.8, -10, 0)" style="fill-rule: evenodd; stroke: rgb(0, 0, 0); stroke-width: 1pt; marker-start: none;" d="M 0,0 L 5,-5 L -12.5,0 L 5,5 L 0,0 z" id="path5673"/>
|
||||
</marker>
|
||||
<marker style="overflow: visible;" id="Arrow1Lstart" refX="0" refY="0" orient="auto" inkscape:stockid="Arrow1Lstart">
|
||||
<path transform="matrix(0.8, 0, 0, 0.8, 10, 0)" style="fill-rule: evenodd; stroke: rgb(0, 0, 0); stroke-width: 1pt; marker-start: none;" d="M 0,0 L 5,-5 L -12.5,0 L 5,5 L 0,0 z" id="path5670"/>
|
||||
</marker>
|
||||
<linearGradient id="linearGradient3694">
|
||||
<stop id="stop3696" offset="0" style="stop-color: rgb(250, 251, 230); stop-opacity: 1;"/>
|
||||
<stop style="stop-color: rgb(225, 227, 187); stop-opacity: 0.74902;" offset="0.25" id="stop3795"/>
|
||||
<stop style="stop-color: rgb(200, 203, 145); stop-opacity: 0.498039;" offset="0.5" id="stop3702"/>
|
||||
<stop id="stop3698" offset="1" style="stop-color: rgb(250, 251, 230); stop-opacity: 0;"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3375">
|
||||
<stop id="stop3377" offset="0" style="stop-color: rgb(130, 131, 36); stop-opacity: 0.389381;"/>
|
||||
<stop id="stop3379" offset="1" style="stop-color: rgb(0, 0, 0); stop-opacity: 0;"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3367">
|
||||
<stop id="stop3369" offset="0" style="stop-color: rgb(154, 154, 154); stop-opacity: 1;"/>
|
||||
<stop id="stop3371" offset="1" style="stop-color: rgb(0, 0, 0); stop-opacity: 0;"/>
|
||||
</linearGradient>
|
||||
<inkscape:perspective id="perspective10" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" inkscape:vp_z="477.48915 : -25.408568 : 0" inkscape:vp_y="605.9884 : 795.47349 : 0" inkscape:vp_x="184.81274 : 367.45922 : 0" sodipodi:type="inkscape:persp3d"/>
|
||||
<inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:vp_x="0 : 526.18109 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="744.09448 : 526.18109 : 1" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" id="perspective2410"/>
|
||||
<inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:vp_x="0 : 526.18109 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="744.09448 : 526.18109 : 1" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" id="perspective2463"/>
|
||||
<inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:vp_x="0 : 526.18109 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="744.09448 : 526.18109 : 1" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" id="perspective2476"/>
|
||||
<radialGradient r="10.66466" fy="547.66473" fx="105.4473" cy="547.66473" cx="105.4473" gradientUnits="userSpaceOnUse" id="radialGradient3397" xlink:href="#linearGradient3375" inkscape:collect="always"/>
|
||||
<radialGradient r="4.4774756" fy="545.5434" fx="100.93949" cy="545.5434" cx="100.93949" gradientUnits="userSpaceOnUse" id="radialGradient3399" xlink:href="#linearGradient3367" inkscape:collect="always"/>
|
||||
<radialGradient r="10.66466" fy="547.66473" fx="105.4473" cy="547.66473" cx="105.4473" gradientUnits="userSpaceOnUse" id="radialGradient3447" xlink:href="#linearGradient3375" inkscape:collect="always"/>
|
||||
<radialGradient r="4.4774756" fy="545.5434" fx="100.93949" cy="545.5434" cx="100.93949" gradientUnits="userSpaceOnUse" id="radialGradient3449" xlink:href="#linearGradient3367" inkscape:collect="always"/>
|
||||
<radialGradient r="4.4774756" fy="545.5434" fx="100.93949" cy="545.5434" cx="100.93949" gradientUnits="userSpaceOnUse" id="radialGradient3469" xlink:href="#linearGradient3367" inkscape:collect="always"/>
|
||||
<radialGradient r="10.66466" fy="547.66473" fx="105.4473" cy="547.66473" cx="105.4473" gradientUnits="userSpaceOnUse" id="radialGradient3471" xlink:href="#linearGradient3375" inkscape:collect="always"/>
|
||||
<radialGradient r="10.66466" fy="547.66473" fx="105.4473" cy="547.66473" cx="105.4473" gradientUnits="userSpaceOnUse" id="radialGradient3487" xlink:href="#linearGradient3375" inkscape:collect="always"/>
|
||||
<radialGradient r="4.4774756" fy="545.5434" fx="100.93949" cy="545.5434" cx="100.93949" gradientUnits="userSpaceOnUse" id="radialGradient3489" xlink:href="#linearGradient3367" inkscape:collect="always"/>
|
||||
<inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:vp_x="0 : 526.18109 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="744.09448 : 526.18109 : 1" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" id="perspective3560"/>
|
||||
<linearGradient spreadMethod="reflect" gradientUnits="userSpaceOnUse" y2="420.9158" x2="436.07776" y1="420.9158" x1="409.62192" id="linearGradient3700" xlink:href="#linearGradient3694" inkscape:collect="always"/>
|
||||
<linearGradient spreadMethod="reflect" y2="420.9158" x2="436.07776" y1="420.9158" x1="409.62192" gradientTransform="matrix(-1, 0, 0, -1, 801.161, 848.543)" gradientUnits="userSpaceOnUse" id="linearGradient3793" xlink:href="#linearGradient3694" inkscape:collect="always"/>
|
||||
<linearGradient y2="334.11847" x2="451.48767" y1="334.11847" x1="389.26227" spreadMethod="reflect" gradientUnits="userSpaceOnUse" id="linearGradient5289" xlink:href="#linearGradient3694" inkscape:collect="always"/>
|
||||
<linearGradient gradientTransform="translate(103.75, -6.75)" y2="354.61218" x2="387.75" y1="354.61218" x1="323.75" spreadMethod="reflect" gradientUnits="userSpaceOnUse" id="linearGradient5309" xlink:href="#linearGradient3694" inkscape:collect="always"/>
|
||||
<marker style="overflow: visible;" id="Arrow2Lends" refX="0" refY="0" orient="auto" inkscape:stockid="Arrow2Lends">
|
||||
<path transform="matrix(-1.1, 0, 0, -1.1, -1.1, 0)" d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.97309,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z" style="font-size: 12px; fill: rgb(0, 0, 0); fill-rule: evenodd; stroke: rgb(0, 0, 0); stroke-width: 0.625; stroke-linejoin: round;" id="path8066"/>
|
||||
</marker>
|
||||
<pattern id="pattern2768" patternTransform="translate(135.53, 367.117)" height="124.591" width="471.03999" patternUnits="userSpaceOnUse">
|
||||
<path style="fill: rgb(255, 73, 69); fill-opacity: 1; fill-rule: evenodd; stroke: black; stroke-width: 0.886228; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 0.44311386,124.14788 L 470.59687,0.44311386" id="path1876" inkscape:connector-type="polyline"/>
|
||||
</pattern>
|
||||
<inkscape:perspective id="perspective3896" inkscape:persp3d-origin="170.9816 : 44.63 : 1" inkscape:vp_z="341.9632 : 66.945 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_x="0 : 66.945 : 1" sodipodi:type="inkscape:persp3d"/>
|
||||
<inkscape:perspective id="perspective5024" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" inkscape:vp_z="744.09448 : 526.18109 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_x="0 : 526.18109 : 1" sodipodi:type="inkscape:persp3d"/>
|
||||
<filter inkscape:collect="always" id="filter5116">
|
||||
<feGaussianBlur inkscape:collect="always" stdDeviation="0.69889934" id="feGaussianBlur5118"/>
|
||||
</filter>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3375" id="radialGradient5234" gradientUnits="userSpaceOnUse" cx="105.4473" cy="547.66473" fx="105.4473" fy="547.66473" r="10.66466"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3367" id="radialGradient5236" gradientUnits="userSpaceOnUse" cx="100.93949" cy="545.5434" fx="100.93949" fy="545.5434" r="4.4774756"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3375" id="radialGradient5238" gradientUnits="userSpaceOnUse" cx="105.4473" cy="547.66473" fx="105.4473" fy="547.66473" r="10.66466"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3367" id="radialGradient5240" gradientUnits="userSpaceOnUse" cx="100.93949" cy="545.5434" fx="100.93949" fy="545.5434" r="4.4774756"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3694" id="linearGradient5246" gradientUnits="userSpaceOnUse" spreadMethod="reflect" x1="323.75" y1="354.61218" x2="387.75" y2="354.61218" gradientTransform="translate(76.5051, 478.056)"/>
|
||||
<inkscape:perspective id="perspective5297" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" inkscape:vp_z="744.09448 : 526.18109 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_x="0 : 526.18109 : 1" sodipodi:type="inkscape:persp3d"/>
|
||||
<inkscape:perspective id="perspective5310" inkscape:persp3d-origin="372.04724 : 350.78739 : 1" inkscape:vp_z="744.09448 : 526.18109 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_x="0 : 526.18109 : 1" sodipodi:type="inkscape:persp3d"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3694" id="linearGradient3261" gradientUnits="userSpaceOnUse" spreadMethod="reflect" x1="389.26227" y1="334.11847" x2="451.48767" y2="334.11847"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3694" id="linearGradient3263" gradientUnits="userSpaceOnUse" gradientTransform="translate(103.75, -6.75)" spreadMethod="reflect" x1="323.75" y1="354.61218" x2="387.75" y2="354.61218"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient5120" id="radialGradient3265" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.855164, 0.518357, -0.68892, 1.13655, 798.093, -526.435)" cx="725.97113" cy="332.63196" fx="725.97113" fy="332.63196" r="74.028908"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient5120" id="radialGradient3267" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.855164, 0.518357, -0.68892, 1.13655, 924.971, -531.625)" cx="725.97113" cy="332.63196" fx="725.97113" fy="332.63196" r="74.028908"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient5132" id="radialGradient3269" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.855164, 0.518357, -0.68892, 1.13655, 335.632, -479.206)" cx="725.97113" cy="332.63196" fx="725.97113" fy="332.63196" r="74.028908"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3375" id="radialGradient3271" gradientUnits="userSpaceOnUse" cx="105.4473" cy="547.66473" fx="105.4473" fy="547.66473" r="10.66466"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3367" id="radialGradient3273" gradientUnits="userSpaceOnUse" cx="100.93949" cy="545.5434" fx="100.93949" fy="545.5434" r="4.4774756"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3375" id="radialGradient3275" gradientUnits="userSpaceOnUse" cx="105.4473" cy="547.66473" fx="105.4473" fy="547.66473" r="10.66466"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3367" id="radialGradient3277" gradientUnits="userSpaceOnUse" cx="100.93949" cy="545.5434" fx="100.93949" fy="545.5434" r="4.4774756"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3375" id="radialGradient3279" gradientUnits="userSpaceOnUse" cx="105.4473" cy="547.66473" fx="105.4473" fy="547.66473" r="10.66466"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3367" id="radialGradient3281" gradientUnits="userSpaceOnUse" cx="100.93949" cy="545.5434" fx="100.93949" fy="545.5434" r="4.4774756"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3375" id="radialGradient3283" gradientUnits="userSpaceOnUse" cx="105.4473" cy="547.66473" fx="105.4473" fy="547.66473" r="10.66466"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3367" id="radialGradient3285" gradientUnits="userSpaceOnUse" cx="100.93949" cy="545.5434" fx="100.93949" fy="545.5434" r="4.4774756"/>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient5132" id="radialGradient3287" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.855164, 0.518357, -0.68892, 1.13655, 335.632, -479.206)" cx="725.97113" cy="332.63196" fx="725.97113" fy="332.63196" r="74.028908"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3694" id="linearGradient3656" gradientUnits="userSpaceOnUse" gradientTransform="translate(1069.93, 613.577)" spreadMethod="reflect" x1="323.75" y1="354.61218" x2="387.75" y2="354.61218"/>
|
||||
</defs>
|
||||
<metadata id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(-1000, -522.032)">
|
||||
<rect style="opacity: 1; fill: rgb(246, 246, 240); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" id="rect5770" width="592.55499" height="207.88939" x="1000" y="844.47278" rx="7.1512814" ry="3.4265683"/>
|
||||
<rect style="opacity: 1; fill: rgb(237, 237, 224); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" id="rect5774" width="592.55499" height="165.56349" x="1000" y="672.89893" rx="7.1684713" ry="3.6468365"/>
|
||||
<rect style="opacity: 1; fill: rgb(246, 246, 240); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" id="rect5768" width="592.55548" height="145.56349" x="1000" y="522.03247" rx="7.1171522" ry="3.2063"/>
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 1434.4671,927.26494 C 1434.4671,897.74323 1434.2903,897.74323 1434.2903,897.74323 C 1487.1465,871.22673 1487.1465,871.22673 1487.1465,871.22673" id="path5758"/>
|
||||
<path transform="translate(1005.2, 661.107)" d="M 451.48767,334.11847 A 31.112698,9.0156116 0 1 1 389.26227,334.11847 A 31.112698,9.0156116 0 1 1 451.48767,334.11847 z" sodipodi:ry="9.0156116" sodipodi:rx="31.112698" sodipodi:cy="334.11847" sodipodi:cx="420.37497" id="path5548" style="fill: rgb(253, 253, 253); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" sodipodi:type="arc"/>
|
||||
<rect ry="3.4265683" rx="7.1171522" y="938.68878" x="1394.1827" height="59" width="63" id="rect5550" style="fill: url(#linearGradient3656) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;"/>
|
||||
<path transform="translate(1005.59, 606.859)" d="M 451.48767,334.11847 A 31.112698,9.0156116 0 1 1 389.26227,334.11847 A 31.112698,9.0156116 0 1 1 451.48767,334.11847 z" sodipodi:ry="9.0156116" sodipodi:rx="31.112698" sodipodi:cy="334.11847" sodipodi:cx="420.37497" id="path5552" style="fill: rgb(253, 253, 253); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" sodipodi:type="arc"/>
|
||||
<path transform="matrix(0.991965, 0, 0, 0.991965, 1008.69, 662.755)" d="M 451.48767,334.11847 A 31.112698,9.0156116 0 1 1 389.26227,334.11847 A 31.112698,9.0156116 0 1 1 451.48767,334.11847 z" sodipodi:ry="9.0156116" sodipodi:rx="31.112698" sodipodi:cy="334.11847" sodipodi:cx="420.37497" id="path5554" style="fill: rgb(253, 253, 253); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" sodipodi:type="arc"/>
|
||||
<path transform="matrix(0.991965, 0, 0, 0.991965, 1008.44, 662.755)" d="M 451.48767,334.11847 A 31.112698,9.0156116 0 1 1 389.26227,334.11847 A 31.112698,9.0156116 0 1 1 451.48767,334.11847 z" sodipodi:ry="9.0156116" sodipodi:rx="31.112698" sodipodi:cy="334.11847" sodipodi:cx="420.37497" id="path5556" style="fill: url(#linearGradient3261) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" sodipodi:type="arc"/>
|
||||
<g id="g5558" transform="translate(1072.85, 613.702)">
|
||||
<rect style="opacity: 1; fill: url(#linearGradient3263) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5560" width="58.5" height="33" x="432.5" y="344.36218" rx="0.52616197" ry="3.4265683"/>
|
||||
<path style="fill: rgb(253, 253, 253); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(145, 145, 124); stroke-width: 0.970822px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;" d="M 433.1835,344.1737 C 436.00677,336.63379 438.12809,336.81226 438.12809,336.81226 L 486.97826,336.95258 C 489.15613,337.1119 489.06591,341.96918 490.36369,344.32875 L 433.1835,344.1737 z" id="path5562" sodipodi:nodetypes="ccccc"/>
|
||||
<rect style="opacity: 1; fill: rgb(78, 219, 55); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5564" width="8" height="5" x="439" y="367.86218" rx="0.52616197" ry="3.4265683"/>
|
||||
</g>
|
||||
<g id="g5582" transform="translate(894.433, 518.95)">
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(134, 134, 134); stroke-width: 2.3; stroke-linecap: butt; stroke-linejoin: miter; marker-start: none; marker-end: none; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 564.36816,457.7548 C 605.49316,457.7548 605.49316,457.7548 605.49316,457.7548" id="path5584"/>
|
||||
<path sodipodi:nodetypes="ccccc" id="path5586" d="M 608.57301,457.98055 L 597.97215,461.85992 L 599.33893,457.71598 L 598.01712,453.65159 L 608.57301,457.98055 z" style="fill: rgb(134, 134, 134); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(134, 134, 134); stroke-width: 0.276426px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;"/>
|
||||
</g>
|
||||
<g id="g5588" transform="matrix(-1, 0, 0, -1, 2067.91, 1425.91)">
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(134, 134, 134); stroke-width: 2.3; stroke-linecap: butt; stroke-linejoin: miter; marker-start: none; marker-end: none; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 564.36816,457.7548 C 605.49316,457.7548 605.49316,457.7548 605.49316,457.7548" id="path5590"/>
|
||||
<path sodipodi:nodetypes="ccccc" id="path5592" d="M 608.57301,457.98055 L 597.97215,461.85992 L 599.33893,457.71598 L 598.01712,453.65159 L 608.57301,457.98055 z" style="fill: rgb(134, 134, 134); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(134, 134, 134); stroke-width: 0.276426px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;"/>
|
||||
</g>
|
||||
<path style="fill: rgb(255, 0, 0); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 0.307256px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;" d="M 1414.9178,926.08546 L 1419.2299,914.30229 L 1414.6237,915.8215 L 1410.1061,914.35228 L 1414.9178,926.08546 z" id="path5742" sodipodi:nodetypes="ccccc"/>
|
||||
<text xml:space="preserve" style="font-size: 14px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1390.14" y="1020.54" id="text3009"><tspan y="1020.54" x="1390.14" sodipodi:role="line" id="tspan3011"><tspan x="1390.14" y="1020.54" id="tspan3013">Database</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 14px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1505.38" y="1007.08" id="text3015"><tspan y="1007.08" x="1505.38" sodipodi:role="line" id="tspan3017"><tspan x="1505.38" y="1007.08" id="tspan3019">Storage</tspan></tspan></text>
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 1344.5761,787.61135 C 1344.7529,878.47458 1344.7529,878.47458 1344.7529,878.47458" id="path5750"/>
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 1504.5591,785.84358 C 1504.7359,876.70681 1504.7359,876.70681 1504.7359,876.70681" id="path5748"/>
|
||||
<path style="fill: url(#radialGradient3265) rgb(0, 0, 0); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(104, 105, 48); stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: round; stroke-opacity: 1; filter: url(#filter5116);" d="M 1105.2895,149.56928 C 1104.94,183.08983 1106.2895,282.56928 1106.2895,282.56928 L 1106.2895,282.56928 C 1252.2895,282.56928 1252.2895,282.56928 1252.2895,282.56928 C 1252.2895,149.56928 1251.7895,149.56928 1251.7895,149.56928 C 1235.2895,135.06928 1227.7895,136.06928 1227.7895,136.06928 C 1133.7895,134.06928 1131.2895,136.56928 1131.2895,136.56928 C 1131.2895,136.56928 1105.3804,140.84909 1105.2895,149.56928 z" id="path5034" sodipodi:nodetypes="cccccccs" transform="matrix(0.660912, 0, 0, 0.660912, 567.035, 450.586)"/>
|
||||
<rect style="fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(47, 47, 47); stroke-width: 1.5201; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" id="rect5030" width="77.987648" height="62.786667" x="1306.9241" y="551.53455" rx="4.7038136" ry="2.2646611"/>
|
||||
<path style="fill: url(#radialGradient3267) rgb(0, 0, 0); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(104, 105, 48); stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: round; stroke-opacity: 1; filter: url(#filter5116);" d="M 1232.1672,144.37863 C 1231.8177,177.89918 1233.1672,277.37863 1233.1672,277.37863 L 1233.1672,277.37863 C 1379.1672,277.37863 1379.1672,277.37863 1379.1672,277.37863 C 1379.1672,144.37863 1378.6672,144.37863 1378.6672,144.37863 C 1362.1672,129.87862 1354.6672,130.87862 1354.6672,130.87862 C 1260.6672,128.87862 1258.1672,131.37862 1258.1672,131.37862 C 1258.1672,131.37862 1232.2582,135.65843 1232.1672,144.37863 z" id="path5148" sodipodi:nodetypes="cccccccs" transform="matrix(0.670464, 0, 0, 0.670464, 629.406, 452.538)"/>
|
||||
<rect style="fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(47, 47, 47); stroke-width: 1.54207; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" id="rect5150" width="79.114761" height="63.694084" x="1465.0555" y="551.46503" rx="4.7717948" ry="2.2973909"/>
|
||||
<path style="fill: url(#radialGradient3269) rgb(0, 0, 0); fill-opacity: 1; fill-rule: evenodd; stroke: none; stroke-width: 1.0007; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1; filter: url(#filter5116);" d="M 642.82808,196.7977 C 642.47856,230.31825 643.82808,329.7977 643.82808,329.7977 L 643.82808,329.7977 C 789.82808,329.7977 789.82808,329.7977 789.82808,329.7977 C 789.82808,196.7977 789.32808,196.7977 789.32808,196.7977 C 772.82808,182.2977 765.32808,183.2977 765.32808,183.2977 C 671.32808,181.2977 668.82808,183.7977 668.82808,183.7977 C 668.82808,183.7977 642.91901,188.07751 642.82808,196.7977 z" id="path5152" sodipodi:nodetypes="cccccccs" transform="matrix(0.420735, 0, 0, 0.411447, 1212.42, 479.614)"/>
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(255, 255, 255); stroke-width: 0.636053px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;" d="M 1471.0818,589.82652 C 1538.6654,589.82652 1538.6654,589.45689 1538.6654,589.45689" id="path5182"/>
|
||||
<g id="g5316" transform="matrix(0.536791, 0.0577592, -0.0577592, 0.536791, 1208.09, 505.119)">
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5318" width="7.230413" height="72.96875" x="65.939308" y="460.66718" rx="1.6370747" ry="3.6790967" transform="matrix(0.943099, -0.332513, 0.332513, 0.943099, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5320" width="7.230413" height="72.96875" x="-501.26709" y="32.945621" rx="1.6370747" ry="3.6790967" transform="matrix(-0.332513, -0.943099, 0.943099, -0.332513, 0, 0)" inkscape:transform-center-x="-22.797407" inkscape:transform-center-y="-26.781993"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5322" width="7.230413" height="72.96875" x="-304.91528" y="364.952" rx="1.6370747" ry="3.6790967" transform="matrix(0.435242, -0.900314, 0.900314, 0.435242, 0, 0)" inkscape:transform-center-x="-0.054580855" inkscape:transform-center-y="-0.11405819"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5324" width="7.230413" height="72.96875" x="-405.55185" y="-337.909" rx="1.6370747" ry="3.6790967" transform="matrix(-0.900314, -0.435242, 0.435242, -0.900314, 0, 0)" inkscape:transform-center-x="-0.45083121" inkscape:transform-center-y="0.21580039"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5326" width="7.2304077" height="72.96875" x="-129.78862" y="448.93118" rx="1.6370734" ry="3.6790941" transform="matrix(0.744062, -0.66811, 0.66811, 0.744062, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5328" width="7.2304068" height="72.96875" x="-488.87778" y="-162.5117" rx="1.6370732" ry="3.6790936" transform="matrix(-0.66811, -0.744062, 0.744062, -0.66811, 0, 0)" inkscape:transform-center-x="-10.812994" inkscape:transform-center-y="-33.467625"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5330" width="7.2304125" height="72.96875" x="-434.65988" y="219.1375" rx="1.6370746" ry="3.6790965" transform="matrix(0.0575757, -0.998341, 0.998341, 0.0575757, 0, 0)" inkscape:transform-center-x="-0.0067134245" inkscape:transform-center-y="-0.12624831"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5332" width="7.2304125" height="72.96875" x="-260.01053" y="-468.30582" rx="1.6370746" ry="3.6790965" transform="matrix(-0.998341, -0.0575757, 0.0575757, -0.998341, 0, 0)" inkscape:transform-center-y="0.026899316" inkscape:transform-center-x="-0.49908664"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.912671; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5334" width="7.474649" height="43.927952" x="-377.0932" y="-400.33444" rx="1.6923734" ry="2.2148545" transform="matrix(-0.925212, -0.379451, 0.379451, -0.925212, 0, 0)" inkscape:transform-center-x="-4.2716058" inkscape:transform-center-y="-7.6715504"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.912671; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5336" width="7.474649" height="43.927952" x="-382.27505" y="351.12729" rx="1.6923734" ry="2.2148545" transform="matrix(0.379451, -0.925212, 0.925212, 0.379451, 0, 0)" inkscape:transform-center-x="-7.671692" inkscape:transform-center-y="4.2715427"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.912671; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5338" width="7.474649" height="43.927948" x="-535.38159" y="-25.514301" rx="1.6923734" ry="2.2148545" transform="matrix(-0.385911, -0.922536, 0.922536, -0.385911, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.912671; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5340" width="7.474649" height="43.927948" x="-7.163105" y="509.94055" rx="1.6923734" ry="2.2148545" transform="matrix(0.922536, -0.385911, 0.385911, 0.922536, 0, 0)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5342" sodipodi:cx="116.31907" sodipodi:cy="545.80859" sodipodi:rx="14.495689" sodipodi:ry="14.495689" d="M 130.81476,545.80859 A 14.495689,14.495689 0 1 1 101.82338,545.80859 A 14.495689,14.495689 0 1 1 130.81476,545.80859 z" transform="matrix(0.960398, -0.401749, 0.401749, 0.960398, -129.006, 14.0137)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: url(#radialGradient3271) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(190, 190, 179); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5344" sodipodi:cx="105.4473" sodipodi:cy="547.66473" sodipodi:rx="10.16466" sodipodi:ry="10.16466" d="M 115.61196,547.66473 A 10.16466,10.16466 0 1 1 95.282643,547.66473 A 10.16466,10.16466 0 1 1 115.61196,547.66473 z" transform="matrix(0.960398, -0.401749, 0.401749, 0.960398, -119.396, 7.89883)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: url(#radialGradient3273) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5346" sodipodi:cx="100.93949" sodipodi:cy="545.5434" sodipodi:rx="3.9774756" sodipodi:ry="3.9774756" d="M 104.91697,545.5434 A 3.9774756,3.9774756 0 1 1 96.962016,545.5434 A 3.9774756,3.9774756 0 1 1 104.91697,545.5434 z" transform="matrix(0.960398, -0.401749, 0.401749, 0.960398, -114.285, 7.95537)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.519676; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5348" sodipodi:cx="116.31907" sodipodi:cy="545.80859" sodipodi:rx="14.495689" sodipodi:ry="14.495689" d="M 130.81476,545.80859 A 14.495689,14.495689 0 1 1 101.82338,545.80859 A 14.495689,14.495689 0 1 1 130.81476,545.80859 z" transform="matrix(1.96359, -0.692314, 0.692314, 1.96359, -375.858, -545.202)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: url(#radialGradient3275) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(190, 190, 179); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5350" sodipodi:cx="105.4473" sodipodi:cy="547.66473" sodipodi:rx="10.16466" sodipodi:ry="10.16466" d="M 115.61196,547.66473 A 10.16466,10.16466 0 1 1 95.282643,547.66473 A 10.16466,10.16466 0 1 1 115.61196,547.66473 z" transform="matrix(1.55459, -0.548111, 0.548111, 1.55459, -233.943, -347.364)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: url(#radialGradient3277) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5352" sodipodi:cx="100.93949" sodipodi:cy="545.5434" sodipodi:rx="3.9774756" sodipodi:ry="3.9774756" d="M 104.91697,545.5434 A 3.9774756,3.9774756 0 1 1 96.962016,545.5434 A 3.9774756,3.9774756 0 1 1 104.91697,545.5434 z" transform="matrix(1.30537, -0.460241, 0.460241, 1.30537, -152.884, -220.026)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5354" width="7.1152625" height="4.7729135" x="65.982826" y="465.99457" rx="1.6576668" ry="2.3864567" transform="matrix(0.943099, -0.332513, 0.332513, 0.943099, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5356" width="7.1152625" height="4.7729135" x="65.960732" y="523.66797" rx="1.6576668" ry="2.3864567" transform="matrix(0.943099, -0.332513, 0.332513, 0.943099, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5358" width="7.1152625" height="4.7729135" x="-501.19525" y="95.592987" rx="1.6576668" ry="2.3864567" transform="matrix(-0.332513, -0.943099, 0.943099, -0.332513, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5360" width="7.1152625" height="4.7729135" x="-501.19519" y="37.786999" rx="1.6576668" ry="2.3864567" transform="matrix(-0.332513, -0.943099, 0.943099, -0.332513, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5362" width="7.1152625" height="4.7729135" x="-306.59344" y="427.05988" rx="1.6576668" ry="2.3864567" transform="matrix(0.431749, -0.901994, 0.901994, 0.431749, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5364" width="7.1152625" height="4.7729135" x="-306.31866" y="369.14117" rx="1.6576668" ry="2.3864567" transform="matrix(0.431749, -0.901994, 0.901994, 0.431749, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5366" width="7.1152625" height="4.7729135" x="-404.36612" y="-276.25415" rx="1.6576668" ry="2.3864567" transform="matrix(-0.901994, -0.431749, 0.431749, -0.901994, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5368" width="7.1152625" height="4.7729135" x="-404.18933" y="-334.01593" rx="1.6576668" ry="2.3864567" transform="matrix(-0.901994, -0.431749, 0.431749, -0.901994, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5370" width="7.1152616" height="4.7729135" x="-129.78484" y="454.45309" rx="1.6576666" ry="2.3864567" transform="matrix(0.744062, -0.66811, 0.66811, 0.744062, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5372" width="7.1152616" height="4.7729135" x="-129.78716" y="512.4054" rx="1.6576666" ry="2.3864567" transform="matrix(0.744062, -0.66811, 0.66811, 0.744062, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5374" width="7.1152616" height="4.7729135" x="-488.91299" y="-100.00322" rx="1.6576666" ry="2.3864567" transform="matrix(-0.66811, -0.744062, 0.744062, -0.66811, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5376" width="7.1152616" height="4.7729135" x="-488.91293" y="-157.80919" rx="1.6576666" ry="2.3864567" transform="matrix(-0.66811, -0.744062, 0.744062, -0.66811, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5378" width="7.1152616" height="4.7729135" x="-435.77966" y="280.63101" rx="1.6576666" ry="2.3864567" transform="matrix(0.0537059, -0.998557, 0.998557, 0.0537059, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5380" width="7.1152616" height="4.7729135" x="-435.53876" y="222.79398" rx="1.6576666" ry="2.3864567" transform="matrix(0.0537059, -0.998557, 0.998557, 0.0537059, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5382" width="7.1152616" height="4.7729135" x="-258.33395" y="-406.56134" rx="1.6576666" ry="2.3864567" transform="matrix(-0.998557, -0.0537059, 0.0537059, -0.998557, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5384" width="7.1152616" height="4.7729135" x="-258.15717" y="-464.32318" rx="1.6576666" ry="2.3864567" transform="matrix(-0.998557, -0.0537059, 0.0537059, -0.998557, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5386" width="6.75" height="3.4375" x="390.19818" y="340.07739" rx="0.47016668" ry="1.71875" transform="matrix(0.901994, 0.431749, -0.431749, 0.901994, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5388" width="6.7500005" height="3.4375002" x="373.94806" y="386.9249" rx="0.47016671" ry="1.7187501" transform="matrix(0.921316, 0.388814, -0.388814, 0.921316, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5390" width="6.7500005" height="3.4375002" x="361.05246" y="-402.72012" rx="0.47016671" ry="1.7187501" transform="matrix(-0.412213, 0.911088, -0.911088, -0.412213, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5392" width="6.7500005" height="3.4375002" x="393.76926" y="-339.68652" rx="0.47016671" ry="1.7187501" transform="matrix(-0.329492, 0.944158, -0.944158, -0.329492, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5394" width="6.7500005" height="3.4375002" x="527.9704" y="-20.811844" rx="0.47016671" ry="1.7187501" transform="matrix(0.372629, 0.92798, -0.92798, 0.372629, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5397" width="6.7500005" height="3.4375002" x="528.52869" y="8.4513111" rx="0.47016671" ry="1.7187501" transform="matrix(0.372629, 0.92798, -0.92798, 0.372629, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5399" width="6.75" height="3.4375" x="36.094131" y="-545.8858" rx="0.47016668" ry="1.71875" transform="matrix(-0.895012, 0.446042, -0.446042, -0.895012, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5401" width="6.75" height="3.4375" x="34.23085" y="-517.57141" rx="0.47016668" ry="1.71875" transform="matrix(-0.895012, 0.446042, -0.446042, -0.895012, 0, 0)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: none; fill-opacity: 1; fill-rule: nonzero; stroke: rgb(239, 239, 235); stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 0.418269;" id="path5403" sodipodi:cx="171.87114" sodipodi:cy="489.99133" sodipodi:rx="29.74268" sodipodi:ry="29.74268" d="M 201.61382,489.99133 A 29.74268,29.74268 0 1 1 142.12846,489.99133 A 29.74268,29.74268 0 1 1 201.61382,489.99133 z" transform="matrix(0.901994, 0.431749, -0.431749, 0.901994, 287.033, -70.2209)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: none; fill-opacity: 1; fill-rule: nonzero; stroke: rgb(239, 239, 235); stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 0.418269;" id="path5405" sodipodi:cx="152.11635" sodipodi:cy="538.56073" sodipodi:rx="14.407301" sodipodi:ry="14.407301" d="M 166.52365,538.56073 A 14.407301,14.407301 0 1 1 137.70905,538.56073 A 14.407301,14.407301 0 1 1 166.52365,538.56073 z" transform="matrix(0.901994, 0.431749, -0.431749, 0.901994, 297.439, -60.2057)"/>
|
||||
</g>
|
||||
<g id="g5415" transform="matrix(0.536791, 0.0577592, -0.0577592, 0.536791, 1368.72, 499.781)">
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5418" width="7.230413" height="72.96875" x="65.939308" y="460.66718" rx="1.6370747" ry="3.6790967" transform="matrix(0.943099, -0.332513, 0.332513, 0.943099, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5420" width="7.230413" height="72.96875" x="-501.26709" y="32.945621" rx="1.6370747" ry="3.6790967" transform="matrix(-0.332513, -0.943099, 0.943099, -0.332513, 0, 0)" inkscape:transform-center-x="-22.797407" inkscape:transform-center-y="-26.781993"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5422" width="7.230413" height="72.96875" x="-304.91528" y="364.952" rx="1.6370747" ry="3.6790967" transform="matrix(0.435242, -0.900314, 0.900314, 0.435242, 0, 0)" inkscape:transform-center-x="-0.054580855" inkscape:transform-center-y="-0.11405819"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5424" width="7.230413" height="72.96875" x="-405.55185" y="-337.909" rx="1.6370747" ry="3.6790967" transform="matrix(-0.900314, -0.435242, 0.435242, -0.900314, 0, 0)" inkscape:transform-center-x="-0.45083121" inkscape:transform-center-y="0.21580039"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5426" width="7.2304077" height="72.96875" x="-129.78862" y="448.93118" rx="1.6370734" ry="3.6790941" transform="matrix(0.744062, -0.66811, 0.66811, 0.744062, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5428" width="7.2304068" height="72.96875" x="-488.87778" y="-162.5117" rx="1.6370732" ry="3.6790936" transform="matrix(-0.66811, -0.744062, 0.744062, -0.66811, 0, 0)" inkscape:transform-center-x="-10.812994" inkscape:transform-center-y="-33.467625"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5430" width="7.2304125" height="72.96875" x="-434.65988" y="219.1375" rx="1.6370746" ry="3.6790965" transform="matrix(0.0575757, -0.998341, 0.998341, 0.0575757, 0, 0)" inkscape:transform-center-x="-0.0067134245" inkscape:transform-center-y="-0.12624831"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1.15691; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5432" width="7.2304125" height="72.96875" x="-260.01053" y="-468.30582" rx="1.6370746" ry="3.6790965" transform="matrix(-0.998341, -0.0575757, 0.0575757, -0.998341, 0, 0)" inkscape:transform-center-y="0.026899316" inkscape:transform-center-x="-0.49908664"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.912671; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5434" width="7.474649" height="43.927952" x="-377.0932" y="-400.33444" rx="1.6923734" ry="2.2148545" transform="matrix(-0.925212, -0.379451, 0.379451, -0.925212, 0, 0)" inkscape:transform-center-x="-4.2716058" inkscape:transform-center-y="-7.6715504"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.912671; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5436" width="7.474649" height="43.927952" x="-382.27505" y="351.12729" rx="1.6923734" ry="2.2148545" transform="matrix(0.379451, -0.925212, 0.925212, 0.379451, 0, 0)" inkscape:transform-center-x="-7.671692" inkscape:transform-center-y="4.2715427"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.912671; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5438" width="7.474649" height="43.927948" x="-535.38159" y="-25.514301" rx="1.6923734" ry="2.2148545" transform="matrix(-0.385911, -0.922536, 0.922536, -0.385911, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.912671; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5440" width="7.474649" height="43.927948" x="-7.163105" y="509.94055" rx="1.6923734" ry="2.2148545" transform="matrix(0.922536, -0.385911, 0.385911, 0.922536, 0, 0)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5442" sodipodi:cx="116.31907" sodipodi:cy="545.80859" sodipodi:rx="14.495689" sodipodi:ry="14.495689" d="M 130.81476,545.80859 A 14.495689,14.495689 0 1 1 101.82338,545.80859 A 14.495689,14.495689 0 1 1 130.81476,545.80859 z" transform="matrix(0.960398, -0.401749, 0.401749, 0.960398, -129.006, 14.0137)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: url(#radialGradient3279) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(190, 190, 179); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5444" sodipodi:cx="105.4473" sodipodi:cy="547.66473" sodipodi:rx="10.16466" sodipodi:ry="10.16466" d="M 115.61196,547.66473 A 10.16466,10.16466 0 1 1 95.282643,547.66473 A 10.16466,10.16466 0 1 1 115.61196,547.66473 z" transform="matrix(0.960398, -0.401749, 0.401749, 0.960398, -119.396, 7.89883)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: url(#radialGradient3281) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5446" sodipodi:cx="100.93949" sodipodi:cy="545.5434" sodipodi:rx="3.9774756" sodipodi:ry="3.9774756" d="M 104.91697,545.5434 A 3.9774756,3.9774756 0 1 1 96.962016,545.5434 A 3.9774756,3.9774756 0 1 1 104.91697,545.5434 z" transform="matrix(0.960398, -0.401749, 0.401749, 0.960398, -114.285, 7.95537)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(156, 156, 142); stroke-width: 0.519676; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5448" sodipodi:cx="116.31907" sodipodi:cy="545.80859" sodipodi:rx="14.495689" sodipodi:ry="14.495689" d="M 130.81476,545.80859 A 14.495689,14.495689 0 1 1 101.82338,545.80859 A 14.495689,14.495689 0 1 1 130.81476,545.80859 z" transform="matrix(1.96359, -0.692314, 0.692314, 1.96359, -375.858, -545.202)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: url(#radialGradient3283) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(190, 190, 179); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5450" sodipodi:cx="105.4473" sodipodi:cy="547.66473" sodipodi:rx="10.16466" sodipodi:ry="10.16466" d="M 115.61196,547.66473 A 10.16466,10.16466 0 1 1 95.282643,547.66473 A 10.16466,10.16466 0 1 1 115.61196,547.66473 z" transform="matrix(1.55459, -0.548111, 0.548111, 1.55459, -233.943, -347.364)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: url(#radialGradient3285) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: rgb(145, 145, 124); stroke-width: 1; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="path5452" sodipodi:cx="100.93949" sodipodi:cy="545.5434" sodipodi:rx="3.9774756" sodipodi:ry="3.9774756" d="M 104.91697,545.5434 A 3.9774756,3.9774756 0 1 1 96.962016,545.5434 A 3.9774756,3.9774756 0 1 1 104.91697,545.5434 z" transform="matrix(1.30537, -0.460241, 0.460241, 1.30537, -152.884, -220.026)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5454" width="7.1152625" height="4.7729135" x="65.982826" y="465.99457" rx="1.6576668" ry="2.3864567" transform="matrix(0.943099, -0.332513, 0.332513, 0.943099, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5456" width="7.1152625" height="4.7729135" x="65.960732" y="523.66797" rx="1.6576668" ry="2.3864567" transform="matrix(0.943099, -0.332513, 0.332513, 0.943099, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5458" width="7.1152625" height="4.7729135" x="-501.19525" y="95.592987" rx="1.6576668" ry="2.3864567" transform="matrix(-0.332513, -0.943099, 0.943099, -0.332513, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5460" width="7.1152625" height="4.7729135" x="-501.19519" y="37.786999" rx="1.6576668" ry="2.3864567" transform="matrix(-0.332513, -0.943099, 0.943099, -0.332513, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5462" width="7.1152625" height="4.7729135" x="-306.59344" y="427.05988" rx="1.6576668" ry="2.3864567" transform="matrix(0.431749, -0.901994, 0.901994, 0.431749, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5464" width="7.1152625" height="4.7729135" x="-306.31866" y="369.14117" rx="1.6576668" ry="2.3864567" transform="matrix(0.431749, -0.901994, 0.901994, 0.431749, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5466" width="7.1152625" height="4.7729135" x="-404.36612" y="-276.25415" rx="1.6576668" ry="2.3864567" transform="matrix(-0.901994, -0.431749, 0.431749, -0.901994, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5468" width="7.1152625" height="4.7729135" x="-404.18933" y="-334.01593" rx="1.6576668" ry="2.3864567" transform="matrix(-0.901994, -0.431749, 0.431749, -0.901994, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5470" width="7.1152616" height="4.7729135" x="-129.78484" y="454.45309" rx="1.6576666" ry="2.3864567" transform="matrix(0.744062, -0.66811, 0.66811, 0.744062, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5472" width="7.1152616" height="4.7729135" x="-129.78716" y="512.4054" rx="1.6576666" ry="2.3864567" transform="matrix(0.744062, -0.66811, 0.66811, 0.744062, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5474" width="7.1152616" height="4.7729135" x="-488.91299" y="-100.00322" rx="1.6576666" ry="2.3864567" transform="matrix(-0.66811, -0.744062, 0.744062, -0.66811, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5476" width="7.1152616" height="4.7729135" x="-488.91293" y="-157.80919" rx="1.6576666" ry="2.3864567" transform="matrix(-0.66811, -0.744062, 0.744062, -0.66811, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5478" width="7.1152616" height="4.7729135" x="-435.77966" y="280.63101" rx="1.6576666" ry="2.3864567" transform="matrix(0.0537059, -0.998557, 0.998557, 0.0537059, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5480" width="7.1152616" height="4.7729135" x="-435.53876" y="222.79398" rx="1.6576666" ry="2.3864567" transform="matrix(0.0537059, -0.998557, 0.998557, 0.0537059, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5482" width="7.1152616" height="4.7729135" x="-258.33395" y="-406.56134" rx="1.6576666" ry="2.3864567" transform="matrix(-0.998557, -0.0537059, 0.0537059, -0.998557, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5484" width="7.1152616" height="4.7729135" x="-258.15717" y="-464.32318" rx="1.6576666" ry="2.3864567" transform="matrix(-0.998557, -0.0537059, 0.0537059, -0.998557, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5486" width="6.75" height="3.4375" x="390.19818" y="340.07739" rx="0.47016668" ry="1.71875" transform="matrix(0.901994, 0.431749, -0.431749, 0.901994, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5488" width="6.7500005" height="3.4375002" x="373.94806" y="386.9249" rx="0.47016671" ry="1.7187501" transform="matrix(0.921316, 0.388814, -0.388814, 0.921316, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5490" width="6.7500005" height="3.4375002" x="361.05246" y="-402.72012" rx="0.47016671" ry="1.7187501" transform="matrix(-0.412213, 0.911088, -0.911088, -0.412213, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5492" width="6.7500005" height="3.4375002" x="393.76926" y="-339.68652" rx="0.47016671" ry="1.7187501" transform="matrix(-0.329492, 0.944158, -0.944158, -0.329492, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5494" width="6.7500005" height="3.4375002" x="527.9704" y="-20.811844" rx="0.47016671" ry="1.7187501" transform="matrix(0.372629, 0.92798, -0.92798, 0.372629, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5496" width="6.7500005" height="3.4375002" x="528.52869" y="8.4513111" rx="0.47016671" ry="1.7187501" transform="matrix(0.372629, 0.92798, -0.92798, 0.372629, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5498" width="6.75" height="3.4375" x="36.094131" y="-545.8858" rx="0.47016668" ry="1.71875" transform="matrix(-0.895012, 0.446042, -0.446042, -0.895012, 0, 0)"/>
|
||||
<rect style="opacity: 1; fill: rgb(250, 251, 230); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;" id="rect5500" width="6.75" height="3.4375" x="34.23085" y="-517.57141" rx="0.47016668" ry="1.71875" transform="matrix(-0.895012, 0.446042, -0.446042, -0.895012, 0, 0)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: none; fill-opacity: 1; fill-rule: nonzero; stroke: rgb(239, 239, 235); stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 0.418269;" id="path5502" sodipodi:cx="171.87114" sodipodi:cy="489.99133" sodipodi:rx="29.74268" sodipodi:ry="29.74268" d="M 201.61382,489.99133 A 29.74268,29.74268 0 1 1 142.12846,489.99133 A 29.74268,29.74268 0 1 1 201.61382,489.99133 z" transform="matrix(0.901994, 0.431749, -0.431749, 0.901994, 287.033, -70.2209)"/>
|
||||
<path sodipodi:type="arc" style="opacity: 1; fill: none; fill-opacity: 1; fill-rule: nonzero; stroke: rgb(239, 239, 235); stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 0.418269;" id="path5504" sodipodi:cx="152.11635" sodipodi:cy="538.56073" sodipodi:rx="14.407301" sodipodi:ry="14.407301" d="M 166.52365,538.56073 A 14.407301,14.407301 0 1 1 137.70905,538.56073 A 14.407301,14.407301 0 1 1 166.52365,538.56073 z" transform="matrix(0.901994, 0.431749, -0.431749, 0.901994, 297.439, -60.2057)"/>
|
||||
</g>
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 1414.4913,917.89578 C 1414.4913,897.74323 1414.4913,897.74323 1414.4913,897.74323 C 1365.8777,873.52482 1365.8777,873.52482 1365.8777,873.52482" id="path5756"/>
|
||||
<g id="g5514" transform="matrix(0.713522, 0, 0, 0.713522, 1092.85, 388.956)">
|
||||
<path id="path5516" d="M 327.75998,612.9113 C 327.75998,700.20702 327.75998,700.20702 327.75998,700.20702 C 396.31737,700.20702 396.31737,700.20702 396.31737,700.20702 C 396.31737,630.43917 396.31737,630.43917 396.31737,630.43917 C 376.17621,612.9113 376.17621,612.9113 376.17621,612.9113 C 328.92196,613.25498 327.75998,612.9113 327.75998,612.9113 z" style="fill: rgb(253, 253, 253); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(190, 190, 190); stroke-width: 1.03197px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;"/>
|
||||
<path id="path5518" d="M 376.56353,613.59867 L 376.56353,629.40812 C 395.93003,630.09549 395.5427,630.09549 395.5427,630.09549" style="fill: none; fill-rule: evenodd; stroke: rgb(138, 138, 138); stroke-width: 1.03197px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;"/>
|
||||
</g>
|
||||
<g id="g5528" transform="matrix(0.713522, 0, 0, 0.713522, 1247.63, 390.042)">
|
||||
<path id="path5530" d="M 327.75998,612.9113 C 327.75998,700.20702 327.75998,700.20702 327.75998,700.20702 C 396.31737,700.20702 396.31737,700.20702 396.31737,700.20702 C 396.31737,630.43917 396.31737,630.43917 396.31737,630.43917 C 376.17621,612.9113 376.17621,612.9113 376.17621,612.9113 C 328.92196,613.25498 327.75998,612.9113 327.75998,612.9113 z" style="fill: rgb(253, 253, 253); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(190, 190, 190); stroke-width: 1.03197px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;"/>
|
||||
<path id="path5532" d="M 376.56353,613.59867 L 376.56353,629.40812 C 395.93003,630.09549 395.5427,630.09549 395.5427,630.09549" style="fill: none; fill-rule: evenodd; stroke: rgb(138, 138, 138); stroke-width: 1.03197px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;"/>
|
||||
</g>
|
||||
<path style="fill: rgb(255, 0, 0); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 0.307256px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;" d="M 1504.5791,643.21934 L 1508.8912,655.00251 L 1504.285,653.4833 L 1499.7674,654.95252 L 1504.5791,643.21934 z" id="path5726" sodipodi:nodetypes="ccccc"/>
|
||||
<path style="fill: rgb(255, 0, 0); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 0.307256px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;" d="M 1504.7204,778.89134 L 1509.0325,790.67451 L 1504.4263,789.1553 L 1499.9087,790.62452 L 1504.7204,778.89134 z" id="path5738" sodipodi:nodetypes="ccccc"/>
|
||||
<path style="fill: rgb(255, 0, 0); fill-opacity: 1; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 0.307256px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1;" d="M 1345.2679,741.53059 L 1349.58,729.74742 L 1344.9738,731.26663 L 1340.4562,729.79741 L 1345.2679,741.53059 z" id="path5740" sodipodi:nodetypes="ccccc"/>
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 1344.6645,641.94735 C 1344.8413,732.81058 1344.8413,732.81058 1344.8413,732.81058" id="path5744"/>
|
||||
<path style="fill: none; fill-rule: evenodd; stroke: rgb(255, 0, 0); stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" d="M 1504.4907,652.58241 C 1504.6675,743.44564 1504.6675,743.44564 1504.6675,743.44564" id="path5746"/>
|
||||
<path style="fill: url(#radialGradient3287) rgb(0, 0, 0); fill-opacity: 1; fill-rule: evenodd; stroke: none; stroke-width: 1.0007; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1; filter: url(#filter5116);" d="M 642.82808,196.7977 C 642.47856,230.31825 643.82808,329.7977 643.82808,329.7977 L 643.82808,329.7977 C 789.82808,329.7977 789.82808,329.7977 789.82808,329.7977 C 789.82808,196.7977 789.32808,196.7977 789.32808,196.7977 C 772.82808,182.2977 765.32808,183.2977 765.32808,183.2977 C 671.32808,181.2977 668.82808,183.7977 668.82808,183.7977 C 668.82808,183.7977 642.91901,188.07751 642.82808,196.7977 z" id="path5128" sodipodi:nodetypes="cccccccs" transform="matrix(0.414741, 0, 0, 0.405585, 1057.88, 480.707)"/>
|
||||
<text xml:space="preserve" style="font-size: 7.93095px; font-style: normal; font-weight: bold; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1312.74" y="565.631" id="text2773"><tspan y="565.631" x="1312.74" sodipodi:role="line" id="tspan2775"><tspan x="1312.74" y="565.631" id="tspan2777">>GET SALES </tspan><tspan dx="0" x="1370.27" y="565.631" id="tspan2779"/></tspan><tspan y="575.545" x="1312.74" sodipodi:role="line" id="tspan2781"><tspan x="1312.74" y="575.545" id="tspan2783"> TOTAL</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 8.04557px; font-style: normal; font-weight: bold; fill: rgb(153, 153, 153); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1470.95" y="565.765" id="text2785"><tspan y="565.765" x="1470.95" sodipodi:role="line" id="tspan2787"><tspan x="1470.95" y="565.765" id="tspan2789" style="fill: rgb(153, 153, 153);">>GET SALES </tspan><tspan dx="0" x="1529.31" y="565.765" id="tspan2791" style="fill: rgb(153, 153, 153);"/></tspan><tspan y="575.822" x="1470.95" sodipodi:role="line" id="tspan2793"><tspan x="1470.95" y="575.822" id="tspan2795" style="fill: rgb(153, 153, 153);"> TOTAL</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 10px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1331.43" y="755.035" id="text2943"><tspan y="755.035" x="1331.43" sodipodi:role="line" id="tspan2945"><tspan x="1331.43" y="755.035" id="tspan2947">GET LIST OF ALL</tspan><tspan dx="0" x="1423.63" y="755.035" id="tspan2949"/></tspan><tspan y="767.535" x="1331.43" sodipodi:role="line" id="tspan2951"><tspan x="1331.43" y="767.535" id="tspan2953">SALES MADE</tspan><tspan dx="0" x="1403.09" y="767.535" id="tspan2955"/></tspan><tspan y="780.035" x="1331.43" sodipodi:role="line" id="tspan2957"><tspan x="1331.43" y="780.035" id="tspan2959">LAST YEAR</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 10px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1493.37" y="757.841" id="text2961"><tspan y="757.841" x="1493.37" sodipodi:role="line" id="tspan2963"><tspan x="1493.37" y="757.841" id="tspan2965">ADD ALL SALES</tspan><tspan dx="0" x="1580.48" y="757.841" id="tspan2967"/></tspan><tspan y="770.341" x="1493.37" sodipodi:role="line" id="tspan2969"><tspan x="1493.37" y="770.341" id="tspan2971">TOGETHER</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 8.04557px; font-style: normal; font-weight: bold; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1470.64" y="588.715" id="text2973"><tspan y="588.715" x="1470.64" sodipodi:role="line" id="tspan2975"><tspan x="1470.64" y="588.715" id="tspan2977">4 TOTAL SALES</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 10px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1332.85" y="860.491" id="text2979"><tspan y="860.491" x="1332.85" sodipodi:role="line" id="tspan2981"><tspan x="1332.85" y="860.491" id="tspan2983">QUERY</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 9px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1487.72" y="847.577" id="text2985"><tspan y="847.577" x="1487.72" sodipodi:role="line" id="tspan2987"><tspan x="1487.72" y="847.577" id="tspan2989">SALE 1</tspan><tspan dx="0" x="1522.44" y="847.577" id="tspan2991"/></tspan><tspan y="858.827" x="1487.72" sodipodi:role="line" id="tspan2993"><tspan x="1487.72" y="858.827" id="tspan2995">SALE 2</tspan><tspan dx="0" x="1522.44" y="858.827" id="tspan2997"/></tspan><tspan y="870.077" x="1487.72" sodipodi:role="line" id="tspan2999"><tspan x="1487.72" y="870.077" id="tspan3001">SALE 3</tspan><tspan dx="0" x="1522.44" y="870.077" id="tspan3003"/></tspan><tspan y="881.327" x="1487.72" sodipodi:role="line" id="tspan3005"><tspan x="1487.72" y="881.327" id="tspan3007">SALE 4</tspan></tspan></text>
|
||||
<rect ry="0.022097087" rx="0" y="460.38123" x="164.5349" height="0.044194173" width="0" id="rect5413" style="opacity: 1; fill: rgb(78, 219, 55); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1.082; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 3.1; stroke-dasharray: none; stroke-opacity: 1;"/>
|
||||
<flowRoot xml:space="preserve" id="flowRoot5253" style="font-size:12px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><flowRegion id="flowRegion5255"><rect id="rect5257" width="62.225395" height="33.941124" x="330.21887" y="640.82605"/></flowRegion><flowPara id="flowPara5259"/></flowRoot> <flowRoot xml:space="preserve" id="flowRoot5631" style="font-size:12px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><flowRegion id="flowRegion5633"><rect id="rect5636" width="226.27417" height="67.17514" x="839.33575" y="169.18582"/></flowRegion><flowPara id="flowPara5638"/></flowRoot> <rect style="opacity: 1; fill: rgb(246, 246, 240); fill-opacity: 0; fill-rule: nonzero; stroke: none; stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: round; stroke-miterlimit: 4; stroke-dasharray: none; stroke-opacity: 1;" id="rect5762" width="595.38391" height="154.14928" x="0" y="524.8609" rx="7.1171522" ry="3.4265683"/>
|
||||
<flowRoot xml:space="preserve" id="flowRoot2676" style="font-size:12px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><flowRegion id="flowRegion2678"><rect id="rect2680" width="220" height="45" x="14" y="-59.669922"/></flowRegion><flowPara id="flowPara2682"/></flowRoot> <flowRoot xml:space="preserve" id="flowRoot2684" style="font-size:12px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><flowRegion id="flowRegion2686"><rect id="rect2688" width="337" height="117" x="-27" y="-211.66992"/></flowRegion><flowPara id="flowPara2690"/></flowRoot> <flowRoot xml:space="preserve" id="flowRoot2692" style="font-size:12px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><flowRegion id="flowRegion2694"><rect id="rect2696" width="123" height="189" x="120" y="-349.66992"/></flowRegion><flowPara id="flowPara2698"/></flowRoot> <flowRoot xml:space="preserve" id="flowRoot2728" style="font-size:12px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><flowRegion id="flowRegion2730"><rect id="rect2732" width="248.19447" height="79.549515" x="22.98097" y="54.800766"/></flowRegion><flowPara id="flowPara2734"/></flowRoot> <flowRoot xml:space="preserve" id="flowRoot2798" style="font-size:10px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><flowRegion id="flowRegion2800"><rect id="rect2802" width="8.485281" height="12.727922" x="101.82338" y="423.55695"/></flowRegion><flowPara id="flowPara2804"/></flowRoot> <flowRoot xml:space="preserve" id="flowRoot2806" style="font-size:10px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><flowRegion id="flowRegion2808"><rect id="rect2810" width="451.13412" height="149.90663" x="21.213203" y="623.66815"/></flowRegion><flowPara id="flowPara2812"/></flowRoot> <text xml:space="preserve" style="font-size: 14px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1018.53" y="879.429" id="text2737"><tspan y="879.429" x="1018.53" sodipodi:role="line" id="tspan2739"><tspan x="1018.53" y="879.429" style="font-size: 16px;" id="tspan2741">Data tier</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 14px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1018.53" y="558.691" id="text2694"><tspan y="558.691" x="1018.53" sodipodi:role="line" id="tspan2696"><tspan x="1018.53" y="558.691" style="font-size: 16px;" id="tspan2698">Presentation tier</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 14px; font-style: normal; font-weight: bold; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1018.53" y="702.198" id="text2700"><tspan y="702.198" x="1018.53" sodipodi:role="line" id="tspan2702"><tspan x="1018.53" y="702.198" style="font-size: 16px;" id="tspan2704">Logic tier</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 12px; font-style: normal; font-weight: normal; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1020.04" y="725.487" id="text3427"><tspan sodipodi:role="line" id="tspan3429" x="1020.04" y="725.487"><tspan x="1020.04" y="725.487" id="tspan3431">This layer coordinates the </tspan></tspan><tspan sodipodi:role="line" id="tspan3433" x="1020.04" y="740.487"><tspan x="1020.04" y="740.487" id="tspan3435">application, processes commands, </tspan></tspan><tspan sodipodi:role="line" id="tspan3437" x="1020.04" y="755.487"><tspan x="1020.04" y="755.487" id="tspan3439">makes logical decisions and </tspan></tspan><tspan sodipodi:role="line" id="tspan3441" x="1020.04" y="770.487"><tspan x="1020.04" y="770.487" id="tspan3443">evaluations, and performs </tspan></tspan><tspan sodipodi:role="line" id="tspan3445" x="1020.04" y="785.487"><tspan x="1020.04" y="785.487" id="tspan3447">calculations. It also moves and </tspan></tspan><tspan sodipodi:role="line" id="tspan3449" x="1020.04" y="800.487"><tspan x="1020.04" y="800.487" id="tspan3451">processes data between the two </tspan></tspan><tspan sodipodi:role="line" id="tspan3453" x="1020.04" y="815.487"><tspan x="1020.04" y="815.487" id="tspan3455">surrounding layers.</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 12px; font-style: normal; font-weight: normal; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1019.72" y="903.985" id="text3457"><tspan sodipodi:role="line" id="tspan3459" x="1019.72" y="903.985"><tspan x="1019.72" y="903.985" id="tspan3461">Here information is stored and retrieved </tspan></tspan><tspan sodipodi:role="line" id="tspan3463" x="1019.72" y="918.985"><tspan x="1019.72" y="918.985" id="tspan3465">from a database or file system. The </tspan></tspan><tspan sodipodi:role="line" id="tspan3467" x="1019.72" y="933.985"><tspan x="1019.72" y="933.985" id="tspan3469">information is then passed back to the </tspan></tspan><tspan sodipodi:role="line" id="tspan3471" x="1019.72" y="948.985"><tspan x="1019.72" y="948.985" id="tspan3473">logic tier for processing, and then </tspan></tspan><tspan sodipodi:role="line" id="tspan3475" x="1019.72" y="963.985"><tspan x="1019.72" y="963.985" id="tspan3477">eventually back to the user.</tspan></tspan></text>
|
||||
<text xml:space="preserve" style="font-size: 12px; font-style: normal; font-weight: normal; fill: rgb(0, 0, 0); fill-opacity: 1; stroke: none; stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; font-family: Bitstream Vera Sans;" x="1020.04" y="579.832" id="text3714"><tspan sodipodi:role="line" id="tspan3716"><tspan x="1020.04" y="579.832" id="tspan3718">The top-most level of the application</tspan><tspan dx="0" x="1241.47" y="579.832" id="tspan3720"/></tspan><tspan sodipodi:role="line" id="tspan3722"><tspan x="1020.04" y="594.832" id="tspan3724">is the user interface. The main function</tspan><tspan dx="0" x="1257.22" y="594.832" id="tspan3726"/></tspan><tspan sodipodi:role="line" id="tspan3728"><tspan x="1020.04" y="609.832" id="tspan3730">of the interface is to translate tasks </tspan><tspan dx="0" x="1238.45" y="609.832" id="tspan3732"/></tspan><tspan sodipodi:role="line" id="tspan3734"><tspan x="1020.04" y="624.832" id="tspan3736">and results to something the user can </tspan><tspan dx="0" x="1252.98" y="624.832" id="tspan3738"/></tspan><tspan sodipodi:role="line" id="tspan3740"><tspan x="1020.04" y="639.832" id="tspan3742">understand.</tspan></tspan></text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 85 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 2: A New Application
|
||||
============================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
The purpose of this chapter is to lay the foundation for the creation of a completely new Odoo module.
|
||||
We will start from scratch with the minimum needed to have our module recognized by Odoo.
|
||||
In the upcoming chapters, we will progressively add features to build a realistic business case.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 3: Models And Basic Fields
|
||||
==================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
At the end of the :doc:`previous chapter <02_newapp>`, we were able to
|
||||
create an Odoo module. However, at this point it is still an empty shell which doesn't allow us to
|
||||
store any data. In our real estate module, we want to store the information related to the
|
@ -2,6 +2,12 @@
|
||||
Chapter 4: Security - A Brief Introduction
|
||||
==========================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
In the :doc:`previous chapter <03_basicmodel>`, we created our first table
|
||||
intended to store business data. In a business application such as Odoo, one of the first questions
|
||||
to consider is who\ [#who]_ can access the data. Odoo provides a security mechanism to allow access
|
@ -2,6 +2,12 @@
|
||||
Chapter 5: Finally, Some UI To Play With
|
||||
========================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
Now that we've created our new :doc:`model <03_basicmodel>` and its
|
||||
corresponding :doc:`access rights <04_securityintro>`, it is time to
|
||||
interact with the user interface.
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 239 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 6: Basic Views
|
||||
======================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
We have seen in the :doc:`previous chapter <05_firstui>` that Odoo is able
|
||||
to generate default views for a given model. In practice, the default view is **never** acceptable
|
||||
for a business application. Instead, we should at least organize the various fields in a logical
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 7: Relations Between Models
|
||||
===================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
The :doc:`previous chapter <06_basicviews>` covered the creation of custom
|
||||
views for a model containing basic fields. However, in any real business scenario we need more than
|
||||
one model. Moreover, links between models are necessary. One can easily imagine one model containing
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 8: Computed Fields And Onchanges
|
||||
========================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
The :doc:`relations between models <07_relations>` are a key component of
|
||||
any Odoo module. They are necessary for the modelization of any business case. However, we may want
|
||||
links between the fields within a given model. Sometimes the value of one field is determined from
|
||||
@ -50,7 +56,7 @@ method should set the value of the computed field for every record in
|
||||
|
||||
By convention, :attr:`~odoo.fields.Field.compute` methods are private, meaning that they cannot
|
||||
be called from the presentation tier, only from the business tier (see
|
||||
:ref:`tutorials/server_framework_101/01_architecture`). Private methods have a name starting with an
|
||||
:ref:`tutorials/server_framework_101_legacy/01_architecture`). Private methods have a name starting with an
|
||||
underscore ``_``.
|
||||
|
||||
Dependencies
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 254 KiB |
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 229 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 9: Ready For Some Action?
|
||||
=================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
So far we have mostly built our module by declaring fields and views. We just introduced business
|
||||
logic in the :doc:`previous chapter <08_compute_onchange>` thanks to
|
||||
computed fields and onchanges. In any real business scenario, we would want to link some business
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 10: Constraints
|
||||
=======================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
The :doc:`previous chapter <09_actions>` introduced the ability to add
|
||||
some business logic to our model. We can now link buttons to business code, but how can we prevent
|
||||
users from entering incorrect data? For example, in our real estate module nothing prevents
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 316 KiB |
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 225 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 11: Add The Sprinkles
|
||||
=============================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
Our real estate module now makes sense from a business perspective. We created
|
||||
:doc:`specific views <06_basicviews>`, added several
|
||||
:doc:`action buttons <09_actions>` and
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 283 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 266 KiB |
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 12: Inheritance
|
||||
=======================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
A powerful aspect of Odoo is its modularity. A module is dedicated to a business need, but
|
||||
modules can also interact with one another. This is useful for extending the functionality of an existing
|
||||
module. For example, in our real estate scenario we want to display the list of a salesperson's properties
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 356 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 13: Interact With Other Modules
|
||||
=======================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
In the :doc:`previous chapter <12_inheritance>`, we used inheritance to
|
||||
modify the behavior of a module. In our real estate scenario, we would like to go a step further
|
||||
and be able to generate invoices for our customers. Odoo provides an Invoicing module, so it
|
||||
@ -47,7 +53,7 @@ When the ``estate_account`` module appears in the list, go ahead and install it!
|
||||
the Invoicing application is installed as well. This is expected since your module depends on it.
|
||||
If you uninstall the Invoicing application, your module will be uninstalled as well.
|
||||
|
||||
.. _tutorials/server_framework_101/13_other_module/create:
|
||||
.. _tutorials/server_framework_101_legacy/13_other_module/create:
|
||||
|
||||
Invoice Creation
|
||||
----------------
|
Before Width: | Height: | Size: 513 KiB After Width: | Height: | Size: 513 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 14: A Brief History Of QWeb
|
||||
===================================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
So far the interface design of our real estate module has been rather limited. Building
|
||||
a list view is straightforward since only the list of fields is necessary. The same holds true
|
||||
for the form view: despite the use of a few tags such as ``<group>`` or ``<page>``, there
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@ -2,6 +2,12 @@
|
||||
Chapter 15: The final word
|
||||
==========================
|
||||
|
||||
.. danger::
|
||||
This tutorial is outdated. We recommend reading :doc:`../server_framework_101` instead.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Homepage of the tutorial <../server_framework_101_legacy>`
|
||||
|
||||
Coding guidelines
|
||||
=================
|
||||
|
@ -10,8 +10,10 @@ Odoo community and Odoo employees alike, the preferred way is to perform a sourc
|
||||
Follow the :ref:`contributing/development/setup` section of the contributing guide to prepare
|
||||
your environment for pushing local changes to the Odoo repositories.
|
||||
|
||||
Adapt the environment for the tutorials
|
||||
=======================================
|
||||
.. _tutorials/setup_guide/adapt_env:
|
||||
|
||||
Adapt the environment to the tutorials
|
||||
======================================
|
||||
|
||||
By now, you should have downloaded the source code into two local repositories, one for `odoo/odoo`
|
||||
and one for `odoo/enterprise`. These repositories are set up to push changes to pre-defined
|
||||
@ -29,10 +31,11 @@ will be part of the `addons-path` that references all directories containing Odo
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ git clone git@github.com:odoo/tutorials.git
|
||||
$ git clone --branch {BRANCH} --single-branch git@github.com:odoo/tutorials.git
|
||||
|
||||
#. Configure your fork and Git to push changes to your fork rather than to the main codebase. If you
|
||||
work at Odoo, configure Git to push changes to the shared fork created on the account **odoo-dev**.
|
||||
work at Odoo, configure Git to push changes to the shared fork created on the account
|
||||
**odoo-dev**.
|
||||
|
||||
.. tabs::
|
||||
|
||||
@ -41,8 +44,8 @@ will be part of the `addons-path` that references all directories containing Odo
|
||||
#. Visit `github.com/odoo/tutorials <https://github.com/odoo/tutorials>`_ and click the
|
||||
:guilabel:`Fork` button to create a fork of the repository on your account.
|
||||
|
||||
#. In the command below, replace `<your_github_account>` with the name of the GitHub account
|
||||
on which you created the fork.
|
||||
#. In the command below, replace `<your_github_account>` with the name of the GitHub
|
||||
account on which you created the fork.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
@ -58,57 +61,67 @@ will be part of the `addons-path` that references all directories containing Odo
|
||||
$ git remote set-url --push origin you_should_not_push_on_this_repository
|
||||
|
||||
That's it! Your environment is now prepared to run Odoo from the sources, and you have successfully
|
||||
created a repository to serve as an addons directory. This will allow you to push your work to GitHub.
|
||||
created a local repository to serve as an addons directory. This will allow you to push your work to
|
||||
GitHub.
|
||||
|
||||
.. important::
|
||||
|
||||
**For Odoo employees only:**
|
||||
|
||||
#. Make sure to read very carefully :ref:`contributing/development/first-contribution`. In particular,
|
||||
your branch name must follow our conventions.
|
||||
#. Make sure to read very carefully :ref:`contributing/development/first-contribution`. In
|
||||
particular:
|
||||
|
||||
#. Once you have pushed your first change to the shared fork on **odoo-dev**, create a
|
||||
:abbr:`PR (Pull Request)`. Please put your quadrigram in the PR title (e.g., "abcd - Technical
|
||||
Training").
|
||||
|
||||
This will enable you to share your upcoming work and receive feedback from your coaches. To ensure
|
||||
a continuous feedback loop, we recommend pushing a new commit as soon as you complete a chapter
|
||||
of the tutorial. Note that the PR is automatically updated with commits you push to **odoo-dev**,
|
||||
you don't need to open multiple PRs.
|
||||
- Your code must follow the :doc:`guidelines </contributing/development/coding_guidelines>`.
|
||||
- Your commit messages must be :doc:`correctly written
|
||||
</contributing/development/git_guidelines>`.
|
||||
- Your branch name must follow our conventions.
|
||||
|
||||
#. Once you have pushed your first change to the shared fork on **odoo-dev**, create a **draft**
|
||||
:abbr:`PR (Pull Request)` with your quadrigram in the title. This will enable you to share
|
||||
your upcoming work and receive feedback from your coaches. To ensure a continuous feedback
|
||||
loop, push a new commit as soon as you complete an exercise of a tutorial.
|
||||
#. At Odoo we use `Runbot <https://runbot.odoo.com>`_ extensively for our :abbr:`CI (Continuous
|
||||
Integration)` tests. When you push your changes to **odoo-dev**, Runbot creates a new build
|
||||
and test your code. Once logged in, you will be able to see your branches `Tutorials project
|
||||
<https://runbot.odoo.com/runbot/tutorials-12>`_.
|
||||
and tests your code. Once logged in, you will be able to see your branch on the `Tutorials
|
||||
project <https://runbot.odoo.com/runbot/tutorials-12>`_.
|
||||
|
||||
.. note::
|
||||
|
||||
The specific location of the repositories on your file system is not crucial. However, for the
|
||||
sake of simplicity, we will assume that you have cloned all the repositories under the same
|
||||
directory. If this is not the case, make sure to adjust the following commands accordingly,
|
||||
providing the appropriate relative path from the `odoo/odoo` repository to the
|
||||
`odoo/tutorials` repository.
|
||||
|
||||
Run the server
|
||||
==============
|
||||
.. _tutorials/setup_guide/start_server:
|
||||
|
||||
Launch with `odoo-bin`
|
||||
----------------------
|
||||
Start the server
|
||||
================
|
||||
|
||||
Once all dependencies are set up, Odoo can be launched by running `odoo-bin`, the command-line
|
||||
interface of the server.
|
||||
interface of the server, and passing the comma-separated list of repositories with the `addons-path`
|
||||
argument. If you have access to the `odoo/enterprise` repository, add it to the `addons-path`.
|
||||
|
||||
.. code-block:: console
|
||||
.. tabs::
|
||||
|
||||
$ cd $HOME/src/odoo/
|
||||
$ ./odoo-bin --addons-path="addons/,../enterprise/,../tutorials" -d rd-demo
|
||||
.. tab:: Run the community edition
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cd $HOME/src/odoo/
|
||||
$ ./odoo-bin --addons-path="addons/,../tutorials" -d tutorials
|
||||
|
||||
.. tab:: Run the enterprise edition
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cd $HOME/src/odoo/
|
||||
$ ./odoo-bin --addons-path="addons/,../enterprise/,../tutorials" -d tutorials
|
||||
|
||||
There are multiple :ref:`command-line arguments <reference/cmdline/server>` that you can use to run
|
||||
the server. In this training you will only need some of them.
|
||||
|
||||
.. option:: -d <database>
|
||||
|
||||
The database that is going to be used.
|
||||
The database to use.
|
||||
|
||||
.. option:: --addons-path <directories>
|
||||
|
||||
@ -117,66 +130,59 @@ the server. In this training you will only need some of them.
|
||||
|
||||
.. option:: --limit-time-cpu <limit>
|
||||
|
||||
Prevent the worker from using more than <limit> CPU seconds for each request.
|
||||
Prevent the worker from using more than `<limit>` CPU seconds for each request.
|
||||
|
||||
.. option:: --limit-time-real <limit>
|
||||
|
||||
Prevent the worker from taking longer than <limit> seconds to process a request.
|
||||
Prevent the worker from taking longer than `<limit>` seconds to process a request.
|
||||
|
||||
.. tip::
|
||||
- The :option:`--limit-time-cpu` and :option:`--limit-time-real` arguments can be used to prevent
|
||||
the worker from being killed when debugging the source code.
|
||||
- | You may face an error similar to `AttributeError: module '<MODULE_NAME>' has no attribute
|
||||
'<$ATTRIBUTE'>`. In this case, you may need to re-install the module with :command:`$ pip
|
||||
install --upgrade --force-reinstall <MODULE_NAME>`.
|
||||
| If this error occurs with more than one module, you may need to re-install all the
|
||||
requirements with :command:`$ pip install --upgrade --force-reinstall -r requirements.txt`.
|
||||
| You can also clear the python cache to solve the issue:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cd $HOME/.local/lib/python3.8/site-packages/
|
||||
$ find -name '*.pyc' -type f -delete
|
||||
|
||||
- Other commonly used arguments are:
|
||||
|
||||
- :option:`-i <odoo-bin --init>`: Install some modules before running the server
|
||||
(comma-separated list). This is equivalent to going to :guilabel:`Apps` in the user interface,
|
||||
(comma-separated list). This is equivalent to going to :guilabel:`Apps` in the user interface
|
||||
and installing the module from there.
|
||||
- :option:`-u <odoo-bin --update>`: Update some modules before running the server
|
||||
(comma-separated list). This is equivalent to going to :guilabel:`Apps` in the user interface,
|
||||
selecting a module, and upgrading it from there.
|
||||
(comma-separated list). This is equivalent to going to :guilabel:`Apps` in the user interface
|
||||
and updating the module from there.
|
||||
|
||||
.. _tutorials/setup_guide/log_in:
|
||||
|
||||
Log in to Odoo
|
||||
--------------
|
||||
==============
|
||||
|
||||
Open http://localhost:8069/ on your browser. We recommend using `Chrome
|
||||
Open http://localhost:8069/ in your browser. We recommend using `Chrome
|
||||
<https://www.google.com/intl/en/chrome/>`_, `Firefox <https://www.mozilla.org/firefox/new/>`_, or
|
||||
any other browser with development tools.
|
||||
|
||||
To log in as the administrator user, use the following credentials:
|
||||
|
||||
- email: `admin`
|
||||
- password: `admin`
|
||||
- Email: `admin`
|
||||
- Password: `admin`
|
||||
|
||||
Enable the developer mode
|
||||
=========================
|
||||
|
||||
The developer or debug mode is useful for training as it gives access to additional (advanced)
|
||||
tools. :ref:`Enable the developer mode <developer-mode>` now. Choose the method that you prefer;
|
||||
they are all equivalent.
|
||||
.. _tutorials/setup_guide/extra_tools:
|
||||
|
||||
Extra tools
|
||||
===========
|
||||
|
||||
.. _tutorials/setup_guide/extra_tools/dev_mode:
|
||||
|
||||
Developer mode
|
||||
--------------
|
||||
|
||||
:ref:`Enable the developer mode <developer-mode>` to get access to developer-oriented tools in the
|
||||
interface.
|
||||
|
||||
.. _tutorials/setup_guide/extra_tools/git_commands:
|
||||
|
||||
Useful Git commands
|
||||
-------------------
|
||||
|
||||
Here are some useful Git commands for your day-to-day work.
|
||||
|
||||
- | Switch branches:
|
||||
| When you switch branches, both repositories (odoo and enterprise) must be synchronized, i.e.
|
||||
both need to be in the same branch.
|
||||
- Switch branches:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
@ -186,6 +192,10 @@ Here are some useful Git commands for your day-to-day work.
|
||||
$ cd $HOME/src/enterprise
|
||||
$ git switch {BRANCH}
|
||||
|
||||
.. important::
|
||||
When you switch branches, both repositories (odoo and enterprise) must be synchronized, i.e.
|
||||
both need to be in the same branch.
|
||||
|
||||
- Fetch and rebase:
|
||||
|
||||
.. code-block:: console
|
||||
@ -198,57 +208,53 @@ Here are some useful Git commands for your day-to-day work.
|
||||
$ git fetch --all --prune
|
||||
$ git rebase --autostash enterprise/{BRANCH}
|
||||
|
||||
Code Editor
|
||||
.. _tutorials/setup_guide/extra_tools/code_editor:
|
||||
|
||||
Code editor
|
||||
-----------
|
||||
|
||||
If you are working at Odoo, many of your colleagues are using `VSCode
|
||||
You are free to choose your code preferred editor. Most Odoo developers use `VSCode
|
||||
<https://code.visualstudio.com>`_, `VSCodium <https://vscodium.com>`_ (the open source equivalent),
|
||||
`PyCharm <https://www.jetbrains.com/pycharm/download/#section=linux>`_, or `Sublime Text
|
||||
<https://www.sublimetext.com>`_. However, you are free to choose your preferred editor.
|
||||
<https://www.sublimetext.com>`_.
|
||||
|
||||
It is important to configure your linters correctly. Using a linter helps you by showing syntax and
|
||||
semantic warnings or errors. Odoo source code tries to respect Python's and JavaScript's standards,
|
||||
but some of them can be ignored.
|
||||
semantic warnings or errors. For JavaScript, we use ESLint and you can find a `configuration file
|
||||
example here <https://github.com/odoo/odoo/wiki/Javascript-coding-guidelines#use-a-linter>`_.
|
||||
|
||||
For Python, we use PEP8 with these options ignored:
|
||||
|
||||
- `E501`: line too long
|
||||
- `E301`: expected 1 blank line, found 0
|
||||
- `E302`: expected 2 blank lines, found 1
|
||||
|
||||
For JavaScript, we use ESLint and you can find a `configuration file example here
|
||||
<https://github.com/odoo/odoo/wiki/Javascript-coding-guidelines#use-a-linter>`_.
|
||||
.. _tutorials/setup_guide/extra_tools/psql_tools:
|
||||
|
||||
Administrator tools for PostgreSQL
|
||||
----------------------------------
|
||||
|
||||
You can manage your PostgreSQL databases using the command line as demonstrated earlier or using
|
||||
a GUI application such as `pgAdmin <https://www.pgadmin.org/download/pgadmin-4-apt/>`_ or `DBeaver
|
||||
<https://dbeaver.io/>`_.
|
||||
You can manage your PostgreSQL databases using the command line or a GUI application such as
|
||||
`pgAdmin <https://www.pgadmin.org/download/pgadmin-4-apt/>`_ or `DBeaver <https://dbeaver.io/>`_.
|
||||
|
||||
To connect the GUI application to your database we recommend you connect using the Unix socket.
|
||||
We recommend you connect the GUI application to your database using the Unix socket.
|
||||
|
||||
- Host name/address: `/var/run/postgresql`
|
||||
- Port: `5432`
|
||||
- Username: `$USER`
|
||||
|
||||
Python Debugging
|
||||
.. _tutorials/setup_guide/extra_tools/python_debugging:
|
||||
|
||||
Python debugging
|
||||
----------------
|
||||
|
||||
When facing a bug or trying to understand how the code works, simply printing things out can go a
|
||||
long way, but a proper debugger can save a lot of time.
|
||||
When facing a bug or trying to understand how the code works, simply printing things out can help a
|
||||
lot, but a proper debugger can save a lot of time.
|
||||
|
||||
You can use a classic Python library debugger (`pdb <https://docs.python.org/3/library/pdb.html>`_,
|
||||
`pudb <https://pypi.org/project/pudb/>`_ or `ipdb <https://pypi.org/project/ipdb/>`_), or you can
|
||||
use your editor's debugger.
|
||||
You can use your editor's debugger, or a classic Python library debugger (`pdb
|
||||
<https://docs.python.org/3/library/pdb.html>`_, `pudb <https://pypi.org/project/pudb/>`_, or `ipdb
|
||||
<https://pypi.org/project/ipdb/>`_).
|
||||
|
||||
In the following example we use ipdb, but the process is similar with other libraries.
|
||||
In the following example, we use ipdb, but the process is similar to other libraries.
|
||||
|
||||
#. Install the library:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
pip install ipdb
|
||||
$ pip install ipdb
|
||||
|
||||
#. Place a trigger (breakpoint):
|
||||
|
||||
|
@ -7,3 +7,21 @@ applications/finance/accounting/payments/internal_transfers.rst applications/fin
|
||||
# applications/point of sale
|
||||
|
||||
content/applications/sales/point_of_sale/payment_methods/terminals/vantiv.rst content/applications/sales/point_of_sale/payment_methods/terminals.rst
|
||||
|
||||
# developer/tutorials
|
||||
|
||||
developer/tutorials/server_framework_101/01_architecture.rst developer/tutorials/server_framework_101_legacy/01_architecture.rst
|
||||
developer/tutorials/server_framework_101/02_newapp.rst developer/tutorials/server_framework_101_legacy/02_newapp.rst
|
||||
developer/tutorials/server_framework_101/03_basicmodel.rst developer/tutorials/server_framework_101_legacy/03_basicmodel.rst
|
||||
developer/tutorials/server_framework_101/04_securityintro.rst developer/tutorials/server_framework_101_legacy/04_securityintro.rst
|
||||
developer/tutorials/server_framework_101/05_firstui.rst developer/tutorials/server_framework_101_legacy/05_firstui.rst
|
||||
developer/tutorials/server_framework_101/06_basicviews.rst developer/tutorials/server_framework_101_legacy/06_basicviews.rst
|
||||
developer/tutorials/server_framework_101/07_relations.rst developer/tutorials/server_framework_101_legacy/07_relations.rst
|
||||
developer/tutorials/server_framework_101/08_compute_onchange.rst developer/tutorials/server_framework_101_legacy/08_compute_onchange.rst
|
||||
developer/tutorials/server_framework_101/09_actions.rst developer/tutorials/server_framework_101_legacy/09_actions.rst
|
||||
developer/tutorials/server_framework_101/10_constraints.rst developer/tutorials/server_framework_101_legacy/10_constraints.rst
|
||||
developer/tutorials/server_framework_101/11_sprinkles.rst developer/tutorials/server_framework_101_legacy/11_sprinkles.rst
|
||||
developer/tutorials/server_framework_101/12_inheritance.rst developer/tutorials/server_framework_101_legacy/12_inheritance.rst
|
||||
developer/tutorials/server_framework_101/13_other_module.rst developer/tutorials/server_framework_101_legacy/13_other_module.rst
|
||||
developer/tutorials/server_framework_101/14_qwebintro.rst developer/tutorials/server_framework_101_legacy/14_qwebintro.rst
|
||||
developer/tutorials/server_framework_101/15_final_word.rst developer/tutorials/server_framework_101_legacy/15_final_word.rst
|
||||
|