[Merge]Merge lp:openobject-server

bzr revid: vja@tinyerp.com-20130128120243-dclyb3u0aqrojhon
This commit is contained in:
Vishmita
2013-01-28 17:32:43 +05:30
255 changed files with 129766 additions and 78249 deletions
+20 -13
View File
@@ -1,14 +1,21 @@
.*
*.egg-info
*.orig
*.vim
.*.swp
.bzrignore
.idea
.project
.pydevproject
.ropeproject
.settings
.DS_Store
openerp/addons/*
openerp/filestore*
.Python
*.pyc
*.pyo
bin/*
build/
RE:^bin/
RE:^dist/
RE:^include/
RE:^share/
RE:^man/
RE:^lib/
RE:^addons/\w+/doc/_build/
include/
lib/
share/
doc/_build/*
win32/*.bat
win32/meta.py
+1
View File
@@ -19,6 +19,7 @@ Depends:
python-docutils,
python-feedparser,
python-gdata,
python-jinja2,
python-ldap,
python-libxslt1,
python-lxml,
+3
View File
@@ -135,6 +135,9 @@ The view describes how the edition form or the data tree/list appear on screen.
A form can be called by an action opening in 'Tree' mode. The form view is generally opened from the list mode (like if the user pushes on 'switch view').
.. _domain:
.. _domains:
The domain
----------
-4
View File
@@ -141,10 +141,6 @@ for users who do not belong to the authorized groups:
.. note:: The tests related to this feature are in ``openerp/tests/test_acl.py``.
.. warning:: At the time of writing the implementation of this feature is partial
and does not yet restrict read/write RPC access to the field.
The corresponding test is written already but currently disabled.
Workflow transition rules
+++++++++++++++++++++++++
+1
View File
@@ -10,3 +10,4 @@ Miscellanous
06_misc_need_action_specs.rst
06_misc_user_img_specs.rst
06_misc_import.rst
06_misc_auto_join.rst
+75
View File
@@ -0,0 +1,75 @@
.. _performing_joins_in_select:
Perfoming joins in select
=========================
.. versionadded:: 7.0
Starting with OpenERP 7.0, an ``auto_join`` attribute is added on *many2one* and
*one2many* fields. The purpose is to allow the automatic generation of joins in
select queries. This attribute is set to False by default, therefore not changing
the default behavior. Please note that we consider this feature as still experimental
and should be used only if you understand its limitations and targets.
Without ``_auto_join``, the behavior of expression.parse() is the same as before.
Leafs holding a path beginning with many2one or one2many fields perform a search
on the relational table. The result is then used to replace the leaf content.
For example, if you have on res.partner a domain like ``[('bank_ids.name',
'like', 'foo')]`` with bank_ids linking to res.partner.bank, 3 queries will be
performed :
- 1 on res_partner_bank, with domain ``[('name', '=', 'foo')]``, that returns a
list of res.partner.bank ids (bids)
- 1 on res_partner, with a domain ``['bank_ids', 'in', bids)]``, that returns a
list of res.partner ids (pids)
- 1 on res_partner, with a domain ``[('id', 'in', pids)]``
When the ``auto_join`` attribute is True on a relational field, the destination
table will be joined to produce only one query.
- the relational table is accessed using an alias: ``'"res_partner_bank"
as res_partner__bank_ids``. The alias is generated using the relational field
name. This allows to have multiple joins with different join conditions on the
same table, depending on the domain.
- there is a join condition between the destination table and the main table:
``res_partner__bank_ids."partner_id"=res_partner."id"``
- the condition is then written on the relational table:
``res_partner__bank_ids."name" = 'foo'``
This manipulation is performed in expression.parse(). It checks leafs that
contain a path, i.e. any domain containing a '.'. It then checks whether the
first item of the path is a *many2one* or *one2many* field with the ``auto_join``
attribute set. If set, it adds a join query and recursively analyzes the
remaining of the leaf, using the same behavior. If the remaining path also holds
a path with auto_join fields, it will add all tables and add every necessary
join conditions.
Chaining joins allows to reduce the number of queries performed, and to avoid
having too long equivalent leaf replacement in domains. Indeed, the internal
queries produced by this behavior can be very costly, because they were generally
select queries without limit that could lead to huge ('id', 'in', [...])
leafs to analyze and execute.
Some limitations exist on this feature that limits its current use as of version
7.0. **This feature is therefore considered as experimental, and used
to speedup some precise bottlenecks in OpenERP**.
List of known issues and limitations:
- using ``auto_join`` bypasses the business logic; no name search is performed,
only direct matches between ids using join conditions
- ir.rules are not taken into account when analyzing and adding the join
conditions
List of already-supported corner cases :
- one2many fields having a domain attribute. Static domains as well as dynamic
domain are supported
- auto_join leading to functional searchable fields
Typical use in OpenERP 7.0:
- in mail module: notification_ids field on mail_message, allowing to speedup
the display of the various mailboxes
- in mail module: message_ids field on mail_thread, allowing to speedup the
display of needaction counters and documents having unread messages
+11 -11
View File
@@ -76,9 +76,9 @@ This phase also generates the ``rows`` indexes for any
Conversion
++++++++++
This second phase takes the record dicts, extracts the :ref:`dbid` and
:ref:`xid` if present and attempts to convert each field to a type
matching what OpenERP expects to write.
This second phase takes the record dicts, extracts the :term:`database
ID` and :term:`external ID` if present and attempts to convert each
field to a type matching what OpenERP expects to write.
* Empty fields (empty strings) are replaced with the ``False`` value
@@ -141,14 +141,14 @@ If ``name_search`` finds no value, an error is generated. If
``name_search`` finds multiple value, a warning is generated to warn
the user of ``name_search`` collisions.
If the specified field is a :ref:`xid` (``m2o/id``), the
If the specified field is a :term:`external ID` (``m2o/id``), the
corresponding record it looked up in the database and used as the
field's value. If no record is found matching the provided external
ID, an error is generated.
If the specified field is a :ref:`dbid` (``m2o/.id``), the process is
the same as for external ids (on database identifiers instead of
external ones).
If the specified field is a :term:`database ID` (``m2o/.id``), the
process is the same as for external ids (on database identifiers
instead of external ones).
Many to Many field
~~~~~~~~~~~~~~~~~~
@@ -161,11 +161,11 @@ One to Many field
~~~~~~~~~~~~~~~~~
For each o2m record extracted, if the record has a ``name``,
:ref:`xid` or :ref:`dbid` the :ref:`dbid` is looked up and checked
through the same process as for m2o fields.
:term:`external ID` or :term:`database ID` the :term:`database ID` is
looked up and checked through the same process as for m2o fields.
If a :ref:`dbid` was found, a LINK_TO command is emmitted, followed by
an UPDATE with the non-db values for the relational field.
If a :term:`database ID` was found, a LINK_TO command is emmitted,
followed by an UPDATE with the non-db values for the relational field.
Otherwise a CREATE command is emmitted.
+34
View File
@@ -0,0 +1,34 @@
.. _adding-command:
Adding a new command
====================
``oe`` uses the argparse_ library to implement commands. Each
command lives in its own ``openerpcommand/<command>.py`` file.
.. _argparse: http://docs.python.org/2.7/library/argparse.html
To create a new command, probably the most simple way to get started is to
copy/paste an existing command, say ``openerpcommand/initialize.py`` to
``openerpcommand/foo.py``. In the newly created file, the important bits
are the ``run(args)`` and ``add_parser(subparsers)`` functions.
``add_parser``'s responsability is to create a (sub-)parser for the command,
i.e. describe the different options and flags. The last thing it does is to set
``run`` as the function to call when the command is invoked.
.. code-block:: python
> def add_parser(subparsers):
> parser = subparsers.add_parser('<command-name>',
> description='...')
> parser.add_argument(...)
> ...
> parser.set_defaults(run=run)
``run(args)`` actually implements the command. It should be kept as simple as
possible and delegate most of its work to small functions (probably placed at
the top of the new file). In other words, its responsability is mainly to
deal with the presence/absence/pre-processing of ``argparse``'s arguments.
Finally, the module must be added to ``openerpcommand/__init__.py``.
+40
View File
@@ -0,0 +1,40 @@
.. _commands:
Available commands
==================
This page explain some of the available ``oe`` commands. For an overview about
``oe``, see :doc:`openerp-command`.
Keep in mind that ``oe --help`` and ``oe <command> --help`` already give a lot
of information about the commands and their options and flags.
``web``
-------
The ``web`` command is used to create a single OpenERP server process to handle
regular HTTP requests and XML-RPC requests. It is possible to execute such
process multiple times, possibly on different machines.
It is possible to chose the ``--threaded`` or ``--gevent`` flags. It is
recommanded to use ``--threaded`` only when running a single process.
``--gevent`` is experimental; it is planned to use it for the embedded chat
feature.
Example invocation::
> oe web --addons ../../addons/trunk:../../web/trunk/addons --threaded
``cron``
--------
The ``cron`` command is used to create a single OpenERP process to execute
so-called cron jobs, also called scheduled tasks in the OpenERP interface. As
for the ``web`` command, multiple cron processes can be run side by side.
It is necessary to specify on the command-line which database need to be
watched by the cron process with the ``--database`` option.
Example invocation::
> oe cron --addons ../../addons/trunk:../../web/trunk/addons --database production
+256
View File
@@ -0,0 +1,256 @@
# -*- coding: utf-8 -*-
#
# OpenERP Technical Documentation configuration file, created by
# sphinx-quickstart on Fri Feb 17 16:14:06 2012.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
sys.path.append(os.path.abspath('_themes'))
sys.path.append(os.path.abspath('..'))
sys.path.append(os.path.abspath('../openerp'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'OpenERP Server Developers Documentation'
copyright = u'2012, OpenERP s.a.'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '7.0'
# The full version, including alpha/beta/rc tags.
release = '7.0b'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'flask'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
'sourcelink.html', 'searchbox.html']
}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'openerp-server-doc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'openerp-server-doc.tex', u'OpenERP Server Developers Documentation',
u'OpenERP s.a.', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'openerp-server-doc', u'OpenERP Server Developers Documentation',
[u'OpenERP s.a.'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'OpenERPServerDocumentation', u'OpenERP Server Developers Documentation',
u'OpenERP s.a.', 'OpenERPServerDocumentation', 'Developers documentation for the openobject-server project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('http://docs.python.org/', None),
'openerpweb': ('http://doc.openerp.com/trunk/developers/web', None),
}
@@ -16,20 +16,20 @@ Summary
Configuring and starting an OpenERP server with Gunicorn is straightfoward. The
different sections below give more details but the following steps are all it
takes::
takes:
1. Use a configuration file, passing it to ``gunicorn`` using the ``-c``
option.
2. Within the same configuration file, also configure OpenERP.
3. Run ``gunicorn openerp:wsgi.core.application -c gunicorn.conf.py``.
1. Use a configuration file, passing it to ``gunicorn`` using the ``-c``
option.
2. Within the same configuration file, also configure OpenERP.
3. Run ``gunicorn openerp:service.wsgi_server.application -c openerp-wsgi.py``.
Sample configuration file
-------------------------
A sample ``gunicorn.conf.py`` configuration file for Gunicorn can be found in
the OpenERP server source tree. It is fairly well commented and easily
A sample ``openerp-wsgi.py`` configuration file for WSGI servers can be found
in the OpenERP server source tree. It is fairly well commented and easily
customizable for your own usage. While reading the remaining of this page, it
is advised you take a look at the sample ``gunicorn.conf.py`` file as it makes
is advised you take a look at the sample ``openerp-wsgi.py`` file as it makes
things easier to follow.
Configuration
@@ -37,7 +37,7 @@ Configuration
Gunicorn can be configured by a configuration file and/or command-line
arguments. For a list of available options, you can refer to the official
Gunicorn documentation http://gunicorn.org/configure.html.
Gunicorn documentation http://docs.gunicorn.org/en/latest/configure.html.
When the OpenERP server is started on its own, by using the ``openerp-server``
script, it can also be configured by a configuration file or its command-line
@@ -46,7 +46,7 @@ as the Gunicorn configuration file is a full-fledged Python file, we can
``import openerp`` in it and configure directly the server.
The principle can be summarized with this three lines (although they are spread
across the whole sample ``gunicorn.conf.py`` file)::
across the whole sample ``openerp-wsgi.py`` file)::
import openerp
conf = openerp.tools.config
@@ -57,45 +57,16 @@ containing the OpenERP server implementation). The second one is really to
shorten repeated usage of the same variable. The third one sets a parameter, in
this case the equivalent of the ``--addons-path`` command-line option.
Finally, Gunicorn offers a few hooks so we can call our own code at some points
in its execution. The most important one is the ``on_starting`` hook. It lets
us properly initialize the ``openerp`` library before Gunicorn starts handling
requests. ``pre_request`` and ``post_request`` are called before and after
requests are handled. We provide functions in ``openerp.wsgi.core`` that can be
used to define those hooks: a typical Gunicorn configuration for OpenERP will
thus contains::
on_starting = openerp.wsgi.core.on_starting
pre_request = openerp.wsgi.core.pre_request
post_request = openerp.wsgi.core.post_request
Running
-------
Once a proper configuration file is available, running the OpenERP server with
Gunicorn can be done with the following command::
> gunicorn openerp:wsgi.core.application -c gunicorn.conf.py
> gunicorn openerp:service.wsgi_server.application -c openerp-wsgi.py
``openerp`` must be importable by Python. The simplest way is to run the above
command from the server source directory (i.e. the directory containing the
``openerp`` module). Alternatively, the module can be installed on your machine
as a regular Python library or added to your ``PYTHONPATH``.
Running behind a reverse proxy
------------------------------
If you intend to run Gunicorn behind a reverse proxy (nginx_ is recommended),
an alternative entry point is available in ``openerp.wsgi.proxied``. That entry
point uses werkzeug's ProxyFix_ class to set a few headers. You first have to
explicitely import that sub-module if you want to use it. So add this line in
the configuration file::
import openerp.wsgi.proxied
and then adapt the command-line::
> gunicorn openerp:wsgi.proxied.application -c gunicorn.conf.py
.. _nginx: http://nginx.org/en/
.. _ProxyFix: http://werkzeug.pocoo.org/docs/contrib/fixers/#werkzeug.contrib.fixers.ProxyFix
+21 -2
View File
@@ -16,7 +16,17 @@ OpenERP Server
04_security
05_test_framework
06_misc
09_deployment
deployment-gunicorn
OpenERP Command
'''''''''''''''
.. toctree::
:maxdepth: 1
openerp-command.rst
commands.rst
adding-command.rst
OpenERP Server API
''''''''''''''''''
@@ -24,8 +34,17 @@ OpenERP Server API
.. toctree::
:maxdepth: 1
api_core.rst
api_models.rst
Concepts
''''''''
.. glossary::
Database ID
The primary key of a record in a PostgreSQL table (or a
virtual version thereof), usually varies from one database to
the next.
External ID
+66
View File
@@ -0,0 +1,66 @@
.. _openerp-command:
The ``oe`` script
=================
The ``oe`` script provides a set of command-line tools around the OpenERP
framework. It is meant to replace the older ``openerp-server`` script (which
is still available).
Using ``oe``
------------
In contrast to the previous ``openerp-server`` script, ``oe`` defines a few
commands, each with its own set of flags and options. You can get some
information for any of them with
::
> oe <command> --help
For instance::
> oe run-tests --help
Some ``oe`` options can be provided via environment variables. For instance::
> export OPENERP_DATABASE=trunk
> export OPENERP_HOST=127.0.0.1
> export OPENERP_PORT=8069
Depending on your needs, you can group all of the above in one single script;
for instance here is a, say, ``test-trunk-view-validation.sh`` file::
COMMAND_REPO=/home/thu/repos/command/trunk/
SERVER_REPO=/home/thu/repos/server/trunk
export PYTHONPATH=$SERVER_REPO:$COMMAND_REPO
export PATH=$SERVER_REPO:$COMMAND_REPO:$PATH
export OPENERP_DATABASE=trunk
export OPENERP_HOST=127.0.0.1
export OPENERP_PORT=8069
# The -d ignored is actually needed by `oe` even though `test_view_validation`
# itself does not need it.
oe run-tests -d ignored -m openerp.test_view_validation
Available commands
-------------------
See the :doc:`commands` page.
Adding new commands
-------------------
See the :doc:`adding-command` page.
Bash completion
---------------
A preliminary ``oe-bash-completion`` file is provided. After sourcing it,
::
> . oe-bash-completion
completion (using the TAB character) in Bash should work.
Executable
+5
View File
@@ -0,0 +1,5 @@
#! /usr/bin/env python2
if __name__ == '__main__':
import openerpcommand.main
openerpcommand.main.run()
+89
View File
@@ -0,0 +1,89 @@
_oe()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
cmd="${COMP_WORDS[0]}"
subcmd=""
if [[ ${COMP_CWORD} > 0 ]] ; then
subcmd="${COMP_WORDS[1]}"
fi
# oe
opts="initialize model read run-tests scaffold update \
call open show consume-nothing consume-memory leak-memory \
consume-cpu bench-read bench-fields-view-get bench-dummy bench-login \
bench-sale-mrp --help"
if [[ ${prev} == oe && ${cur} != -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# oe call
opts="--database --user --password --host --port --help"
if [[ ${subcmd} == call ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# oe initialize
opts="--database --addons --all-modules --exclude --no-create --help"
if [[ ${subcmd} == initialize ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# oe model
opts="--database --model --field --verbose --help"
if [[ ${subcmd} == model ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# oe read
opts="--database --model --id --field --verbose --short --help"
if [[ ${subcmd} == read ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# oe run-tests
opts="--database --addons --module --dry-run --help"
if [[ ${subcmd} == run-tests ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# oe scaffold
opts="--help"
if [[ ${subcmd} == scaffold ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
# fallback for unimplemented completion
opts="--help"
if [[ true ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
complete -F _oe oe
+2
View File
@@ -51,6 +51,8 @@ wsgi.register_wsgi_handler = wsgi.wsgi_server.register_wsgi_handler
# its own copy of the data structure and we don't need to care about
# locks between threads.
multi_process = False
# Is the server running with gevent.
evented = False
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+5 -7
View File
@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -80,12 +80,12 @@ The kernel of OpenERP, needed for all installation.
'res/res_bank_view.xml',
'res/res_country_view.xml',
'res/res_currency_view.xml',
'res/wizard/change_password_wizard_view.xml',
'res/res_users_view.xml',
'res/res_partner_data.xml',
'res/ir_property_view.xml',
'security/base_security.xml',
'security/ir.model.access.csv',
'security/ir.model.access-1.csv', # res.partner.address is deprecated; it is still there for backward compability only and will be removed in next version
],
'demo': [
'base_demo.xml',
@@ -94,19 +94,17 @@ The kernel of OpenERP, needed for all installation.
'res/res_partner_image_demo.xml',
],
'test': [
'test/base_test.xml',
'test/base_test.yml',
'test/test_context.xml',
'test/bug_lp541545.xml',
'test/test_osv_expression.yml',
'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules.
# Commented because this takes some time.
# This must be (un)commented with the corresponding import statement
# in test/__init__.py.
# 'test/test_ir_cron.yml', # <-- These tests perform a roolback.
],
'installable': True,
'auto_install': True,
'css': ['static/src/css/modules.css'],
'js': [
'static/src/js/apps.js',
],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+32 -9
View File
@@ -149,7 +149,6 @@ CREATE TABLE res_users (
active boolean default True,
login varchar(64) NOT NULL UNIQUE,
password varchar(64) default null,
lang varchar(64) default '',
-- No FK references below, will be added later by ORM
-- (when the destination rows exist)
company_id int,
@@ -316,13 +315,29 @@ CREATE TABLE ir_module_module_dependency (
primary key(id)
);
CREATE TABLE res_company (
CREATE TABLE res_partner (
id serial NOT NULL,
name character varying(64) not null,
parent_id integer references res_company on delete set null,
name character varying(128),
lang varchar(64),
company_id int,
primary key(id)
);
CREATE TABLE res_currency (
id serial PRIMARY KEY,
name VARCHAR(32) NOT NULL
);
CREATE TABLE res_company (
id serial PRIMARY KEY,
name character varying(128) not null,
parent_id integer references res_company on delete set null,
partner_id integer not null references res_partner,
currency_id integer not null references res_currency
);
CREATE TABLE res_lang (
id serial PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE,
@@ -375,16 +390,24 @@ CREATE TABLE ir_model_relation (
module integer NOT NULL references ir_module_module on delete restrict,
model integer NOT NULL references ir_model on delete restrict,
name character varying(128) NOT NULL
);
);
---------------------------------
-- Users
---------------------------------
insert into res_users (id,login,password,active,company_id,partner_id) VALUES (1,'admin','admin',true,1,1);
insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('user_root','base','res.users',true,1);
insert into res_users (id,login,password,active,company_id,partner_id,lang) values (1,'admin','admin',True,1,1,'en_US');
insert into ir_model_data (name,module,model,noupdate,res_id) values ('user_root','base','res.users',True,1);
insert into res_partner (id, name, lang, company_id) VALUES (1, 'Your Company', 'en_US', 1);
insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('main_partner','base','res.partner',true,1);
-- Compatibility purpose, to remove V6.0
insert into ir_model_data (name,module,model,noupdate,res_id) values ('user_admin','base','res.users',True,1);
insert into res_currency (id, name) VALUES (1, 'EUR');
insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('EUR','base','res.currency',true,1);
insert into res_company (id, name, partner_id, currency_id) VALUES (1, 'Your Company', 1, 1);
insert into ir_model_data (name,module,model,noupdate,res_id) VALUES ('main_company','base','res.company',true,1);
select setval('res_company_id_seq', 2);
select setval('res_users_id_seq', 2);
select setval('res_partner_id_seq', 2);
select setval('res_currency_id_seq', 2);
+10 -2
View File
@@ -32,6 +32,7 @@
<record id="main_partner" model="res.partner" context="{'default_is_company': True}">
<field name="name">Your Company</field>
<field name="company_id" eval="None"/>
<field name="image" eval="False"/>
<field name="customer" eval="False"/>
<field name="is_company" eval="True"/>
<field name="street"></field>
@@ -61,7 +62,7 @@
<record id="main_company" model="res.company">
<field name="name">Your Company</field>
<field name="partner_id" ref="main_partner"/>
<field name="rml_header1">Your Company Slogan</field>
<field name="rml_header1">Your Company Tagline</field>
<field name="currency_id" ref="base.EUR"/>
</record>
@@ -88,6 +89,13 @@ Administrator</field>
<record id="EUR" model="res.currency">
<field name="company_id" ref="main_company"/>
</record>
<record id="ir_mail_server_localhost0" model="ir.mail_server">
<field name="name">localhost</field>
<field name="smtp_host">localhost</field>
<field eval="25" name="smtp_port"/>
<field eval="10" name="sequence"/>
</record>
</data>
</openerp>
+69 -1
View File
@@ -7,6 +7,74 @@
<field name="customer" eval="False"/>
<field name="email">demo@example.com</field>
</record>
<record id="main_partner" model="res.partner">
<field name="image">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
BZrmED0AAAAASUVORK5CYII=
</field>
</record>
<record id="user_demo" model="res.users">
<field name="partner_id" ref="base.partner_demo"/>
<field name="login">demo</field>
@@ -14,7 +82,7 @@
<field name="signature">--
Mr Demo</field>
<field name="company_id" ref="main_company"/>
<field name="groups_id" eval="[(6,0,[ref('base.group_user')])]"/>
<field name="groups_id" eval="[(6,0,[ref('base.group_user'), ref('base.group_partner_manager')])]"/>
<field name="image">/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP
ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e
Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCACEAIQDASIA
+1 -8
View File
@@ -510,14 +510,6 @@
<field name="currency_id" ref="CRC"/>
<field eval="time.strftime('%Y-01-01')" name="name"/>
</record>
<record id="ir_mail_server_localhost0" model="ir.mail_server">
<field name="name">localhost</field>
<field name="smtp_host">localhost</field>
<field eval="25" name="smtp_port"/>
<field eval="10" name="sequence"/>
</record>
<record id="MUR" model="res.currency">
<field name="name">MUR</field>
<field name="symbol">Rs</field>
@@ -1977,6 +1969,7 @@
<field name="symbol">£</field>
<field name="rounding">0.01</field>
<field name="accuracy">4</field>
<field name="position">before</field>
<field name="company_id" ref="main_company"/>
</record>
<record id="rateGBP" model="res.currency.rate">
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+24 -26
View File
@@ -22,17 +22,16 @@
import logging
import os
import re
import time
import tools
import netsvc
from osv import fields,osv
from report.report_sxw import report_sxw, report_rml
from tools.config import config
from tools.safe_eval import safe_eval as eval
from tools.translate import _
from socket import gethostname
import time
from openerp import SUPERUSER_ID
from openerp import netsvc, tools
from openerp.osv import fields, osv
from openerp.report.report_sxw import report_sxw, report_rml
from openerp.tools.config import config
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
@@ -615,16 +614,6 @@ class actions_server(osv.osv):
if action.state == 'email':
email_from = config['email_from']
address = str(action.email)
try:
address = eval(str(action.email), cxt)
except:
pass
if not address:
_logger.info('No partner email address specified, not sending any email.')
continue
if not email_from:
_logger.debug('--email-from command line option is not specified, using a fallback value instead.')
if user.email:
@@ -632,16 +621,27 @@ class actions_server(osv.osv):
else:
email_from = "%s@%s" % (user.login, gethostname())
try:
address = eval(str(action.email), cxt)
except Exception:
address = str(action.email)
if not address:
_logger.info('No partner email address specified, not sending any email.')
continue
# handle single and multiple recipient addresses
addresses = address if isinstance(address, (tuple, list)) else [address]
subject = self.merge_message(cr, uid, action.subject, action, context)
body = self.merge_message(cr, uid, action.message, action, context)
ir_mail_server = self.pool.get('ir.mail_server')
msg = ir_mail_server.build_email(email_from, [address], subject, body)
msg = ir_mail_server.build_email(email_from, addresses, subject, body)
res_email = ir_mail_server.send_email(cr, uid, msg)
if res_email:
_logger.info('Email successfully sent to: %s', address)
_logger.info('Email successfully sent to: %s', addresses)
else:
_logger.warning('Failed to send email to: %s', address)
_logger.warning('Failed to send email to: %s', addresses)
if action.state == 'trigger':
wf_service = netsvc.LocalService("workflow")
@@ -671,7 +671,7 @@ class actions_server(osv.osv):
context['object'] = obj
for i in expr:
context['active_id'] = i.id
result = self.run(cr, uid, [action.loop_action.id], context)
self.run(cr, uid, [action.loop_action.id], context)
if action.state == 'object_write':
res = {}
@@ -716,8 +716,6 @@ class actions_server(osv.osv):
expr = exp.value
res[exp.col1.name] = expr
obj_pool = None
res_id = False
obj_pool = self.pool.get(action.srcmodel_id.model)
res_id = obj_pool.create(cr, uid, res)
if action.record_id:
@@ -736,7 +734,7 @@ class actions_server(osv.osv):
model = action.copy_object.split(',')[0]
cid = action.copy_object.split(',')[1]
obj_pool = self.pool.get(model)
res_id = obj_pool.copy(cr, uid, int(cid), res)
obj_pool.copy(cr, uid, int(cid), res)
return False
+164 -58
View File
@@ -19,13 +19,165 @@
#
##############################################################################
import hashlib
import itertools
import os
import re
from osv import fields,osv
from osv.orm import except_orm
import tools
from openerp import tools
from openerp.osv import fields,osv
class ir_attachment(osv.osv):
"""Attachments are used to link binary files or url to any openerp document.
External attachment storage
---------------------------
The 'data' function field (_data_get,data_set) is implemented using
_file_read, _file_write and _file_delete which can be overridden to
implement other storage engines, shuch methods should check for other
location pseudo uri (example: hdfs://hadoppserver)
The default implementation is the file:dirname location that stores files
on the local filesystem using name based on their sha1 hash
"""
def _name_get_resname(self, cr, uid, ids, object, method, context):
data = {}
for attachment in self.browse(cr, uid, ids, context=context):
model_object = attachment.res_model
res_id = attachment.res_id
if model_object and res_id:
model_pool = self.pool.get(model_object)
res = model_pool.name_get(cr,uid,[res_id],context)
res_name = res and res[0][1] or False
if res_name:
field = self._columns.get('res_name',False)
if field and len(res_name) > field.size:
res_name = res_name[:field.size-3] + '...'
data[attachment.id] = res_name
else:
data[attachment.id] = False
return data
# 'data' field implementation
def _full_path(self, cr, uid, location, path):
# location = 'file:filestore'
assert location.startswith('file:'), "Unhandled filestore location %s" % location
location = location[5:]
# sanitize location name and path
location = re.sub('[.]','',location)
location = location.strip('/\\')
path = re.sub('[.]','',path)
path = path.strip('/\\')
return os.path.join(tools.config['root_path'], location, cr.dbname, path)
def _file_read(self, cr, uid, location, fname, bin_size=False):
full_path = self._full_path(cr, uid, location, fname)
r = ''
try:
if bin_size:
r = os.path.getsize(full_path)
else:
r = open(full_path).read().encode('base64')
except IOError:
_logger.error("_read_file reading %s",full_path)
return r
def _file_write(self, cr, uid, location, value):
bin_value = value.decode('base64')
fname = hashlib.sha1(bin_value).hexdigest()
# scatter files across 1024 dirs
# we use '/' in the db (even on windows)
fname = fname[:3] + '/' + fname
full_path = self._full_path(cr, uid, location, fname)
try:
dirname = os.path.dirname(full_path)
if not os.path.isdir(dirname):
os.makedirs(dirname)
open(full_path,'wb').write(bin_value)
except IOError:
_logger.error("_file_write writing %s",full_path)
return fname
def _file_delete(self, cr, uid, location, fname):
count = self.search(cr, 1, [('store_fname','=',fname)], count=True)
if count <= 1:
full_path = self._full_path(cr, uid, location, fname)
try:
os.unlink(full_path)
except OSError:
_logger.error("_file_delete could not unlink %s",full_path)
except IOError:
# Harmless and needed for race conditions
_logger.error("_file_delete could not unlink %s",full_path)
def _data_get(self, cr, uid, ids, name, arg, context=None):
if context is None:
context = {}
result = {}
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
bin_size = context.get('bin_size')
for attach in self.browse(cr, uid, ids, context=context):
if location and attach.store_fname:
result[attach.id] = self._file_read(cr, uid, location, attach.store_fname, bin_size)
else:
result[attach.id] = attach.db_datas
return result
def _data_set(self, cr, uid, id, name, value, arg, context=None):
# We dont handle setting data to null
if not value:
return True
if context is None:
context = {}
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
file_size = len(value.decode('base64'))
if location:
attach = self.browse(cr, uid, id, context=context)
if attach.store_fname:
self._file_delete(cr, uid, location, attach.store_fname)
fname = self._file_write(cr, uid, location, value)
super(ir_attachment, self).write(cr, uid, [id], {'store_fname': fname, 'file_size': file_size}, context=context)
else:
super(ir_attachment, self).write(cr, uid, [id], {'db_datas': value, 'file_size': file_size}, context=context)
return True
_name = 'ir.attachment'
_columns = {
'name': fields.char('Attachment Name',size=256, required=True),
'datas_fname': fields.char('File Name',size=256),
'description': fields.text('Description'),
'res_name': fields.function(_name_get_resname, type='char', size=128, string='Resource Name', store=True),
'res_model': fields.char('Resource Model',size=64, readonly=True, help="The database object this attachment will be attached to"),
'res_id': fields.integer('Resource ID', readonly=True, help="The record id this is attached to"),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Owner', readonly=True),
'company_id': fields.many2one('res.company', 'Company', change_default=True),
'type': fields.selection( [ ('url','URL'), ('binary','Binary'), ],
'Type', help="Binary File or URL", required=True, change_default=True),
'url': fields.char('Url', size=1024),
# al: We keep shitty field names for backward compatibility with document
'datas': fields.function(_data_get, fnct_inv=_data_set, string='File Content', type="binary", nodrop=True),
'store_fname': fields.char('Stored Filename', size=256),
'db_datas': fields.binary('Database Data'),
'file_size': fields.integer('File Size'),
}
_defaults = {
'type': 'binary',
'file_size': 0,
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c),
}
def _auto_init(self, cr, context=None):
super(ir_attachment, self)._auto_init(cr, context)
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
if not cr.fetchone():
cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
cr.commit()
def check(self, cr, uid, ids, mode, context=None, values=None):
"""Restricts the access to an ir.attachment, according to referred model
In the 'document' module, it is overriden to relax this hard rule, since
@@ -111,6 +263,8 @@ class ir_attachment(osv.osv):
def write(self, cr, uid, ids, vals, context=None):
self.check(cr, uid, ids, 'write', context=context, values=vals)
if 'file_size' in vals:
del vals['file_size']
return super(ir_attachment, self).write(cr, uid, ids, vals, context)
def copy(self, cr, uid, id, default=None, context=None):
@@ -119,70 +273,22 @@ class ir_attachment(osv.osv):
def unlink(self, cr, uid, ids, context=None):
self.check(cr, uid, ids, 'unlink', context=context)
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
if location:
for attach in self.browse(cr, uid, ids, context=context):
if attach.store_fname:
self._file_delete(cr, uid, location, attach.store_fname)
return super(ir_attachment, self).unlink(cr, uid, ids, context)
def create(self, cr, uid, values, context=None):
self.check(cr, uid, [], mode='create', context=context, values=values)
if 'file_size' in values:
del values['file_size']
return super(ir_attachment, self).create(cr, uid, values, context)
def action_get(self, cr, uid, context=None):
return self.pool.get('ir.actions.act_window').for_xml_id(
cr, uid, 'base', 'action_attachment', context=context)
def _name_get_resname(self, cr, uid, ids, object, method, context):
data = {}
for attachment in self.browse(cr, uid, ids, context=context):
model_object = attachment.res_model
res_id = attachment.res_id
if model_object and res_id:
model_pool = self.pool.get(model_object)
res = model_pool.name_get(cr,uid,[res_id],context)
res_name = res and res[0][1] or False
if res_name:
field = self._columns.get('res_name',False)
if field and len(res_name) > field.size:
res_name = res_name[:field.size-3] + '...'
data[attachment.id] = res_name
else:
data[attachment.id] = False
return data
_name = 'ir.attachment'
_columns = {
'name': fields.char('Attachment Name',size=256, required=True),
'datas': fields.binary('Data'),
'datas_fname': fields.char('File Name',size=256),
'description': fields.text('Description'),
'res_name': fields.function(_name_get_resname, type='char', size=128,
string='Resource Name', store=True),
'res_model': fields.char('Resource Object',size=64, readonly=True,
help="The database object this attachment will be attached to"),
'res_id': fields.integer('Resource ID', readonly=True,
help="The record id this is attached to"),
'url': fields.char('Url', size=512, oldname="link"),
'type': fields.selection(
[ ('url','URL'), ('binary','Binary'), ],
'Type', help="Binary File or external URL", required=True, change_default=True),
'create_date': fields.datetime('Date Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Owner', readonly=True),
'company_id': fields.many2one('res.company', 'Company', change_default=True),
}
_defaults = {
'type': 'binary',
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c),
}
def _auto_init(self, cr, context=None):
super(ir_attachment, self)._auto_init(cr, context)
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',))
if not cr.fetchone():
cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)')
cr.commit()
ir_attachment()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -22,11 +22,12 @@
Store database-specific configuration parameters
"""
from osv import osv,fields
import uuid
import datetime
from tools import misc, config
from openerp import SUPERUSER_ID
from openerp.osv import osv, fields
from openerp.tools import misc, config
"""
A dictionary holding some configuration parameters to be initialized when the database is created.
+54 -53
View File
@@ -18,23 +18,18 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import calendar
import time
import logging
import threading
import psycopg2
from datetime import datetime
from dateutil.relativedelta import relativedelta
import netsvc
import openerp
import pooler
import tools
from osv import fields, osv
from tools import DEFAULT_SERVER_DATETIME_FORMAT
from tools.safe_eval import safe_eval as eval
from tools.translate import _
from openerp import netsvc
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
@@ -140,13 +135,18 @@ class ir_cron(osv.osv):
_logger.debug('%.3fs (%s, %s)' % (end_time - start_time, model_name, method_name))
except Exception, e:
self._handle_callback_exception(cr, uid, model_name, method_name, args, job_id, e)
else:
msg = "Method `%s.%s` do not exist." % (model._name, method_name) \
if model else "Model `%s` do not exist." % model._name
_logger.warning(msg)
def _process_job(self, cr, job):
def _process_job(self, job_cr, job, cron_cr):
""" Run a given job taking care of the repetition.
The cursor has a lock on the job (aquired by _acquire_job()).
:param job_cr: cursor to use to execute the job, safe to commit/rollback
:param job: job to be run (as a dictionary).
:param cron_cr: cursor holding lock on the cron job row, to use to update the next exec date,
must not be committed/rolled back!
"""
try:
now = datetime.now()
@@ -158,19 +158,19 @@ class ir_cron(osv.osv):
if numbercall > 0:
numbercall -= 1
if not ok or job['doall']:
self._callback(cr, job['user_id'], job['model'], job['function'], job['args'], job['id'])
self._callback(job_cr, job['user_id'], job['model'], job['function'], job['args'], job['id'])
if numbercall:
nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
ok = True
addsql = ''
if not numbercall:
addsql = ', active=False'
cr.execute("UPDATE ir_cron SET nextcall=%s, numbercall=%s"+addsql+" WHERE id=%s",
cron_cr.execute("UPDATE ir_cron SET nextcall=%s, numbercall=%s"+addsql+" WHERE id=%s",
(nextcall.strftime(DEFAULT_SERVER_DATETIME_FORMAT), numbercall, job['id']))
finally:
cr.commit()
cr.close()
job_cr.commit()
cron_cr.commit()
@classmethod
def _acquire_job(cls, db_name):
@@ -186,44 +186,14 @@ class ir_cron(osv.osv):
"""
db = openerp.sql_db.db_connect(db_name)
cr = db.cursor()
jobs = []
try:
# Careful to compare timestamps with 'UTC' - everything is UTC as of v6.1.
cr.execute("""SELECT * FROM ir_cron
WHERE numbercall != 0
AND active AND nextcall <= (now() at time zone 'UTC')
ORDER BY priority""")
for job in cr.dictfetchall():
task_cr = db.cursor()
try:
# Try to grab an exclusive lock on the job row from within the task transaction
acquired_lock = False
task_cr.execute("""SELECT *
FROM ir_cron
WHERE id=%s
FOR UPDATE NOWAIT""",
(job['id'],), log_exceptions=False)
acquired_lock = True
except psycopg2.OperationalError, e:
if e.pgcode == '55P03':
# Class 55: Object not in prerequisite state; 55P03: lock_not_available
_logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['name'])
continue
else:
# Unexpected OperationalError
raise
finally:
if not acquired_lock:
# we're exiting due to an exception while acquiring the lot
task_cr.close()
# Got the lock on the job row, run its code
_logger.debug('Starting job `%s`.', job['name'])
openerp.modules.registry.RegistryManager.check_registry_signaling(db_name)
registry = openerp.pooler.get_pool(db_name)
registry[cls._name]._process_job(task_cr, job)
openerp.modules.registry.RegistryManager.signal_caches_change(db_name)
return True
jobs = cr.dictfetchall()
except psycopg2.ProgrammingError, e:
if e.pgcode == '42P01':
# Class 42 — Syntax Error or Access Rule Violation; 42P01: undefined_table
@@ -231,14 +201,45 @@ class ir_cron(osv.osv):
_logger.warning('Tried to poll an undefined table on database %s.', db_name)
else:
raise
except Exception, ex:
except Exception:
_logger.warning('Exception in cron:', exc_info=True)
finally:
cr.commit()
cr.close()
return False
for job in jobs:
lock_cr = db.cursor()
try:
# Try to grab an exclusive lock on the job row from within the task transaction
lock_cr.execute("""SELECT *
FROM ir_cron
WHERE id=%s
FOR UPDATE NOWAIT""",
(job['id'],), log_exceptions=False)
# Got the lock on the job row, run its code
_logger.debug('Starting job `%s`.', job['name'])
job_cr = db.cursor()
try:
openerp.modules.registry.RegistryManager.check_registry_signaling(db_name)
registry = openerp.pooler.get_pool(db_name)
registry[cls._name]._process_job(job_cr, job, lock_cr)
openerp.modules.registry.RegistryManager.signal_caches_change(db_name)
except Exception:
_logger.exception('Unexpected exception while processing cron job %r', job)
finally:
job_cr.close()
except psycopg2.OperationalError, e:
if e.pgcode == '55P03':
# Class 55: Object not in prerequisite state; 55P03: lock_not_available
_logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['name'])
continue
else:
# Unexpected OperationalError
raise
finally:
# we're exiting due to an exception while acquiring the lock
lock_cr.close()
def _try_lock(self, cr, uid, ids, context=None):
"""Try to grab a dummy exclusive write-lock to the rows with the given ids,
+2 -1
View File
@@ -40,7 +40,7 @@
<record id="ir_cron_view_tree" model="ir.ui.view">
<field name="model">ir.cron</field>
<field name="arch" type="xml">
<tree string="Scheduled Actions">
<tree string="Scheduled Actions" colors="grey:(not active)">
<field name="priority" string="Sequence"/>
<field name="name"/>
<field name="nextcall"/>
@@ -48,6 +48,7 @@
<field name="interval_type"/>
<field name="numbercall"/>
<field name="user_id" invisible="1"/>
<field name="active"/>
</tree>
</field>
</record>
+1 -1
View File
@@ -19,7 +19,7 @@
#
##############################################################################
from osv import fields,osv
from openerp.osv import fields, osv
class ir_default(osv.osv):
_name = 'ir.default'
+1 -1
View File
@@ -19,7 +19,7 @@
#
##############################################################################
from osv import fields,osv
from openerp.osv import fields,osv
class ir_exports(osv.osv):
-1
View File
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import collections
import datetime
import functools
import operator
+2 -5
View File
@@ -20,13 +20,10 @@
##############################################################################
from openerp import exceptions
from osv import osv, fields
from tools.translate import _
from openerp.osv import osv, fields
from openerp.tools.translate import _
class ir_filters(osv.osv):
'''
Filters
'''
_name = 'ir.filters'
_description = 'Filters'
+15 -12
View File
@@ -31,8 +31,7 @@ import re
import smtplib
import threading
from osv import osv
from osv import fields
from openerp.osv import osv, fields
from openerp.tools.translate import _
from openerp.tools import html2text
import openerp.tools as tools
@@ -183,10 +182,12 @@ class ir_mail_server(osv.osv):
"(this is very verbose and may include confidential info!)"),
'sequence': fields.integer('Priority', help="When no specific mail server is requested for a mail, the highest priority one "
"is used. Default priority is 10 (smaller number = higher priority)"),
'active': fields.boolean('Active')
}
_defaults = {
'smtp_port': 25,
'active': True,
'sequence': 10,
'smtp_encryption': 'none',
}
@@ -228,7 +229,7 @@ class ir_mail_server(osv.osv):
:param int port: SMTP port to connect to
:param user: optional username to authenticate with
:param password: optional password to authenticate with
:param string encryption: optional: ``'ssl'`` | ``'starttls'``
:param string encryption: optional, ``'ssl'`` | ``'starttls'``
:param bool smtp_debug: toggle debugging of SMTP sessions (all i/o
will be output in logs)
"""
@@ -369,7 +370,7 @@ class ir_mail_server(osv.osv):
return msg
def send_email(self, cr, uid, message, mail_server_id=None, smtp_server=None, smtp_port=None,
smtp_user=None, smtp_password=None, smtp_encryption='none', smtp_debug=False,
smtp_user=None, smtp_password=None, smtp_encryption=None, smtp_debug=False,
context=None):
"""Sends an email directly (no queuing).
@@ -387,7 +388,7 @@ class ir_mail_server(osv.osv):
extracted from the combined list of ``To``, ``CC`` and ``BCC`` headers.
:param mail_server_id: optional id of ir.mail_server to use for sending. overrides other smtp_* arguments.
:param smtp_server: optional hostname of SMTP server to use
:param smtp_encryption: one of 'none', 'starttls' or 'ssl' (see ir.mail_server fields for explanation)
:param smtp_encryption: optional TLS mode, one of 'none', 'starttls' or 'ssl' (see ir.mail_server fields for explanation)
:param smtp_port: optional SMTP port, if mail_server_id is not passed
:param smtp_user: optional SMTP user, if mail_server_id is not passed
:param smtp_password: optional SMTP password to use, if mail_server_id is not passed
@@ -421,12 +422,6 @@ class ir_mail_server(osv.osv):
mail_server_ids = self.search(cr, uid, [], order='sequence', limit=1)
if mail_server_ids:
mail_server = self.browse(cr, uid, mail_server_ids[0])
else:
# we were passed an explicit smtp_server or nothing at all
smtp_server = smtp_server or tools.config.get('smtp_server')
smtp_port = tools.config.get('smtp_port', 25) if smtp_port is None else smtp_port
smtp_user = smtp_user or tools.config.get('smtp_user')
smtp_password = smtp_password or tools.config.get('smtp_password')
if mail_server:
smtp_server = mail_server.smtp_host
@@ -435,6 +430,14 @@ class ir_mail_server(osv.osv):
smtp_port = mail_server.smtp_port
smtp_encryption = mail_server.smtp_encryption
smtp_debug = smtp_debug or mail_server.smtp_debug
else:
# we were passed an explicit smtp_server or nothing at all
smtp_server = smtp_server or tools.config.get('smtp_server')
smtp_port = tools.config.get('smtp_port', 25) if smtp_port is None else smtp_port
smtp_user = smtp_user or tools.config.get('smtp_user')
smtp_password = smtp_password or tools.config.get('smtp_password')
if smtp_encryption is None and tools.config.get('smtp_ssl'):
smtp_encryption = 'starttls' # STARTTLS is the new meaning of the smtp_ssl flag as of v7.0
if not smtp_server:
raise osv.except_osv(
@@ -453,7 +456,7 @@ class ir_mail_server(osv.osv):
return message_id
try:
smtp = self.connect(smtp_server, smtp_port, smtp_user, smtp_password, smtp_encryption, smtp_debug)
smtp = self.connect(smtp_server, smtp_port, smtp_user, smtp_password, smtp_encryption or False, smtp_debug)
smtp.sendmail(smtp_from, smtp_to_list, message.as_string())
finally:
try:
+12 -13
View File
@@ -158,9 +158,10 @@ class ir_model(osv.osv):
if context is None: context = {}
if isinstance(ids, (int, long)):
ids = [ids]
if not context.get(MODULE_UNINSTALL_FLAG) and \
any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
if not context.get(MODULE_UNINSTALL_FLAG):
for model in self.browse(cr, user, ids, context):
if model.state != 'manual':
raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
self._drop_table(cr, user, ids, context)
res = super(ir_model, self).unlink(cr, user, ids, context)
@@ -198,7 +199,7 @@ class ir_model(osv.osv):
def instanciate(self, cr, user, model, context=None):
class x_custom_model(osv.osv):
pass
_custom = True
x_custom_model._name = model
x_custom_model._module = False
a = x_custom_model.create_instance(self.pool, cr)
@@ -256,7 +257,7 @@ class ir_model_fields(osv.osv):
'selection': "",
'domain': "[]",
'name': 'x_',
'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
'on_delete': 'set null',
'select_level': '0',
'size': 64,
@@ -271,7 +272,7 @@ class ir_model_fields(osv.osv):
except Exception:
_logger.warning('Invalid selection list definition for fields.selection', exc_info=True)
raise except_orm(_('Error'),
_("The Selection Options expression is not a valid Pythonic expression." \
_("The Selection Options expression is not a valid Pythonic expression."
"Please provide an expression in the [('key','Label'), ...] format."))
check = True
@@ -514,7 +515,7 @@ class ir_model_constraint(Model):
# double-check we are really going to delete all the owners of this schema element
cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,))
external_ids = [x[0] for x in cr.fetchall()]
if (set(external_ids)-ids_set):
if set(external_ids)-ids_set:
# as installed modules have defined this element we must not delete it!
continue
@@ -567,13 +568,12 @@ class ir_model_relation(Model):
ids.reverse()
for data in self.browse(cr, uid, ids, context):
model = data.model
model_obj = self.pool.get(model)
name = openerp.tools.ustr(data.name)
# double-check we are really going to delete all the owners of this schema element
cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,))
external_ids = [x[0] for x in cr.fetchall()]
if (set(external_ids)-ids_set):
if set(external_ids)-ids_set:
# as installed modules have defined this element we must not delete it!
continue
@@ -585,7 +585,7 @@ class ir_model_relation(Model):
# drop m2m relation tables
for table in to_drop_table:
cr.execute('DROP TABLE %s CASCADE'% (table),)
cr.execute('DROP TABLE %s CASCADE'% table,)
_logger.info('Dropped table %s', table)
cr.commit()
@@ -862,7 +862,7 @@ class ir_model_data(osv.osv):
res = self.read(cr, uid, data_id, ['model', 'res_id'])
if not res['res_id']:
raise ValueError('No such external ID currently defined in the system: %s.%s' % (module, xml_id))
return (res['model'], res['res_id'])
return res['model'], res['res_id']
def get_object(self, cr, uid, module, xml_id, context=None):
"""Returns a browsable record for the given module name and xml_id or raise ValueError if not found"""
@@ -903,7 +903,7 @@ class ir_model_data(osv.osv):
# records created during module install should not display the messages of OpenChatter
context = dict(context, install_mode=True)
if xml_id and ('.' in xml_id):
assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id)
assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % xml_id
module, xml_id = xml_id.split('.')
if (not xml_id) and (not self.doinit):
return False
@@ -1073,7 +1073,6 @@ class ir_model_data(osv.osv):
if model == 'ir.model.fields')
ir_model_relation = self.pool.get('ir.model.relation')
relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)])
ir_module_module = self.pool.get('ir.module.module')
modules_to_remove_ids = ir_module_module.search(cr, uid, [('name', 'in', modules_to_remove)])
relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove_ids)])

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