Initial commit
- add license - add run script - add config parser - add service object - add web_service - add workflow - add osv/orm/fields - add module management - add ir module
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
import trytond
|
||||
import time
|
||||
trytond.TrytonServer().run()
|
||||
while True:
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1 @@
|
||||
from trytond import *
|
||||
@@ -0,0 +1,250 @@
|
||||
import ConfigParser, optparse, os, sys
|
||||
import logging
|
||||
from version import VERSION
|
||||
from netsvc import LOG_CRITICAL, LOG_ERROR, LOG_WARNING
|
||||
from netsvc import LOG_INFO, LOG_DEBUG
|
||||
|
||||
|
||||
class ConfigManager(object):
|
||||
def __init__(self, fname=None):
|
||||
self.options = {
|
||||
'verbose': False,
|
||||
'interface': '',
|
||||
'port': '8069',
|
||||
'netinterface': '',
|
||||
'netport': '8070',
|
||||
'db_host': False,
|
||||
'db_port': False,
|
||||
'db_name': False,
|
||||
'db_user': False,
|
||||
'db_password': False,
|
||||
'db_maxconn': 64,
|
||||
'reportgz': False,
|
||||
'netrpc': True,
|
||||
'xmlrpc': True,
|
||||
'soap': False,
|
||||
'pg_path': None,
|
||||
'admin_passwd': 'admin',
|
||||
'debug_mode': False,
|
||||
'pidfile': None,
|
||||
'logfile': None,
|
||||
'secure': False,
|
||||
'smtp_server': 'localhost',
|
||||
'smtp_user': False,
|
||||
'smtp_password': False,
|
||||
'stop_after_init': False,
|
||||
'price_accuracy': 2,
|
||||
'assert_exit_level': logging.WARNING,
|
||||
}
|
||||
|
||||
assert_exit_levels = (
|
||||
LOG_CRITICAL,
|
||||
LOG_ERROR,
|
||||
LOG_WARNING,
|
||||
LOG_INFO,
|
||||
LOG_DEBUG,
|
||||
)
|
||||
|
||||
parser = optparse.OptionParser(version=VERSION)
|
||||
|
||||
parser.add_option("-c", "--config", dest="config",
|
||||
help="specify alternate config file")
|
||||
parser.add_option("-s", "--save", action="store_true", dest="save",
|
||||
default=False, help="save configuration to ~/.terp_serverrc")
|
||||
parser.add_option("-v", "--verbose", action="store_true",
|
||||
dest="verbose", default=False, help="enable debugging")
|
||||
parser.add_option("--pidfile", dest="pidfile",
|
||||
help="file where the server pid will be stored")
|
||||
parser.add_option("--logfile", dest="logfile",
|
||||
help="file where the server log will be stored")
|
||||
parser.add_option("-n", "--interface", dest="interface",
|
||||
help="specify the TCP IP address")
|
||||
parser.add_option("-p", "--port", dest="port",
|
||||
help="specify the TCP port")
|
||||
parser.add_option("--net_interface", dest="netinterface",
|
||||
help="specify the TCP IP address for netrpc")
|
||||
parser.add_option("--net_port", dest="netport",
|
||||
help="specify the TCP port for netrpc")
|
||||
parser.add_option("--no-netrpc", dest="netrpc", action="store_false",
|
||||
default=True, help="disable netrpc")
|
||||
parser.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false",
|
||||
default=True, help="disable xmlrpc")
|
||||
parser.add_option("-i", "--init", dest="init",
|
||||
help="init a module (use \"all\" for all modules)")
|
||||
parser.add_option("--without-demo", dest="without_demo",
|
||||
help="load demo data for a module " \
|
||||
"(use \"all\" for all modules)", default=False)
|
||||
parser.add_option("-u", "--update", dest="update",
|
||||
help="update a module (use \"all\" for all modules)")
|
||||
parser.add_option("--stop-after-init", action="store_true",
|
||||
dest="stop_after_init", default=False,
|
||||
help="stop the server after it initializes")
|
||||
parser.add_option('--debug', dest='debug_mode', action='store_true',
|
||||
default=False, help='enable debug mode')
|
||||
parser.add_option("--assert-exit-level", dest='assert_exit_level',
|
||||
help="specify the level at which a failed assertion will " \
|
||||
"stop the server " + str(assert_exit_levels))
|
||||
parser.add_option("-S", "--secure", dest="secure", action="store_true",
|
||||
help="launch server over https instead of http", default=False)
|
||||
parser.add_option('--smtp', dest='smtp_server', default='',
|
||||
help='specify the SMTP server for sending email')
|
||||
parser.add_option('--smtp-user', dest='smtp_user', default='',
|
||||
help='specify the SMTP username for sending email')
|
||||
parser.add_option('--smtp-password', dest='smtp_password', default='',
|
||||
help='specify the SMTP password for sending email')
|
||||
parser.add_option('--price_accuracy', dest='price_accuracy',
|
||||
default='2', help='specify the price accuracy')
|
||||
|
||||
group = optparse.OptionGroup(parser, "Modules related options")
|
||||
group = optparse.OptionGroup(parser, "Database related options")
|
||||
group.add_option("-d", "--database", dest="db_name",
|
||||
help="specify the database name")
|
||||
group.add_option("-r", "--db_user", dest="db_user",
|
||||
help="specify the database user name")
|
||||
group.add_option("-w", "--db_password", dest="db_password",
|
||||
help="specify the database password")
|
||||
group.add_option("--pg_path", dest="pg_path",
|
||||
help="specify the pg executable path")
|
||||
group.add_option("--db_host", dest="db_host",
|
||||
help="specify the database host")
|
||||
group.add_option("--db_port", dest="db_port",
|
||||
help="specify the database port")
|
||||
group.add_option("--db_maxconn", dest="db_maxconn", default='64',
|
||||
help="specify the the maximum number of physical " \
|
||||
"connections to posgresql")
|
||||
parser.add_option_group(group)
|
||||
|
||||
(opt, args) = parser.parse_args()
|
||||
|
||||
# place/search the config file on Win32 near the server installation
|
||||
# (../etc from the server)
|
||||
# if the server is run by an unprivileged user,
|
||||
# he has to specify location of a config file
|
||||
# where he has the rights to write,
|
||||
# else he won't be able to save the configurations,
|
||||
# or even to start the server...
|
||||
if os.name == 'nt':
|
||||
rcfilepath = os.path.join(os.path.abspath(
|
||||
os.path.dirname(sys.argv[0])), 'tinyerp-server.conf')
|
||||
else:
|
||||
rcfilepath = os.path.expanduser('~/.terp_serverrc')
|
||||
|
||||
self.rcfile = fname or opt.config \
|
||||
or os.environ.get('TERP_SERVER') or rcfilepath
|
||||
self.load()
|
||||
|
||||
# Verify that we want to log or not, if not the output will go to stdout
|
||||
if self.options['logfile'] in ('None', 'False'):
|
||||
self.options['logfile'] = False
|
||||
# the same for the pidfile
|
||||
if self.options['pidfile'] in ('None', 'False'):
|
||||
self.options['pidfile'] = False
|
||||
|
||||
for arg in (
|
||||
'interface',
|
||||
'port',
|
||||
'db_name',
|
||||
'db_user',
|
||||
'db_password',
|
||||
'db_host',
|
||||
'db_port',
|
||||
'logfile',
|
||||
'pidfile',
|
||||
'secure',
|
||||
'smtp_server',
|
||||
'smtp_user',
|
||||
'smtp_password',
|
||||
'price_accuracy',
|
||||
'netinterface',
|
||||
'netport',
|
||||
'db_maxconn',
|
||||
):
|
||||
if getattr(opt, arg):
|
||||
self.options[arg] = getattr(opt, arg)
|
||||
|
||||
for arg in (
|
||||
'verbose',
|
||||
'debug_mode',
|
||||
'stop_after_init',
|
||||
'without_demo',
|
||||
'netrpc',
|
||||
'xmlrpc',
|
||||
):
|
||||
self.options[arg] = getattr(opt, arg)
|
||||
|
||||
if opt.assert_exit_level:
|
||||
assert opt.assert_exit_level in assert_exit_levels, \
|
||||
'ERROR: The assert-exit-level must be one ' \
|
||||
'of those values: '+str(assert_exit_levels)
|
||||
self.options['assert_exit_level'] = getattr(logging,
|
||||
opt.assert_exit_level.upper())
|
||||
|
||||
init = {}
|
||||
if opt.init:
|
||||
for i in opt.init.split(','):
|
||||
init[i] = 1
|
||||
self.options['init'] = init
|
||||
self.options["demo"] = not opt.without_demo \
|
||||
and self.options['init'] or {}
|
||||
|
||||
update = {}
|
||||
if opt.update:
|
||||
for i in opt.update.split(','):
|
||||
update[i] = 1
|
||||
self.options['update'] = update
|
||||
|
||||
if opt.pg_path:
|
||||
self.options['pg_path'] = opt.pg_path
|
||||
|
||||
if opt.save:
|
||||
self.save()
|
||||
|
||||
def load(self):
|
||||
parser = ConfigParser.ConfigParser()
|
||||
try:
|
||||
parser.read([self.rcfile])
|
||||
for (name, value) in parser.items('options'):
|
||||
if value == 'True' or value == 'true':
|
||||
value = True
|
||||
if value == 'False' or value == 'false':
|
||||
value = False
|
||||
self.options[name] = value
|
||||
except IOError:
|
||||
pass
|
||||
except ConfigParser.NoSectionError:
|
||||
pass
|
||||
|
||||
def save(self):
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.add_section('options')
|
||||
for opt in [opt for opt in self.options.keys() \
|
||||
if opt not in (
|
||||
'version',
|
||||
'init',
|
||||
'update',
|
||||
)]:
|
||||
parser.set('options', opt, self.options[opt])
|
||||
|
||||
# try to create the directories and write the file
|
||||
try:
|
||||
if not os.path.exists(os.path.dirname(self.rcfile)):
|
||||
os.makedirs(os.path.dirname(self.rcfile))
|
||||
try:
|
||||
parser.write(file(self.rcfile, 'w'))
|
||||
except IOError:
|
||||
sys.stderr.write("ERROR: couldn't write the config file\n")
|
||||
|
||||
except OSError:
|
||||
# what to do if impossible?
|
||||
sys.stderr.write("ERROR: couldn't create the config directory\n")
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.options.get(key, default)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.options[key] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.options[key]
|
||||
|
||||
CONFIG = ConfigManager()
|
||||
@@ -0,0 +1,323 @@
|
||||
-------------------------------------------------------------------------
|
||||
-- Pure SQL
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE inherit (
|
||||
obj_type varchar(128) not null,
|
||||
obj_id int not null,
|
||||
inst_type varchar(128) not null,
|
||||
inst_id int not null
|
||||
);
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- IR dictionary
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
create table ir_values
|
||||
(
|
||||
id serial,
|
||||
name varchar(128) not null,
|
||||
key varchar(128) not null,
|
||||
key2 varchar(128) not null,
|
||||
model varchar(128) not null,
|
||||
value text,
|
||||
meta text default NULL,
|
||||
res_id integer default null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- Modules Description
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE ir_model (
|
||||
id serial,
|
||||
model varchar(64) DEFAULT ''::varchar NOT NULL,
|
||||
name varchar(64),
|
||||
info text,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
CREATE TABLE ir_model_fields (
|
||||
id serial,
|
||||
model varchar(64) DEFAULT ''::varchar NOT NULL,
|
||||
model_id int references ir_model,
|
||||
name varchar(64) DEFAULT ''::varchar NOT NULL,
|
||||
relation varchar(64),
|
||||
field_description varchar(256),
|
||||
ttype varchar(64),
|
||||
group_name varchar(64),
|
||||
view_load boolean,
|
||||
relate boolean default False,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- Actions
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE ir_actions (
|
||||
id serial NOT NULL,
|
||||
name varchar(64) DEFAULT ''::varchar NOT NULL,
|
||||
"type" varchar(64) DEFAULT 'window'::varchar NOT NULL,
|
||||
usage varchar(32) DEFAULT null,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
CREATE TABLE ir_act_window (
|
||||
view_id integer,
|
||||
res_model varchar(64),
|
||||
view_type varchar(16),
|
||||
"domain" varchar(127),
|
||||
primary key(id)
|
||||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
CREATE TABLE ir_act_report_xml (
|
||||
model varchar(64) NOT NULL,
|
||||
report_name varchar(64) NOT NULL,
|
||||
report_xsl varchar(64),
|
||||
report_xml varchar(64),
|
||||
auto boolean default true,
|
||||
primary key(id)
|
||||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
create table ir_act_report_custom (
|
||||
report_id int,
|
||||
-- report_id int references ir_report_custom
|
||||
primary key(id)
|
||||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
CREATE TABLE ir_act_group (
|
||||
exec_type varchar(64) DEFAULT 'serial'::varchar NOT NULL,
|
||||
primary key(id)
|
||||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
CREATE TABLE ir_act_group_link (
|
||||
aid integer NOT NULL,
|
||||
gid integer NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE ir_act_execute (
|
||||
func_name varchar(64) NOT NULL,
|
||||
func_arg varchar(64),
|
||||
primary key(id)
|
||||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
CREATE TABLE ir_act_wizard (
|
||||
wiz_name varchar(64) NOT NULL,
|
||||
primary key(id)
|
||||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
CREATE TABLE ir_act_url (
|
||||
url text NOT NULL,
|
||||
target varchar(64) NOT NULL,
|
||||
primary key(id)
|
||||
)
|
||||
INHERITS (ir_actions);
|
||||
|
||||
CREATE TABLE ir_ui_view (
|
||||
id serial NOT NULL,
|
||||
name varchar(64) DEFAULT ''::varchar NOT NULL,
|
||||
model varchar(64) DEFAULT ''::varchar NOT NULL,
|
||||
"type" varchar(64) DEFAULT 'form'::varchar NOT NULL,
|
||||
arch text NOT NULL,
|
||||
field_parent varchar(64),
|
||||
priority integer DEFAULT 5 NOT NULL,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
CREATE TABLE ir_ui_menu (
|
||||
id serial NOT NULL,
|
||||
parent_id int references ir_ui_menu on delete set null,
|
||||
name varchar(64) DEFAULT ''::varchar NOT NULL,
|
||||
icon varchar(64) DEFAULT ''::varchar,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
select setval('ir_ui_menu_id_seq', 2);
|
||||
|
||||
---------------------------------
|
||||
-- Res users
|
||||
---------------------------------
|
||||
|
||||
-- level:
|
||||
-- 0 RESTRICT TO USER
|
||||
-- 1 RESTRICT TO GROUP
|
||||
-- 2 PUBLIC
|
||||
|
||||
CREATE TABLE res_users (
|
||||
id serial NOT NULL,
|
||||
name varchar(64) not null,
|
||||
active boolean default True,
|
||||
login varchar(64) NOT NULL UNIQUE,
|
||||
password varchar(32) default null,
|
||||
-- action_id int references ir_act_window on delete set null,
|
||||
action_id int,
|
||||
primary key(id)
|
||||
);
|
||||
alter table res_users add constraint res_users_login_uniq unique (login);
|
||||
|
||||
insert into res_users (id,login,password,name,action_id,active) values (1,'root',NULL,'Root',NULL,False);
|
||||
select setval('res_users_id_seq', 2);
|
||||
|
||||
CREATE TABLE res_groups (
|
||||
id serial NOT NULL,
|
||||
name varchar(32) NOT NULL,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
create table res_roles (
|
||||
id serial NOT NULL,
|
||||
parent_id int references res_roles on delete set null,
|
||||
name varchar(32) NOT NULL,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
CREATE TABLE res_roles_users_rel (
|
||||
uid integer NOT NULL references res_users on delete cascade,
|
||||
rid integer NOT NULL references res_roles on delete cascade
|
||||
);
|
||||
|
||||
CREATE TABLE res_groups_users_rel (
|
||||
uid integer NOT NULL references res_users on delete cascade,
|
||||
gid integer NOT NULL references res_groups on delete cascade
|
||||
);
|
||||
|
||||
---------------------------------
|
||||
-- Workflows
|
||||
---------------------------------
|
||||
|
||||
create table wkf
|
||||
(
|
||||
id serial,
|
||||
name varchar(64),
|
||||
osv varchar(64),
|
||||
on_create bool default False,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
create table wkf_activity
|
||||
(
|
||||
id serial,
|
||||
wkf_id int references wkf on delete cascade,
|
||||
subflow_id int references wkf on delete set null,
|
||||
split_mode varchar(3) default 'XOR',
|
||||
join_mode varchar(3) default 'XOR',
|
||||
kind varchar(16) not null default 'dummy',
|
||||
name varchar(64),
|
||||
signal_send varchar(32) default null,
|
||||
flow_start boolean default False,
|
||||
flow_stop boolean default False,
|
||||
action varchar(64) default null,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
create table wkf_transition
|
||||
(
|
||||
id serial,
|
||||
act_from int references wkf_activity on delete cascade,
|
||||
act_to int references wkf_activity on delete cascade,
|
||||
condition varchar(128) default NULL,
|
||||
|
||||
trigger_type varchar(128) default NULL,
|
||||
trigger_expr_id varchar(128) default NULL,
|
||||
|
||||
signal varchar(64) default null,
|
||||
role_id int references res_roles on delete set null,
|
||||
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
create table wkf_instance
|
||||
(
|
||||
id serial,
|
||||
wkf_id int references wkf on delete set null,
|
||||
uid int default null,
|
||||
res_id int not null,
|
||||
res_type varchar(64) not null,
|
||||
state varchar(32) not null default 'active',
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
create table wkf_workitem
|
||||
(
|
||||
id serial,
|
||||
act_id int not null references wkf_activity on delete cascade,
|
||||
inst_id int not null references wkf_instance on delete cascade,
|
||||
subflow_id int references wkf_instance on delete cascade,
|
||||
state varchar(64) default 'blocked',
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
create table wkf_witm_trans
|
||||
(
|
||||
trans_id int not null references wkf_transition on delete cascade,
|
||||
inst_id int not null references wkf_instance on delete cascade
|
||||
);
|
||||
|
||||
create table wkf_logs
|
||||
(
|
||||
id serial,
|
||||
res_type varchar(128) not null,
|
||||
res_id int not null,
|
||||
uid int references res_users on delete set null,
|
||||
act_id int references wkf_activity on delete set null,
|
||||
time time not null,
|
||||
info varchar(128) default NULL,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
---------------------------------
|
||||
-- Modules
|
||||
---------------------------------
|
||||
|
||||
CREATE TABLE ir_module_category (
|
||||
id serial NOT NULL,
|
||||
create_uid integer references res_users on delete set null,
|
||||
create_date timestamp without time zone,
|
||||
write_date timestamp without time zone,
|
||||
write_uid integer references res_users on delete set null,
|
||||
parent_id integer REFERENCES ir_module_category ON DELETE SET NULL,
|
||||
name character varying(128) NOT NULL,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE ir_module_module (
|
||||
id serial NOT NULL,
|
||||
create_uid integer references res_users on delete set null,
|
||||
create_date timestamp without time zone,
|
||||
write_date timestamp without time zone,
|
||||
write_uid integer references res_users on delete set null,
|
||||
website character varying(256),
|
||||
name character varying(128) NOT NULL,
|
||||
author character varying(128),
|
||||
url character varying(128),
|
||||
state character varying(16),
|
||||
latest_version character varying(64),
|
||||
shortdesc character varying(256),
|
||||
category_id integer REFERENCES ir_module_category ON DELETE SET NULL,
|
||||
description text,
|
||||
demo boolean default False,
|
||||
primary key(id)
|
||||
);
|
||||
ALTER TABLE ir_module_module add constraint name_uniq unique (name);
|
||||
|
||||
CREATE TABLE ir_module_module_dependency (
|
||||
id serial NOT NULL,
|
||||
create_uid integer references res_users on delete set null,
|
||||
create_date timestamp without time zone,
|
||||
write_date timestamp without time zone,
|
||||
write_uid integer references res_users on delete set null,
|
||||
name character varying(128),
|
||||
version_pattern character varying(128) default NULL,
|
||||
module_id integer REFERENCES ir_module_module ON DELETE cascade,
|
||||
primary key(id)
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
from sequence import *
|
||||
import ui
|
||||
from actions import *
|
||||
from model import *
|
||||
from attachment import *
|
||||
from cron import *
|
||||
from values import *
|
||||
from translation import *
|
||||
from exports import *
|
||||
from rule import *
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
'name': 'IR',
|
||||
'active': True,
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
"Actions"
|
||||
from trytond.osv import fields, OSV
|
||||
from trytond.tools import file_open
|
||||
|
||||
|
||||
class Actions(OSV):
|
||||
"Actions"
|
||||
_name = 'ir.actions.actions'
|
||||
_table = 'ir_actions'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Action Name', required=True, size=64),
|
||||
'type': fields.char('Action Type', required=True, size=32),
|
||||
'usage': fields.char('Action Usage', size=32)
|
||||
}
|
||||
_defaults = {
|
||||
'usage': lambda *a: False,
|
||||
}
|
||||
|
||||
Actions()
|
||||
|
||||
|
||||
class ActionsExecute(OSV):
|
||||
"Actions execute"
|
||||
_name = 'ir.actions.execute'
|
||||
_table = 'ir_act_execute'
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('name', size=64, required=True, translate=True),
|
||||
'type': fields.char('type', size=32, required=True),
|
||||
'func_name': fields.char('Function Name', size=64, required=True),
|
||||
'func_arg': fields.char('Function Argument', size=64),
|
||||
'usage': fields.char('Action Usage', size=32)
|
||||
}
|
||||
|
||||
ActionsExecute()
|
||||
|
||||
|
||||
class ActionsGroup(OSV):
|
||||
"Actions group"
|
||||
_name = 'ir.actions.group'
|
||||
_table = 'ir_act_group'
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Group Name', size=64, required=True),
|
||||
'type': fields.char('Action Type', size=32, required=True),
|
||||
'exec_type': fields.char('Execution sequence', size=64, required=True),
|
||||
'usage': fields.char('Action Usage', size=32)
|
||||
}
|
||||
|
||||
ActionsGroup()
|
||||
|
||||
|
||||
class ActionsReportCustom(OSV):
|
||||
"Actions report custom"
|
||||
_name = 'ir.actions.report.custom'
|
||||
_table = 'ir_act_report_custom'
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Report Name', size=64, required=True,
|
||||
translate=True),
|
||||
'type': fields.char('Report Type', size=32, required=True),
|
||||
'model':fields.char('Model', size=64, required=True),
|
||||
'report_id': fields.integer('Report Ref.', required=True),
|
||||
'usage': fields.char('Action Usage', size=32),
|
||||
'multi': fields.boolean('On multiple doc.',
|
||||
help="If set to true, the action will not be displayed " \
|
||||
"on the right toolbar of a form views.")
|
||||
}
|
||||
_defaults = {
|
||||
'multi': lambda *a: False,
|
||||
}
|
||||
|
||||
ActionsReportCustom()
|
||||
|
||||
|
||||
class ActionsReportXML(OSV):
|
||||
"Actions report xml"
|
||||
|
||||
def _report_content(self, cursor, user, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for report in self.browse(cursor, user, ids, context=context):
|
||||
data = report[name + '_data']
|
||||
if not data and report[name[:-8]]:
|
||||
try:
|
||||
data = file_open(report[name[:-8]], mode='rb').read()
|
||||
except:
|
||||
data = False
|
||||
res[report.id] = data
|
||||
return res
|
||||
|
||||
def _report_content_inv(self, cursor, user, obj_id, name, value, arg,
|
||||
context=None):
|
||||
self.write(cursor, user, obj_id, {name+'_data': value}, context=context)
|
||||
|
||||
def _report_sxw(self, cursor, user, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for report in self.browse(cursor, user, ids, context=context):
|
||||
if report.report_rml:
|
||||
res[report.id] = report.report_rml.replace('.rml', '.sxw')
|
||||
else:
|
||||
res[report.id] = False
|
||||
return res
|
||||
|
||||
_name = 'ir.actions.report.xml'
|
||||
_table = 'ir_act_report_xml'
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True, translate=True),
|
||||
'type': fields.char('Report Type', size=32, required=True),
|
||||
'model': fields.char('Model', size=64, required=True),
|
||||
'report_name': fields.char('Internal Name', size=64, required=True),
|
||||
'report_xsl': fields.char('XSL path', size=256),
|
||||
'report_xml': fields.char('XML path', size=256),
|
||||
'report_rml': fields.char('RML path', size=256,
|
||||
help="The .rml path of the file or NULL \n" \
|
||||
"if the content is in report_rml_content"),
|
||||
'report_sxw': fields.function(_report_sxw, method=True, type='char',
|
||||
string='SXW path'),
|
||||
'report_sxw_content_data': fields.binary('SXW content'),
|
||||
'report_rml_content_data': fields.binary('RML content'),
|
||||
'report_sxw_content': fields.function(_report_content,
|
||||
fnct_inv=_report_content_inv, method=True,
|
||||
type='binary', string='SXW content',),
|
||||
'report_rml_content': fields.function(_report_content,
|
||||
fnct_inv=_report_content_inv, method=True,
|
||||
type='binary', string='RML content'),
|
||||
'auto': fields.boolean('Automatic XSL:RML', required=True),
|
||||
'usage': fields.char('Action Usage', size=32),
|
||||
'header': fields.boolean('Add RML header',
|
||||
help="Add or not the coporate RML header"),
|
||||
'multi': fields.boolean('On multiple doc.',
|
||||
help="If set to true, the action will not be displayed " \
|
||||
"on the right toolbar of a form views.")
|
||||
}
|
||||
_defaults = {
|
||||
'type': lambda *a: 'ir.actions.report.xml',
|
||||
'multi': lambda *a: False,
|
||||
'auto': lambda *a: True,
|
||||
'header': lambda *a: True,
|
||||
'report_sxw_content': lambda *a: False,
|
||||
}
|
||||
|
||||
ActionsReportXML()
|
||||
|
||||
|
||||
class ActionsActWindow(OSV):
|
||||
"Actions act window"
|
||||
_name = 'ir.actions.act_window'
|
||||
_table = 'ir_act_window'
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
_description = __doc__
|
||||
|
||||
def _views_get_fnc(self, cursor, user, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for act in self.browse(cursor, user, ids, context=context):
|
||||
res[act.id] = [(view.view_id.id, view.view_mode) \
|
||||
for view in act.view_ids]
|
||||
if (not act.view_ids):
|
||||
modes = act.view_mode.split(',')
|
||||
find = False
|
||||
if act.view_id.id:
|
||||
res[act.id].append((act.view_id.id, act.view_id.type))
|
||||
for mode in modes:
|
||||
if act.view_id and (mode == act.view_id.type) and not find:
|
||||
find = True
|
||||
continue
|
||||
res[act.id].append((False, mode))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Action Name', size=64, translate=True),
|
||||
'type': fields.char('Action Type', size=32, required=True),
|
||||
'view_id': fields.many2one('ir.ui.view', 'View Ref.',
|
||||
ondelete='cascade'),
|
||||
'domain': fields.char('Domain Value', size=250),
|
||||
'context': fields.char('Context Value', size=250),
|
||||
'res_model': fields.char('Model', size=64),
|
||||
'src_model': fields.char('Source model', size=64),
|
||||
'view_type': fields.selection([('tree','Tree'), ('form','Form')],
|
||||
string='Type of view'),
|
||||
'view_mode': fields.char('Mode of view', size=250),
|
||||
'usage': fields.char('Action Usage', size=32),
|
||||
'view_ids': fields.one2many('ir.actions.act_window.view',
|
||||
'act_window_id', 'Views'),
|
||||
'views': fields.function(_views_get_fnc, method=True, type='binary',
|
||||
string='Views'),
|
||||
'limit': fields.integer('Limit',
|
||||
help='Default limit for the list view'),
|
||||
'auto_refresh': fields.integer('Auto-Refresh',
|
||||
help='Add an auto-refresh on the view'),
|
||||
}
|
||||
_defaults = {
|
||||
'type': lambda *a: 'ir.actions.act_window',
|
||||
'view_type': lambda *a: 'form',
|
||||
'view_mode': lambda *a: 'tree,form',
|
||||
'context': lambda *a: '{}',
|
||||
'limit': lambda *a: 80,
|
||||
'auto_refresh': lambda *a: 0,
|
||||
}
|
||||
|
||||
ActionsActWindow()
|
||||
|
||||
|
||||
class ActionsActWindowView(OSV):
|
||||
"Actions act window view"
|
||||
_name = 'ir.actions.act_window.view'
|
||||
_table = 'ir_act_window_view'
|
||||
_rec_name = 'view_id'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'view_id': fields.many2one('ir.ui.view', 'View'),
|
||||
'view_mode': fields.selection((
|
||||
('tree', 'Tree'),
|
||||
('form', 'Form'),
|
||||
('graph', 'Graph'),
|
||||
('calendar', 'Calendar')), string='Type of view', required=True),
|
||||
'act_window_id': fields.many2one('ir.actions.act_window', 'Action'),
|
||||
'multi': fields.boolean('On multiple doc.',
|
||||
help="If set to true, the action will not be displayed \n" \
|
||||
"on the right toolbar of a form views."),
|
||||
}
|
||||
_defaults = {
|
||||
'multi': lambda *a: False,
|
||||
}
|
||||
_order = 'sequence'
|
||||
|
||||
ActionsActWindowView()
|
||||
|
||||
|
||||
class ActionsWizard(OSV):
|
||||
"Actions wizard"
|
||||
_name = 'ir.actions.wizard'
|
||||
_table = 'ir_act_wizard'
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Wizard info', size=64, required=True,
|
||||
translate=True),
|
||||
'type': fields.char('Action type', size=32, required=True),
|
||||
'wiz_name': fields.char('Wizard name', size=64, required=True),
|
||||
'multi': fields.boolean('Action on multiple doc.',
|
||||
help="If set to true, the wizard will not be displayed \n" \
|
||||
"on the right toolbar of a form views.")
|
||||
}
|
||||
_defaults = {
|
||||
'type': lambda *a: 'ir.actions.wizard',
|
||||
'multi': lambda *a: False,
|
||||
}
|
||||
|
||||
ActionsWizard()
|
||||
|
||||
|
||||
class ActionsURL(OSV):
|
||||
"Actions URL"
|
||||
_name = 'ir.actions.url'
|
||||
_table = 'ir_act_url'
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Action Name', size=64, translate=True),
|
||||
'type': fields.char('Action Type', size=32, required=True),
|
||||
'url': fields.text('Action Url',required=True),
|
||||
'target': fields.selection([
|
||||
('new', 'New Window'),
|
||||
('self', 'This Window'),
|
||||
], 'Action Target', required=True)
|
||||
}
|
||||
_defaults = {
|
||||
'type': lambda *a: 'ir.actions.act_url',
|
||||
'target': lambda *a: 'new',
|
||||
}
|
||||
|
||||
ActionsURL()
|
||||
@@ -0,0 +1,19 @@
|
||||
"Attachment"
|
||||
from trytond.osv import fields, OSV
|
||||
|
||||
class Attachment(OSV):
|
||||
"Attachment"
|
||||
_name = 'ir.attachment'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Attachment Name',size=64, required=True),
|
||||
'datas': fields.binary('Data'),
|
||||
'datas_fname': fields.char('Data Filename',size=64),
|
||||
'description': fields.text('Description'),
|
||||
# Not required due to the document module !
|
||||
'res_model': fields.char('Resource Model',size=64, readonly=True),
|
||||
'res_id': fields.integer('Resource ID', readonly=True),
|
||||
'link': fields.char('Link', size=256)
|
||||
}
|
||||
|
||||
Attachment()
|
||||
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
SPEC: Execute "model.function(*eval(args))" periodically
|
||||
date : date to execute the job or NULL if directly
|
||||
delete_after: delete the ir.cron entry after execution
|
||||
interval_* : period
|
||||
max_repeat : number of execution or NULL if endlessly
|
||||
"""
|
||||
|
||||
from mx import DateTime
|
||||
import time
|
||||
from trytond import pooler
|
||||
from trytond.osv import fields, OSV
|
||||
from trytond.netsvc import Agent
|
||||
|
||||
NEXT_WAIT = 60
|
||||
|
||||
_INTERVALTYPES = {
|
||||
'work_days': lambda interval: DateTime.RelativeDateTime(days=interval),
|
||||
'days': lambda interval: DateTime.RelativeDateTime(days=interval),
|
||||
'hours': lambda interval: DateTime.RelativeDateTime(hours=interval),
|
||||
'weeks': lambda interval: DateTime.RelativeDateTime(days=7*interval),
|
||||
'months': lambda interval: DateTime.RelativeDateTime(months=interval),
|
||||
'minutes': lambda interval: DateTime.RelativeDateTime(minutes=interval),
|
||||
}
|
||||
|
||||
class Cron(OSV, Agent):
|
||||
"Cron"
|
||||
_name = "ir.cron"
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=60, required=True),
|
||||
'user_id': fields.many2one('res.users', 'User', required=True),
|
||||
'active': fields.boolean('Active'),
|
||||
'interval_number': fields.integer('Interval Number'),
|
||||
'interval_type': fields.selection( [
|
||||
('minutes', 'Minutes'),
|
||||
('hours', 'Hours'),
|
||||
('days', 'Days'),
|
||||
('weeks', 'Weeks'),
|
||||
('months', 'Months'),
|
||||
], 'Interval Unit'),
|
||||
'numbercall': fields.integer('Number of calls',
|
||||
help='Number of time the function is called,\n' \
|
||||
'a negative number indicates that the function ' \
|
||||
'will always be called'),
|
||||
'doall' : fields.boolean('Repeat missed'),
|
||||
'nextcall' : fields.datetime('Next call date', required=True),
|
||||
'model': fields.char('Model', size=64),
|
||||
'function': fields.char('Function', size=64),
|
||||
'args': fields.text('Arguments'),
|
||||
'priority': fields.integer('Priority',
|
||||
help='0=Very Urgent\n10=Not urgent')
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'nextcall' : lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'priority' : lambda *a: 5,
|
||||
'user_id' : lambda obj,cursor,user,context: user,
|
||||
'interval_number' : lambda *a: 1,
|
||||
'interval_type' : lambda *a: 'months',
|
||||
'numbercall' : lambda *a: 1,
|
||||
'active' : lambda *a: 1,
|
||||
'doall' : lambda *a: 1
|
||||
}
|
||||
|
||||
def _callback(self, cursor, user, model, func, args):
|
||||
args = (args or []) and eval(args)
|
||||
obj = self.pool.get(model)
|
||||
if obj and hasattr(obj, func):
|
||||
fct = getattr(obj, func)
|
||||
fct(cursor, user, *args)
|
||||
|
||||
def pool_jobs(self, db_name, check=False):
|
||||
#TODO Error treatment: exception, request, ... -> send request to user
|
||||
now = DateTime.now()
|
||||
try:
|
||||
cursor = pooler.get_db(db_name).cursor()
|
||||
except:
|
||||
return False
|
||||
|
||||
try:
|
||||
cursor.execute('SELECT * FROM ir_cron ' \
|
||||
'WHERE numbercall <> 0 ' \
|
||||
'AND active ' \
|
||||
'AND nextcall <= now() ' \
|
||||
'ORDER BY priority')
|
||||
for job in cursor.dictfetchall():
|
||||
nextcall = DateTime.strptime(job['nextcall'],
|
||||
'%Y-%m-%d %H:%M:%S')
|
||||
numbercall = job['numbercall']
|
||||
done = False
|
||||
while nextcall < now and numbercall:
|
||||
if numbercall > 0:
|
||||
numbercall -= 1
|
||||
if not done or job['doall']:
|
||||
self._callback(cursor, job['user_id'], job['model'],
|
||||
job['function'], job['args'])
|
||||
if numbercall:
|
||||
nextcall += _INTERVALTYPES[job['interval_type']](
|
||||
job['interval_number'])
|
||||
done = True
|
||||
addsql = ''
|
||||
if not numbercall:
|
||||
addsql = ', active=False'
|
||||
cursor.execute("UPDATE ir_cron SET nextcall = %s, " \
|
||||
"numbercall = %d" + addsql + " " \
|
||||
"WHERE id = %d",
|
||||
(nextcall.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
numbercall, job['id']))
|
||||
cursor.commit()
|
||||
finally:
|
||||
cursor.close()
|
||||
#TODO improved to do at the min(min(nextcalls), time() + NEWT_WAIT)
|
||||
if not check:
|
||||
self.set_alarm(self.pool_jobs, int(time.time()) + NEXT_WAIT,
|
||||
[db_name])
|
||||
|
||||
Cron()
|
||||
@@ -0,0 +1,29 @@
|
||||
"Exports"
|
||||
from trytond.osv import fields, OSV
|
||||
|
||||
|
||||
class Exports(OSV):
|
||||
"Exports"
|
||||
_name = "ir.exports"
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Export name', size=128),
|
||||
'resource': fields.char('Resource', size=128),
|
||||
'export_fields': fields.one2many('ir.exports.line', 'export_id',
|
||||
'Export Id'),
|
||||
}
|
||||
|
||||
Exports()
|
||||
|
||||
|
||||
class ExportsLine(OSV):
|
||||
"Exports line"
|
||||
_name = 'ir.exports.line'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Field name', size=64),
|
||||
'export_id': fields.many2one('ir.exports', 'Exportation',
|
||||
select=True),
|
||||
}
|
||||
|
||||
ExportsLine()
|
||||
@@ -0,0 +1,359 @@
|
||||
"model"
|
||||
from trytond.osv import fields, OSV
|
||||
from trytond.netsvc import Logger, LocalService, LOG_ERROR, LOG_INFO
|
||||
from trytond.osv.orm import except_orm
|
||||
from trytond.tools import Cache
|
||||
import time
|
||||
|
||||
class Model(OSV):
|
||||
"Model"
|
||||
_name = 'ir.model'
|
||||
_rec_name = 'model'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Model name', size=64, translate=True),
|
||||
'model': fields.char('Object name', size=64, required=True),
|
||||
'info': fields.text('Information'),
|
||||
'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields',
|
||||
required=True),
|
||||
}
|
||||
_defaults = {
|
||||
'name': lambda *a: 'No Name',
|
||||
}
|
||||
|
||||
Model()
|
||||
|
||||
class ModelFields(OSV):
|
||||
"Model fields"
|
||||
_name = 'ir.model.fields'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64),
|
||||
'model': fields.char('Model Name', size=64, required=True),
|
||||
'relation': fields.char('Model Relation', size=64),
|
||||
'model_id': fields.many2one('ir.model', 'Model id', required=True,
|
||||
select=True),
|
||||
'field_description': fields.char('Field Description', size=256),
|
||||
'ttype': fields.char('Field Type', size=64),
|
||||
'relate': fields.boolean('Click and Relate'),
|
||||
|
||||
'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel',
|
||||
'field_id', 'group_id', 'Groups'),
|
||||
'group_name': fields.char('Group Name', size=128),
|
||||
'view_load': fields.boolean('View Auto-Load'),
|
||||
}
|
||||
_defaults = {
|
||||
'relate': lambda *a: 0,
|
||||
'view_load': lambda *a: 0,
|
||||
'name': lambda *a: 'No Name',
|
||||
'field_description': lambda *a: 'No description available',
|
||||
}
|
||||
_order = "id"
|
||||
|
||||
ModelFields()
|
||||
|
||||
|
||||
class ModelAccess(OSV):
|
||||
"Model access"
|
||||
_name = 'ir.model.access'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
'model_id': fields.many2one('ir.model', 'Model', required=True),
|
||||
'group_id': fields.many2one('res.groups', 'Group'),
|
||||
'perm_read': fields.boolean('Read Access'),
|
||||
'perm_write': fields.boolean('Write Access'),
|
||||
'perm_create': fields.boolean('Create Access'),
|
||||
'perm_unlink': fields.boolean('Delete Permission'),
|
||||
}
|
||||
|
||||
def check(self, cursor, user, model_name, mode='read',
|
||||
raise_exception=True):
|
||||
assert mode in ['read', 'write', 'create', 'unlink'], \
|
||||
'Invalid access mode for security'
|
||||
if user == 1:
|
||||
return True
|
||||
cursor.execute('SELECT MAX(CASE WHEN perm_'+mode+' THEN 1 else 0 END) '
|
||||
'FROM ir_model_access a '
|
||||
'JOIN ir_model m '
|
||||
'ON (a.model_id=m.id) '
|
||||
'JOIN res_groups_users_rel gu '
|
||||
'ON (gu.gid = a.group_id) '
|
||||
'WHERE m.model = %s AND gu.user = %s', (model_name, user,))
|
||||
row = cursor.fetchall()
|
||||
if row[0][0] == None:
|
||||
cursor.execute('SELECT ' \
|
||||
'MAX(CASE WHEN perm_' + mode + ' THEN 1 else 0 END) ' \
|
||||
'FROM ir_model_access a ' \
|
||||
'JOIN ir_model m ' \
|
||||
'ON (a.model_id = m.id) ' \
|
||||
'WHERE a.group_id IS NULL AND m.model = %s', (model_name,))
|
||||
row = cursor.fetchall()
|
||||
if row[0][0] == None:
|
||||
return True
|
||||
|
||||
if not row[0][0]:
|
||||
if raise_exception:
|
||||
if mode == 'read':
|
||||
raise except_orm('AccessError',
|
||||
'You can not read this document!')
|
||||
elif mode == 'write':
|
||||
raise except_orm('AccessError',
|
||||
'You can not write in this document!')
|
||||
elif mode == 'create':
|
||||
raise except_orm('AccessError',
|
||||
'You can not create this kind of document!')
|
||||
elif mode == 'unlink':
|
||||
raise except_orm('AccessError',
|
||||
'You can not delete this document!')
|
||||
raise except_orm('AccessError',
|
||||
'You do not have access to this document!')
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
check = Cache()(check)
|
||||
|
||||
# Methods to clean the cache on the Check Method.
|
||||
def write(self, cursor, user, ids, vals, context=None):
|
||||
res = super(ModelAccess, self).write(cursor, user, ids, vals,
|
||||
context=context)
|
||||
self.check()
|
||||
return res
|
||||
|
||||
def create(self, cursor, user, vals, context=None):
|
||||
res = super(ModelAccess, self).create(cursor, user, vals,
|
||||
context=context)
|
||||
self.check()
|
||||
return res
|
||||
|
||||
def unlink(self, cursor, user, ids, context=None):
|
||||
res = super(ModelAccess, self).unlink(cursor, user, ids,
|
||||
context=context)
|
||||
self.check()
|
||||
return res
|
||||
|
||||
ModelAccess()
|
||||
|
||||
|
||||
class ModelData(OSV):
|
||||
"Model data"
|
||||
_name = 'ir.model.data'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('XML Identifier', required=True, size=64),
|
||||
'model': fields.char('Model', required=True, size=64),
|
||||
'module': fields.char('Module', required=True, size=64),
|
||||
'res_id': fields.integer('Resource ID'),
|
||||
'noupdate': fields.boolean('Non Updatable'),
|
||||
'date_update': fields.datetime('Update Date'),
|
||||
'date_init': fields.datetime('Init Date')
|
||||
}
|
||||
_defaults = {
|
||||
'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'noupdate': lambda *a: False
|
||||
}
|
||||
|
||||
def __init__(self, pool):
|
||||
OSV.__init__(self, pool)
|
||||
self.loads = {}
|
||||
self.doinit = True
|
||||
self.unlink_mark = {}
|
||||
|
||||
def _get_id(self, cursor, user, module, xml_id):
|
||||
ids = self.search(cursor, user, [
|
||||
('module', '=', module),
|
||||
('name', '=', xml_id),
|
||||
])
|
||||
assert len(ids)==1, '%d reference(s) to %s. ' \
|
||||
'You should have only one !' % (len(ids),xml_id)
|
||||
return ids[0]
|
||||
|
||||
_get_id = Cache()(_get_id)
|
||||
|
||||
def _update_dummy(self, cursor, user, model, module, xml_id=False):
|
||||
if not xml_id:
|
||||
return False
|
||||
try:
|
||||
obj_id = self.read(cursor, user, self._get_id(cursor, user, module,
|
||||
xml_id), ['res_id'])['res_id']
|
||||
self.loads[(module, xml_id)] = (model, obj_id)
|
||||
except:
|
||||
obj_id = False
|
||||
return obj_id
|
||||
|
||||
def _update(self, cursor, user, model, module, values, xml_id='',
|
||||
noupdate=False, mode='init', res_id=False):
|
||||
model_obj = self.pool.get(model)
|
||||
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)
|
||||
module, xml_id = xml_id.split('.')
|
||||
if (not xml_id) and (not self.doinit):
|
||||
return False
|
||||
action_id = False
|
||||
if xml_id:
|
||||
cursor.execute('SELECT id, res_id FROM ir_model_data ' \
|
||||
'WHERE module = %s AND name = %s', (module,xml_id))
|
||||
results = cursor.fetchall()
|
||||
for action_id2, res_id2 in results:
|
||||
cursor.execute('SELECT id ' \
|
||||
'FROM ' + self.pool.get(model)._table + ' ' \
|
||||
'WHERE id = %d', (res_id2,))
|
||||
result3 = cursor.fetchone()
|
||||
if not result3:
|
||||
cursor.execute('DELETE FROM ir_model_data ' \
|
||||
'WHERE id = %d', (action_id2,))
|
||||
else:
|
||||
res_id, action_id = res_id2, action_id2
|
||||
|
||||
if action_id and res_id:
|
||||
model_obj.write(cursor, user, [res_id], values)
|
||||
self.write(cursor, user, [action_id], {
|
||||
'date_update': time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
})
|
||||
elif res_id:
|
||||
model_obj.write(cursor, user, [res_id], values)
|
||||
if xml_id:
|
||||
self.create(cursor, user, {
|
||||
'name': xml_id,
|
||||
'model': model,
|
||||
'module':module,
|
||||
'res_id':res_id,
|
||||
'noupdate': noupdate,
|
||||
})
|
||||
if model_obj._inherits:
|
||||
for table in model_obj._inherits:
|
||||
inherit_id = model_obj.browse(cursor, user,
|
||||
res_id)[model_obj._inherits[table]]
|
||||
self.create(cursor, user, {
|
||||
'name': xml_id + '_' + table.replace('.', '_'),
|
||||
'model': table,
|
||||
'module': module,
|
||||
'res_id': inherit_id,
|
||||
'noupdate': noupdate,
|
||||
})
|
||||
else:
|
||||
if mode == 'init' or (mode == 'update' and xml_id):
|
||||
res_id = model_obj.create(cursor, user, values)
|
||||
if xml_id:
|
||||
self.create(cursor, user, {
|
||||
'name': xml_id,
|
||||
'model': model,
|
||||
'module': module,
|
||||
'res_id': res_id,
|
||||
'noupdate': noupdate
|
||||
})
|
||||
if model_obj._inherits:
|
||||
for table in model_obj._inherits:
|
||||
inherit_id = model_obj.browse(cursor, user,
|
||||
res_id)[model_obj._inherits[table]]
|
||||
self.create(cursor, user, {
|
||||
'name': xml_id + '_' + table.replace('.', '_'),
|
||||
'model': table,
|
||||
'module': module,
|
||||
'res_id': inherit_id,
|
||||
'noupdate': noupdate,
|
||||
})
|
||||
if xml_id:
|
||||
if res_id:
|
||||
self.loads[(module, xml_id)] = (model, res_id)
|
||||
return res_id
|
||||
|
||||
def _unlink(self, cursor, user, model, ids, direct=False):
|
||||
#self.pool.get(model).unlink(cursor, user, ids)
|
||||
for obj_id in ids:
|
||||
self.unlink_mark[(model, obj_id)]=False
|
||||
cursor.execute('DELETE FROM ir_model_data ' \
|
||||
'WHERE res_id = %d AND model = %s', (obj_id, model))
|
||||
return True
|
||||
|
||||
def ir_set(self, cursor, user, key, key2, name, models, value,
|
||||
replace=True, isobject=False, meta=None, xml_id=False):
|
||||
obj = self.pool.get('ir.values')
|
||||
if type(models[0])==type([]) or type(models[0])==type(()):
|
||||
model, res_id = models[0]
|
||||
else:
|
||||
res_id = None
|
||||
model = models[0]
|
||||
|
||||
if res_id:
|
||||
where = ' AND res_id = %d' % (res_id,)
|
||||
else:
|
||||
where = ' AND (res_id IS NULL)'
|
||||
|
||||
if key2:
|
||||
where += ' AND key2 = \'%s\'' % (key2,)
|
||||
else:
|
||||
where += ' AND (key2 IS NULL)'
|
||||
|
||||
cursor.execute('SELECT * FROM ir_values ' \
|
||||
'WHERE model = %s AND key = %s AND name = %s' + where,
|
||||
(model, key, name))
|
||||
res = cursor.fetchone()
|
||||
if not res:
|
||||
res = obj.set(cursor, user, key, key2, name, models, value,
|
||||
replace, isobject, meta)
|
||||
elif xml_id:
|
||||
cursor.execute('UPDATE ir_values SET value = %s ' \
|
||||
'WHERE model = %s AND key = %s AND name = %s' + where,
|
||||
(value, model, key, name))
|
||||
return True
|
||||
|
||||
def _process_end(self, cursor, user, modules):
|
||||
if not modules:
|
||||
return True
|
||||
module_str = ["'%s'" % m for m in modules]
|
||||
cursor.execute('SELECT id, name, model, res_id, module ' \
|
||||
'FROM ir_model_data ' \
|
||||
'WHERE module IN (' + ','.join(module_str) + ') ' \
|
||||
'AND NOT noupdate')
|
||||
wkf_todo = []
|
||||
for (obj_id, name, model, res_id, module) in cursor.fetchall():
|
||||
if (module, name) not in self.loads:
|
||||
self.unlink_mark[(model, res_id)] = obj_id
|
||||
if model == 'workflow.activity':
|
||||
cursor.execute('SELECT res_type, res_id ' \
|
||||
'FROM wkf_instance ' \
|
||||
'WHERE id IN (' \
|
||||
'SELECT inst_id FROM wkf_workitem ' \
|
||||
'WHERE act_id = %d)', (res_id,))
|
||||
wkf_todo.extend(cursor.fetchall())
|
||||
cursor.execute("UPDATE wkf_transition " \
|
||||
"SET condition = 'True', role_id = NULL, " \
|
||||
"signal = NULL, act_to = act_from, " \
|
||||
"act_from = %d " \
|
||||
"WHERE act_to = %d", (res_id, res_id))
|
||||
cursor.execute("DELETE FROM wkf_transition " \
|
||||
"WHERE act_to = %d", (res_id,))
|
||||
|
||||
for model, obj_id in wkf_todo:
|
||||
wf_service = LocalService("workflow")
|
||||
wf_service.trg_write(user, model, obj_id, cursor)
|
||||
|
||||
for (model, obj_id) in self.unlink_mark.keys():
|
||||
if self.pool.get(model):
|
||||
logger = Logger()
|
||||
logger.notify_channel('init', LOG_INFO,
|
||||
'Deleting %s@%s' % (obj_id, model))
|
||||
try:
|
||||
self.pool.get(model).unlink(cursor, user, [obj_id])
|
||||
if self.unlink_mark[(model, obj_id)]:
|
||||
self.unlink(cursor, user,
|
||||
[self.unlink_mark[(model, obj_id)]])
|
||||
cursor.execute('DELETE FROM ir_values WHERE value=%s',
|
||||
(model + ',' + str(obj_id),))
|
||||
except:
|
||||
logger.notify_channel('init', LOG_ERROR,
|
||||
'Could not delete id: %d of model %s\t' \
|
||||
'There should be some relation ' \
|
||||
'that points to this resource\t' \
|
||||
'You should manually fix this ' \
|
||||
'and restart --update=module' % \
|
||||
(obj_id, model))
|
||||
return True
|
||||
|
||||
ModelData()
|
||||
@@ -0,0 +1,235 @@
|
||||
"Rule"
|
||||
from trytond.osv import fields, OSV
|
||||
from trytond.tools import Cache
|
||||
import time
|
||||
|
||||
|
||||
class RuleGroup(OSV):
|
||||
"Rule group"
|
||||
_name = 'ir.rule.group'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=128, select=1),
|
||||
'model_id': fields.many2one('ir.model', 'Model', select=1,
|
||||
required=True),
|
||||
'global': fields.boolean('Global', select=1,
|
||||
help="Make the rule global \n" \
|
||||
"or it needs to be put on a group or user"),
|
||||
'rules': fields.one2many('ir.rule', 'rule_group', 'Tests',
|
||||
help="The rule is satisfied if at least one test is True"),
|
||||
'groups': fields.many2many('res.groups', 'group_rule_group_rel',
|
||||
'rule_group_id', 'group_id', 'Groups'),
|
||||
'users': fields.many2many('res.users', 'user_rule_group_rel',
|
||||
'rule_group_id', 'user_id', 'Users'),
|
||||
}
|
||||
_order = 'model_id, global DESC'
|
||||
_defaults = {
|
||||
'global': lambda *a: True,
|
||||
}
|
||||
|
||||
def unlink(self, cursor, user, ids, context=None):
|
||||
res = super(RuleGroup, self).unlink(cursor, user, ids,
|
||||
context=context)
|
||||
# Restart the cache on the domain_get method of ir.rule
|
||||
self.pool.get('ir.rule').domain_get()
|
||||
return res
|
||||
|
||||
def create(self, cursor, user, vals, context=None):
|
||||
res = super(RuleGroup, self).create(cursor, user, vals,
|
||||
context=context)
|
||||
# Restart the cache on the domain_get method of ir.rule
|
||||
self.pool.get('ir.rule').domain_get()
|
||||
return res
|
||||
|
||||
def write(self, cursor, user, ids, vals, context=None):
|
||||
res = super(RuleGroup, self).write(cursor, user, ids, vals,
|
||||
context=context)
|
||||
# Restart the cache on the domain_get method of ir.rule
|
||||
self.pool.get('ir.rule').domain_get()
|
||||
return res
|
||||
|
||||
RuleGroup()
|
||||
|
||||
|
||||
class Rule(OSV):
|
||||
"Rule"
|
||||
_name = 'ir.rule'
|
||||
_rec_name = 'field_id'
|
||||
_description = __doc__
|
||||
|
||||
def _operand(self, cursor, user, context):
|
||||
|
||||
def get(obj_name, level=3, recur=None, root_tech='', root=''):
|
||||
res = []
|
||||
if not recur:
|
||||
recur = []
|
||||
obj_fields = self.pool.get(obj_name).fields_get(cursor, user)
|
||||
key = obj_fields.keys()
|
||||
key.sort()
|
||||
for k in key:
|
||||
|
||||
if obj_fields[k]['type'] in ('many2one'):
|
||||
res.append((root_tech + '.' + k + '.id',
|
||||
root + '/' + obj_fields[k]['string']))
|
||||
|
||||
elif obj_fields[k]['type'] in ('many2many', 'one2many'):
|
||||
res.append(('\',\'.join(map(lambda x: str(x.id), ' + \
|
||||
root_tech + '.' + k + '))',
|
||||
root + '/' + obj_fields[k]['string']))
|
||||
|
||||
else:
|
||||
res.append((root_tech + '.' + k,
|
||||
root + '/' + obj_fields[k]['string']))
|
||||
|
||||
if (obj_fields[k]['type'] in recur) and (level>0):
|
||||
res.extend(get(obj_fields[k]['relation'], level-1,
|
||||
recur, root_tech + '.' + k, root + '/' + \
|
||||
obj_fields[k]['string']))
|
||||
|
||||
return res
|
||||
|
||||
res = [("False", "False"), ("True", "True"), ("user.id", "User")]
|
||||
res += get('res.users', level=1,
|
||||
recur=['many2one'], root_tech='user', root='User')
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'field_id': fields.many2one('ir.model.fields', 'Field',
|
||||
domain="[('model_id','=', parent.model_id)]", select=1,
|
||||
required=True),
|
||||
'operator':fields.selection([
|
||||
('=', '='),
|
||||
('<>', '<>'),
|
||||
('<=', '<='),
|
||||
('>=', '>='),
|
||||
('in', 'in'),
|
||||
('child_of', 'child_of'),
|
||||
], 'Operator', required=True),
|
||||
'operand':fields.selection(_operand,'Operand', size=64, required=True),
|
||||
'rule_group': fields.many2one('ir.rule.group', 'Group', select=2,
|
||||
required=True, ondelete="cascade")
|
||||
}
|
||||
|
||||
def domain_get(self, cursor, user, model_name):
|
||||
# root user above constraint
|
||||
if user == 1:
|
||||
return '', []
|
||||
|
||||
cursor.execute("SELECT r.id FROM ir_rule r " \
|
||||
"JOIN (ir_rule_group g " \
|
||||
"JOIN ir_model m ON (g.model_id = m.id)) " \
|
||||
"ON (g.id = r.rule_group) " \
|
||||
"WHERE m.model = %s "
|
||||
"AND (g.id IN (" \
|
||||
"SELECT rule_group_id FROM user_rule_group_rel " \
|
||||
"WHERE user_id = %d " \
|
||||
"UNION SELECT rule_group_id " \
|
||||
"FROM group_rule_group_rel g_rel " \
|
||||
"JOIN res_groups_users_rel u_rel " \
|
||||
"ON (g_rel.group_id = u_rel.gid) " \
|
||||
"WHERE u_rel.user = %d) "
|
||||
"OR g.global)", (model_name, user, user))
|
||||
ids = [x[0] for x in cursor.fetchall()]
|
||||
if not ids:
|
||||
return '', []
|
||||
obj = self.pool.get(model_name)
|
||||
clause = {}
|
||||
clause_global = {}
|
||||
# Use root user to prevent recursion
|
||||
for rule in self.browse(cursor, 1, ids):
|
||||
if rule.operator in ('in', 'child_of'):
|
||||
dom = eval("[('%s', '%s', [%s])]" % \
|
||||
(rule.field_id.name, rule.operator, rule.operand),
|
||||
{'user': self.pool.get('res.users').browse(cursor, 1,
|
||||
user), 'time': time})
|
||||
else:
|
||||
dom = eval("[('%s', '%s', %s)]" % \
|
||||
(rule.field_id.name, rule.operator, rule.operand),
|
||||
{'user': self.pool.get('res.users').browse(cursor, 1,
|
||||
user), 'time': time})
|
||||
|
||||
if rule.rule_group['global']:
|
||||
clause_global.setdefault(rule.rule_group.id, [])
|
||||
clause_global[rule.rule_group.id].append(
|
||||
obj._where_calc(cursor, user, dom, active_test=False))
|
||||
else:
|
||||
clause.setdefault(rule.rule_group.id, [])
|
||||
clause[rule.rule_group.id].append(
|
||||
obj._where_calc(cursor, user, dom, active_test=False))
|
||||
|
||||
def _query(clauses, test):
|
||||
query = ''
|
||||
val = []
|
||||
for groups in clauses.values():
|
||||
if not groups:
|
||||
continue
|
||||
if len(query):
|
||||
query += ' '+test+' '
|
||||
query += '('
|
||||
first = True
|
||||
for group in groups:
|
||||
if not first:
|
||||
query += ' OR '
|
||||
first = False
|
||||
query += '('
|
||||
first2 = True
|
||||
for clause in group[0]:
|
||||
if not first2:
|
||||
query += ' AND '
|
||||
first2 = False
|
||||
query += clause
|
||||
query += ')'
|
||||
val += group[1]
|
||||
query += ')'
|
||||
return query, val
|
||||
|
||||
query = ''
|
||||
val = []
|
||||
|
||||
# Test if there is no rule_group that have no rule
|
||||
cursor.execute("""SELECT g.id FROM
|
||||
ir_rule_group g
|
||||
JOIN ir_model m ON (g.model_id = m.id)
|
||||
WHERE m.model = %s
|
||||
AND (g.id NOT IN (SELECT rule_group FROM ir_rule))
|
||||
AND (g.id IN (SELECT rule_group_id FROM user_rule_group_rel
|
||||
WHERE user_id = %d
|
||||
UNION SELECT rule_group_id FROM group_rule_group_rel g_rel
|
||||
JOIN res_groups_users_rel u_rel
|
||||
ON g_rel.group_id = u_rel.gid
|
||||
WHERE u_rel.user = %d))""", (model_name, user, user))
|
||||
if not cursor.fetchall():
|
||||
query, val = _query(clause, 'OR')
|
||||
|
||||
query_global, val_global = _query(clause_global, 'AND')
|
||||
if query_global:
|
||||
if query:
|
||||
query = '('+query+') AND '+query_global
|
||||
val.extend(val_global)
|
||||
else:
|
||||
query = query_global
|
||||
val = val_global
|
||||
|
||||
return query, val
|
||||
domain_get = Cache()(domain_get)
|
||||
|
||||
def unlink(self, cursor, user, ids, context=None):
|
||||
res = super(Rule, self).unlink(cursor, user, ids, context=context)
|
||||
# Restart the cache on the domain_get method of ir.rule
|
||||
self.domain_get()
|
||||
return res
|
||||
|
||||
def create(self, cursor, user, vals, context=None):
|
||||
res = super(Rule, self).create(cursor, user, vals, context=context)
|
||||
# Restart the cache on the domain_get method of ir.rule
|
||||
self.domain_get()
|
||||
return res
|
||||
|
||||
def write(self, cursor, user, ids, vals, context=None):
|
||||
res = super(Rule, self).write(cursor, user, ids, vals,
|
||||
context=context)
|
||||
# Restart the cache on the domain_get method
|
||||
self.domain_get()
|
||||
return res
|
||||
|
||||
Rule()
|
||||
@@ -0,0 +1,76 @@
|
||||
import time
|
||||
from trytond.osv import fields, OSV
|
||||
|
||||
|
||||
class SequenceType(OSV):
|
||||
"Sequence type"
|
||||
_name = 'ir.sequence.type'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Sequence Name',size=64, required=True),
|
||||
'code': fields.char('Sequence Code',size=32, required=True),
|
||||
}
|
||||
|
||||
SequenceType()
|
||||
|
||||
|
||||
class Sequence(OSV):
|
||||
"Sequence"
|
||||
_name = 'ir.sequence'
|
||||
_description = __doc__
|
||||
|
||||
@staticmethod
|
||||
def _code_get(obj, cursor, user, context=None):
|
||||
cursor.execute('select code, name from ir_sequence_type')
|
||||
return cursor.fetchall()
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Sequence Name',size=64, required=True),
|
||||
'code': fields.selection(_code_get, 'Sequence Code',size=64,
|
||||
required=True),
|
||||
'active': fields.boolean('Active'),
|
||||
'prefix': fields.char('Prefix',size=64),
|
||||
'suffix': fields.char('Suffix',size=64),
|
||||
'number_next': fields.integer('Next Number', required=True),
|
||||
'number_increment': fields.integer('Increment Number', required=True),
|
||||
'padding' : fields.integer('Number padding', required=True),
|
||||
}
|
||||
_defaults = {
|
||||
'active': lambda *a: True,
|
||||
'number_increment': lambda *a: 1,
|
||||
'number_next': lambda *a: 1,
|
||||
'padding' : lambda *a : 0,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _process(string):
|
||||
return (string or '') % {
|
||||
'year':time.strftime('%Y'),
|
||||
'month': time.strftime('%m'),
|
||||
'day':time.strftime('%d'),
|
||||
}
|
||||
|
||||
def get_id(self, cursor, user, sequence_id, test='id=%d'):
|
||||
cursor.execute('lock table ir_sequence')
|
||||
cursor.execute('SELECT id, number_next, number_increment, prefix, ' \
|
||||
'suffix, padding ' \
|
||||
'FROM ir_sequence ' \
|
||||
'WHERE ' + test + ' AND active = True', (sequence_id,))
|
||||
res = cursor.dictfetchone()
|
||||
if res:
|
||||
cursor.execute('UPDATE ir_sequence ' \
|
||||
'SET number_next = number_next + number_increment ' \
|
||||
'WHERE id = %d AND active = True', (res['id'],))
|
||||
if res['number_next']:
|
||||
return self._process(res['prefix']) + \
|
||||
'%%0%sd' % res['padding'] % res['number_next'] + \
|
||||
self._process(res['suffix'])
|
||||
else:
|
||||
return self._process(res['prefix']) + \
|
||||
self._process(res['suffix'])
|
||||
return False
|
||||
|
||||
def get(self, cursor, user, code):
|
||||
return self.get_id(cursor, user, code, test='code=%s')
|
||||
|
||||
Sequence()
|
||||
@@ -0,0 +1,137 @@
|
||||
"Translation"
|
||||
from trytond.osv import fields, OSV, Cacheable
|
||||
from trytond import tools
|
||||
|
||||
TRANSLATION_TYPE = [
|
||||
('field', 'Field'),
|
||||
('model', 'Model'),
|
||||
('rml', 'RML'),
|
||||
('selection', 'Selection'),
|
||||
('view', 'View'),
|
||||
('wizard_button', 'Wizard Button'),
|
||||
('wizard_field', 'Wizard Field'),
|
||||
('wizard_view', 'Wizard View'),
|
||||
('xsl', 'XSL'),
|
||||
('help', 'Help'),
|
||||
]
|
||||
|
||||
class Translation(OSV, Cacheable):
|
||||
"Translation"
|
||||
_name = "ir.translation"
|
||||
_log_access = False
|
||||
_description = __doc__
|
||||
|
||||
def _get_language(self, cursor, user, context):
|
||||
lang_obj = self.pool.get('res.lang')
|
||||
lang_ids = lang_obj.search(cursor, user, [('translatable', '=', True)],
|
||||
context=context)
|
||||
langs = lang_obj.browse(cursor, user, lang_ids, context=context)
|
||||
res = [(lang.code, lang.name) for lang in langs]
|
||||
for lang_dict in tools.scan_languages():
|
||||
if lang_dict not in res:
|
||||
res.append(lang_dict)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Field Name', size=128, required=True),
|
||||
'res_id': fields.integer('Resource ID'),
|
||||
'lang': fields.selection(_get_language, string='Language', size=5),
|
||||
'type': fields.selection(TRANSLATION_TYPE, string='Type', size=16),
|
||||
'src': fields.text('Source'),
|
||||
'value': fields.text('Translation Value'),
|
||||
}
|
||||
_sql = """
|
||||
CREATE INDEX ir_translation_ltn ON ir_translation (lang, type, name);
|
||||
CREATE INDEX ir_translation_res_id ON ir_translation (res_id);
|
||||
"""
|
||||
|
||||
def _get_ids(self, cursor, name, ttype, lang, ids):
|
||||
translations, to_fetch = {}, []
|
||||
for obj_id in ids:
|
||||
trans = self.get((lang, name, obj_id))
|
||||
if trans is not None:
|
||||
translations[obj_id] = trans
|
||||
else:
|
||||
to_fetch.append(obj_id)
|
||||
if to_fetch:
|
||||
cursor.execute('SELECT res_id, value ' \
|
||||
'FROM ir_translation ' \
|
||||
'WHERE lang = %s ' \
|
||||
'AND type = %s ' \
|
||||
'AND name = %s ' \
|
||||
'AND res_id in (' + \
|
||||
','.join([str(x) for x in to_fetch]) + ')',
|
||||
(lang, ttype, name))
|
||||
for res_id, value in cursor.fetchall():
|
||||
self.add((lang, ttype, name, res_id), value)
|
||||
translations[res_id] = value
|
||||
for res_id in ids:
|
||||
if res_id not in translations:
|
||||
self.add((lang, ttype, name, res_id), False)
|
||||
translations[res_id] = False
|
||||
return translations
|
||||
|
||||
def _set_ids(self, cursor, user, name, ttype, lang, ids, value):
|
||||
cursor.execute('DELETE FROM ir_translation ' \
|
||||
'WHERE lang = %s ' \
|
||||
'AND type = %s ' \
|
||||
'AND name = %s ' \
|
||||
'AND res_id IN (' + ','.join([str(x) for x in ids]) + ')',
|
||||
(lang, ttype, name))
|
||||
for obj_id in ids:
|
||||
self.create(cursor, user, {
|
||||
'lang': lang,
|
||||
'type': ttype,
|
||||
'name': name,
|
||||
'res_id': obj_id,
|
||||
'value': value,
|
||||
})
|
||||
return len(ids)
|
||||
|
||||
def _get_source(self, cursor, name, ttype, lang, source=None):
|
||||
trans = self.get((lang, ttype, name, source))
|
||||
if trans is not None:
|
||||
return trans
|
||||
|
||||
if source:
|
||||
source = source.strip().replace('\n',' ')
|
||||
if isinstance(source, unicode):
|
||||
source = source.encode('utf8')
|
||||
cursor.execute('select value ' \
|
||||
'from ir_translation ' \
|
||||
'where lang=%s ' \
|
||||
'and type=%s ' \
|
||||
'and name=%s ' \
|
||||
'and src=%s',
|
||||
(lang, ttype, str(name), source))
|
||||
else:
|
||||
cursor.execute('select value ' \
|
||||
'from ir_translation ' \
|
||||
'where lang=%s ' \
|
||||
'and type=%s ' \
|
||||
'and name=%s',
|
||||
(lang, ttype, str(name)))
|
||||
res = cursor.fetchone()
|
||||
if res:
|
||||
self.add((lang, ttype, name, source), res[0])
|
||||
return res[0]
|
||||
else:
|
||||
self.add((lang, ttype, name, source), False)
|
||||
return False
|
||||
|
||||
def unlink(self, cursor, user, ids, context=None):
|
||||
self.clear()
|
||||
return super(Translation, self).unlink(cursor, user, ids,
|
||||
context=context)
|
||||
|
||||
def create(self, cursor, user, vals, context=None):
|
||||
self.clear()
|
||||
return super(Translation, self).create(cursor, user, vals,
|
||||
context=context)
|
||||
|
||||
def write(self, cursor, user, ids, vals, context=None):
|
||||
self.clear()
|
||||
return super(Translation, self).write(cursor, user, ids, vals,
|
||||
context=context)
|
||||
|
||||
Translation()
|
||||
@@ -0,0 +1,2 @@
|
||||
from menu import *
|
||||
from view import *
|
||||
@@ -0,0 +1,260 @@
|
||||
"UI menu"
|
||||
from trytond.osv import fields, OSV
|
||||
|
||||
def one_in(i, j):
|
||||
"""Check the presence of an element of setA in setB
|
||||
"""
|
||||
for k in i:
|
||||
if k in j:
|
||||
return True
|
||||
return False
|
||||
|
||||
ICONS = [(x, x) for x in [
|
||||
'STOCK_ABOUT',
|
||||
'STOCK_ADD',
|
||||
'STOCK_APPLY',
|
||||
'STOCK_BOLD',
|
||||
'STOCK_CANCEL',
|
||||
'STOCK_CDROM',
|
||||
'STOCK_CLEAR',
|
||||
'STOCK_CLOSE',
|
||||
'STOCK_COLOR_PICKER',
|
||||
'STOCK_CONNECT',
|
||||
'STOCK_CONVERT',
|
||||
'STOCK_COPY',
|
||||
'STOCK_CUT',
|
||||
'STOCK_DELETE',
|
||||
'STOCK_DIALOG_AUTHENTICATION',
|
||||
'STOCK_DIALOG_ERROR',
|
||||
'STOCK_DIALOG_INFO',
|
||||
'STOCK_DIALOG_QUESTION',
|
||||
'STOCK_DIALOG_WARNING',
|
||||
'STOCK_DIRECTORY',
|
||||
'STOCK_DISCONNECT',
|
||||
'STOCK_DND',
|
||||
'STOCK_DND_MULTIPLE',
|
||||
'STOCK_EDIT',
|
||||
'STOCK_EXECUTE',
|
||||
'STOCK_FILE',
|
||||
'STOCK_FIND',
|
||||
'STOCK_FIND_AND_REPLACE',
|
||||
'STOCK_FLOPPY',
|
||||
'STOCK_GOTO_BOTTOM',
|
||||
'STOCK_GOTO_FIRST',
|
||||
'STOCK_GOTO_LAST',
|
||||
'STOCK_GOTO_TOP',
|
||||
'STOCK_GO_BACK',
|
||||
'STOCK_GO_DOWN',
|
||||
'STOCK_GO_FORWARD',
|
||||
'STOCK_GO_UP',
|
||||
'STOCK_HARDDISK',
|
||||
'STOCK_HELP',
|
||||
'STOCK_HOME',
|
||||
'STOCK_INDENT',
|
||||
'STOCK_INDEX',
|
||||
'STOCK_ITALIC',
|
||||
'STOCK_JUMP_TO',
|
||||
'STOCK_JUSTIFY_CENTER',
|
||||
'STOCK_JUSTIFY_FILL',
|
||||
'STOCK_JUSTIFY_LEFT',
|
||||
'STOCK_JUSTIFY_RIGHT',
|
||||
'STOCK_MEDIA_FORWARD',
|
||||
'STOCK_MEDIA_NEXT',
|
||||
'STOCK_MEDIA_PAUSE',
|
||||
'STOCK_MEDIA_PLAY',
|
||||
'STOCK_MEDIA_PREVIOUS',
|
||||
'STOCK_MEDIA_RECORD',
|
||||
'STOCK_MEDIA_REWIND',
|
||||
'STOCK_MEDIA_STOP',
|
||||
'STOCK_MISSING_IMAGE',
|
||||
'STOCK_NETWORK',
|
||||
'STOCK_NEW',
|
||||
'STOCK_NO',
|
||||
'STOCK_OK',
|
||||
'STOCK_OPEN',
|
||||
'STOCK_PASTE',
|
||||
'STOCK_PREFERENCES',
|
||||
'STOCK_PRINT',
|
||||
'STOCK_PRINT_PREVIEW',
|
||||
'STOCK_PROPERTIES',
|
||||
'STOCK_QUIT',
|
||||
'STOCK_REDO',
|
||||
'STOCK_REFRESH',
|
||||
'STOCK_REMOVE',
|
||||
'STOCK_REVERT_TO_SAVED',
|
||||
'STOCK_SAVE',
|
||||
'STOCK_SAVE_AS',
|
||||
'STOCK_SELECT_COLOR',
|
||||
'STOCK_SELECT_FONT',
|
||||
'STOCK_SORT_ASCENDING',
|
||||
'STOCK_SORT_DESCENDING',
|
||||
'STOCK_SPELL_CHECK',
|
||||
'STOCK_STOP',
|
||||
'STOCK_STRIKETHROUGH',
|
||||
'STOCK_UNDELETE',
|
||||
'STOCK_UNDERLINE',
|
||||
'STOCK_UNDO',
|
||||
'STOCK_UNINDENT',
|
||||
'STOCK_YES',
|
||||
'STOCK_ZOOM_100',
|
||||
'STOCK_ZOOM_FIT',
|
||||
'STOCK_ZOOM_IN',
|
||||
'STOCK_ZOOM_OUT',
|
||||
'terp-account',
|
||||
'terp-crm',
|
||||
'terp-mrp',
|
||||
'terp-product',
|
||||
'terp-purchase',
|
||||
'terp-sale',
|
||||
'terp-tools',
|
||||
'terp-administration',
|
||||
'terp-hr',
|
||||
'terp-partner',
|
||||
'terp-project',
|
||||
'terp-report',
|
||||
'terp-stock',
|
||||
'terp-calendar',
|
||||
'terp-graph',
|
||||
]]
|
||||
|
||||
|
||||
class Many2ManyUniq(fields.Many2Many):
|
||||
|
||||
def set(self, cursor, obj, obj_id, name, values, user=None, context=None):
|
||||
if not values:
|
||||
return
|
||||
val = values[:]
|
||||
for act in values:
|
||||
if act[0] == 4:
|
||||
cursor.execute('SELECT * FROM ' + self._rel + ' ' \
|
||||
'WHERE ' + self._id1 + ' = %d ' \
|
||||
'AND ' + self._id2 + ' = %d',
|
||||
(obj_id, act[1]))
|
||||
if cursor.fetchall():
|
||||
val.remove(act)
|
||||
return super(Many2ManyUniq, self).set(cursor, obj, obj_id, name, val,
|
||||
user=user, context=context)
|
||||
|
||||
|
||||
class UIMenu(OSV):
|
||||
"UI menu"
|
||||
_name = 'ir.ui.menu'
|
||||
_description = __doc__
|
||||
|
||||
def search(self, cursor, user, args, offset=0, limit=2000, order=None,
|
||||
context=None, count=False):
|
||||
res_user_obj = self.pool.get('res.users')
|
||||
if context is None:
|
||||
context = {}
|
||||
ids = super(UIMenu, self).search(cursor, user, args, offset, limit,
|
||||
order, context=context)
|
||||
user_groups = res_user_obj.read(cursor, user, [user])[0]['groups_id']
|
||||
result = []
|
||||
for menu in self.browse(cursor, user, ids):
|
||||
if not len(menu.groups_id):
|
||||
result.append(menu.id)
|
||||
continue
|
||||
for group in menu.groups_id:
|
||||
if group.id in user_groups:
|
||||
result.append(menu.id)
|
||||
break
|
||||
if count:
|
||||
return len(result)
|
||||
return result
|
||||
|
||||
def _get_full_name(self, cursor, user, ids, name, args, context):
|
||||
res = {}
|
||||
for menu in self.browse(cursor, user, ids):
|
||||
res[menu.id] = self._get_one_full_name(menu)
|
||||
return res
|
||||
|
||||
def _get_one_full_name(self, menu, level=6):
|
||||
if level <= 0:
|
||||
return '...'
|
||||
if menu.parent_id:
|
||||
parent_path = self._get_one_full_name(menu.parent_id, level-1) + "/"
|
||||
else:
|
||||
parent_path = ''
|
||||
return parent_path + menu.name
|
||||
|
||||
def copy(self, cursor, user, obj_id, default=None, context=None):
|
||||
ir_values_obj = self.pool.get('ir.values')
|
||||
res = super(UIMenu, self).copy(cursor, user, obj_id, context=context)
|
||||
ids = ir_values_obj.search(cursor, user, [
|
||||
('model', '=', 'ir.ui.menu'),
|
||||
('res_id', '=', obj_id),
|
||||
])
|
||||
for ir_value in ir_values_obj.browse(cursor, user, ids):
|
||||
ir_values_obj.copy(cursor, user, ir_value.id,
|
||||
default={'res_id': res}, context=context)
|
||||
return res
|
||||
|
||||
def _action(self, cursor, user, ids, name, arg, context=None):
|
||||
res = {}
|
||||
values_obj = self.pool.get('ir.values')
|
||||
value_ids = values_obj.search(cursor, user, [
|
||||
('model', '=', self._name), ('key', '=', 'action'),
|
||||
('key2', '=', 'tree_but_open'), ('res_id', 'in', ids)],
|
||||
context=context)
|
||||
values_action = {}
|
||||
for value in values_obj.browse(cursor, user, value_ids,
|
||||
context=context):
|
||||
values_action[value.res_id] = value.value
|
||||
for menu_id in ids:
|
||||
res[menu_id] = values_action.get(menu_id, False)
|
||||
return res
|
||||
|
||||
def _action_inv(self, cursor, user, menu_id, name, value, arg,
|
||||
context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
ctx = context.copy()
|
||||
if 'read_delta' in ctx:
|
||||
del ctx['read_delta']
|
||||
values_obj = self.pool.get('ir.values')
|
||||
values_ids = values_obj.search(cursor, user, [
|
||||
('model', '=', self._name), ('key', '=', 'action'),
|
||||
('key2', '=', 'tree_but_open'), ('res_id', '=', menu_id)],
|
||||
context=context)
|
||||
if values_ids:
|
||||
values_obj.write(cursor, user, values_ids[0], {'value': value},
|
||||
context=ctx)
|
||||
else:
|
||||
values_obj.create(cursor, user, {
|
||||
'name': 'Menuitem',
|
||||
'model': self._name,
|
||||
'value': value,
|
||||
'object': True,
|
||||
'key': 'action',
|
||||
'key2': 'tree_but_open',
|
||||
'res_id': menu_id,
|
||||
}, context=ctx)
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Menu', size=64, required=True, translate=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'child_id' : fields.one2many('ir.ui.menu', 'parent_id','Child ids'),
|
||||
'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True),
|
||||
'groups_id': Many2ManyUniq('res.groups', 'ir_ui_menu_group_rel',
|
||||
'menu_id', 'gid', 'Groups'),
|
||||
'complete_name': fields.function(_get_full_name, method=True,
|
||||
string='Complete Name', type='char', size=128),
|
||||
'icon': fields.selection(ICONS, 'Icon', size=64),
|
||||
'action': fields.function(_action, fnct_inv=_action_inv,
|
||||
method=True, type='reference', string='Action',
|
||||
selection=[
|
||||
('ir.actions.report.custom', 'ir.actions.report.custom'),
|
||||
('ir.actions.report.xml', 'ir.actions.report.xml'),
|
||||
('ir.actions.act_window', 'ir.actions.act_window'),
|
||||
('ir.actions.wizard', 'ir.actions.wizard'),
|
||||
]),
|
||||
}
|
||||
_defaults = {
|
||||
'icon' : lambda *a: 'STOCK_OPEN',
|
||||
'sequence' : lambda *a: 10,
|
||||
}
|
||||
_order = "sequence, id"
|
||||
|
||||
UIMenu()
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"View"
|
||||
from trytond.osv import fields, OSV
|
||||
from xml import dom
|
||||
|
||||
|
||||
class View(OSV):
|
||||
"View"
|
||||
_name = 'ir.ui.view'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('View Name',size=64, required=True),
|
||||
'model': fields.char('Model', size=64, required=True),
|
||||
'priority': fields.integer('Priority', required=True),
|
||||
'type': fields.selection((
|
||||
('tree','Tree'),
|
||||
('form','Form'),
|
||||
('graph', 'Graph'),
|
||||
('calendar', 'Calendar')), 'View Type', required=True),
|
||||
'arch': fields.text('View Architecture', required=True),
|
||||
'inherit_id': fields.many2one('ir.ui.view', 'Inherited View'),
|
||||
'field_parent': fields.char('Childs Field',size=64)
|
||||
}
|
||||
_defaults = {
|
||||
'arch': lambda *a: '<?xml version="1.0"?>\n' \
|
||||
'<tree title="Unknwown">\n\t<field name="name"/>\n</tree>',
|
||||
'priority': lambda *a: 16,
|
||||
}
|
||||
_order = "priority"
|
||||
|
||||
@staticmethod
|
||||
def _check_xml(obj, cursor, user, ids):
|
||||
"Check XML"
|
||||
cursor.execute('SELECT arch FROM ir_ui_view ' \
|
||||
'WHERE id IN (' + ','.join([str(x) for x in ids]) + ')')
|
||||
for row in cursor.fetchall():
|
||||
try:
|
||||
dom.minidom.parseString(row[0])
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_xml, 'Invalid XML for View Architecture!', ['arch'])
|
||||
]
|
||||
|
||||
View()
|
||||
|
||||
|
||||
class ViewShortcut(OSV):
|
||||
"View shortcut"
|
||||
_name = 'ir.ui.view_sc'
|
||||
_description = __doc__
|
||||
_columns = {
|
||||
'name': fields.char('Shortcut Name', size=64, required=True),
|
||||
'res_id': fields.integer('Resource Ref.', required=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'user_id': fields.many2one('res.users', 'User Ref.', required=True,
|
||||
ondelete='cascade'),
|
||||
'resource': fields.char('Resource Name', size=64, required=True)
|
||||
}
|
||||
|
||||
def get_sc(self, cursor, user, user_id, model='ir.ui.menu', context=None):
|
||||
"Provide user's shortcuts"
|
||||
ids = self.search(cursor, user, [
|
||||
('user_id','=',user_id),
|
||||
('resource','=',model),
|
||||
], context=context)
|
||||
return self.read(cursor, user, ids, ['res_id', 'name'], context=context)
|
||||
|
||||
_order = 'sequence'
|
||||
_defaults = {
|
||||
'resource': lambda *a: 'ir.ui.menu',
|
||||
}
|
||||
|
||||
ViewShortcut()
|
||||
@@ -0,0 +1,226 @@
|
||||
"Values"
|
||||
from trytond.osv import OSV, fields
|
||||
import pickle
|
||||
|
||||
class Values(OSV):
|
||||
"Values"
|
||||
_name = 'ir.values'
|
||||
_description = __doc__
|
||||
|
||||
def _value_unpickle(self, cursor, user, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for report in self.browse(cursor, user, ids, context=context):
|
||||
value = report[name[:-9]]
|
||||
if not report.object and value:
|
||||
try:
|
||||
value = str(pickle.loads(value))
|
||||
except:
|
||||
pass
|
||||
res[report.id] = value
|
||||
return res
|
||||
|
||||
def _value_pickle(self, cursor, user, obj_id, name, value, arg,
|
||||
context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
ctx = context.copy()
|
||||
if 'read_delta' in ctx:
|
||||
del ctx['read_delta']
|
||||
values = self.browse(cursor, user, obj_id, context=context)
|
||||
if not values.object:
|
||||
value = pickle.dumps(eval(value))
|
||||
self.write(cursor, user, obj_id, {name[:-9]: value}, context=ctx)
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=128),
|
||||
'model': fields.char('Model', size=128),
|
||||
'value': fields.text('Value'),
|
||||
'value_unpickle': fields.function(_value_unpickle,
|
||||
fnct_inv=_value_pickle, method=True, type='text', string='Value'),
|
||||
'object': fields.boolean('Is Object'),
|
||||
'key': fields.char('Type', size=128),
|
||||
'key2': fields.char('Value', size=256),
|
||||
'meta': fields.text('Meta Datas'),
|
||||
'meta_unpickle': fields.function(_value_unpickle,
|
||||
fnct_inv=_value_pickle, method=True, type='text',
|
||||
string='Meta Datas'),
|
||||
'res_id': fields.integer('Resource ID'),
|
||||
'user_id': fields.many2one('res.users', 'User', ondelete='cascade'),
|
||||
|
||||
|
||||
#TODO add in company module
|
||||
#'company_id': fields.many2one('res.company', 'Company')
|
||||
}
|
||||
_defaults = {
|
||||
'key': lambda *a: 'action',
|
||||
'key2': lambda *a: 'tree_but_open',
|
||||
#'company_id': lambda *a: False,
|
||||
}
|
||||
|
||||
def _auto_init(self, cursor):
|
||||
super(Values, self)._auto_init(cursor)
|
||||
cursor.execute('SELECT indexname FROM pg_indexes ' \
|
||||
'WHERE indexname = \'ir_values_key_model_key2_index\'')
|
||||
if not cursor.fetchone():
|
||||
cursor.execute('CREATE INDEX ir_values_key_model_key2_index ' \
|
||||
'ON ir_values (key, model, key2)')
|
||||
cursor.commit()
|
||||
|
||||
def set(self, cursor, user, key, key2, name, models, value, replace=True,
|
||||
isobject=False, meta=False, preserve_user=False, company=False):
|
||||
if type(value)==type(u''):
|
||||
value = value.encode('utf8')
|
||||
if not isobject:
|
||||
value = pickle.dumps(value)
|
||||
if meta:
|
||||
meta = pickle.dumps(meta)
|
||||
ids_res = []
|
||||
for model in models:
|
||||
if type(model)==type([]) or type(model)==type(()):
|
||||
model, res_id = model
|
||||
else:
|
||||
res_id = False
|
||||
if replace:
|
||||
if key in ('meta', 'default'):
|
||||
ids = self.search(cursor, user, [
|
||||
('key', '=', key),
|
||||
('key2', '=', key2),
|
||||
('name', '=', name),
|
||||
('model', '=', model),
|
||||
('res_id', '=', res_id),
|
||||
('user_id', '=', preserve_user and user)
|
||||
])
|
||||
else:
|
||||
ids = self.search(cursor, user, [
|
||||
('key', '=', key),
|
||||
('key2', '=', key2),
|
||||
('value', '=', value),
|
||||
('model', '=', model),
|
||||
('res_id', '=', res_id),
|
||||
('user_id', '=', preserve_user and user)
|
||||
])
|
||||
self.unlink(cursor, user, ids)
|
||||
vals = {
|
||||
'name': name,
|
||||
'value': value,
|
||||
'model': model,
|
||||
'object': isobject,
|
||||
'key': key,
|
||||
'key2': key2 and key2[:200],
|
||||
'meta': meta,
|
||||
'user_id': preserve_user and user,
|
||||
}
|
||||
if company:
|
||||
cid = self.pool.get('res.users').browse(cursor, user, user,
|
||||
context={}).company_id.id
|
||||
vals['company_id'] = cid
|
||||
if res_id:
|
||||
vals['res_id'] = res_id
|
||||
ids_res.append(self.create(cursor, user, vals))
|
||||
return ids_res
|
||||
|
||||
def get(self, cursor, user, key, key2, models, meta=False, context=None,
|
||||
res_id_req=False, without_user=True, key2_req=True):
|
||||
result = []
|
||||
for model in models:
|
||||
if type(model)==type([]) or type(model)==type(()):
|
||||
model, res_id = model
|
||||
else:
|
||||
res_id = False
|
||||
where1 = ['key = %s', 'model = %s']
|
||||
where2 = [key, str(model)]
|
||||
where_opt = []
|
||||
if key2:
|
||||
where1.append('key2 = %s')
|
||||
where2.append(key2[:200])
|
||||
else:
|
||||
dest = where1
|
||||
if not key2_req or meta:
|
||||
dest = where_opt
|
||||
dest.append('key2 IS NULL')
|
||||
|
||||
if res_id_req and (models[-1][0]==model):
|
||||
if res_id:
|
||||
where1.append('res_id = %d' % (res_id,))
|
||||
else:
|
||||
where1.append('(res_id IS NULL)')
|
||||
elif res_id:
|
||||
if (models[-1][0]==model):
|
||||
where1.append('(res_id = %d OR (res_id IS NULL))' % (res_id,))
|
||||
where_opt.append('res_id = %d' % (res_id,))
|
||||
else:
|
||||
where1.append('res_id = %d' % (res_id,))
|
||||
|
||||
# if not without_user:
|
||||
where_opt.append('user_id = %d' % (user,))
|
||||
|
||||
|
||||
result = []
|
||||
test = True
|
||||
while test:
|
||||
if not where_opt:
|
||||
cursor.execute('SELECT id FROM ir_values ' \
|
||||
'where ' + ' and '.join(where1) + ' ' \
|
||||
'AND user_id IS NULL', where2)
|
||||
else:
|
||||
cursor.execute('select id FROM ir_values ' \
|
||||
'where ' + ' and '.join(where1 + where_opt),
|
||||
where2)
|
||||
result.extend([x[0] for x in cursor.fetchall()])
|
||||
if len(where_opt):
|
||||
where_opt.pop()
|
||||
else:
|
||||
test = False
|
||||
|
||||
if result:
|
||||
break
|
||||
|
||||
if not result:
|
||||
return []
|
||||
cid = self.pool.get('res.users').browse(cursor, user, user,
|
||||
context={}).company_id.id
|
||||
cursor.execute('SELECT id, name, value, object, meta, key ' \
|
||||
'FROM ir_values ' \
|
||||
'WHERE id IN (' + ','.join([str(x) for x in result])+') ' \
|
||||
'AND (company_id IS NULL OR company_id = %d) '\
|
||||
'ORDER BY user_id', (cid,))
|
||||
result = cursor.fetchall()
|
||||
|
||||
def _result_get(i, keys):
|
||||
if i[1] in keys:
|
||||
return False
|
||||
keys.append(i[1])
|
||||
if i[3]:
|
||||
model, obj_id = i[2].split(',')
|
||||
try:
|
||||
obj_id = int(obj_id)
|
||||
except:
|
||||
return False
|
||||
datas = self.pool.get(model).read(cursor, user, [obj_id], False,
|
||||
context=context)
|
||||
if not len(datas):
|
||||
#ir_del(cursor, user, i[0])
|
||||
return False
|
||||
def clean(j):
|
||||
for key in (
|
||||
'report_sxw_content',
|
||||
'report_rml_content',
|
||||
'report_sxw', 'report_rml',
|
||||
'report_sxw_content_data',
|
||||
'report_rml_content_data',
|
||||
):
|
||||
if key in j:
|
||||
del j[key]
|
||||
return j
|
||||
datas = clean(datas[0])
|
||||
else:
|
||||
datas = pickle.loads(i[2])
|
||||
if meta:
|
||||
meta2 = pickle.loads(i[4])
|
||||
return (i[0], i[1], datas, meta2)
|
||||
return (i[0], i[1], datas)
|
||||
keys = []
|
||||
res = filter(bool, map(lambda x: _result_get(x, keys), list(result)))
|
||||
return res
|
||||
|
||||
Values()
|
||||
@@ -0,0 +1,312 @@
|
||||
import os, sys, imp
|
||||
import itertools
|
||||
from sets import Set
|
||||
from config import CONFIG
|
||||
import tools
|
||||
import pooler
|
||||
from netsvc import Logger, LOG_ERROR, LOG_INFO
|
||||
import zipfile
|
||||
|
||||
OPJ = os.path.join
|
||||
ADDONS_PATH = OPJ(os.path.dirname(__file__), 'addons')
|
||||
sys.path.insert(1, ADDONS_PATH)
|
||||
|
||||
class Graph(dict):
|
||||
|
||||
def add_node(self, name, deps):
|
||||
max_depth, father = 0, None
|
||||
for i in [Node(x, self) for x in deps]:
|
||||
if i.depth >= max_depth:
|
||||
father = i
|
||||
max_depth = i.depth
|
||||
if father:
|
||||
father.add_child(name)
|
||||
else:
|
||||
Node(name, self)
|
||||
|
||||
def __iter__(self):
|
||||
level = 0
|
||||
done = Set(self.keys())
|
||||
while done:
|
||||
level_modules = [(name, module) for name, module in self.items() \
|
||||
if module.depth==level]
|
||||
for name, module in level_modules:
|
||||
done.remove(name)
|
||||
yield module
|
||||
level += 1
|
||||
|
||||
|
||||
class Singleton(object):
|
||||
|
||||
def __new__(cls, name, graph):
|
||||
if name in graph:
|
||||
inst = graph[name]
|
||||
else:
|
||||
inst = object.__new__(cls)
|
||||
graph[name] = inst
|
||||
return inst
|
||||
|
||||
|
||||
class Node(Singleton):
|
||||
|
||||
def __init__(self, name, graph):
|
||||
super(Node, self).__init__()
|
||||
self.name = name
|
||||
self.graph = graph
|
||||
self.datas = None
|
||||
if not hasattr(self, 'childs'):
|
||||
self.childs = []
|
||||
if not hasattr(self, 'depth'):
|
||||
self.depth = 0
|
||||
|
||||
def add_child(self, name):
|
||||
node = Node(name, self.graph)
|
||||
node.depth = self.depth + 1
|
||||
if node not in self.childs:
|
||||
self.childs.append(node)
|
||||
for attr in ('init', 'update', 'demo'):
|
||||
if hasattr(self, attr):
|
||||
setattr(node, attr, True)
|
||||
self.childs.sort(lambda x, y: cmp(x.name, y.name))
|
||||
|
||||
def has_child(self, name):
|
||||
return Node(name, self.graph) in self.childs or \
|
||||
bool([c for c in self.childs if c.has_child(name)])
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
super(Node, self).__setattr__(name, value)
|
||||
if name in ('init', 'update', 'demo'):
|
||||
CONFIG[name][self.name] = 1
|
||||
for child in self.childs:
|
||||
setattr(child, name, value)
|
||||
if name == 'depth':
|
||||
for child in self.childs:
|
||||
setattr(child, name, value + 1)
|
||||
|
||||
def __iter__(self):
|
||||
return itertools.chain(iter(self.childs),
|
||||
*[iter(x) for x in self.childs])
|
||||
|
||||
def __str__(self):
|
||||
return self.pprint()
|
||||
|
||||
def pprint(self, depth=0):
|
||||
res = '%s\n' % self.name
|
||||
for child in self.childs:
|
||||
res += '%s`-> %s' % (' ' * depth, child.pprint(depth + 1))
|
||||
return res
|
||||
|
||||
def create_graph(module_list, force=None):
|
||||
if force is None:
|
||||
force = []
|
||||
graph = Graph()
|
||||
packages = []
|
||||
|
||||
for module in module_list:
|
||||
if module[-4:] == '.zip':
|
||||
module = module[:-4]
|
||||
terp_file = OPJ(ADDONS_PATH, module, '__terp__.py')
|
||||
mod_path = OPJ(ADDONS_PATH, module)
|
||||
if module in ('ir', 'res'):
|
||||
root_path = os.path.dirname(__file__)
|
||||
terp_file = OPJ(root_path, module, '__terp__.py')
|
||||
mod_path = OPJ(root_path, module)
|
||||
if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
|
||||
try:
|
||||
info = eval(tools.file_open(terp_file).read())
|
||||
except:
|
||||
Logger().notify_channel('init', LOG_ERROR,
|
||||
'addon:%s:eval file %s' % (module, terp_file))
|
||||
raise
|
||||
if info.get('installable', True):
|
||||
packages.append((module, info.get('depends', []), info))
|
||||
|
||||
current, later = Set([x[0] for x in packages]), Set()
|
||||
while packages and current > later:
|
||||
package, deps, datas = packages[0]
|
||||
|
||||
# if all dependencies of 'package' are already in the graph,
|
||||
# add 'package' in the graph
|
||||
if reduce(lambda x, y: x and y in graph, deps, True):
|
||||
if not package in current:
|
||||
packages.pop(0)
|
||||
continue
|
||||
later.clear()
|
||||
current.remove(package)
|
||||
graph.add_node(package, deps)
|
||||
node = Node(package, graph)
|
||||
node.datas = datas
|
||||
for kind in ('init', 'demo', 'update'):
|
||||
if (package in CONFIG[kind]) \
|
||||
or ('all' in CONFIG[kind]) \
|
||||
or (kind in force):
|
||||
setattr(node, kind, True)
|
||||
else:
|
||||
later.add(package)
|
||||
packages.append((package, deps, datas))
|
||||
packages.pop(0)
|
||||
|
||||
for package in later:
|
||||
Logger().notify_channel('init', LOG_ERROR,
|
||||
'addon:%s:Unmet dependency' % package)
|
||||
return graph
|
||||
|
||||
def init_module_objects(cursor, module_name, obj_list):
|
||||
Logger().notify_channel('init', LOG_INFO,
|
||||
'addon:%s:creating or updating database tables' % module_name)
|
||||
for obj in obj_list:
|
||||
obj.auto_init(cursor)
|
||||
|
||||
def load_module_graph(cursor, graph, **kwargs):
|
||||
# **kwargs is passed directly to convert_xml_import
|
||||
package_todo = []
|
||||
statusi = 0
|
||||
for package in graph:
|
||||
module = package.name
|
||||
Logger().notify_channel('init', LOG_INFO, 'addon:%s' % module)
|
||||
sys.stdout.flush()
|
||||
pool = pooler.get_pool(cursor.dbname)
|
||||
modules = pool.instanciate(module)
|
||||
cursor.execute('SELECT state, demo FROM ir_module_module WHERE name = %s',
|
||||
(module,))
|
||||
(package_state, package_demo) = (cursor.rowcount and cursor.fetchone()) \
|
||||
or ('uninstalled', False)
|
||||
idref = {}
|
||||
if hasattr(package, 'init') \
|
||||
or hasattr(package, 'update') \
|
||||
or (package_state in ('to install', 'to upgrade')):
|
||||
init_module_objects(cursor, module, modules)
|
||||
for kind in ('init', 'update'):
|
||||
for filename in package.datas.get('%s_xml' % kind, []):
|
||||
mode = 'update'
|
||||
if hasattr(package, 'init') or package_state=='to install':
|
||||
mode = 'init'
|
||||
Logger().notify_channel('init', LOG_INFO,
|
||||
'addon:%s:loading %s' % (module, filename))
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if ext == '.csv':
|
||||
tools.convert_csv_import(cursor, module,
|
||||
os.path.basename(filename),
|
||||
tools.file_open(OPJ(module, filename)).read(),
|
||||
idref, mode=mode)
|
||||
elif ext == '.sql':
|
||||
queries = tools.file_open(OPJ(module,
|
||||
filename)).read().split(';')
|
||||
for query in queries:
|
||||
new_query = ' '.join(query.split())
|
||||
if new_query:
|
||||
cursor.execute(new_query)
|
||||
else:
|
||||
tools.convert_xml_import(cursor, module,
|
||||
tools.file_open(OPJ(module, filename)).read(),
|
||||
idref, mode=mode, **kwargs)
|
||||
if hasattr(package, 'demo') \
|
||||
or (package_demo and package_state != 'installed'):
|
||||
for xml in package.datas.get('demo_xml', []):
|
||||
ext = os.path.splitext(xml)[1]
|
||||
Logger().notify_channel('init', LOG_INFO,
|
||||
'addon:%s:loading %s' % (module, xml))
|
||||
if ext == '.csv':
|
||||
tools.convert_csv_import(cursor, module,
|
||||
os.path.basename(xml),
|
||||
tools.file_open(OPJ(module, xml)).read(), idref,
|
||||
noupdate=True)
|
||||
else:
|
||||
tools.convert_xml_import(cursor, module,
|
||||
tools.file_open(OPJ(module, xml)).read(), idref,
|
||||
noupdate=True, **kwargs)
|
||||
cursor.execute('UPDATE ir_module_module SET demo = %s ' \
|
||||
'WHERE name = %s', (True, package.name))
|
||||
package_todo.append(package.name)
|
||||
cursor.execute("UPDATE ir_module_module SET state = 'installed' " \
|
||||
"WHERE state IN ('to upgrade', 'to install') " \
|
||||
"AND name = %s", (package.name,))
|
||||
cursor.commit()
|
||||
statusi += 1
|
||||
|
||||
pool = pooler.get_pool(cursor.dbname)
|
||||
pool.get('ir.model.data')._process_end(cursor, 1, package_todo)
|
||||
cursor.commit()
|
||||
|
||||
def register_classes():
|
||||
module_list = os.listdir(ADDONS_PATH)
|
||||
module_list.append('ir')
|
||||
import ir
|
||||
import res
|
||||
for package in create_graph(module_list):
|
||||
module = package.name
|
||||
Logger().notify_channel('init', LOG_INFO,
|
||||
'addon:%s:registering classes' % module)
|
||||
sys.stdout.flush()
|
||||
|
||||
if module in ('ir', 'res'):
|
||||
continue
|
||||
|
||||
if not os.path.isfile(OPJ(ADDONS_PATH, module+'.zip')):
|
||||
# XXX must restrict to only addons paths
|
||||
imp.load_module(module, *imp.find_module(module))
|
||||
else:
|
||||
import zipimport
|
||||
mod_path = OPJ(ADDONS_PATH, module+'.zip')
|
||||
try:
|
||||
zimp = zipimport.zipimporter(mod_path)
|
||||
zimp.load_module(module)
|
||||
except zipimport.ZipImportError:
|
||||
Logger().notify_channel('init', LOG_ERROR,
|
||||
'Couldn\'t find module %s' % module)
|
||||
|
||||
def load_modules(database, force_demo=False, update_module=False):
|
||||
cursor = database.cursor()
|
||||
force = []
|
||||
if force_demo:
|
||||
force.append('demo')
|
||||
if update_module:
|
||||
cursor.execute("SELECT name FROM ir_module_module " \
|
||||
"WHERE state IN ('installed', 'to install', " \
|
||||
"'to upgrade', 'to remove')")
|
||||
else:
|
||||
cursor.execute("SELECT name FROM ir_module_module " \
|
||||
"WHERE state IN ('installed', 'to upgrade', 'to remove')")
|
||||
module_list = [name for (name,) in cursor.fetchall()]
|
||||
graph = create_graph(module_list, force)
|
||||
report = tools.AssertionReport()
|
||||
load_module_graph(cursor, graph, report=report)
|
||||
if report.get_report():
|
||||
Logger().notify_channel('init', LOG_INFO, 'assert:%s' % report)
|
||||
|
||||
for kind in ('init', 'demo', 'update'):
|
||||
CONFIG[kind] = {}
|
||||
|
||||
if update_module:
|
||||
cursor.execute("SELECT name FROM ir_module_module " \
|
||||
"WHERE state IN ('to remove')")
|
||||
for (mod_name,) in cursor.fetchall():
|
||||
pool = pooler.get_pool(cursor.dbname)
|
||||
cursor.execute('SELECT model,res_id FROM ir_model_data ' \
|
||||
'WHERE NOT noupdate AND module = %s ' \
|
||||
'ORDER BY id DESC', (mod_name,))
|
||||
for rmod, rid in cursor.fetchall():
|
||||
# TODO: Improved
|
||||
# I can not use the class_pool has _table could be
|
||||
# defined in __init__ and I can not use the pool has
|
||||
# the module could not be loaded in the pool
|
||||
uid = 1
|
||||
pool.get(rmod).unlink(cursor, uid, [rid])
|
||||
cursor.commit()
|
||||
# TODO: remove menu without actions of childs
|
||||
cursor.execute('DELETE FROM ir_ui_menu ' \
|
||||
'WHERE (id NOT IN (' \
|
||||
'SELECT parent_id FROM ir_ui_menu ' \
|
||||
'WHERE parent_id IS NOT NULL)) ' \
|
||||
'AND (id NOT IN (' \
|
||||
'SELECT res_id FROM ir_values ' \
|
||||
'WHERE model = \'ir.ui.menu\')) ' \
|
||||
'AND (id NOT IN (' \
|
||||
'SELECT res_id FROM ir_model_data ' \
|
||||
'WHERE model = \'ir.ui.menu\'))')
|
||||
|
||||
cursor.execute("UPDATE ir_module_module SET state = %s " \
|
||||
"WHERE state IN ('to remove')", ('uninstalled',))
|
||||
cursor.commit()
|
||||
pooler.restart_pool(cursor.dbname)
|
||||
cursor.close()
|
||||
@@ -0,0 +1,453 @@
|
||||
import time
|
||||
import threading
|
||||
import SimpleXMLRPCServer, signal, sys, xmlrpclib
|
||||
import SocketServer
|
||||
import socket
|
||||
import logging
|
||||
import os
|
||||
import tiny_socket
|
||||
|
||||
_SERVICE = {}
|
||||
_GROUP = {}
|
||||
_RES = {}
|
||||
|
||||
LOG_DEBUG = 'debug'
|
||||
LOG_INFO = 'info'
|
||||
LOG_WARNING = 'warn'
|
||||
LOG_ERROR = 'error'
|
||||
LOG_CRITICAL = 'critical'
|
||||
|
||||
from config import CONFIG
|
||||
|
||||
|
||||
class ServiceEndPointCall(object):
|
||||
|
||||
def __init__(self, id, method):
|
||||
self._id = id
|
||||
self._meth = method
|
||||
|
||||
def __call__(self, *args):
|
||||
_RES[self._id] = self._meth(*args)
|
||||
return self._id
|
||||
|
||||
|
||||
class ServiceEndPoint(object):
|
||||
|
||||
def __init__(self, name, id):
|
||||
self._id = id
|
||||
self._meth = {}
|
||||
service = _SERVICE[name]
|
||||
for method in service.method:
|
||||
self._meth[method] = service.method[method]
|
||||
|
||||
def __getattr__(self, name):
|
||||
return ServiceEndPointCall(self._id, self._meth[name])
|
||||
|
||||
|
||||
class Service(object):
|
||||
_serviceEndPointID = 0
|
||||
|
||||
def __init__(self, name=None):
|
||||
if name is None:
|
||||
return
|
||||
_SERVICE[name] = self
|
||||
self.__name = name
|
||||
self.method = {}
|
||||
self.exportedmethods = None
|
||||
self._response_process = None
|
||||
self._response_process_id = None
|
||||
self.response = None
|
||||
|
||||
def join_group(self, name):
|
||||
if not name in _GROUP:
|
||||
_GROUP[name] = {}
|
||||
_GROUP[name][self.__name] = self
|
||||
|
||||
def export_method(self, method):
|
||||
if callable(method):
|
||||
self.method[method.__name__] = method
|
||||
|
||||
def service_end_point(self, service):
|
||||
if Service._serviceEndPointID >= 2**16:
|
||||
Service._serviceEndPointID = 0
|
||||
Service._serviceEndPointID += 1
|
||||
return ServiceEndPoint(service, self._serviceEndPointID)
|
||||
|
||||
def conversation_id(self):
|
||||
return 1
|
||||
|
||||
def process_response(self, service, id):
|
||||
self._response_process, self._response_process_id = service, id
|
||||
|
||||
def process_failure(self, service, id):
|
||||
pass
|
||||
|
||||
def resume_response(self, service):
|
||||
pass
|
||||
|
||||
def cancel_response(self, service):
|
||||
pass
|
||||
|
||||
def suspend_response(self, service):
|
||||
if self._response_process:
|
||||
self._response_process(self._response_process_id,
|
||||
_RES[self._response_process_id])
|
||||
self._response_process = None
|
||||
self.response = service(self._response_process_id)
|
||||
|
||||
def abort_response(self, description, origin, details):
|
||||
if not CONFIG['debug_mode']:
|
||||
raise Exception("%s -- %s\n\n%s" % (origin, description, details))
|
||||
else:
|
||||
raise
|
||||
|
||||
def current_failure(self, service):
|
||||
pass
|
||||
|
||||
def exec_workflow(self, database, user, passwd, object_name, method,
|
||||
object_id):
|
||||
return
|
||||
|
||||
def execute(self, database, user, passwd, object_name, method, *args):
|
||||
return
|
||||
|
||||
def execute_cr(self, cursor, user, object_name, method, *args, **kargs):
|
||||
return
|
||||
|
||||
def obj_list(self, database, user, passwd):
|
||||
return
|
||||
|
||||
def create(self, cursor, user, ids, datas, context):
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def trg_validate(user, res_type, res_id, signal, cursor):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def trg_delete(user, res_type, res_id, cursor):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def trg_write(user, res_type, res_id, cursor):
|
||||
pass
|
||||
|
||||
def trg_create(self, user, res_type, res_id, cursor):
|
||||
pass
|
||||
|
||||
class LocalService(Service):
|
||||
|
||||
def __init__(self, name):
|
||||
super(LocalService, self).__init__()
|
||||
self.__name = name
|
||||
service = _SERVICE[name]
|
||||
self.service = service
|
||||
for method in service.method:
|
||||
setattr(self, method, service.method[method])
|
||||
|
||||
|
||||
class ServiceUnavailable(Exception):
|
||||
pass
|
||||
|
||||
def service_exist(name):
|
||||
return (name in _SERVICE) and bool(_SERVICE[name])
|
||||
|
||||
def init_logger():
|
||||
if CONFIG['logfile']:
|
||||
logf = CONFIG['logfile']
|
||||
# test if the directories exist, else create them
|
||||
try:
|
||||
if not os.path.exists(os.path.dirname(logf)):
|
||||
os.makedirs(os.path.dirname(logf))
|
||||
try:
|
||||
fd_log = open(logf, 'a')
|
||||
handler = logging.StreamHandler(fd_log)
|
||||
except IOError:
|
||||
sys.stderr.write("ERROR: couldn't open the logfile\n")
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
except OSError:
|
||||
sys.stderr.write("ERROR: couldn't create the logfile directory\n")
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
else:
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
|
||||
# create a format for log messages and dates
|
||||
formatter = logging.Formatter(
|
||||
'[%(asctime)s] %(levelname)s:%(name)s:%(message)s',
|
||||
'%a %b %d %H:%M:%S %Y')
|
||||
|
||||
# tell the handler to use this format
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
# add the handler to the root logger
|
||||
logging.getLogger().addHandler(handler)
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
|
||||
class Logger(object):
|
||||
|
||||
def notify_channel(self, name, level, msg):
|
||||
log = logging.getLogger(name)
|
||||
getattr(log, level)(msg)
|
||||
|
||||
|
||||
class Agent(object):
|
||||
_timers = []
|
||||
_logger = Logger()
|
||||
|
||||
def set_alarm(self, function, time_start, args=None, kwargs=None):
|
||||
if not args:
|
||||
args = []
|
||||
if not kwargs:
|
||||
kwargs = {}
|
||||
wait = time_start - time.time()
|
||||
if wait > 0:
|
||||
self._logger.notify_channel('timers', LOG_DEBUG,
|
||||
"Job scheduled in %s seconds for %s.%s" % \
|
||||
(wait, function.im_class.__name__,
|
||||
function.func_name))
|
||||
timer = threading.Timer(wait, function, args, kwargs)
|
||||
timer.start()
|
||||
self._timers.append(timer)
|
||||
for timer in self._timers[:]:
|
||||
if not timer.isAlive():
|
||||
self._timers.remove(timer)
|
||||
|
||||
def quit(cls):
|
||||
for timer in cls._timers:
|
||||
timer.cancel()
|
||||
quit = classmethod(quit)
|
||||
|
||||
|
||||
class RpcGateway(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
class Dispatcher(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def monitor(self, sig):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
|
||||
class XmlRpc(object):
|
||||
|
||||
|
||||
class RpcGateway(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
class GenericXMLRPCRequestHandler(object):
|
||||
|
||||
def __init__(self):
|
||||
self.path = ''
|
||||
|
||||
def _dispatch(self, method, params):
|
||||
import traceback
|
||||
try:
|
||||
name = self.path.split("/")[-1]
|
||||
service = LocalService(name)
|
||||
meth = getattr(service, method)
|
||||
service.service.response = None
|
||||
res = meth(*params)
|
||||
res2 = service.service.response
|
||||
if res2 != None:
|
||||
res = res2
|
||||
return res
|
||||
except Exception, exp:
|
||||
tb_s = reduce(lambda x, y: x+y, traceback.format_exception(
|
||||
sys.exc_type, sys.exc_value, sys.exc_traceback))
|
||||
if CONFIG['debug_mode']:
|
||||
import pdb
|
||||
traceb = sys.exc_info()[2]
|
||||
pdb.post_mortem(traceb)
|
||||
raise xmlrpclib.Fault(str(exp), tb_s)
|
||||
|
||||
|
||||
class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler,
|
||||
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
|
||||
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.rpc_paths = (
|
||||
'/xmlrpc/db',
|
||||
'/xmlrpc/common',
|
||||
'/xmlrpc/object',
|
||||
'/xmlrpc/report',
|
||||
'/xmlrpc/wizard',
|
||||
)
|
||||
|
||||
|
||||
class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn,
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer):
|
||||
|
||||
def server_bind(self):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR, 1)
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self)
|
||||
|
||||
|
||||
class HttpDaemon(threading.Thread):
|
||||
|
||||
def __init__(self, interface, port, secure=False):
|
||||
threading.Thread.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
self.secure = secure
|
||||
self.running = False
|
||||
if secure:
|
||||
# from ssl import SecureXMLRPCServer
|
||||
# class SecureXMLRPCRequestHandler(GenericXMLRPCRequestHandler,
|
||||
# SecureXMLRPCServer.SecureXMLRPCRequestHandler):
|
||||
# SecureXMLRPCServer.SecureXMLRPCRequestHandler.rpc_paths = (
|
||||
# '/xmlrpc/db',
|
||||
# '/xmlrpc/common',
|
||||
# '/xmlrpc/object',
|
||||
# '/xmlrpc/report',
|
||||
# '/xmlrpc/wizard',
|
||||
# )
|
||||
# class SecureThreadedXMLRPCServer(SocketServer.ThreadingMixIn,
|
||||
# SecureXMLRPCServer.SecureXMLRPCServer):
|
||||
#
|
||||
# def server_bind(self):
|
||||
# self.socket.setsockopt(socket.SOL_SOCKET,
|
||||
# socket.SO_REUSEADDR, 1)
|
||||
# SecureXMLRPCServer.SecureXMLRPCServer.server_bind(self)
|
||||
#
|
||||
# self.server = SecureThreadedXMLRPCServer((interface, port),
|
||||
# SecureXMLRPCRequestHandler,0)
|
||||
raise
|
||||
else:
|
||||
self.server = SimpleThreadedXMLRPCServer((interface, port),
|
||||
SimpleXMLRPCRequestHandler,0)
|
||||
|
||||
def attach(self, path, gateway):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
if os.name != 'nt':
|
||||
if hasattr(socket, 'SHUT_RDWR'):
|
||||
if self.secure:
|
||||
self.server.socket.sock_shutdown(socket.SHUT_RDWR)
|
||||
else:
|
||||
self.server.socket.shutdown(socket.SHUT_RDWR)
|
||||
else:
|
||||
if self.secure:
|
||||
self.server.socket.sock_shutdown(2)
|
||||
else:
|
||||
self.server.socket.shutdown(2)
|
||||
self.server.socket.close()
|
||||
|
||||
def run(self):
|
||||
self.server.register_introspection_functions()
|
||||
|
||||
self.running = True
|
||||
while self.running:
|
||||
self.server.handle_request()
|
||||
return True
|
||||
|
||||
# If the server need to be run recursively
|
||||
#
|
||||
#signal.signal(signal.SIGALRM, self.my_handler)
|
||||
#signal.alarm(6)
|
||||
#while True:
|
||||
# self.server.handle_request()
|
||||
#signal.alarm(0) # Disable the alarm
|
||||
|
||||
|
||||
class TinySocketClientThread(threading.Thread):
|
||||
def __init__(self, sock, threads):
|
||||
threading.Thread.__init__(self)
|
||||
self.sock = sock
|
||||
self.threads = threads
|
||||
self.running = False
|
||||
|
||||
def run(self):
|
||||
import traceback
|
||||
self.running = True
|
||||
try:
|
||||
tsock = tiny_socket.MySocket(self.sock)
|
||||
except:
|
||||
self.sock.close()
|
||||
self.threads.remove(self)
|
||||
return False
|
||||
while self.running:
|
||||
try:
|
||||
msg = tsock.myreceive()
|
||||
except:
|
||||
self.sock.close()
|
||||
self.threads.remove(self)
|
||||
return False
|
||||
try:
|
||||
service = LocalService(msg[0])
|
||||
method = getattr(service, msg[1])
|
||||
service.service.response = None
|
||||
res = method(*msg[2:])
|
||||
res2 = service.service.response
|
||||
if res2 != None:
|
||||
res = res2
|
||||
tsock.mysend(res)
|
||||
except Exception, exp:
|
||||
tb_s = reduce(lambda x, y: x+y,
|
||||
traceback.format_exception(sys.exc_type,
|
||||
sys.exc_value, sys.exc_traceback))
|
||||
if CONFIG['debug_mode']:
|
||||
import pdb
|
||||
tback = sys.exc_info()[2]
|
||||
pdb.post_mortem(tback)
|
||||
tsock.mysend(exp, exception=True, traceback=tb_s)
|
||||
except:
|
||||
pass
|
||||
self.sock.close()
|
||||
self.threads.remove(self)
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
class TinySocketServerThread(threading.Thread):
|
||||
def __init__(self, interface, port, secure=False):
|
||||
threading.Thread.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.bind((self.__interface, self.__port))
|
||||
self.socket.listen(5)
|
||||
self.threads = []
|
||||
self.secure = secure
|
||||
self.running = False
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.running = True
|
||||
while self.running:
|
||||
(clientsocket, address) = self.socket.accept()
|
||||
c_thread = TinySocketClientThread(clientsocket, self.threads)
|
||||
self.threads.append(c_thread)
|
||||
c_thread.start()
|
||||
self.socket.close()
|
||||
except Exception, exp:
|
||||
self.socket.close()
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
for thread in self.threads:
|
||||
thread.stop()
|
||||
try:
|
||||
if hasattr(socket, 'SHUT_RDWR'):
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
else:
|
||||
self.socket.shutdown(2)
|
||||
self.socket.close()
|
||||
except:
|
||||
return False
|
||||
@@ -0,0 +1 @@
|
||||
from osv import *
|
||||
@@ -0,0 +1,633 @@
|
||||
# -*- encoding: iso-8859-1 -*-
|
||||
"""
|
||||
Fields:
|
||||
- simple
|
||||
- relations (one2many, many2one, many2many)
|
||||
- function
|
||||
|
||||
Fields Attributes:
|
||||
_classic_read: is a classic sql fields
|
||||
_type : field type
|
||||
readonly
|
||||
required
|
||||
size
|
||||
|
||||
Relationals fields
|
||||
|
||||
Values: (0, 0, { fields }) create
|
||||
(1, ID, { fields }) modification
|
||||
(2, ID) remove (delete)
|
||||
(3, ID) unlink one (target id or target of relation)
|
||||
(4, ID) link
|
||||
(5, ID) unlink all (only valid for one2many)
|
||||
(6, ?, ids) set a list of links
|
||||
"""
|
||||
|
||||
import psycopg
|
||||
import warnings
|
||||
import __builtin__
|
||||
|
||||
def _symbol_set(symb):
|
||||
if symb == None or symb == False:
|
||||
return None
|
||||
elif isinstance(symb, unicode):
|
||||
return symb.encode('utf-8')
|
||||
return str(symb)
|
||||
|
||||
class _column(object):
|
||||
_classic_read = True
|
||||
_classic_write = True
|
||||
_properties = False
|
||||
_type = 'unknown'
|
||||
_obj = None
|
||||
_symbol_c = '%s'
|
||||
_symbol_f = _symbol_set
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
_symbol_get = None
|
||||
|
||||
def __init__(self, string='unknown', required=False, readonly=False,
|
||||
domain=None, context='', states=None, priority=0,
|
||||
change_default=False, size=None, ondelete="set null",
|
||||
translate=False, select=False, **args):
|
||||
self.states = states or {}
|
||||
self.string = string
|
||||
self.readonly = readonly
|
||||
self.required = required
|
||||
self.size = size
|
||||
self.help = args.get('help', '')
|
||||
self.priority = priority
|
||||
self.change_default = change_default
|
||||
self.ondelete = ondelete
|
||||
self.translate = translate
|
||||
self._domain = domain or []
|
||||
self._context = context
|
||||
self.group_name = False
|
||||
self.view_load = 0
|
||||
self.select = select
|
||||
for i in args:
|
||||
if args[i]:
|
||||
setattr(self, i, args[i])
|
||||
|
||||
def restart(self):
|
||||
pass
|
||||
|
||||
def set(self, cursor, obj, obj_id, name, value, user=None, context=None):
|
||||
raise Exception, 'undefined get method !'
|
||||
|
||||
def get(self, cursor, obj, ids, name, user=None, offset=0, context=None,
|
||||
values=None):
|
||||
raise Exception, 'undefined get method !'
|
||||
|
||||
|
||||
class Boolean(_column):
|
||||
_type = 'boolean'
|
||||
_symbol_c = '%s'
|
||||
_symbol_f = lambda x: x and 'True' or 'False'
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
|
||||
boolean = Boolean
|
||||
|
||||
|
||||
class Integer(_column):
|
||||
_type = 'integer'
|
||||
_symbol_c = '%d'
|
||||
_symbol_f = lambda x: int(x or 0)
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
|
||||
integer = Integer
|
||||
|
||||
|
||||
class Reference(_column):
|
||||
_type = 'reference'
|
||||
|
||||
def __init__(self, string, selection, size, **args):
|
||||
_column.__init__(self, string=string, size=size, selection=selection,
|
||||
**args)
|
||||
|
||||
reference = Reference
|
||||
|
||||
|
||||
class Char(_column):
|
||||
_type = 'char'
|
||||
|
||||
def __init__(self, string, size, **args):
|
||||
_column.__init__(self, string=string, size=size, **args)
|
||||
self._symbol_set = (self._symbol_c, self._symbol_set_char)
|
||||
|
||||
@staticmethod
|
||||
def _symbol_set_char(symb):
|
||||
"""
|
||||
takes a string (encoded in utf8)
|
||||
and returns a string (encoded in utf8)
|
||||
"""
|
||||
#TODO we need to remove the "symb==False" from the next line BUT
|
||||
#TODO for now too many things rely on this broken behavior
|
||||
#TODO the symb==None test should be common to all data types
|
||||
if symb == None or symb == False:
|
||||
return None
|
||||
|
||||
# we need to convert the string to a unicode object to be able
|
||||
# to evaluate its length (and possibly truncate it) reliably
|
||||
if isinstance(symb, str):
|
||||
u_symb = unicode(symb, 'utf8')
|
||||
elif isinstance(symb, unicode):
|
||||
u_symb = symb
|
||||
else:
|
||||
u_symb = unicode(symb)
|
||||
return u_symb.encode('utf8')
|
||||
|
||||
char = Char
|
||||
|
||||
|
||||
class Text(_column):
|
||||
_type = 'text'
|
||||
|
||||
text = Text
|
||||
|
||||
|
||||
class Float(_column):
|
||||
_type = 'float'
|
||||
_symbol_c = '%f'
|
||||
_symbol_f = lambda x: __builtin__.float(x or 0.0)
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
|
||||
def __init__(self, string='unknown', digits=None, **args):
|
||||
_column.__init__(self, string=string, **args)
|
||||
self.digits = digits
|
||||
|
||||
float = Float
|
||||
|
||||
|
||||
class Date(_column):
|
||||
_type = 'date'
|
||||
|
||||
date = Date
|
||||
|
||||
|
||||
class DateTime(_column):
|
||||
_type = 'datetime'
|
||||
|
||||
datetime = DateTime
|
||||
|
||||
|
||||
class Time(_column):
|
||||
_type = 'time'
|
||||
|
||||
time = Time
|
||||
|
||||
|
||||
class Binary(_column):
|
||||
_type = 'binary'
|
||||
_symbol_c = '%s'
|
||||
_symbol_f = lambda symb: symb and psycopg.Binary(symb) or None
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
|
||||
binary = Binary
|
||||
|
||||
|
||||
class Selection(_column):
|
||||
_type = 'selection'
|
||||
|
||||
def __init__(self, selections, string='unknown', **args):
|
||||
_column.__init__(self, string=string, **args)
|
||||
self.selection = selections
|
||||
|
||||
selection = Selection
|
||||
|
||||
|
||||
class One2One(_column):
|
||||
_classic_read = False
|
||||
_classic_write = True
|
||||
_type = 'one2one'
|
||||
|
||||
def __init__(self, obj, string='unknown', **args):
|
||||
warnings.warn("The one2one field doesn't work anymore",
|
||||
DeprecationWarning)
|
||||
_column.__init__(self, string=string, **args)
|
||||
self._obj = obj
|
||||
|
||||
def set(self, cursor, obj_src, src_id, field, act, user=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
obj = obj_src.pool.get(self._obj)
|
||||
if act[0] == 0:
|
||||
id_new = obj.create(cursor, user, act[1])
|
||||
cursor.execute('UPDATE "' + obj_src._table + '" ' \
|
||||
'SET "' + field + '" = %d ' \
|
||||
'WHERE id = %d', (id_new, src_id))
|
||||
else:
|
||||
cursor.execute('SELECT "' + field + '" ' \
|
||||
'FROM "' + obj_src._table + '" ' \
|
||||
'WHERE id = %d', (act[0],))
|
||||
(obj_id,) = cursor.fetchone()
|
||||
obj.write(cursor, user, [obj_id] , act[1], context=context)
|
||||
|
||||
one2one = One2One
|
||||
|
||||
|
||||
class Many2One(_column):
|
||||
_classic_read = False
|
||||
_classic_write = True
|
||||
_type = 'many2one'
|
||||
|
||||
def __init__(self, obj, string='unknown', **args):
|
||||
_column.__init__(self, string=string, **args)
|
||||
self._obj = obj
|
||||
|
||||
# TODO: speed improvement
|
||||
# name is the name of the relation field
|
||||
def get(self, cursor, obj, ids, name, user=None, offset=0, context=None,
|
||||
values=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if values is None:
|
||||
values = {}
|
||||
res = {}
|
||||
for i in values:
|
||||
res[i['id']] = i[name]
|
||||
for i in ids:
|
||||
res.setdefault(i, '')
|
||||
obj = obj.pool.get(self._obj)
|
||||
names = obj.name_get(cursor, user, res.values(), context=context)
|
||||
|
||||
for i in res.keys():
|
||||
if res[i] and res[i] in names:
|
||||
res[i] = (res[i], names[res[i]])
|
||||
else:
|
||||
res[i] = False
|
||||
return res
|
||||
|
||||
def set(self, cursor, obj_src, obj_id, field, values, user=None,
|
||||
context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
obj = obj_src.pool.get(self._obj)
|
||||
table = obj_src.pool.get(self._obj)._table
|
||||
if type(values) == type([]):
|
||||
for act in values:
|
||||
if act[0] == 0:
|
||||
id_new = obj.create(cursor, act[2])
|
||||
cursor.execute('UPDATE "' + obj_src._table + '" ' \
|
||||
'SET "' + field + '" = %d ' \
|
||||
'WHERE id = %d', (id_new, obj_id))
|
||||
elif act[0] == 1:
|
||||
obj.write(cursor, [act[1]], act[2], context=context)
|
||||
elif act[0] == 2:
|
||||
cursor.execute('DELETE FROM "' + table + '" ' \
|
||||
'WHERE id = %d', (act[1],))
|
||||
elif act[0] == 3 or act[0] == 5:
|
||||
cursor.execute('UPDATE "' + obj_src._table + '" ' \
|
||||
'SET "' + field + '" = NULL ' \
|
||||
'WHERE id = %d', (obj_id,))
|
||||
elif act[0] == 4:
|
||||
cursor.execute('UPDATE "' + obj_src._table + '" ' \
|
||||
'SET "' + field + '" = %d ' \
|
||||
'WHERE id = %d', (act[1], obj_id))
|
||||
else:
|
||||
if values:
|
||||
cursor.execute('UPDATE "' + obj_src._table + '" ' \
|
||||
'SET "' + field + '" = %d ' \
|
||||
'WHERE id = %d', (values, obj_id))
|
||||
else:
|
||||
cursor.execute('UPDATE "' + obj_src._table + '" ' \
|
||||
'SET "' + field + '" = NULL ' \
|
||||
'WHERE id = %d', (obj_id,))
|
||||
|
||||
many2one = Many2One
|
||||
|
||||
|
||||
class One2Many(_column):
|
||||
_classic_read = False
|
||||
_classic_write = False
|
||||
_type = 'one2many'
|
||||
|
||||
def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
|
||||
_column.__init__(self, string=string, **args)
|
||||
self._obj = obj
|
||||
self._fields_id = fields_id
|
||||
self._limit = limit
|
||||
#one2many can't be used as condition for defaults
|
||||
assert(self.change_default != True)
|
||||
|
||||
def get(self, cursor, obj, ids, name, user=None, offset=0, context=None,
|
||||
values=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if values is None:
|
||||
values = {}
|
||||
res = {}
|
||||
for i in ids:
|
||||
res[i] = []
|
||||
ids2 = obj.pool.get(self._obj).search(cursor, user,
|
||||
[(self._fields_id, 'in', ids)], offset=offset,
|
||||
limit=self._limit)
|
||||
for i in obj.pool.get(self._obj)._read_flat(cursor, user, ids2,
|
||||
[self._fields_id], context=context, load='_classic_write'):
|
||||
res[i[self._fields_id]].append( i['id'] )
|
||||
return res
|
||||
|
||||
def set(self, cursor, obj, obj_id, field, values, user=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if not values:
|
||||
return
|
||||
_table = obj.pool.get(self._obj)._table
|
||||
obj = obj.pool.get(self._obj)
|
||||
for act in values:
|
||||
if act[0] == 0:
|
||||
act[2][self._fields_id] = obj_id
|
||||
obj.create(cursor, user, act[2], context=context)
|
||||
elif act[0] == 1:
|
||||
obj.write(cursor, user, [act[1]] , act[2], context=context)
|
||||
elif act[0] == 2:
|
||||
obj.unlink(cursor, user, [act[1]], context=context)
|
||||
elif act[0] == 3:
|
||||
cursor.execute('UPDATE "' + _table + '" ' \
|
||||
'SET "' + self._fields_id + '" = NULL ' \
|
||||
'WHERE id = %d', (act[1],))
|
||||
elif act[0] == 4:
|
||||
cursor.execute('UPDATE "' + _table + '" ' \
|
||||
'SET "' + self._fields_id + '" = %d ' \
|
||||
'WHERE id = %d', (obj_id, act[1]))
|
||||
elif act[0] == 5:
|
||||
cursor.execute('UPDATE "' + _table + '" ' \
|
||||
'SET "' + self._fields_id + '" = NULL ' \
|
||||
'WHERE "' + self._fields_id + '" = %d', (obj_id,))
|
||||
elif act[0] == 6:
|
||||
if not act[2]:
|
||||
ids2 = [0]
|
||||
else:
|
||||
ids2 = act[2]
|
||||
cursor.execute('UPDATE "' + _table + '" ' \
|
||||
'SET "' + self._fields_id + '" = NULL ' \
|
||||
'WHERE "' + self._fields_id + '" = %d ' \
|
||||
'AND id not IN (' + \
|
||||
','.join([str(x) for x in ids2]) + ')',
|
||||
(obj_id,))
|
||||
if act[2]:
|
||||
cursor.execute('UPDATE "' + _table + '" ' \
|
||||
'SET "' + self._fields_id + '" = %d ' \
|
||||
'WHERE id IN (' + \
|
||||
','.join([str(x) for x in act[2]]) + ')',
|
||||
(obj_id,))
|
||||
|
||||
one2many = One2Many
|
||||
|
||||
|
||||
class Many2Many(_column):
|
||||
_classic_read = False
|
||||
_classic_write = False
|
||||
_type = 'many2many'
|
||||
|
||||
def __init__(self, obj, rel, id1, id2, string='unknown', limit=None,
|
||||
**args):
|
||||
_column.__init__(self, string=string, **args)
|
||||
self._obj = obj
|
||||
self._rel = rel
|
||||
self._id1 = id1
|
||||
self._id2 = id2
|
||||
self._limit = limit
|
||||
|
||||
def get(self, cursor, obj, ids, name, user=None, offset=0, context=None,
|
||||
values=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if values is None:
|
||||
values = {}
|
||||
res = {}
|
||||
if not ids:
|
||||
return res
|
||||
for i in ids:
|
||||
res[i] = []
|
||||
ids_s = ','.join([str(x) for x in ids])
|
||||
limit_str = self._limit is not None and ' limit %d' % self._limit or ''
|
||||
obj = obj.pool.get(self._obj)
|
||||
|
||||
domain1, domain2 = obj.pool.get('ir.rule').domain_get(cursor,
|
||||
user, obj._name)
|
||||
if domain1:
|
||||
domain1 = ' and '+domain1
|
||||
|
||||
cursor.execute('SELECT ' + self._rel + '.' + self._id2 + ', ' + \
|
||||
self._rel + '.' + self._id1 + ' ' \
|
||||
'FROM "' + self._rel + '" , "' + obj._table + '" ' \
|
||||
'WHERE ' + \
|
||||
self._rel + '.' + self._id1 + ' IN (' + ids_s + ') ' \
|
||||
'AND ' + self._rel + '.' + self._id2 + ' = ' + \
|
||||
obj._table + '.id ' + domain1 + \
|
||||
limit_str + ' ORDER BY ' + obj._table + '.' + obj._order + \
|
||||
' offset %d', domain2 + [offset])
|
||||
for i in cursor.fetchall():
|
||||
res[i[1]].append(i[0])
|
||||
return res
|
||||
|
||||
def set(self, cursor, obj, obj_id, name, values, user=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if not values:
|
||||
return
|
||||
obj = obj.pool.get(self._obj)
|
||||
for act in values:
|
||||
if act[0] == 0:
|
||||
idnew = obj.create(cursor, user, act[2])
|
||||
cursor.execute('INSERT INTO "' + self._rel + '" ' \
|
||||
'(' + self._id1 + ', ' + self._id2 + ') ' \
|
||||
'VALUES (%d, %d)', (obj_id, idnew))
|
||||
elif act[0] == 1:
|
||||
obj.write(cursor, user, [act[1]] , act[2], context=context)
|
||||
elif act[0] == 2:
|
||||
obj.unlink(cursor, user, [act[1]], context=context)
|
||||
elif act[0] == 3:
|
||||
cursor.execute('DELETE FROM "' + self._rel + '" ' \
|
||||
'WHERE "' + self._id1 + '" = %d ' \
|
||||
'AND "'+ self._id2 + '" = %d', (obj_id, act[1]))
|
||||
elif act[0] == 4:
|
||||
cursor.execute('INSERT INTO "' + self._rel + '" ' \
|
||||
'(' + self._id1 + ', ' + self._id2 + ') ' \
|
||||
'VALUES (%d, %d)', (obj_id, act[1]))
|
||||
elif act[0] == 5:
|
||||
cursor.execute('UPDATE "' + self._rel + '" ' \
|
||||
'SET "' + self._id2 + '" = NULL ' \
|
||||
'WHERE "' + self._id2 + '" = %d', (obj_id,))
|
||||
elif act[0] == 6:
|
||||
domain1, domain2 = obj.pool.get('ir.rule').domain_get(cursor,
|
||||
user, obj._name)
|
||||
if domain1:
|
||||
domain1 = ' AND ' + domain1
|
||||
cursor.execute('DELETE FROM "' + self._rel + '" ' \
|
||||
'WHERE "' + self._id1 + '" = %d ' \
|
||||
'AND "' + self._id2 + '" IN (' \
|
||||
'SELECT ' + self._rel + '.' + self._id2 + ' ' \
|
||||
'FROM "' + self._rel + '", "' + obj._table + '" ' \
|
||||
'WHERE ' + self._rel + '.' + self._id1 + ' = %d ' \
|
||||
'AND ' + self._rel + '.' + self._id2 + ' = ' + \
|
||||
obj._table + '.id ' + domain1 + ')',
|
||||
[obj_id, obj_id] + domain2)
|
||||
|
||||
for act_nbr in act[2]:
|
||||
cursor.execute('INSERT INTO "' + self._rel + '" ' \
|
||||
'(' + self._id1 + ', ' + self._id2 + ') ' \
|
||||
'VALUES (%d, %d)', (obj_id, act_nbr))
|
||||
|
||||
many2many = Many2Many
|
||||
|
||||
|
||||
class Function(_column):
|
||||
_classic_read = False
|
||||
_classic_write = False
|
||||
_type = 'function'
|
||||
_properties = True
|
||||
|
||||
def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None,
|
||||
type='float', fnct_search=None, obj=None, method=False,
|
||||
store=False, **args):
|
||||
_column.__init__(self, **args)
|
||||
self._obj = obj
|
||||
self._method = method
|
||||
self._fnct = fnct
|
||||
self._fnct_inv = fnct_inv
|
||||
self._arg = arg
|
||||
if 'relation' in args:
|
||||
self._obj = args['relation']
|
||||
self._fnct_inv_arg = fnct_inv_arg
|
||||
if not fnct_inv:
|
||||
self.readonly = 1
|
||||
self._type = type
|
||||
self._fnct_search = fnct_search
|
||||
self.store = store
|
||||
if type == 'float':
|
||||
self._symbol_c = '%f'
|
||||
self._symbol_f = lambda x: __builtin__.float(x or 0.0)
|
||||
self._symbol_set = (self._symbol_c, self._symbol_f)
|
||||
|
||||
def search(self, cursor, uid, obj, name, args):
|
||||
if not self._fnct_search:
|
||||
return []
|
||||
return self._fnct_search(obj, cursor, uid, obj, name, args)
|
||||
|
||||
def get(self, cursor, obj, ids, name, user=None, offset=0, context=None,
|
||||
values=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if values is None:
|
||||
values = {}
|
||||
res = {}
|
||||
table = obj._table
|
||||
if self._method:
|
||||
# TODO get HAS to receive uid for permissions !
|
||||
return self._fnct(obj, cursor, user, ids, name, self._arg, context)
|
||||
else:
|
||||
return self._fnct(cursor, table, ids, name, self._arg, context)
|
||||
|
||||
def set(self, cursor, obj, obj_id, name, value, user=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if self._fnct_inv:
|
||||
self._fnct_inv(obj, cursor, user, obj_id, name, value,
|
||||
self._fnct_inv_arg, context)
|
||||
|
||||
function = Function
|
||||
|
||||
|
||||
class Serialized(_column):
|
||||
def __init__(self, string='unknown', serialize_func=repr,
|
||||
deserialize_func=eval, type='text', **args):
|
||||
self._serialize_func = serialize_func
|
||||
self._deserialize_func = deserialize_func
|
||||
self._type = type
|
||||
self._symbol_set = (self._symbol_c, self._serialize_func)
|
||||
self._symbol_get = self._deserialize_func
|
||||
super(serialized, self).__init__(string=string, **args)
|
||||
|
||||
serialized = Serialized
|
||||
|
||||
|
||||
class Property(function):
|
||||
|
||||
def _fnct_write(self, obj, cursor, user, obj_id, prop, id_val, val,
|
||||
context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
(obj_dest,) = val
|
||||
definition_id = self._field_get(cursor, user, obj._name, prop)
|
||||
|
||||
property = obj.pool.get('ir.property')
|
||||
nid = property.search(cursor, user, [('fields_id', '=', definition_id),
|
||||
('res_id', '=', obj._name + ',' + str(obj_id))])
|
||||
# TODO remove query from while statement
|
||||
while len(nid):
|
||||
cursor.execute('DELETE FROM ir_property WHERE id = %d',
|
||||
(nid.pop(),))
|
||||
|
||||
nid = property.search(cursor, user, [('fields_id', '=', definition_id),
|
||||
('res_id', '=', False)])
|
||||
default_val = False
|
||||
if nid:
|
||||
default_val = property.browse(cursor, user, nid[0], context).value
|
||||
|
||||
company_id = obj.pool.get('res.users').company_get(cursor, user, user)
|
||||
res = False
|
||||
newval = (id_val and obj_dest + ',' + str(id_val)) or False
|
||||
if (newval != default_val) and newval:
|
||||
propdef = obj.pool.get('ir.model.fields').browse(cursor, user,
|
||||
definition_id, context=context)
|
||||
res = property.create(cursor, user, {
|
||||
'name': propdef.name,
|
||||
'value': newval,
|
||||
'res_id': obj._name + ',' + str(obj_id),
|
||||
'company_id': company_id,
|
||||
'fields_id': definition_id,
|
||||
}, context=context)
|
||||
return res
|
||||
|
||||
def _fnct_read(self, obj, cursor, user, ids, prop, val, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
property = obj.pool.get('ir.property')
|
||||
definition_id = self._field_get(cursor, user, obj._name, prop)
|
||||
|
||||
nid = property.search(cursor, user, [('fields_id', '=', definition_id),
|
||||
('res_id', '=', False)])
|
||||
default_val = False
|
||||
if nid:
|
||||
value = property.browse(cursor, user, nid[0], context).value
|
||||
default_val = (value and int(value.split(',')[1])) or False
|
||||
|
||||
vids = [obj._name + ',' + str(obj_id) for obj_id in ids]
|
||||
nids = property.search(cursor, user, [('fields_id', '=', definition_id),
|
||||
('res_id', 'in', vids)])
|
||||
|
||||
res = {}
|
||||
for obj_id in ids:
|
||||
res[obj_id] = default_val
|
||||
for prop in property.browse(cursor, user, nids):
|
||||
res[int(prop.res_id.split(',')[1])] = (prop.value and \
|
||||
int(prop.value.split(',')[1])) or False
|
||||
|
||||
obj = obj.pool.get(self._obj)
|
||||
names = obj.name_get(cursor, user, res.values(), context=context)
|
||||
for i in res.keys():
|
||||
if res[i] and res[i] in names:
|
||||
res[i] = (res[i], names[res[i]])
|
||||
else:
|
||||
res[i] = False
|
||||
return res
|
||||
|
||||
def _field_get(self, cursor, user, model_name, prop):
|
||||
if not self.field_id.get(cursor.dbname):
|
||||
cursor.execute('SELECT id ' \
|
||||
'FROM ir_model_fields ' \
|
||||
'WHERE name = %s AND model = %s', (prop, model_name))
|
||||
res = cursor.fetchone()
|
||||
self.field_id[cursor.dbname] = res and res[0]
|
||||
return self.field_id[cursor.dbname]
|
||||
|
||||
def __init__(self, obj_prop, **args):
|
||||
self.field_id = {}
|
||||
function.__init__(self, self._fnct_read, False, self._fnct_write,
|
||||
(obj_prop, ), **args)
|
||||
|
||||
def restart(self):
|
||||
self.field_id = {}
|
||||
|
||||
property = Property
|
||||
+2272
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,226 @@
|
||||
"Objects Services"
|
||||
|
||||
from orm import orm, except_orm
|
||||
from trytond.netsvc import Service, LocalService, Logger, LOG_ERROR
|
||||
from trytond import pooler
|
||||
import copy
|
||||
import sys
|
||||
from psycopg import IntegrityError
|
||||
from trytond.tools import UpdateableDict
|
||||
|
||||
MODULE_LIST = []
|
||||
MODULE_CLASS_LIST = {}
|
||||
CLASS_POOL = {}
|
||||
|
||||
class ExceptOSV(Exception):
|
||||
|
||||
def __init__(self, name, value, exc_type='warning'):
|
||||
Exception.__init__(self)
|
||||
self.name = name
|
||||
self.exc_type = exc_type
|
||||
self.value = value
|
||||
self.args = (exc_type, name)
|
||||
|
||||
except_osv = ExceptOSV
|
||||
|
||||
class OSVService(Service):
|
||||
|
||||
def __init__(self):
|
||||
self.object_name_pool = {}
|
||||
self.module_obj_list = {}
|
||||
self.created = []
|
||||
self._sql_error = {}
|
||||
Service.__init__(self, 'obj_proxy')
|
||||
Service.join_group(self, 'web-services')
|
||||
Service.export_method(self, self.object_name_list)
|
||||
Service.export_method(self, self.exec_workflow)
|
||||
Service.export_method(self, self.execute)
|
||||
Service.export_method(self, self.execute_cr)
|
||||
|
||||
def execute_cr(self, cursor, user, object_name, method, *args, **kargs):
|
||||
# TODO: check security level
|
||||
try:
|
||||
obj = pooler.get_pool(cursor.dbname).get(object_name)
|
||||
if not obj:
|
||||
self.abort_response('Object Error', 'warning',
|
||||
'Object %s doesn\'t exist' % str(object_name))
|
||||
if (not method in getattr(obj,'_protected')) and len(args) \
|
||||
and args[0] and len(obj._inherits):
|
||||
types = {object_name: args[0]}
|
||||
cursor.execute('SELECT inst_type, inst_id, object_name_id ' \
|
||||
'FROM inherit ' \
|
||||
'WHERE object_name_type = %s '\
|
||||
'AND object_name_id in (' + \
|
||||
','.join([str(x) for x in args[0]]) + ')',
|
||||
(object_name,))
|
||||
for inst_type, inst_id, object_name_id in cursor.fetchall():
|
||||
if not inst_type in types:
|
||||
types[inst_type] = []
|
||||
types[inst_type].append(inst_id)
|
||||
types[object_name].remove(object_name_id)
|
||||
for i, ids in types.items():
|
||||
if len(ids):
|
||||
obj_t = pooler.get_pool(cursor.dbname).get(i)
|
||||
res = getattr(obj_t, method)(cursor, user, ids,
|
||||
*args[1:], **kargs)
|
||||
else:
|
||||
res = getattr(obj, method)(cursor, user, *args, **kargs)
|
||||
return res
|
||||
except except_orm, inst:
|
||||
self.abort_response(inst.name, 'warning', inst.value)
|
||||
except ExceptOSV, inst:
|
||||
self.abort_response(inst.name, inst.exc_type, inst.value)
|
||||
except IntegrityError, inst:
|
||||
for key in self._sql_error.keys():
|
||||
if key in inst[0]:
|
||||
self.abort_response('Constraint Error', 'warning',
|
||||
self._sql_error[key])
|
||||
self.abort_response('Integrity Error', 'warning', inst[0])
|
||||
except:
|
||||
import traceback
|
||||
tb_s = reduce(lambda x, y: x+y, traceback.format_exception(
|
||||
sys.exc_type, sys.exc_value, sys.exc_traceback))
|
||||
logger = Logger()
|
||||
logger.notify_channel("web-services", LOG_ERROR,
|
||||
'Exception in call: ' + tb_s)
|
||||
raise
|
||||
|
||||
def execute(self, dbname, user, object_name, method, *args, **kargs):
|
||||
database, pool = pooler.get_db_and_pool(dbname)
|
||||
cursor = database.cursor()
|
||||
# TODO add retry when exception for concurency update
|
||||
try:
|
||||
try:
|
||||
res = pool.execute_cr(cursor, user, object_name, method,
|
||||
*args, **kargs)
|
||||
cursor.commit()
|
||||
except Exception:
|
||||
cursor.rollback()
|
||||
raise
|
||||
finally:
|
||||
cursor.close()
|
||||
return res
|
||||
|
||||
def exec_workflow_cr(self, cursor, user, object_name, method, *args):
|
||||
wf_service = LocalService("workflow")
|
||||
wf_service.trg_validate(user, object_name, args[0], method, cursor)
|
||||
return True
|
||||
|
||||
def exec_workflow(self, dbname, user, object_name, method, *args):
|
||||
cursor = pooler.get_db(dbname).cursor()
|
||||
# TODO add retry when exception for concurency update
|
||||
try:
|
||||
try:
|
||||
res = self.exec_workflow_cr(cursor, user, object_name, method,
|
||||
*args)
|
||||
cursor.commit()
|
||||
except Exception:
|
||||
cursor.rollback()
|
||||
raise
|
||||
finally:
|
||||
cursor.close()
|
||||
return res
|
||||
|
||||
def object_name_list(self):
|
||||
return self.object_name_pool.keys()
|
||||
|
||||
def add(self, name, object_name_inst):
|
||||
"""
|
||||
adds a new obj instance to the obj pool.
|
||||
if it already existed, the instance is replaced
|
||||
"""
|
||||
if self.object_name_pool.has_key(name):
|
||||
del self.object_name_pool[name]
|
||||
self.object_name_pool[name] = object_name_inst
|
||||
|
||||
module = str(object_name_inst.__class__)[6:]
|
||||
module = module[:len(module)-1]
|
||||
module = module.split('.')[0][2:]
|
||||
self.module_obj_list.setdefault(module, []).append(object_name_inst)
|
||||
|
||||
def get(self, name):
|
||||
return self.object_name_pool.get(name, None)
|
||||
|
||||
def instanciate(self, module):
|
||||
res = []
|
||||
class_list = MODULE_CLASS_LIST.get(module, [])
|
||||
for klass in class_list:
|
||||
res.append(klass.create_instance(self, module))
|
||||
return res
|
||||
|
||||
osv_pool = OSVService
|
||||
|
||||
|
||||
class OSV(orm):
|
||||
|
||||
def __new__(cls):
|
||||
for module in cls.__module__.split('.'):
|
||||
if module != 'trytond' and module != 'addons':
|
||||
break
|
||||
if not hasattr(cls, '_module'):
|
||||
cls._module = module
|
||||
MODULE_CLASS_LIST.setdefault(cls._module, []).append(cls)
|
||||
CLASS_POOL[cls._name] = cls
|
||||
if module not in MODULE_LIST:
|
||||
MODULE_LIST.append(cls._module)
|
||||
return None
|
||||
|
||||
def create_instance(cls, pool, module):
|
||||
"""
|
||||
try to apply inheritancy at the instanciation level and
|
||||
put objs in the pool var
|
||||
"""
|
||||
parent_name = hasattr(cls, '_inherit') and cls._inherit
|
||||
if parent_name:
|
||||
parent_class = pool.get(parent_name).__class__
|
||||
assert parent_class, "parent class %s does not exist !" % \
|
||||
parent_name
|
||||
nattr = {}
|
||||
for i in (
|
||||
'_columns',
|
||||
'_defaults',
|
||||
'_inherits',
|
||||
'_constraints',
|
||||
'_sql_constraints',
|
||||
):
|
||||
new = copy.copy(getattr(pool.get(parent_name), i))
|
||||
if hasattr(new, 'update'):
|
||||
new.update(cls.__dict__.get(i, {}))
|
||||
else:
|
||||
new.extend(cls.__dict__.get(i, []))
|
||||
nattr[i] = new
|
||||
name = hasattr(cls,'_name') and cls._name or cls._inherit
|
||||
cls = type(name, (cls, parent_class), nattr)
|
||||
|
||||
obj = object.__new__(cls)
|
||||
obj.__init__(pool)
|
||||
return obj
|
||||
|
||||
create_instance = classmethod(create_instance)
|
||||
|
||||
def __init__(self, pool):
|
||||
pool.add(self._name, self)
|
||||
self.pool = pool
|
||||
orm.__init__(self)
|
||||
|
||||
osv = OSV
|
||||
|
||||
|
||||
class Cacheable(object):
|
||||
|
||||
_cache = UpdateableDict()
|
||||
|
||||
def add(self, key, value):
|
||||
self._cache[key] = value
|
||||
|
||||
def invalidate(self, key):
|
||||
del self._cache[key]
|
||||
|
||||
def get(self, key):
|
||||
try:
|
||||
return self._cache[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def clear(self):
|
||||
self._cache.clear()
|
||||
@@ -0,0 +1,56 @@
|
||||
from sql_db import db_connect
|
||||
from netsvc import Logger, LOG_INFO
|
||||
|
||||
_DB = {}
|
||||
_POOL = {}
|
||||
|
||||
def get_db_and_pool(db_name, force_demo=False, update_module=False):
|
||||
if db_name in _DB:
|
||||
database = _DB[db_name]
|
||||
else:
|
||||
logger = Logger()
|
||||
logger.notify_channel('pooler', LOG_INFO,
|
||||
'Connecting to %s' % (db_name))
|
||||
database = db_connect(db_name)
|
||||
_DB[db_name] = database
|
||||
|
||||
if db_name in _POOL:
|
||||
pool = _POOL[db_name]
|
||||
else:
|
||||
from osv.osv import OSVService
|
||||
pool = OSVService()
|
||||
_POOL[db_name] = pool
|
||||
from module import load_modules
|
||||
load_modules(database, force_demo, update_module)
|
||||
|
||||
if not update_module:
|
||||
import report
|
||||
#report.interface.register_all(database)
|
||||
pool.get('ir.cron').pool_jobs(database.dbname)
|
||||
return database, pool
|
||||
|
||||
def restart_pool(db_name, force_demo=False, update_module=False):
|
||||
del _POOL[db_name]
|
||||
return get_db_and_pool(db_name, force_demo, update_module=update_module)
|
||||
|
||||
def close_db(db_name):
|
||||
if db_name in _DB:
|
||||
_DB[db_name].truedb.close()
|
||||
del _DB[db_name]
|
||||
if db_name in _POOL:
|
||||
del _POOL[db_name]
|
||||
|
||||
def get_db_only(db_name):
|
||||
if db_name in _DB:
|
||||
database = _DB[db_name]
|
||||
else:
|
||||
database = db_connect(db_name)
|
||||
_DB[db_name] = database
|
||||
return database
|
||||
|
||||
def get_db(db_name):
|
||||
return get_db_and_pool(db_name)[0]
|
||||
|
||||
def get_pool(db_name, force_demo=False, update_module=False):
|
||||
pool = get_db_and_pool(db_name, force_demo, update_module)[1]
|
||||
return pool
|
||||
@@ -0,0 +1,37 @@
|
||||
import pooler
|
||||
from config import CONFIG
|
||||
|
||||
_USER_CACHE = {}
|
||||
|
||||
def login(dbname, loginname, password):
|
||||
cursor = pooler.get_db(dbname).cursor()
|
||||
cursor.execute('SELECT id FROM res_users '
|
||||
'WHERE login = %s and password = %s and active',
|
||||
(loginname.encode('utf-8'), password.encode('utf-8')))
|
||||
res = cursor.fetchone()
|
||||
cursor.close()
|
||||
if res:
|
||||
return res[0]
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_super(passwd):
|
||||
if passwd == CONFIG['admin_passwd']:
|
||||
return True
|
||||
else:
|
||||
raise Exception('AccessDenied')
|
||||
|
||||
def check(dbname, user, passwd):
|
||||
# FIXME: this should be db dependent
|
||||
if _USER_CACHE.has_key(user) and (_USER_CACHE[user]==passwd):
|
||||
return True
|
||||
cursor = pooler.get_db(dbname).cursor()
|
||||
cursor.execute('SELECT count(*) FROM res_users ' \
|
||||
'WHERE id = %d AND password = %s', (int(user), passwd))
|
||||
res = cursor.fetchone()[0]
|
||||
cursor.close()
|
||||
if not bool(res):
|
||||
raise Exception('AccessDenied')
|
||||
if res:
|
||||
_USER_CACHE[user] = passwd
|
||||
return bool(res)
|
||||
@@ -0,0 +1,187 @@
|
||||
import psycopg
|
||||
import re
|
||||
import os
|
||||
from mx import DateTime as mdt
|
||||
import zipfile
|
||||
import version
|
||||
from config import CONFIG
|
||||
|
||||
RE_FROM = re.compile('.* from "?([a-zA-Z_0-9]+)"? .*$')
|
||||
RE_INTO = re.compile('.* into "?([a-zA-Z_0-9]+)"? .*$')
|
||||
|
||||
class FakeCursor:
|
||||
nbr = 0
|
||||
_tables = {}
|
||||
sql_from_log = {}
|
||||
sql_into_log = {}
|
||||
sql_log = False
|
||||
count = 0
|
||||
|
||||
def __init__(self, database, con, dbname):
|
||||
self.db = database
|
||||
self.obj = database.cursor()
|
||||
self.con = con
|
||||
self.dbname = dbname
|
||||
|
||||
def execute(self, sql, params=None):
|
||||
if not params:
|
||||
params = ()
|
||||
|
||||
def base_string(string):
|
||||
if isinstance(string, unicode):
|
||||
return string.encode('utf-8')
|
||||
return string
|
||||
|
||||
para = [base_string(string) for string in params]
|
||||
if isinstance(sql, unicode):
|
||||
sql = sql.encode('utf-8')
|
||||
if self.sql_log:
|
||||
now = mdt.now()
|
||||
if para:
|
||||
res = self.obj.execute(sql, para)
|
||||
else:
|
||||
res = self.obj.execute(sql)
|
||||
if self.sql_log:
|
||||
self.count += 1
|
||||
res_from = RE_FROM.match(sql.lower())
|
||||
if res_from:
|
||||
self.sql_from_log.setdefault(res_from.group(1), [0, 0])
|
||||
self.sql_from_log[res_from.group(1)][0] += 1
|
||||
self.sql_from_log[res_from.group(1)][1] += mdt.now() - now
|
||||
res_into = RE_INTO.match(sql.lower())
|
||||
if res_into:
|
||||
self.sql_into_log.setdefault(res_into.group(1), [0, 0])
|
||||
self.sql_into_log[res_into.group(1)][0] += 1
|
||||
self.sql_into_log[res_into.group(1)][1] += mdt.now() - now
|
||||
return res
|
||||
|
||||
def print_log(self, sql_type='from'):
|
||||
print "SQL LOG %s:" % (sql_type,)
|
||||
if sql_type == 'from':
|
||||
logs = self.sql_from_log.items()
|
||||
else:
|
||||
logs = self.sql_into_log.items()
|
||||
logs.sort(lambda x, y: cmp(x[1][1], y[1][1]))
|
||||
amount = 0
|
||||
for log in logs:
|
||||
print "table:", log[0], ":", str(log[1][1]), "/", log[1][0]
|
||||
amount += log[1][1]
|
||||
print "SUM:%s/%d"% (amount, self.count)
|
||||
|
||||
def close(self):
|
||||
if self.sql_log:
|
||||
self.print_log('from')
|
||||
self.print_log('into')
|
||||
self.obj.close()
|
||||
|
||||
# This force the cursor to be freed, and thus, available again. It is
|
||||
# important because otherwise we can overload the server very easily
|
||||
# because of a cursor shortage (because cursors are not garbage
|
||||
# collected as fast as they should). The problem is probably due in
|
||||
# part because browse records keep a reference to the cursor.
|
||||
del self.obj
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.obj, name)
|
||||
|
||||
class FakeDB:
|
||||
|
||||
def __init__(self, truedb, dbname):
|
||||
self.truedb = truedb
|
||||
self.dbname = dbname
|
||||
|
||||
def cursor(self):
|
||||
return FakeCursor(self.truedb, {}, self.dbname)
|
||||
|
||||
def db_connect(db_name, serialize=0):
|
||||
host = CONFIG['db_host'] and "host=%s" % CONFIG['db_host'] or ''
|
||||
port = CONFIG['db_port'] and "port=%s" % CONFIG['db_port'] or ''
|
||||
name = "dbname=%s" % db_name
|
||||
user = CONFIG['db_user'] and "user=%s" % CONFIG['db_user'] or ''
|
||||
password = CONFIG['db_password'] \
|
||||
and "password=%s" % CONFIG['db_password'] or ''
|
||||
maxconn = int(CONFIG['db_maxconn']) or 64
|
||||
tdb = psycopg.connect('%s %s %s %s %s' % (host, port, name, user, password),
|
||||
serialize=serialize, maxconn=maxconn)
|
||||
fdb = FakeDB(tdb, db_name)
|
||||
return fdb
|
||||
|
||||
def init_db(cursor):
|
||||
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
|
||||
for line in file(sql_file).read().split(';'):
|
||||
if (len(line)>0) and (not line.isspace()):
|
||||
cursor.execute(line)
|
||||
|
||||
opj = os.path.join
|
||||
addons_path = os.path.join(os.path.dirname(__file__), 'addons')
|
||||
|
||||
for i in (os.listdir(addons_path) + ['ir', 'res']):
|
||||
terp_file = opj(addons_path, i, '__terp__.py')
|
||||
mod_path = opj(addons_path, i)
|
||||
if i in ('ir', 'res'):
|
||||
root_path = os.path.dirname(__file__)
|
||||
terp_file = opj(root_path, i, '__terp__.py')
|
||||
mod_path = opj(root_path, i)
|
||||
info = {}
|
||||
if os.path.isfile(terp_file) \
|
||||
and not os.path.isfile(opj(addons_path, i + '.zip')):
|
||||
info = eval(file(terp_file).read())
|
||||
elif zipfile.is_zipfile(mod_path):
|
||||
zfile = zipfile.ZipFile(mod_path)
|
||||
i = os.path.splitext(i)[0]
|
||||
info = eval(zfile.read(opj(i, '__terp__.py')))
|
||||
if info:
|
||||
categs = info.get('category', 'Uncategorized').split('/')
|
||||
p_id = None
|
||||
while categs:
|
||||
if p_id is not None:
|
||||
cursor.execute('SELECT id ' \
|
||||
'FROM ir_module_category ' \
|
||||
'WHERE name = %s AND parent_id = %d',
|
||||
(categs[0], p_id))
|
||||
else:
|
||||
cursor.execute('SELECT id ' \
|
||||
'FROM ir_module_category ' \
|
||||
'WHERE name = %s AND parent_id is NULL',
|
||||
(categs[0],))
|
||||
c_id = cursor.fetchone()
|
||||
if not c_id:
|
||||
cursor.execute(
|
||||
'SELECT NEXTVAL(\'ir_module_category_id_seq\')')
|
||||
c_id = cursor.fetchone()[0]
|
||||
cursor.execute('INSERT INTO ir_module_category ' \
|
||||
'(id, name, parent_id) ' \
|
||||
'VALUES (%d, %s, %d)', (c_id, categs[0], p_id))
|
||||
else:
|
||||
c_id = c_id[0]
|
||||
p_id = c_id
|
||||
categs = categs[1:]
|
||||
|
||||
active = info.get('active', False)
|
||||
installable = info.get('installable', True)
|
||||
if installable:
|
||||
if active:
|
||||
state = 'to install'
|
||||
else:
|
||||
state = 'uninstalled'
|
||||
else:
|
||||
state = 'uninstallable'
|
||||
cursor.execute('SELECT NEXTVAL(\'ir_module_module_id_seq\')')
|
||||
module_id = cursor.fetchone()[0]
|
||||
cursor.execute('INSERT INTO ir_module_module ' \
|
||||
'(id, author, latest_version, website, name, shortdesc, ' \
|
||||
'description, category_id, state) ' \
|
||||
'VALUES (%d, %s, %s, %s, %s, %s, %s, %d, %s)',
|
||||
(module_id, info.get('author', ''),
|
||||
version.VERSION.rsplit('.', 1)[0] + '.' + info.get('version', ''),
|
||||
info.get('website', ''), i, info.get('name', False),
|
||||
info.get('description', ''), p_id, state))
|
||||
dependencies = info.get('depends', [])
|
||||
for dependency in dependencies:
|
||||
cursor.execute('INSERT INTO ir_module_module_dependency ' \
|
||||
'(module_id, name) VALUES (%s, %s)',
|
||||
(module_id, dependency))
|
||||
|
||||
psycopg.register_type(psycopg.new_type((1082,), "date", lambda x:x))
|
||||
psycopg.register_type(psycopg.new_type((1083,), "time", lambda x:x))
|
||||
psycopg.register_type(psycopg.new_type((1114,), "datetime", lambda x:x))
|
||||
@@ -0,0 +1,71 @@
|
||||
import socket
|
||||
import cPickle
|
||||
|
||||
|
||||
class MyException(Exception):
|
||||
|
||||
def __init__(self, faultcode, faultstring):
|
||||
Exception.__init__(self)
|
||||
self.faultcode = faultcode
|
||||
self.faultstring = faultstring
|
||||
self.args = (faultcode, faultstring)
|
||||
|
||||
|
||||
class MySocket:
|
||||
|
||||
def __init__(self, sock=None):
|
||||
if sock is None:
|
||||
self.sock = socket.socket(
|
||||
socket.AF_INET, socket.SOCK_STREAM)
|
||||
else:
|
||||
self.sock = sock
|
||||
self.sock.settimeout(120)
|
||||
|
||||
def connect(self, host, port=False):
|
||||
if not port:
|
||||
protocol, buf = host.split('//')
|
||||
host, port = buf.split(':')
|
||||
self.sock.connect((host, int(port)))
|
||||
|
||||
def disconnect(self):
|
||||
self.sock.shutdown(socket.SHUT_RDWR)
|
||||
self.sock.close()
|
||||
|
||||
def mysend(self, msg, exception=False, traceback=None):
|
||||
msg = cPickle.dumps([msg, traceback])
|
||||
size = len(msg)
|
||||
self.sock.send('%8d' % size)
|
||||
self.sock.send(exception and "1" or "0")
|
||||
totalsent = 0
|
||||
while totalsent < size:
|
||||
sent = self.sock.send(msg[totalsent:])
|
||||
if sent == 0:
|
||||
raise RuntimeError, "socket connection broken"
|
||||
totalsent = totalsent + sent
|
||||
|
||||
def myreceive(self):
|
||||
buf = ''
|
||||
while len(buf) < 8:
|
||||
chunk = self.sock.recv(8 - len(buf))
|
||||
if chunk == '':
|
||||
raise RuntimeError, "socket connection broken"
|
||||
buf += chunk
|
||||
size = int(buf)
|
||||
buf = self.sock.recv(1)
|
||||
if buf != "0":
|
||||
exception = buf
|
||||
else:
|
||||
exception = False
|
||||
msg = ''
|
||||
while len(msg) < size:
|
||||
chunk = self.sock.recv(size-len(msg))
|
||||
if chunk == '':
|
||||
raise RuntimeError, "socket connection broken"
|
||||
msg = msg + chunk
|
||||
res = cPickle.loads(msg)
|
||||
if isinstance(res[0], Exception):
|
||||
if exception:
|
||||
raise MyException(str(res[0]), str(res[1]))
|
||||
raise res[0]
|
||||
else:
|
||||
return res[0]
|
||||
@@ -0,0 +1,4 @@
|
||||
from config import *
|
||||
from misc import *
|
||||
from translate import *
|
||||
from convert import *
|
||||
@@ -0,0 +1,766 @@
|
||||
"Convert"
|
||||
import re
|
||||
import StringIO, xml.dom.minidom
|
||||
from trytond import pooler
|
||||
from trytond.osv.fields import Integer
|
||||
import csv
|
||||
import os.path
|
||||
from trytond.netsvc import Logger, LOG_ERROR, LOG_INFO, LocalService
|
||||
from trytond.config import CONFIG
|
||||
from trytond.version import VERSION
|
||||
import logging
|
||||
|
||||
|
||||
def _ref(self, cursor):
|
||||
return lambda x: self.id_get(cursor, x)
|
||||
|
||||
def _obj(pool, cursor, user, model_str, context=None):
|
||||
model = pool.get(model_str)
|
||||
return lambda x: model.browse(cursor, user, x, context=context)
|
||||
|
||||
def _eval_xml(self, node, pool, cursor, user, idref, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
return node.data.encode("utf8")
|
||||
elif node.nodeType == node.ELEMENT_NODE:
|
||||
if node.nodeName in ('field','value'):
|
||||
f_type = node.getAttribute('type') or 'char'
|
||||
f_model = node.getAttribute("model").encode('ascii')
|
||||
if len(node.getAttribute('search')):
|
||||
f_search = node.getAttribute("search").encode('utf-8')
|
||||
f_use = node.getAttribute("use").encode('ascii')
|
||||
f_name = node.getAttribute("name").encode('utf-8')
|
||||
if len(f_use)==0:
|
||||
f_use = "id"
|
||||
args = eval(f_search, idref)
|
||||
ids = pool.get(f_model).search(cursor, user, args)
|
||||
if f_use != 'id':
|
||||
ids = [x[f_use] for x in pool.get(f_model).read(
|
||||
cursor, user, ids, [f_use])]
|
||||
_cols = pool.get(f_model)._columns
|
||||
if (f_name in _cols) and _cols[f_name]._type=='many2many':
|
||||
return ids
|
||||
f_val = False
|
||||
if len(ids):
|
||||
f_val = ids[0]
|
||||
if isinstance(f_val, tuple):
|
||||
f_val = f_val[0]
|
||||
return f_val
|
||||
a_eval = node.getAttribute('eval')
|
||||
if len(a_eval):
|
||||
import time
|
||||
idref['time'] = time
|
||||
idref['version'] = VERSION.rsplit('.', 1)[0]
|
||||
idref['ref'] = lambda x: self.id_get(cursor, x)
|
||||
if len(f_model):
|
||||
idref['obj'] = _obj(self.pool, cursor, user, f_model,
|
||||
context=context)
|
||||
try:
|
||||
import pytz
|
||||
except:
|
||||
Logger().notify_channel("init", LOG_INFO,
|
||||
'could not find pytz library')
|
||||
|
||||
|
||||
class Pytz(object):
|
||||
all_timezones = []
|
||||
|
||||
pytz = Pytz()
|
||||
idref['pytz'] = pytz
|
||||
return eval(a_eval, idref)
|
||||
if f_type == 'xml':
|
||||
|
||||
def _process(string, idref):
|
||||
matches = re.findall('[^%]%\((.*?)\)[ds]', string)
|
||||
for i in matches:
|
||||
if not i in idref:
|
||||
idref[i] = self.id_get(cursor, i)
|
||||
return string % idref
|
||||
|
||||
txt = '<?xml version="1.0"?>\n' + \
|
||||
_process("".join([x.toxml().encode("utf8") \
|
||||
for x in node.childNodes]), idref)
|
||||
return txt
|
||||
if f_type in ('char', 'int', 'float'):
|
||||
value = ""
|
||||
for child_node in node.childNodes:
|
||||
value += str(_eval_xml(self, child_node, pool, cursor,
|
||||
user, idref))
|
||||
if f_type == 'int':
|
||||
value = value.strip()
|
||||
if value == 'None':
|
||||
return None
|
||||
else:
|
||||
value = int(value.strip())
|
||||
elif f_type == 'float':
|
||||
value = float(value.strip())
|
||||
return value
|
||||
elif f_type in ('list', 'tuple'):
|
||||
res = []
|
||||
for child_node in node.childNodes:
|
||||
if child_node.nodeType != child_node.ELEMENT_NODE \
|
||||
or child_node.nodeName != 'value':
|
||||
continue
|
||||
res.append(_eval_xml(self, child_node, pool, cursor,
|
||||
user, idref))
|
||||
if f_type == 'tuple':
|
||||
return tuple(res)
|
||||
return res
|
||||
elif node.nodeName=="getitem":
|
||||
for child_node in node.childNodes:
|
||||
if child_node.nodeType != child_node.ELEMENT_NODE:
|
||||
continue
|
||||
res = _eval_xml(self, child_node, pool, cursor, user, idref)
|
||||
if not res:
|
||||
raise LookupError
|
||||
elif node.getAttribute('type') in ("int", "list"):
|
||||
return res[int(node.getAttribute('index'))]
|
||||
else:
|
||||
return res[node.getAttribute('index').encode("utf8")]
|
||||
elif node.nodeName=="function":
|
||||
args = []
|
||||
a_eval = node.getAttribute('eval')
|
||||
if len(a_eval):
|
||||
idref['ref'] = lambda x: self.id_get(cursor, x)
|
||||
args = eval(a_eval, idref)
|
||||
for child_node in node.childNodes:
|
||||
if child_node.nodeType != child_node.ELEMENT_NODE:
|
||||
continue
|
||||
args.append(_eval_xml(self, child_node, pool, cursor, user,
|
||||
idref, context))
|
||||
model = pool.get(node.getAttribute('model'))
|
||||
method = node.getAttribute('name')
|
||||
res = getattr(model, method)(cursor, user, *args)
|
||||
return res
|
||||
elif node.nodeName=="test":
|
||||
value = ""
|
||||
for child_node in node.childNodes:
|
||||
value += str(_eval_xml(self, child_node, pool, cursor, user,
|
||||
idref, context=context))
|
||||
return value
|
||||
|
||||
ESCAPE_RE = re.compile(r'(?<!\\)/')
|
||||
def escape(i):
|
||||
return i.replace('\\/', '/')
|
||||
|
||||
|
||||
class AssertionReport(object):
|
||||
|
||||
def __init__(self):
|
||||
self._report = {}
|
||||
|
||||
def record_assertion(self, success, severity):
|
||||
"""
|
||||
Records the result of an assertion for the failed/success count
|
||||
retrurns success
|
||||
"""
|
||||
if severity in self._report:
|
||||
self._report[severity][success] += 1
|
||||
else:
|
||||
self._report[severity] = {success:1, not success: 0}
|
||||
return success
|
||||
|
||||
def get_report(self):
|
||||
return self._report
|
||||
|
||||
def __str__(self):
|
||||
res = '\nAssertions report:\nLevel\tsuccess\tfailed\n'
|
||||
success = failed = 0
|
||||
for sev in self._report:
|
||||
res += sev + '\t' + str(self._report[sev][True]) + '\t' + \
|
||||
str(self._report[sev][False]) + '\n'
|
||||
success += self._report[sev][True]
|
||||
failed += self._report[sev][False]
|
||||
res += 'total\t' + str(success) + '\t' + str(failed) + '\n'
|
||||
res += 'end of report (' + str(success + failed) + \
|
||||
' assertion(s) checked)'
|
||||
return res
|
||||
|
||||
class XMLImport(object):
|
||||
|
||||
def isnoupdate(self, data_node = None):
|
||||
return self.noupdate or (data_node \
|
||||
and data_node.getAttribute('noupdate'))
|
||||
|
||||
def get_context(self, data_node, node, eval_dict):
|
||||
data_node_context = (data_node \
|
||||
and data_node.getAttribute('context').encode('utf8'))
|
||||
if data_node_context:
|
||||
context = eval(data_node_context, eval_dict)
|
||||
else:
|
||||
context = {}
|
||||
|
||||
node_context = node.getAttribute("context").encode('utf8')
|
||||
if len(node_context):
|
||||
context.update(eval(node_context, eval_dict))
|
||||
|
||||
return context
|
||||
|
||||
def get_uid(self, cursor, user, data_node, node):
|
||||
node_uid = node.getAttribute('user') or (data_node \
|
||||
and data_node.getAttribute('user'))
|
||||
if len(node_uid):
|
||||
return self.id_get(cursor, node_uid)
|
||||
return user
|
||||
|
||||
def _test_xml_id(self, xml_id):
|
||||
obj_id = xml_id
|
||||
if '.' in xml_id:
|
||||
obj_id = xml_id.split('.')[1]
|
||||
if len(obj_id) > 64:
|
||||
Logger().notify_channel('init', LOG_ERROR,
|
||||
'id: %s is to long (max: 64)' % xml_id)
|
||||
def _tag_delete(self, cursor, rec, data_node=None):
|
||||
d_model = rec.getAttribute("model")
|
||||
d_search = rec.getAttribute("search")
|
||||
d_id = rec.getAttribute("id")
|
||||
ids = []
|
||||
if len(d_search):
|
||||
ids = self.pool.get(d_model).search(cursor, self.user,
|
||||
eval(d_search))
|
||||
if len(d_id):
|
||||
ids.append(self.id_get(cursor, d_id))
|
||||
if len(ids):
|
||||
self.pool.get(d_model).unlink(cursor, self.user, ids)
|
||||
#self.pool.get('ir.model.data')._unlink(cursor, self.user, d_model,
|
||||
# ids, direct=True)
|
||||
return False
|
||||
|
||||
def _tag_report(self, cursor, rec, data_node=None):
|
||||
res = {}
|
||||
for dest, attr in (
|
||||
('name', 'string'),
|
||||
('model', 'model'),
|
||||
('report_name', 'name'),
|
||||
):
|
||||
res[dest] = rec.getAttribute(attr).encode('utf8')
|
||||
assert res[dest], "Attribute %s of report is empty !" % (attr,)
|
||||
for field, dest in (
|
||||
('rml', 'report_rml'),
|
||||
('xml', 'report_xml'),
|
||||
('xsl', 'report_xsl'),
|
||||
):
|
||||
if rec.hasAttribute(field):
|
||||
res[dest] = rec.getAttribute(field).encode('utf8')
|
||||
if rec.hasAttribute('auto'):
|
||||
res['auto'] = eval(rec.getAttribute('auto'))
|
||||
if rec.hasAttribute('header'):
|
||||
res['header'] = eval(rec.getAttribute('header'))
|
||||
res['multi'] = rec.hasAttribute('multi') \
|
||||
and eval(rec.getAttribute('multi'))
|
||||
xml_id = rec.getAttribute('id').encode('utf8')
|
||||
self._test_xml_id(xml_id)
|
||||
obj_id = self.pool.get('ir.model.data')._update(cursor, self.user,
|
||||
"ir.actions.report.xml", self.module, res, xml_id,
|
||||
mode=self.mode)
|
||||
self.idref[xml_id] = int(obj_id)
|
||||
if not rec.hasAttribute('menu') or eval(rec.getAttribute('menu')):
|
||||
keyword = str(rec.getAttribute('keyword') or 'client_print_multi')
|
||||
value = 'ir.actions.report.xml,' + str(obj_id)
|
||||
replace = rec.hasAttribute('replace') \
|
||||
and rec.getAttribute("replace")
|
||||
self.pool.get('ir.model.data').ir_set(cursor, self.user, 'action',
|
||||
keyword, res['name'], [res['model']], value,
|
||||
replace=replace, isobject=True, xml_id=xml_id)
|
||||
return False
|
||||
|
||||
def _tag_function(self, cursor, rec, data_node=None):
|
||||
if self.isnoupdate(data_node) and self.mode != 'init':
|
||||
return
|
||||
context = self.get_context(data_node, rec, {'ref': _ref(self, cursor)})
|
||||
user = self.get_uid(cursor, self.user, data_node, rec)
|
||||
_eval_xml(self, rec, self.pool, cursor, user, self.idref,
|
||||
context=context)
|
||||
return False
|
||||
|
||||
def _tag_wizard(self, cursor, rec, data_node=None):
|
||||
string = rec.getAttribute("string").encode('utf8')
|
||||
model = rec.getAttribute("model").encode('utf8')
|
||||
name = rec.getAttribute("name").encode('utf8')
|
||||
xml_id = rec.getAttribute('id').encode('utf8')
|
||||
self._test_xml_id(xml_id)
|
||||
multi = rec.hasAttribute('multi') and eval(rec.getAttribute('multi'))
|
||||
res = {'name': string, 'wiz_name': name, 'multi':multi}
|
||||
|
||||
obj_id = self.pool.get('ir.model.data')._update(cursor, self.user,
|
||||
"ir.actions.wizard", self.module, res, xml_id, mode=self.mode)
|
||||
self.idref[xml_id] = int(obj_id)
|
||||
# ir_set
|
||||
if (not rec.hasAttribute('menu') or eval(rec.getAttribute('menu'))) \
|
||||
and obj_id:
|
||||
keyword = str(rec.getAttribute('keyword') or 'client_action_multi')
|
||||
value = 'ir.actions.wizard,' + str(obj_id)
|
||||
replace = rec.hasAttribute('replace') and \
|
||||
rec.getAttribute("replace") or True
|
||||
self.pool.get('ir.model.data').ir_set(cursor, self.user, 'action',
|
||||
keyword, string, [model], value, replace=replace,
|
||||
isobject=True, xml_id=xml_id)
|
||||
return False
|
||||
|
||||
def _tag_act_window(self, cursor, rec, data_node=None):
|
||||
name = rec.hasAttribute('name') \
|
||||
and rec.getAttribute('name').encode('utf-8')
|
||||
xml_id = rec.getAttribute('id').encode('utf8')
|
||||
self._test_xml_id(xml_id)
|
||||
ftype = rec.hasAttribute('type') \
|
||||
and rec.getAttribute('type').encode('utf-8') \
|
||||
or 'ir.actions.act_window'
|
||||
view_id = False
|
||||
if rec.hasAttribute('view'):
|
||||
view_id = self.id_get(cursor,
|
||||
rec.getAttribute('view').encode('utf-8'))
|
||||
domain = rec.hasAttribute('domain') \
|
||||
and rec.getAttribute('domain').encode('utf-8')
|
||||
context = rec.hasAttribute('context') \
|
||||
and rec.getAttribute('context').encode('utf-8') \
|
||||
or '{}'
|
||||
res_model = rec.getAttribute('res_model').encode('utf-8')
|
||||
src_model = rec.hasAttribute('src_model') \
|
||||
and rec.getAttribute('src_model').encode('utf-8')
|
||||
view_type = rec.hasAttribute('view_type') \
|
||||
and rec.getAttribute('view_type').encode('utf-8') \
|
||||
or 'form'
|
||||
view_mode = rec.hasAttribute('view_mode') \
|
||||
and rec.getAttribute('view_mode').encode('utf-8') \
|
||||
or 'tree,form'
|
||||
usage = rec.hasAttribute('usage') \
|
||||
and rec.getAttribute('usage').encode('utf-8')
|
||||
limit = rec.hasAttribute('limit') \
|
||||
and rec.getAttribute('limit').encode('utf-8')
|
||||
auto_refresh = rec.hasAttribute('auto_refresh') \
|
||||
and rec.getAttribute('auto_refresh').encode('utf-8')
|
||||
|
||||
res = {
|
||||
'name': name,
|
||||
'type': ftype,
|
||||
'view_id': view_id,
|
||||
'domain': domain,
|
||||
'context': context,
|
||||
'res_model': res_model,
|
||||
'src_model': src_model,
|
||||
'view_type': view_type,
|
||||
'view_mode': view_mode,
|
||||
'usage': usage,
|
||||
'limit': limit,
|
||||
'auto_refresh': auto_refresh,
|
||||
}
|
||||
|
||||
obj_id = self.pool.get('ir.model.data')._update(cursor, self.user,
|
||||
'ir.actions.act_window', self.module, res, xml_id,
|
||||
mode=self.mode)
|
||||
self.idref[xml_id] = int(obj_id)
|
||||
|
||||
if src_model:
|
||||
keyword = 'client_action_relate'
|
||||
value = 'ir.actions.act_window,' + str(obj_id)
|
||||
replace = rec.hasAttribute('replace') \
|
||||
and rec.getAttribute('replace')
|
||||
self.pool.get('ir.model.data').ir_set(cursor, self.user, 'action',
|
||||
keyword, xml_id, [src_model], value, replace=replace,
|
||||
isobject=True, xml_id=xml_id)
|
||||
# TODO add remove ir.model.data
|
||||
return False
|
||||
|
||||
def _tag_ir_set(self, cursor, rec, data_node=None):
|
||||
if not self.mode == 'init':
|
||||
return False
|
||||
res = {}
|
||||
for field in [i for i in rec.childNodes \
|
||||
if (i.nodeType == i.ELEMENT_NODE and i.nodeName=="field")]:
|
||||
f_name = field.getAttribute("name").encode('utf-8')
|
||||
f_val = _eval_xml(self, field, self.pool, cursor, self.user,
|
||||
self.idref)
|
||||
res[f_name] = f_val
|
||||
self.pool.get('ir.model.data').ir_set(cursor, self.user, res['key'],
|
||||
res['key2'], res['name'], res['models'], res['value'],
|
||||
replace=res.get('replace',True),
|
||||
isobject=res.get('isobject', False), meta=res.get('meta',None))
|
||||
return False
|
||||
|
||||
def _tag_workflow(self, cursor, rec, data_node=None):
|
||||
if self.isnoupdate(data_node) and self.mode != 'init':
|
||||
return
|
||||
model = str(rec.getAttribute('model'))
|
||||
w_ref = rec.getAttribute('ref')
|
||||
if len(w_ref):
|
||||
obj_id = self.id_get(cursor, w_ref)
|
||||
else:
|
||||
assert rec.childNodes, 'You must define a child node ' \
|
||||
'if you dont give a ref'
|
||||
element_childs = [i for i in rec.childNodes \
|
||||
if i.nodeType == i.ELEMENT_NODE]
|
||||
assert len(element_childs) == 1, 'Only one child node ' \
|
||||
'is accepted (%d given)' % len(rec.childNodes)
|
||||
obj_id = _eval_xml(self, element_childs[0], self.pool, cursor,
|
||||
self.user, self.idref)
|
||||
|
||||
user = self.get_uid(cursor, self.user, data_node, rec)
|
||||
wf_service = LocalService("workflow")
|
||||
wf_service.trg_validate(user, model,
|
||||
obj_id,
|
||||
str(rec.getAttribute('action')), cursor)
|
||||
return False
|
||||
|
||||
def _tag_menuitem(self, cursor, rec, data_node=None):
|
||||
rec_id = rec.getAttribute("id").encode('ascii')
|
||||
self._test_xml_id(rec_id)
|
||||
m_l = [escape(x) for x in ESCAPE_RE.split(
|
||||
rec.getAttribute("name").encode('utf8'))]
|
||||
pid = False
|
||||
for idx, menu_elem in enumerate(m_l):
|
||||
if pid:
|
||||
cursor.execute('SELECT id FROM ir_ui_menu ' \
|
||||
'WHERE parent_id = %d AND name = %s',
|
||||
(pid, menu_elem))
|
||||
else:
|
||||
cursor.execute('SELECT id FROM ir_ui_menu ' \
|
||||
'WHERE parent_id IS NULL AND name = %s',
|
||||
(menu_elem,))
|
||||
res = cursor.fetchone()
|
||||
if idx == (len(m_l) - 1):
|
||||
# we are at the last menu element/level (it's a leaf)
|
||||
values = {'parent_id': pid, 'name': menu_elem}
|
||||
|
||||
if rec.hasAttribute('action'):
|
||||
a_action = rec.getAttribute('action').encode('utf8')
|
||||
a_type = rec.getAttribute('type').encode('utf8') \
|
||||
or 'act_window'
|
||||
icons = {
|
||||
"act_window": 'STOCK_NEW',
|
||||
"report.xml": 'STOCK_PASTE',
|
||||
"wizard": 'STOCK_EXECUTE',
|
||||
"url": 'STOCK_JUMP_TO'
|
||||
}
|
||||
values['icon'] = icons.get(a_type,'STOCK_NEW')
|
||||
if a_type == 'act_window':
|
||||
a_id = self.id_get(cursor, a_action)
|
||||
cursor.execute('SELECT view_type, view_mode, ' \
|
||||
'name, view_id ' \
|
||||
'FROM ir_act_window ' \
|
||||
'WHERE id = %d', (int(a_id),))
|
||||
action_type, action_mode, action_name, view_id = \
|
||||
cursor.fetchone()
|
||||
if view_id:
|
||||
cursor.execute('SELECT type FROM ir_ui_view ' \
|
||||
'WHERE id = %d', (int(view_id),))
|
||||
action_mode, = cursor.fetchone()
|
||||
cursor.execute('SELECT view_mode ' \
|
||||
'FROM ir_act_window_view ' \
|
||||
'WHERE act_window_id = %d ' \
|
||||
'ORDER BY sequence LIMIT 1', (int(a_id),))
|
||||
if cursor.rowcount:
|
||||
action_mode, = cursor.fetchone()
|
||||
if action_type == 'tree':
|
||||
values['icon'] = 'STOCK_INDENT'
|
||||
elif action_mode and action_mode.startswith('tree'):
|
||||
values['icon'] = 'STOCK_JUSTIFY_FILL'
|
||||
elif action_mode and action_mode.startswith('graph'):
|
||||
values['icon'] = 'terp-graph'
|
||||
elif action_mode and action_mode.startswith('calendar'):
|
||||
values['icon'] = 'terp-calendar'
|
||||
if not values['name']:
|
||||
values['name'] = action_name
|
||||
if rec.hasAttribute('sequence'):
|
||||
values['sequence'] = int(rec.getAttribute('sequence'))
|
||||
if rec.hasAttribute('icon'):
|
||||
values['icon'] = str(rec.getAttribute('icon'))
|
||||
if rec.hasAttribute('groups'):
|
||||
g_names = rec.getAttribute('groups').split(',')
|
||||
groups_value = []
|
||||
groups_obj = self.pool.get('res.groups')
|
||||
for group in g_names:
|
||||
if group.startswith('-'):
|
||||
obj_id = groups_obj.search(cursor, self.user,
|
||||
[('name', '=', group[1:])])[0]
|
||||
groups_value.append((3, obj_id))
|
||||
else:
|
||||
obj_id = groups_obj.search(cursor, self.user,
|
||||
[('name', '=', group)])[0]
|
||||
groups_value.append((4, obj_id))
|
||||
values['groups_id'] = groups_value
|
||||
xml_id = rec.getAttribute('id').encode('utf8')
|
||||
self._test_xml_id(xml_id)
|
||||
pid = self.pool.get('ir.model.data')._update(cursor, self.user,
|
||||
'ir.ui.menu', self.module, values, xml_id,
|
||||
mode=self.mode, res_id=(res and res[0] or False))
|
||||
elif res:
|
||||
# the menuitem already exists
|
||||
pid = res[0]
|
||||
xml_id = (idx==len(m_l) - 1) \
|
||||
and rec.getAttribute('id').encode('utf8')
|
||||
self.pool.get('ir.model.data')._update_dummy(cursor,
|
||||
self.user, 'ir.ui.menu', self.module, xml_id)
|
||||
else:
|
||||
# the menuitem does't exist but we are in branch (not a leaf)
|
||||
pid = self.pool.get('ir.ui.menu').create(cursor, self.user,
|
||||
{'parent_id': pid, 'name': menu_elem})
|
||||
if rec_id and pid:
|
||||
self.idref[rec_id] = int(pid)
|
||||
if rec.hasAttribute('action') and pid:
|
||||
a_action = rec.getAttribute('action').encode('utf8')
|
||||
a_type = rec.getAttribute('type').encode('utf8') or 'act_window'
|
||||
a_id = self.id_get(cursor, a_action)
|
||||
action = "ir.actions.%s,%d" % (a_type, a_id)
|
||||
self.pool.get('ir.model.data').ir_set(cursor, self.user, 'action',
|
||||
'tree_but_open', 'Menuitem', [('ir.ui.menu', int(pid))],
|
||||
action, True, True, xml_id=rec_id)
|
||||
return ('ir.ui.menu', pid)
|
||||
|
||||
@staticmethod
|
||||
def _assert_equals(i, j, prec=4):
|
||||
return not round(i - j, prec)
|
||||
|
||||
def _tag_assert(self, cursor, rec, data_node=None):
|
||||
if self.isnoupdate(data_node) and self.mode != 'init':
|
||||
return
|
||||
|
||||
rec_model = rec.getAttribute("model").encode('ascii')
|
||||
model = self.pool.get(rec_model)
|
||||
assert model, "The model %s does not exist !" % (rec_model,)
|
||||
rec_id = rec.getAttribute("id").encode('ascii')
|
||||
self._test_xml_id(rec_id)
|
||||
rec_src = rec.getAttribute("search").encode('utf8')
|
||||
rec_src_count = rec.getAttribute("count")
|
||||
|
||||
severity = rec.getAttribute("severity").encode('ascii') or 'info'
|
||||
|
||||
rec_string = rec.getAttribute("string").encode('utf8') or 'unknown'
|
||||
|
||||
ids = None
|
||||
eval_dict = {'ref': _ref(self, cursor)}
|
||||
context = self.get_context(data_node, rec, eval_dict)
|
||||
user = self.get_uid(cursor, self.user, data_node, rec)
|
||||
if len(rec_id):
|
||||
ids = [self.id_get(cursor, rec_id)]
|
||||
elif len(rec_src):
|
||||
args = eval(rec_src, eval_dict)
|
||||
ids = self.pool.get(rec_model).search(cursor, user, args,
|
||||
context=context)
|
||||
if len(rec_src_count):
|
||||
count = int(rec_src_count)
|
||||
if len(ids) != count:
|
||||
self.assert_report.record_assertion(False, severity)
|
||||
Logger().notify_channel('init', severity,
|
||||
'assertion "' + rec_string + \
|
||||
'" failed ! (search count is incorrect: ' \
|
||||
+ str(len(ids)) + ')' )
|
||||
sevval = getattr(logging, severity.upper())
|
||||
if sevval > CONFIG['assert_exit_level']:
|
||||
# TODO: define a dedicated exception
|
||||
raise Exception('Severe assertion failure')
|
||||
return
|
||||
|
||||
assert ids != None, 'You must give either an id or a search criteria'
|
||||
|
||||
ref = _ref(self, cursor)
|
||||
for brrec in model.browse(cursor, user, ids, context):
|
||||
|
||||
|
||||
class Dict(dict):
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in brrec:
|
||||
return brrec[key]
|
||||
return dict.__getitem__(self, key)
|
||||
|
||||
eval_globals = Dict()
|
||||
eval_globals['floatEqual'] = self._assert_equals
|
||||
eval_globals['ref'] = ref
|
||||
eval_globals['_ref'] = ref
|
||||
for test in [i for i in rec.childNodes \
|
||||
if (i.nodeType == i.ELEMENT_NODE and i.nodeName=="test")]:
|
||||
f_expr = test.getAttribute("expr").encode('utf-8')
|
||||
f_val = _eval_xml(self, test, self.pool, cursor, user,
|
||||
self.idref, context=context) or True
|
||||
if eval(f_expr, eval_globals) != f_val: # assertion failed
|
||||
self.assert_report.record_assertion(False, severity)
|
||||
Logger().notify_channel('init', severity,
|
||||
'assertion "' + rec_string + \
|
||||
'" failed ! (tag ' + test.toxml() + ')' )
|
||||
sevval = getattr(logging, severity.upper())
|
||||
if sevval > CONFIG['assert_exit_level']:
|
||||
# TODO: define a dedicated exception
|
||||
raise Exception('Severe assertion failure')
|
||||
return
|
||||
else: # all tests were successful for this assertion tag (no break)
|
||||
self.assert_report.record_assertion(True, severity)
|
||||
|
||||
def _tag_record(self, cursor, rec, data_node=None):
|
||||
rec_model = rec.getAttribute("model").encode('ascii')
|
||||
model = self.pool.get(rec_model)
|
||||
assert model, "The model %s does not exist !" % (rec_model,)
|
||||
rec_id = rec.getAttribute("id").encode('ascii')
|
||||
self._test_xml_id(rec_id)
|
||||
|
||||
# if not rec_id and not self.isnoupdate(data_node):
|
||||
# print "Warning", rec_model
|
||||
|
||||
if self.isnoupdate(data_node) and not self.mode == 'init':
|
||||
# check if the xml record has an id string
|
||||
if rec_id:
|
||||
obj_id = self.pool.get('ir.model.data')._update_dummy(cursor,
|
||||
self.user, rec_model, self.module, rec_id)
|
||||
# check if the resource already existed at the last update
|
||||
if obj_id:
|
||||
# if it existed, we don't update the data, but we need to
|
||||
# know the id of the existing record anyway
|
||||
self.idref[rec_id] = int(obj_id)
|
||||
return None
|
||||
else:
|
||||
# if the resource didn't exist
|
||||
if rec.getAttribute("forcecreate"):
|
||||
# we want to create it, so we let the normal
|
||||
# "update" behavior happen
|
||||
pass
|
||||
else:
|
||||
# otherwise do nothing
|
||||
return None
|
||||
else:
|
||||
# otherwise it is skipped
|
||||
return None
|
||||
|
||||
res = {}
|
||||
for field in [i for i in rec.childNodes \
|
||||
if (i.nodeType == i.ELEMENT_NODE and i.nodeName=="field")]:
|
||||
#TODO: most of this code is duplicated above (in _eval_xml)
|
||||
f_name = field.getAttribute("name").encode('utf-8')
|
||||
f_ref = field.getAttribute("ref").encode('ascii')
|
||||
f_search = field.getAttribute("search").encode('utf-8')
|
||||
f_model = field.getAttribute("model").encode('ascii')
|
||||
if not f_model and model._columns.get(f_name, False):
|
||||
f_model = model._columns[f_name]._obj
|
||||
f_use = field.getAttribute("use").encode('ascii') or 'id'
|
||||
f_val = False
|
||||
|
||||
if len(f_search):
|
||||
args = eval(f_search, self.idref)
|
||||
field = []
|
||||
assert f_model, 'Define an attribute ' \
|
||||
'model="..." in your .XML file !'
|
||||
f_obj = self.pool.get(f_model)
|
||||
# browse the objects searched
|
||||
objs = f_obj.browse(cursor, self.user, f_obj.search(cursor,
|
||||
self.user, args))
|
||||
# column definitions of the "local" object
|
||||
_cols = self.pool.get(rec_model)._columns
|
||||
# if the current field is many2many
|
||||
if (f_name in _cols) and _cols[f_name]._type=='many2many':
|
||||
f_val = [(6, 0, [x[f_use] for x in objs])]
|
||||
elif len(objs):
|
||||
# otherwise (we are probably in a many2one field),
|
||||
# take the first element of the search
|
||||
f_val = objs[0][f_use]
|
||||
elif len(f_ref):
|
||||
if f_ref == "null":
|
||||
f_val = False
|
||||
else:
|
||||
f_val = self.id_get(cursor, f_ref)
|
||||
else:
|
||||
f_val = _eval_xml(self, field, self.pool, cursor, self.user,
|
||||
self.idref)
|
||||
if model._columns.has_key(f_name):
|
||||
if isinstance(model._columns[f_name], Integer):
|
||||
f_val = int(f_val)
|
||||
res[f_name] = f_val
|
||||
obj_id = self.pool.get('ir.model.data')._update(cursor, self.user,
|
||||
rec_model, self.module, res, rec_id or False,
|
||||
noupdate=self.isnoupdate(data_node), mode=self.mode)
|
||||
if rec_id:
|
||||
self.idref[rec_id] = int(obj_id)
|
||||
return rec_model, obj_id
|
||||
|
||||
def id_get(self, cursor, id_str):
|
||||
if id_str in self.idref:
|
||||
return self.idref[id_str]
|
||||
mod = self.module
|
||||
if '.' in id_str:
|
||||
mod, id_str = id_str.split('.')
|
||||
result = self.pool.get('ir.model.data')._get_id(cursor, self.user,
|
||||
mod, id_str)
|
||||
return int(self.pool.get('ir.model.data').read(cursor, self.user,
|
||||
[result], ['res_id'])[0]['res_id'])
|
||||
|
||||
def parse(self, xmlstr):
|
||||
doc = xml.dom.minidom.parseString(xmlstr)
|
||||
elem = doc.documentElement
|
||||
for node in [i for i in elem.childNodes \
|
||||
if (i.nodeType == i.ELEMENT_NODE and i.nodeName=="data")]:
|
||||
for rec in node.childNodes:
|
||||
if rec.nodeType == rec.ELEMENT_NODE:
|
||||
if rec.nodeName in self._tags:
|
||||
try:
|
||||
self._tags[rec.nodeName](self.cursor, rec, node)
|
||||
except:
|
||||
Logger().notify_channel("init", LOG_INFO,
|
||||
'\n'+rec.toxml())
|
||||
self.cursor.rollback()
|
||||
raise
|
||||
return True
|
||||
|
||||
def __init__(self, cursor, module, idref, mode, report=AssertionReport(),
|
||||
noupdate = False):
|
||||
self.mode = mode
|
||||
self.module = module
|
||||
self.cursor = cursor
|
||||
self.idref = idref
|
||||
self.pool = pooler.get_pool(cursor.dbname)
|
||||
self.user = 1
|
||||
self.assert_report = report
|
||||
self.noupdate = noupdate
|
||||
self._tags = {
|
||||
'menuitem': self._tag_menuitem,
|
||||
'record': self._tag_record,
|
||||
'assert': self._tag_assert,
|
||||
'report': self._tag_report,
|
||||
'wizard': self._tag_wizard,
|
||||
'delete': self._tag_delete,
|
||||
'ir_set': self._tag_ir_set,
|
||||
'function': self._tag_function,
|
||||
'workflow': self._tag_workflow,
|
||||
'act_window': self._tag_act_window,
|
||||
}
|
||||
|
||||
def convert_csv_import(cursor, module, fname, csvcontent, idref=None,
|
||||
mode='init', noupdate=False):
|
||||
'''
|
||||
Import csv file :
|
||||
quote: "
|
||||
delimiter: ,
|
||||
encoding: utf-8
|
||||
'''
|
||||
if idref is None:
|
||||
idref = {}
|
||||
model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
|
||||
#remove folder path from model
|
||||
model = os.path.split(model)[1]
|
||||
|
||||
pool = pooler.get_pool(cursor.dbname)
|
||||
|
||||
input_file = StringIO.StringIO(csvcontent)
|
||||
reader = csv.reader(input_file, quotechar='"', delimiter=',')
|
||||
fields = reader.next()
|
||||
|
||||
if not (mode == 'init' or 'id' in fields):
|
||||
return
|
||||
|
||||
user = 1
|
||||
datas = []
|
||||
for line in reader:
|
||||
if (not line) or not reduce(lambda x, y: x or y, line) :
|
||||
continue
|
||||
datas.append([x.decode('utf8').encode('utf8') for x in line])
|
||||
pool.get(model).import_data(cursor, user, fields, datas, mode,
|
||||
module, noupdate)
|
||||
|
||||
def convert_xml_import(cursor, module, xmlstr, idref=None, mode='init',
|
||||
noupdate=False, report=None):
|
||||
if idref is None:
|
||||
idref = {}
|
||||
if report is None:
|
||||
report = AssertionReport()
|
||||
obj = XMLImport(cursor, module, idref, mode, report=report,
|
||||
noupdate=noupdate)
|
||||
obj.parse(xmlstr)
|
||||
del obj
|
||||
return True
|
||||
@@ -0,0 +1,411 @@
|
||||
# -*- coding: utf8 -*-
|
||||
"""
|
||||
Miscelleanous tools used by tryton
|
||||
"""
|
||||
import os, time, sys
|
||||
import inspect
|
||||
from config import CONFIG
|
||||
import socket
|
||||
import zipfile
|
||||
|
||||
if sys.version_info[:2] < (2, 4):
|
||||
from threadinglocal import local
|
||||
else:
|
||||
from threading import local
|
||||
|
||||
def find_in_path(name):
|
||||
if os.name == "nt":
|
||||
sep = ';'
|
||||
else:
|
||||
sep = ':'
|
||||
path = [directory for directory in os.environ['PATH'].split(sep)
|
||||
if os.path.isdir(directory)]
|
||||
for directory in path:
|
||||
val = os.path.join(directory, name)
|
||||
if os.path.isfile(val) or os.path.islink(val):
|
||||
return val
|
||||
return None
|
||||
|
||||
def find_pg_tool(name):
|
||||
if CONFIG['pg_path'] and CONFIG['pg_path'] != 'None':
|
||||
return os.path.join(CONFIG['pg_path'], name)
|
||||
else:
|
||||
return find_in_path(name)
|
||||
|
||||
def exec_pg_command(name, *args):
|
||||
prog = find_pg_tool(name)
|
||||
if not prog:
|
||||
raise Exception('Couldn\'t find %s' % name)
|
||||
args2 = (os.path.basename(prog),) + args
|
||||
return os.spawnv(os.P_WAIT, prog, args2)
|
||||
|
||||
def exec_pg_command_pipe(name, *args):
|
||||
prog = find_pg_tool(name)
|
||||
if not prog:
|
||||
raise Exception('Couldn\'t find %s' % name)
|
||||
if os.name == "nt":
|
||||
cmd = '"' + prog + '" ' + ' '.join(args)
|
||||
else:
|
||||
cmd = prog + ' ' + ' '.join(args)
|
||||
return os.popen2(cmd, 'b')
|
||||
|
||||
def exec_command_pipe(name, *args):
|
||||
prog = find_in_path(name)
|
||||
if not prog:
|
||||
raise Exception('Couldn\'t find %s' % name)
|
||||
if os.name == "nt":
|
||||
cmd = '"'+prog+'" '+' '.join(args)
|
||||
else:
|
||||
cmd = prog+' '+' '.join(args)
|
||||
return os.popen2(cmd, 'b')
|
||||
|
||||
def file_open(name, mode="r", subdir='addons'):
|
||||
"""Open a file from the root dir, using a subdir folder."""
|
||||
root_path = os.path.dirname(os.path.dirname(__file__))
|
||||
if subdir:
|
||||
if subdir == 'addons' and name in ('ir', 'res'):
|
||||
name = os.path.join(root_path, name)
|
||||
else:
|
||||
name = os.path.join(root_path, subdir, name)
|
||||
else:
|
||||
name = os.path.join(root_path, name)
|
||||
|
||||
# Check for a zipfile in the path
|
||||
head = name
|
||||
zipname = False
|
||||
name2 = False
|
||||
while True:
|
||||
head, tail = os.path.split(head)
|
||||
if not tail:
|
||||
break
|
||||
if zipname:
|
||||
zipname = os.path.join(tail, zipname)
|
||||
else:
|
||||
zipname = tail
|
||||
if zipfile.is_zipfile(head+'.zip'):
|
||||
import StringIO
|
||||
zfile = zipfile.ZipFile(head+'.zip')
|
||||
try:
|
||||
return StringIO.StringIO(zfile.read(os.path.join(
|
||||
os.path.basename(head), zipname).replace(
|
||||
os.sep, '/')))
|
||||
except:
|
||||
name2 = os.path.normpath(os.path.join(head + '.zip', zipname))
|
||||
for i in (name2, name):
|
||||
if i and os.path.isfile(i):
|
||||
return file(i, mode)
|
||||
|
||||
raise IOError, 'File not found : '+str(name)
|
||||
|
||||
def email_send(email_from, email_to, subject, body, email_cc=None,
|
||||
email_bcc=None, reply_to=False, tinycrm=False):
|
||||
"""Send an email."""
|
||||
if not email_cc:
|
||||
email_cc = []
|
||||
if not email_bcc:
|
||||
email_bcc = []
|
||||
import smtplib
|
||||
from email.MIMEText import MIMEText
|
||||
from email.Header import Header
|
||||
from email.Utils import formatdate, COMMASPACE
|
||||
|
||||
msg = MIMEText(body or '', _charset='utf-8')
|
||||
msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
|
||||
msg['From'] = email_from
|
||||
del msg['Reply-To']
|
||||
if reply_to:
|
||||
msg['Reply-To'] = msg['From']+', '+reply_to
|
||||
msg['To'] = COMMASPACE.join(email_to)
|
||||
if email_cc:
|
||||
msg['Cc'] = COMMASPACE.join(email_cc)
|
||||
if email_bcc:
|
||||
msg['Bcc'] = COMMASPACE.join(email_bcc)
|
||||
msg['Date'] = formatdate(localtime=True)
|
||||
if tinycrm:
|
||||
msg['Message-Id'] = '<' + str(time.time()) + '-tinycrm-' + \
|
||||
str(tinycrm) + '@' + socket.gethostname() + '>'
|
||||
try:
|
||||
smtp = smtplib.SMTP()
|
||||
smtp.connect(CONFIG['smtp_server'])
|
||||
if CONFIG['smtp_user'] or CONFIG['smtp_password']:
|
||||
smtp.login(CONFIG['smtp_user'], CONFIG['smtp_password'])
|
||||
smtp.sendmail(email_from, email_to + email_cc + email_bcc,
|
||||
msg.as_string())
|
||||
smtp.quit()
|
||||
except Exception, exp:
|
||||
import logging
|
||||
logging.getLogger().info(str(exp))
|
||||
return True
|
||||
|
||||
def email_send_attach(email_from, email_to, subject, body, email_cc=None,
|
||||
email_bcc=None, reply_to=False, attach=None,
|
||||
tinycrm=False):
|
||||
"""Send an email."""
|
||||
if not email_cc:
|
||||
email_cc = []
|
||||
if not email_bcc:
|
||||
email_bcc = []
|
||||
if not attach:
|
||||
attach = []
|
||||
import smtplib
|
||||
from email.MIMEText import MIMEText
|
||||
from email.MIMEBase import MIMEBase
|
||||
from email.MIMEMultipart import MIMEMultipart
|
||||
from email.Header import Header
|
||||
from email.Utils import formatdate, COMMASPACE
|
||||
from email import Encoders
|
||||
|
||||
msg = MIMEMultipart()
|
||||
|
||||
msg['Subject'] = Header(subject.decode('utf8'), 'utf-8')
|
||||
msg['From'] = email_from
|
||||
del msg['Reply-To']
|
||||
if reply_to:
|
||||
msg['Reply-To'] = reply_to
|
||||
msg['To'] = COMMASPACE.join(email_to)
|
||||
if email_cc:
|
||||
msg['Cc'] = COMMASPACE.join(email_cc)
|
||||
if email_bcc:
|
||||
msg['Bcc'] = COMMASPACE.join(email_bcc)
|
||||
if tinycrm:
|
||||
msg['Message-Id'] = '<' + str(time.time()) + '-tinycrm-' + \
|
||||
str(tinycrm) + '@' + socket.gethostname()+'>'
|
||||
msg['Date'] = formatdate(localtime=True)
|
||||
msg.attach( MIMEText(body or '', _charset='utf-8') )
|
||||
for (fname, fcontent) in attach:
|
||||
part = MIMEBase('application', "octet-stream")
|
||||
part.set_payload( fcontent )
|
||||
Encoders.encode_base64(part)
|
||||
part.add_header('Content-Disposition',
|
||||
'attachment; filename="%s"' % (fname,))
|
||||
msg.attach(part)
|
||||
try:
|
||||
smtp = smtplib.SMTP()
|
||||
smtp.connect(CONFIG['smtp_server'])
|
||||
if CONFIG['smtp_user'] or CONFIG['smtp_password']:
|
||||
smtp.login(CONFIG['smtp_user'], CONFIG['smtp_password'])
|
||||
smtp.sendmail(email_from, email_to + email_cc + email_bcc,
|
||||
msg.as_string())
|
||||
smtp.quit()
|
||||
except Exception, exp:
|
||||
import logging
|
||||
logging.getLogger().info(str(exp))
|
||||
return True
|
||||
|
||||
def sms_send(user, password, api_id, text, to):
|
||||
"text must be latin-1 encoded"
|
||||
import urllib
|
||||
params = urllib.urlencode({
|
||||
'user': user,
|
||||
'password': password,
|
||||
'api_id': api_id,
|
||||
'text': text,
|
||||
'to':to,
|
||||
})
|
||||
#urllib.urlopen("http://api.clickatell.com/http/sendmsg", params)
|
||||
urllib.urlopen("http://196.7.150.220/http/sendmsg", params)
|
||||
return True
|
||||
|
||||
|
||||
class UpdateableStr(local):
|
||||
'''Stores an updateable str to use in wizards'''
|
||||
|
||||
def __init__(self, string=''):
|
||||
super(UpdateableStr, self).__init__()
|
||||
self.string = string
|
||||
|
||||
def __str__(self):
|
||||
return str(self.string)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.string)
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.string)
|
||||
|
||||
|
||||
class UpdateableDict(local):
|
||||
'''Stores an updateable dict to use in wizards'''
|
||||
|
||||
def __init__(self, value=None):
|
||||
super(UpdateableDict, self).__init__()
|
||||
if value is None:
|
||||
value = {}
|
||||
self.dict = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.dict)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.dict)
|
||||
|
||||
def clear(self):
|
||||
return self.dict.clear()
|
||||
|
||||
def keys(self):
|
||||
return self.dict.keys()
|
||||
|
||||
def __setitem__(self, i, j):
|
||||
self.dict.__setitem__(i, j)
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.dict.__getitem__(i)
|
||||
|
||||
def copy(self):
|
||||
return self.dict.copy()
|
||||
|
||||
def iteritems(self):
|
||||
return self.dict.iteritems()
|
||||
|
||||
def iterkeys(self):
|
||||
return self.dict.iterkeys()
|
||||
|
||||
def itervalues(self):
|
||||
return self.dict.itervalues()
|
||||
|
||||
def pop(self, k, d=None):
|
||||
return self.dict.pop(k, d)
|
||||
|
||||
def popitem(self):
|
||||
return self.dict.popitem()
|
||||
|
||||
def setdefault(self, k, d=None):
|
||||
return self.dict.setdefault(k, d)
|
||||
|
||||
def update(self, E, **F):
|
||||
return self.dict.update(E, F)
|
||||
|
||||
def values(self):
|
||||
return self.dict.values()
|
||||
|
||||
def get(self, k, d=None):
|
||||
return self.dict.get(k, d)
|
||||
|
||||
def has_key(self, k):
|
||||
return self.dict.has_key(k)
|
||||
|
||||
def items(self):
|
||||
return self.dict.items()
|
||||
|
||||
def __cmp__(self, y):
|
||||
return self.dict.__cmp__(y)
|
||||
|
||||
def __contains__(self, k):
|
||||
return self.dict.__contains__(k)
|
||||
|
||||
def __delitem__(self, y):
|
||||
return self.dict.__delitem__(y)
|
||||
|
||||
def __eq__(self, y):
|
||||
return self.dict.__eq__(y)
|
||||
|
||||
def __ge__(self, y):
|
||||
return self.dict.__ge__(y)
|
||||
|
||||
def __gt__(self, y):
|
||||
return self.dict.__gt__(y)
|
||||
|
||||
def __hash__(self):
|
||||
return self.dict.__hash__()
|
||||
|
||||
def __iter__(self):
|
||||
return self.dict.__iter__()
|
||||
|
||||
def __le__(self, y):
|
||||
return self.dict.__le__(y)
|
||||
|
||||
def __len__(self):
|
||||
return self.dict.__len__()
|
||||
|
||||
def __lt__(self, y):
|
||||
return self.dict.__lt__(y)
|
||||
|
||||
def __ne__(self, y):
|
||||
return self.dict.__ne__(y)
|
||||
|
||||
|
||||
class Cache(object):
|
||||
"""
|
||||
Use it as a decorator of the function you plan to cache
|
||||
Timeout: 0 = no timeout, otherwise in seconds
|
||||
"""
|
||||
|
||||
def __init__(self, timeout=10000):
|
||||
self.timeout = timeout
|
||||
self.cache = {}
|
||||
|
||||
def __call__(self, function):
|
||||
arg_names = inspect.getargspec(function)[0][2:]
|
||||
def cached_result(self2, cursor=None, *args, **kwargs):
|
||||
if cursor is None:
|
||||
self.cache = {}
|
||||
return True
|
||||
|
||||
# Update named arguments with positional argument values
|
||||
kwargs.update(dict(zip(arg_names, args)))
|
||||
kwargs = kwargs.items()
|
||||
kwargs.sort()
|
||||
|
||||
# Work out key as a tuple of ('argname', value) pairs
|
||||
key = (('dbname', cursor.dbname),) + tuple(kwargs)
|
||||
|
||||
# Check cache and return cached value if possible
|
||||
if key in self.cache:
|
||||
(value, last_time) = self.cache[key]
|
||||
mintime = time.time() - self.timeout
|
||||
if self.timeout <= 0 or mintime <= last_time:
|
||||
return value
|
||||
|
||||
# Work out new value, cache it and return it
|
||||
# Should copy() this value to avoid futur modf of the cacle ?
|
||||
result = function(self2, cursor, **dict(kwargs))
|
||||
|
||||
self.cache[key] = (result, time.time())
|
||||
return result
|
||||
|
||||
return cached_result
|
||||
|
||||
def get_languages():
|
||||
languages = {
|
||||
'zh_CN': 'Chinese (CN)',
|
||||
'zh_TW': 'Chinese (TW)',
|
||||
'cs_CZ': 'Czech',
|
||||
'de_DE': 'Deutsch',
|
||||
'es_AR': 'Español (Argentina)',
|
||||
'es_ES': 'Español (España)',
|
||||
'fr_FR': 'Français',
|
||||
'fr_CH': 'Français (Suisse)',
|
||||
'en_EN': 'English (default)',
|
||||
'hu_HU': 'Hungarian',
|
||||
'it_IT': 'Italiano',
|
||||
'pt_BR': 'Portugese (Brasil)',
|
||||
'pt_PT': 'Portugese (Portugal)',
|
||||
'nl_NL': 'Nederlands',
|
||||
'ro_RO': 'Romanian',
|
||||
'ru_RU': 'Russian',
|
||||
'sv_SE': 'Swedish',
|
||||
}
|
||||
return languages
|
||||
|
||||
def scan_languages():
|
||||
import glob
|
||||
file_list = [os.path.splitext(os.path.basename(f))[0] \
|
||||
for f in glob.glob(os.path.join(CONFIG['root_path'],
|
||||
'i18n', '*.csv'))]
|
||||
lang_dict = get_languages()
|
||||
return [(lang, lang_dict.get(lang, lang)) for lang in file_list]
|
||||
|
||||
def mod10r(number):
|
||||
"""
|
||||
Input number : account or invoice number
|
||||
Output return: the same number completed with the recursive mod10
|
||||
key
|
||||
"""
|
||||
codec = [0, 9, 4, 6, 8, 2, 7, 1, 3, 5]
|
||||
report = 0
|
||||
result = ""
|
||||
for digit in number:
|
||||
result += digit
|
||||
if digit.isdigit():
|
||||
report = codec[ (int(digit) + report) % 10 ]
|
||||
return result + str((10 - report) % 10)
|
||||
@@ -0,0 +1,381 @@
|
||||
import os
|
||||
from os.path import join
|
||||
import fnmatch
|
||||
import csv, xml.dom, re
|
||||
from trytond import pooler
|
||||
from trytond.netsvc import Logger, LOG_ERROR, LOG_WARNING, LOG_INFO, _SERVICE
|
||||
from misc import UpdateableStr, file_open, get_languages
|
||||
from trytond.config import CONFIG
|
||||
|
||||
|
||||
class TINY(csv.excel):
|
||||
lineterminator = '\n'
|
||||
|
||||
csv.register_dialect("TINY", TINY)
|
||||
|
||||
# TODO: a caching method
|
||||
def translate(cursor, user, name, source_type, lang, source=None):
|
||||
if source and name:
|
||||
cursor.execute('SELECT value FROM ir_translation ' \
|
||||
'WHERE lang = %s AND type = %s AND name = %s AND src = %s',
|
||||
(lang, source_type, str(name), source))
|
||||
elif name:
|
||||
cursor.execute('SELECT value FROM ir_translation ' \
|
||||
'WHERE lang = %s AND type = %s AND name = %s',
|
||||
(lang, source_type, str(name)))
|
||||
elif source:
|
||||
cursor.execute('SELECT value FROM ir_translation ' \
|
||||
'WHERE lang = %s AND type = %s AND src = %s',
|
||||
(lang, source_type, source))
|
||||
res_trans = cursor.fetchone()
|
||||
res = res_trans and res_trans[0] or False
|
||||
return res
|
||||
|
||||
def translate_code(cursor, user, source, context):
|
||||
lang = context.get('lang', False)
|
||||
if lang:
|
||||
return translate(cursor, user, None, 'code', lang, source)
|
||||
else:
|
||||
return source
|
||||
|
||||
#_ = lambda source: translate_code(cursor, user, source, context)
|
||||
|
||||
def trans_parse_view(view):
|
||||
res = []
|
||||
if view.hasAttribute("string"):
|
||||
string = view.getAttribute('string')
|
||||
if string:
|
||||
res.append(string.encode("utf8"))
|
||||
if view.hasAttribute("sum"):
|
||||
string = view.getAttribute('sum')
|
||||
if string:
|
||||
res.append(string.encode("utf8"))
|
||||
for child_view in [i for i in view.childNodes \
|
||||
if (i.nodeType == i.ELEMENT_NODE)]:
|
||||
res.extend(trans_parse_view(child_view))
|
||||
return res
|
||||
|
||||
# tests whether an object is in a list of modules
|
||||
def in_modules(object_name, modules):
|
||||
if 'all' in modules:
|
||||
return True
|
||||
|
||||
module_dict = {
|
||||
'ir': 'base',
|
||||
'res': 'base',
|
||||
'workflow': 'base',
|
||||
}
|
||||
module = object_name.split('.')[0]
|
||||
module = module_dict.get(module, module)
|
||||
return module in modules
|
||||
|
||||
def trans_generate(lang, modules, dbname=None):
|
||||
logger = Logger()
|
||||
if not dbname:
|
||||
dbname = CONFIG['db_name']
|
||||
pool = pooler.get_pool(dbname)
|
||||
trans_obj = pool.get('ir.translation')
|
||||
model_data_obj = pool.get('ir.model.data')
|
||||
cursor = pooler.get_db(dbname).cursor()
|
||||
user = 1
|
||||
objects = pool.obj_pool.items()
|
||||
objects.sort()
|
||||
out = [["type", "name", "res_id", "src", "value"]]
|
||||
|
||||
to_translate = []
|
||||
|
||||
# object fields
|
||||
for obj_name, obj in objects:
|
||||
if in_modules(obj_name, modules):
|
||||
for field_name, field_def in obj._columns.iteritems():
|
||||
name = obj_name + "," + field_name
|
||||
value = ""
|
||||
if lang:
|
||||
cursor.execute("SELECT * FROM ir_translation " \
|
||||
"WHERE type = 'field' AND name = %s AND lang = %s",
|
||||
(name, lang))
|
||||
res = cursor.dictfetchall()
|
||||
if len(res):
|
||||
value = res[0]['value']
|
||||
out.append(["field", name, "0",
|
||||
field_def.string.encode('utf8'), value])
|
||||
if field_def.help:
|
||||
value = ''
|
||||
if lang:
|
||||
cursor.execute('SELECT * FROM ir_translation ' \
|
||||
'WHERE type=\'help\' ' \
|
||||
'AND name = %s ' \
|
||||
'AND lang = %s', (name, lang))
|
||||
res = cursor.dictfetchall()
|
||||
if res:
|
||||
value = res[0]['value']
|
||||
out.append(['help', name, '0',
|
||||
field_def.help.encode('utf8'), value])
|
||||
if field_def.translate:
|
||||
ids = obj.search(obj, cursor, user, [])
|
||||
obj_values = obj.read(cursor, user, ids, [field_name])
|
||||
for obj_value in obj_values:
|
||||
trans = ""
|
||||
if lang:
|
||||
cursor.execute("SELECT * " \
|
||||
"FROM ir_translation " \
|
||||
"WHERE type='model' " \
|
||||
"AND name=%s AND res_id=%d AND lang=%s",
|
||||
(name, obj_value['id'], lang))
|
||||
res = cursor.dictfetchall()
|
||||
if len(res):
|
||||
trans = res[0]['value']
|
||||
|
||||
res_id = obj_value['id']
|
||||
if obj_name in ('ir.model', 'ir.ui.menu'):
|
||||
res_id = 0
|
||||
model_data_ids = model_data_obj.search(cursor, user, [
|
||||
('model', '=', obj_name),
|
||||
('res_id', '=', obj_value['id']),
|
||||
])
|
||||
if model_data_ids:
|
||||
model_data = model_data_obj.browse(cursor, user,
|
||||
model_data_ids[0])
|
||||
res_id = model_data.module + '.' + model_data.name
|
||||
|
||||
out.append(["model", name, res_id,
|
||||
obj_value[field_name], trans])
|
||||
if hasattr(field_def, 'selection') \
|
||||
and isinstance(field_def.selection, (list, tuple)):
|
||||
for key, val in field_def.selection:
|
||||
to_translate.append(["selection", name,
|
||||
[val.encode('utf8')]])
|
||||
|
||||
# reports (xsl and rml)
|
||||
obj = pool.get("ir.actions.report.xml")
|
||||
for i in obj.read(cursor, user, obj.search(cursor, user, [])):
|
||||
if in_modules(i["model"], modules):
|
||||
name = i["report_name"]
|
||||
fname = ""
|
||||
xmlstr = None
|
||||
report_type = None
|
||||
parse_func = None
|
||||
try:
|
||||
xmlstr = file_open(fname).read()
|
||||
document = xml.dom.minidom.parseString(xmlstr)
|
||||
to_translate.append([report_type, name,
|
||||
parse_func(document.documentElement)])
|
||||
except IOError:
|
||||
if fname:
|
||||
logger.notify_channel("init", LOG_WARNING,
|
||||
"couldn't export translation for report %s %s %s" %\
|
||||
(name, report_type, fname))
|
||||
# views
|
||||
obj = pool.get("ir.ui.view")
|
||||
for i in obj.read(cursor, user, obj.search(cursor, user, [])):
|
||||
if in_modules(i["model"], modules):
|
||||
document = xml.dom.minidom.parseString(i['arch'])
|
||||
to_translate.append(["view", i['model'],
|
||||
trans_parse_view(document.documentElement)])
|
||||
|
||||
# wizards
|
||||
for service_name, obj in _SERVICE.iteritems():
|
||||
if service_name.startswith('wizard.'):
|
||||
for state_name, state_def in obj.states.iteritems():
|
||||
if 'result' in state_def:
|
||||
result = state_def['result']
|
||||
if result['type'] != 'form':
|
||||
continue
|
||||
|
||||
name = obj.wiz_name + ',' + state_name
|
||||
|
||||
# export fields
|
||||
for field_name, field_def in result['fields'].iteritems():
|
||||
if 'string' in field_def:
|
||||
source = field_def['string']
|
||||
res_name = name + ',' + field_name
|
||||
to_translate.append(["wizard_field", res_name,
|
||||
[source]])
|
||||
|
||||
# export arch
|
||||
arch = result['arch']
|
||||
if not isinstance(arch, UpdateableStr):
|
||||
document = xml.dom.minidom.parseString(arch)
|
||||
to_translate.append(["wizard_view", name,
|
||||
trans_parse_view(document.documentElement)])
|
||||
|
||||
# export button labels
|
||||
for but_args in result['state']:
|
||||
button_name = but_args[0]
|
||||
button_label = but_args[1]
|
||||
res_name = name + ',' + button_name
|
||||
to_translate.append(["wizard_button", res_name,
|
||||
[button_label]])
|
||||
|
||||
# code
|
||||
for root, dirs, files in os.walk(CONFIG['root_path']):
|
||||
for fname in fnmatch.filter(files, '*.py'):
|
||||
frelativepath = join(root, fname)
|
||||
code_string = file_open(frelativepath, subdir='').read()
|
||||
|
||||
# TODO: add support for """ and '''... These should use the DOTALL flag
|
||||
# DOTALL
|
||||
# Make the "." special character match any character at all, including a
|
||||
# newline; without this flag, "." will match anything except a newline.
|
||||
# *? is the non-greedy version of the * qualifier
|
||||
for i in re.finditer(
|
||||
'[^a-zA-Z0-9_]_\([\s]*["\'](.*?)["\'][\s]*\)',
|
||||
code_string):
|
||||
source = i.group(1).encode('utf8')
|
||||
# TODO: check whether the same string has already been exported
|
||||
res = trans_obj._get_source(cursor, frelativepath, 'code',
|
||||
lang, source) or ''
|
||||
out.append(["code", frelativepath, "0", source, res])
|
||||
|
||||
# translate strings marked as to be translated
|
||||
for ttype, name, sources in to_translate:
|
||||
for source in sources:
|
||||
trans = trans_obj._get_source(cursor, name, ttype, lang,
|
||||
source)
|
||||
out.append([ttype, name, "0", source, trans or ''])
|
||||
|
||||
cursor.close()
|
||||
return out
|
||||
|
||||
def trans_load(db_name, filename, lang, strict=False):
|
||||
logger = Logger()
|
||||
data = ''
|
||||
try:
|
||||
data = file(filename,'r').read().split('\n')
|
||||
except IOError:
|
||||
logger.notify_channel("init", LOG_ERROR, "couldn't read file")
|
||||
return trans_load_data(db_name, data, lang, strict=strict)
|
||||
|
||||
def trans_load_data(db_name, data, lang, strict=False, lang_name=None):
|
||||
logger = Logger()
|
||||
logger.notify_channel("init", LOG_INFO,
|
||||
'loading translation file for language %s' % (lang))
|
||||
pool = pooler.get_pool(db_name)
|
||||
lang_obj = pool.get('res.lang')
|
||||
trans_obj = pool.get('ir.translation')
|
||||
model_data_obj = pool.get('ir.model.data')
|
||||
values_obj = pool.get('ir.values')
|
||||
try:
|
||||
user = 1
|
||||
cursor = pooler.get_db(db_name).cursor()
|
||||
|
||||
ids = lang_obj.search(cursor, user, [('code', '=', lang)])
|
||||
if not ids:
|
||||
if not lang_name:
|
||||
lang_name = lang
|
||||
languages = get_languages()
|
||||
if lang in languages:
|
||||
lang_name = languages[lang]
|
||||
ids = lang_obj.create(cursor, user, {
|
||||
'code': lang,
|
||||
'name': lang_name,
|
||||
'translatable': 1,
|
||||
})
|
||||
else:
|
||||
lang_obj.write(cursor, user, ids, {'translatable':1})
|
||||
lang_ids = lang_obj.search(cursor, user, [])
|
||||
langs = lang_obj.read(cursor, user, lang_ids)
|
||||
selection = [(x['code'], x['name']) for x in langs]
|
||||
|
||||
values_obj.set(cursor, user, 'meta', 'lang', 'lang',
|
||||
[('res.users', False,)],
|
||||
False, True, False, meta={
|
||||
'type': 'selection',
|
||||
'string': 'Language',
|
||||
'selection': selection,
|
||||
})
|
||||
|
||||
user_ids = pool.get('res.users').search(cursor, user, [])
|
||||
for user_id in user_ids:
|
||||
values_obj.set(cursor, user, 'meta', 'lang', 'lang',
|
||||
[('res.users', user_id,)], lang, True, False)
|
||||
|
||||
reader = csv.reader(data)
|
||||
# read the first line of the file (it contains columns titles)
|
||||
for row in reader:
|
||||
first = row
|
||||
break
|
||||
|
||||
# read the rest of the file
|
||||
line = 1
|
||||
for row in reader:
|
||||
line += 1
|
||||
try:
|
||||
# skip empty rows and rows where the translation field is empty
|
||||
if (not row) or (not row[4]):
|
||||
continue
|
||||
|
||||
# dictionary which holds values for this line of the csv file
|
||||
# {'lang': ..., 'type': ..., 'name': ..., 'res_id': ...,
|
||||
# 'src': ..., 'value': ...}
|
||||
dic = {'lang': lang}
|
||||
for i in range(len(first)):
|
||||
dic[first[i]] = row[i]
|
||||
|
||||
try:
|
||||
dic['res_id'] = int(dic['res_id'])
|
||||
except:
|
||||
model_data_ids = model_data_obj.search(cursor, user, [
|
||||
('model', '=', dic['name'].split(',')[0]),
|
||||
('module', '=', dic['res_id'].split('.', 1)[0]),
|
||||
('name', '=', dic['res_id'].split('.', 1)[1]),
|
||||
])
|
||||
if model_data_ids:
|
||||
dic['res_id'] = model_data_obj.browse(cursor, user,
|
||||
model_data_ids[0]).res_id
|
||||
else:
|
||||
dic['res_id'] = False
|
||||
|
||||
if dic['type'] == 'model' and not strict:
|
||||
(model, field) = dic['name'].split(',')
|
||||
|
||||
# get the ids of the resources of this model which share
|
||||
# the same source
|
||||
obj = pool.get(model)
|
||||
if obj:
|
||||
obj_ids = obj.search(cursor, user,
|
||||
[(field, '=', dic['src'])])
|
||||
|
||||
# if the resource id (res_id) is in that list, use it,
|
||||
# otherwise use the whole list
|
||||
obj_ids = (dic['res_id'] in obj_ids) \
|
||||
and [dic['res_id']] or obj_ids
|
||||
for res_id in obj_ids:
|
||||
dic['res_id'] = res_id
|
||||
trans_ids = trans_obj.search(cursor, user, [
|
||||
('lang', '=', lang),
|
||||
('type', '=', dic['type']),
|
||||
('name', '=', dic['name']),
|
||||
('src', '=', dic['src']),
|
||||
('res_id', '=', dic['res_id'])
|
||||
])
|
||||
if trans_ids:
|
||||
trans_obj.write(cursor, user, trans_ids,
|
||||
{'value': dic['value']})
|
||||
else:
|
||||
trans_obj.create(cursor, user, dic)
|
||||
else:
|
||||
trans_ids = trans_obj.search(cursor, user, [
|
||||
('lang', '=', lang),
|
||||
('type', '=', dic['type']),
|
||||
('name', '=', dic['name']),
|
||||
('src', '=', dic['src'])
|
||||
])
|
||||
if trans_ids:
|
||||
trans_obj.write(cursor, user, trans_ids,
|
||||
{'value': dic['value']})
|
||||
else:
|
||||
trans_obj.create(cursor, user, dic)
|
||||
cursor.commit()
|
||||
except Exception, exp:
|
||||
logger.notify_channel('init', LOG_ERROR,
|
||||
'Import error: %s on line %d: %s!' % \
|
||||
(str(exp), line, row))
|
||||
cursor.rollback()
|
||||
cursor.close()
|
||||
cursor = pooler.get_db(db_name).cursor()
|
||||
cursor.close()
|
||||
logger.notify_channel("init", LOG_INFO,
|
||||
"translation file loaded succesfully")
|
||||
except IOError:
|
||||
logger.notify_channel("init", LOG_ERROR, "couldn't read file")
|
||||
@@ -0,0 +1,203 @@
|
||||
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be)
|
||||
# Copyright (c) 2007 Cedric Krier.
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsability of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# garantees and support are strongly adviced to contact a Free Software
|
||||
# Service Company
|
||||
#
|
||||
# This program is Free Software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
%prog [options]
|
||||
"""
|
||||
|
||||
import sys, os, signal
|
||||
import netsvc
|
||||
import time
|
||||
import psycopg
|
||||
import pooler
|
||||
import sql_db
|
||||
import config
|
||||
from config import CONFIG
|
||||
import web_service
|
||||
import workflow
|
||||
import report
|
||||
from module import register_classes
|
||||
import osv, security, tools, version
|
||||
|
||||
|
||||
class TrytonServer(object):
|
||||
|
||||
def __init__(self):
|
||||
netsvc.init_logger()
|
||||
self.logger = netsvc.Logger()
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import mx.DateTime
|
||||
mx.DateTime.strptime = lambda x, y: mx.DateTime.mktime(
|
||||
time.strptime(x, y))
|
||||
|
||||
self.logger.notify_channel("objects", netsvc.LOG_INFO,
|
||||
'initialising distributed objects services')
|
||||
|
||||
self.dispatcher = netsvc.Dispatcher()
|
||||
self.dispatcher.monitor(signal.SIGINT)
|
||||
|
||||
self.db_name = CONFIG["db_name"]
|
||||
|
||||
cursor = None
|
||||
try:
|
||||
if self.db_name:
|
||||
cursor = pooler.get_db_only(self.db_name).cursor()
|
||||
except psycopg.OperationalError:
|
||||
self.logger.notify_channel("init", netsvc.LOG_INFO,
|
||||
"could not connect to database '%s'!" % self.db_name,)
|
||||
if cursor:
|
||||
cursor.execute("SELECT relname " \
|
||||
"FROM pg_class " \
|
||||
"WHERE relkind = 'r' AND relname in (" \
|
||||
"'inherit', "
|
||||
"'ir_values', "
|
||||
"'ir_model', "
|
||||
"'ir_model_fields', "
|
||||
"'ir_actions', "
|
||||
"'ir_act_window', "
|
||||
"'ir_act_report_xml', "
|
||||
"'ir_act_report_custom', "
|
||||
"'ir_act_group', "
|
||||
"'ir_act_group_link', "
|
||||
"'ir_act_execute', "
|
||||
"'ir_act_wizard', "
|
||||
"'ir_act_url', "
|
||||
"'ir_ui_view', "
|
||||
"'ir_ui_menu', "
|
||||
"'res_users', "
|
||||
"'res_groups', "
|
||||
"'res_roles', "
|
||||
"'res_roles_users_rel', "
|
||||
"'res_groups_users_rel', "
|
||||
"'wkf', "
|
||||
"'wkf_activity', "
|
||||
"'wkf_transition', "
|
||||
"'wkf_instance', "
|
||||
"'wkf_workitem', "
|
||||
"'wkf_witm_trans', "
|
||||
"'wkf_logs', "
|
||||
"'ir_module_category', "
|
||||
"'ir_module_module', "
|
||||
"'ir_module_module_dependency'"
|
||||
")")
|
||||
if len(cursor.fetchall()) == 0:
|
||||
self.logger.notify_channel("init", netsvc.LOG_INFO, "init db")
|
||||
sql_db.init_db(cursor)
|
||||
# in that case, force --init=all
|
||||
CONFIG["init"]["all"] = 1
|
||||
CONFIG['update']['all'] = 1
|
||||
if not CONFIG['without_demo']:
|
||||
CONFIG["demo"]['all'] = 1
|
||||
cursor.commit()
|
||||
cursor.close()
|
||||
|
||||
web_service.DB()
|
||||
web_service.Common()
|
||||
web_service.Object()
|
||||
web_service.Wizard()
|
||||
web_service.Report()
|
||||
|
||||
workflow.WorkflowService()
|
||||
|
||||
# TODO add report service
|
||||
|
||||
def run(self):
|
||||
|
||||
register_classes()
|
||||
if self.db_name:
|
||||
pooler.get_db_and_pool(self.db_name,
|
||||
update_module=bool(CONFIG['init'] or CONFIG['update']))
|
||||
|
||||
if CONFIG["stop_after_init"]:
|
||||
sys.exit(0)
|
||||
|
||||
# Launch Server
|
||||
secure = CONFIG["secure"]
|
||||
if CONFIG['xmlrpc']:
|
||||
interface = CONFIG["interface"]
|
||||
try:
|
||||
port = int(CONFIG["port"])
|
||||
except:
|
||||
self.logger.notify_channel("init", netsvc.LOG_ERROR,
|
||||
"invalid port '%s'!" % (CONFIG["port"],))
|
||||
sys.exit(1)
|
||||
|
||||
httpd = netsvc.HttpDaemon(interface, port, secure)
|
||||
|
||||
xml_gw = netsvc.XmlRpc.RpcGateway('web-services')
|
||||
httpd.attach("/xmlrpc", xml_gw )
|
||||
self.logger.notify_channel("web-services", netsvc.LOG_INFO,
|
||||
"starting XML-RPC" + \
|
||||
(CONFIG['secure'] and ' Secure' or '') + \
|
||||
" services, port " + str(port))
|
||||
|
||||
if CONFIG['netrpc']:
|
||||
netinterface = CONFIG["netinterface"]
|
||||
try:
|
||||
netport = int(CONFIG["netport"])
|
||||
except Exception:
|
||||
self.logger.notify_channel("init", netsvc.LOG_ERROR,
|
||||
"invalid port '%s'!" % (CONFIG["netport"],))
|
||||
sys.exit(1)
|
||||
|
||||
tinysocket = netsvc.TinySocketServerThread(netinterface, netport,
|
||||
False)
|
||||
self.logger.notify_channel("web-services", netsvc.LOG_INFO,
|
||||
"starting netrpc service, port " + str(netport))
|
||||
|
||||
def handler(signum, frame):
|
||||
if CONFIG['netrpc']:
|
||||
tinysocket.stop()
|
||||
if CONFIG['xmlrpc']:
|
||||
httpd.stop()
|
||||
netsvc.Agent.quit()
|
||||
if CONFIG['pidfile']:
|
||||
os.unlink(CONFIG['pidfile'])
|
||||
sys.exit(0)
|
||||
|
||||
if CONFIG['pidfile']:
|
||||
fd_pid = open(CONFIG['pidfile'], 'w')
|
||||
pidtext = "%d" % (os.getpid())
|
||||
fd_pid.write(pidtext)
|
||||
fd_pid.close()
|
||||
|
||||
signal.signal(signal.SIGINT, handler)
|
||||
signal.signal(signal.SIGTERM, handler)
|
||||
|
||||
self.logger.notify_channel("web-services", netsvc.LOG_INFO,
|
||||
'the server is running, waiting for connections...')
|
||||
if CONFIG['netrpc']:
|
||||
tinysocket.start()
|
||||
if CONFIG['xmlrpc']:
|
||||
httpd.start()
|
||||
#DISPATCHER.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
SERVER = TrytonServer()
|
||||
SERVER.run()
|
||||
while True:
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,5 @@
|
||||
PACKAGE = "trytond"
|
||||
VERSION = "0.0.1"
|
||||
LICENSE = "GPL v2"
|
||||
WEBSITE = "http://www.tryton.org"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from db import *
|
||||
from common import *
|
||||
from object import *
|
||||
from wizard import *
|
||||
from report import *
|
||||
@@ -0,0 +1,33 @@
|
||||
from trytond.netsvc import Service, Logger, LOG_INFO
|
||||
from trytond import security
|
||||
from trytond.version import VERSION
|
||||
import time
|
||||
|
||||
|
||||
class Common(Service):
|
||||
|
||||
def __init__(self, name="common"):
|
||||
Service.__init__(self, name)
|
||||
Service.join_group(self, "web-services")
|
||||
Service.export_method(self, self.about)
|
||||
Service.export_method(self, self.login)
|
||||
Service.export_method(self, self.timezone_get)
|
||||
|
||||
def login(self, database, login, password):
|
||||
res = security.login(database, login, password)
|
||||
logger = Logger()
|
||||
msg = res and 'successful login' or 'bad login or password'
|
||||
logger.notify_channel("web-service", LOG_INFO,
|
||||
"%s from '%s' using database '%s'" % (msg, login, database))
|
||||
return res or False
|
||||
|
||||
def about(self):
|
||||
return '''
|
||||
Tryton %s
|
||||
|
||||
The whole source code is distributed under the terms of the
|
||||
GNU Public Licence v2.
|
||||
''' % (VERSION,)
|
||||
|
||||
def timezone_get(self):
|
||||
return time.tzname[0]
|
||||
@@ -0,0 +1,258 @@
|
||||
from trytond.netsvc import Service, Logger, LOG_ERROR, LOG_INFO, LOG_WARNING
|
||||
import threading
|
||||
from trytond import security
|
||||
from trytond import sql_db
|
||||
from trytond import pooler
|
||||
from trytond import tools
|
||||
import base64
|
||||
import os
|
||||
|
||||
|
||||
class DB(Service):
|
||||
|
||||
def __init__(self, name="db"):
|
||||
super(DB, self).__init__(name)
|
||||
self.join_group("web-services")
|
||||
self.export_method(self.create)
|
||||
self.export_method(self.get_progress)
|
||||
self.export_method(self.drop)
|
||||
self.export_method(self.dump)
|
||||
self.export_method(self.restore)
|
||||
self.export_method(self.list)
|
||||
self.export_method(self.list_lang)
|
||||
self.export_method(self.change_admin_password)
|
||||
self.actions = {}
|
||||
self._id = 0
|
||||
self.id_protect = threading.Semaphore()
|
||||
|
||||
def create(self, password, db_name, demo, lang):
|
||||
security.check_super(password)
|
||||
self.id_protect.acquire()
|
||||
self._id += 1
|
||||
db_id = self._id
|
||||
self.id_protect.release()
|
||||
|
||||
self.actions[db_id] = {'clean': False}
|
||||
|
||||
database = sql_db.db_connect('template1', serialize=1)
|
||||
database.truedb.autocommit()
|
||||
cursor = database.cursor()
|
||||
cursor.execute('CREATE DATABASE ' + db_name + ' ENCODING \'unicode\'')
|
||||
cursor.close()
|
||||
|
||||
|
||||
class DBInitialize(object):
|
||||
def __call__(self, service, db_id, db_name, demo, lang):
|
||||
try:
|
||||
service.actions[db_id]['progress'] = 0
|
||||
cursor = sql_db.db_connect(db_name).cursor()
|
||||
sql_db.init_db(cursor)
|
||||
cursor.commit()
|
||||
cursor.close()
|
||||
pooler.get_pool(db_name, demo, service.actions[db_id],
|
||||
update_module=True)
|
||||
if lang and lang != 'en_US':
|
||||
filename = tools.CONFIG["root_path"] + "/i18n/" + \
|
||||
lang + ".csv"
|
||||
tools.trans_load(db_name, filename, lang)
|
||||
service.actions[db_id]['clean'] = True
|
||||
cursor = sql_db.db_connect(db_name).cursor()
|
||||
cursor.execute('select login, password, name ' \
|
||||
'from res_users ' \
|
||||
'where login <> \'root\' order by login')
|
||||
service.actions[db_id]['users'] = cursor.dictfetchall()
|
||||
cursor.close()
|
||||
except Exception, exp:
|
||||
service.actions[db_id]['clean'] = False
|
||||
service.actions[db_id]['exception'] = exp
|
||||
from StringIO import StringIO
|
||||
import traceback
|
||||
e_str = StringIO()
|
||||
traceback.print_exc(file=e_str)
|
||||
traceback_str = e_str.getvalue()
|
||||
e_str.close()
|
||||
service.actions[db_id]['traceback'] = traceback_str
|
||||
cursor.close()
|
||||
|
||||
logger = Logger()
|
||||
logger.notify_channel("web-services", LOG_INFO,
|
||||
'CREATE DB: %s' % (db_name))
|
||||
dbi = DBInitialize()
|
||||
create_thread = threading.Thread(target=dbi,
|
||||
args=(self, db_id, db_name, demo, lang))
|
||||
create_thread.start()
|
||||
self.actions[db_id]['thread'] = create_thread
|
||||
return db_id
|
||||
|
||||
def get_progress(self, password, db_id):
|
||||
security.check_super(password)
|
||||
if self.actions[db_id]['thread'].isAlive():
|
||||
# return addons.init_progress[db_name]
|
||||
return (min(self.actions[db_id].get('progress', 0), 0.95), [])
|
||||
else:
|
||||
clean = self.actions[db_id]['clean']
|
||||
if clean:
|
||||
users = self.actions[db_id]['users']
|
||||
del self.actions[db_id]
|
||||
return (1.0, users)
|
||||
else:
|
||||
exp = self.actions[db_id]['exception']
|
||||
del self.actions[db_id]
|
||||
raise Exception, exp
|
||||
|
||||
def drop(self, password, db_name):
|
||||
security.check_super(password)
|
||||
pooler.close_db(db_name)
|
||||
logger = Logger()
|
||||
|
||||
database = sql_db.db_connect('template1', serialize=1)
|
||||
database.truedb.autocommit()
|
||||
cursor = database.cursor()
|
||||
try:
|
||||
try:
|
||||
cursor.execute('DROP DATABASE ' + db_name)
|
||||
except:
|
||||
logger.notify_channel("web-service", LOG_ERROR,
|
||||
'DROP DB: %s failed' % (db_name,))
|
||||
raise
|
||||
else:
|
||||
logger.notify_channel("web-services", LOG_INFO,
|
||||
'DROP DB: %s' % (db_name))
|
||||
finally:
|
||||
cursor.close()
|
||||
return True
|
||||
|
||||
def dump(self, password, db_name):
|
||||
security.check_super(password)
|
||||
logger = Logger()
|
||||
|
||||
if tools.CONFIG['db_password']:
|
||||
logger.notify_channel("web-service", LOG_ERROR,
|
||||
'DUMP DB: %s doesn\'t work with password' % (db_name,))
|
||||
raise Exception, "Couldn't dump database with password"
|
||||
|
||||
cmd = ['pg_dump', '--format=c']
|
||||
if tools.CONFIG['db_user']:
|
||||
cmd.append('--username=' + tools.CONFIG['db_user'])
|
||||
if tools.CONFIG['db_host']:
|
||||
cmd.append('--host=' + tools.CONFIG['db_host'])
|
||||
if tools.CONFIG['db_port']:
|
||||
cmd.append('--port=' + tools.CONFIG['db_port'])
|
||||
cmd.append(db_name)
|
||||
|
||||
stdin, stdout = tools.exec_pg_command_pipe(*tuple(cmd))
|
||||
stdin.close()
|
||||
data = stdout.read()
|
||||
res = stdout.close()
|
||||
if res:
|
||||
logger.notify_channel("web-service", LOG_ERROR,
|
||||
'DUMP DB: %s failed\n%s' % (db_name, data))
|
||||
raise Exception, "Couldn't dump database"
|
||||
logger.notify_channel("web-services", LOG_INFO,
|
||||
'DUMP DB: %s' % (db_name))
|
||||
return base64.encodestring(data)
|
||||
|
||||
def restore(self, password, db_name, data):
|
||||
security.check_super(password)
|
||||
logger = Logger()
|
||||
|
||||
if self.db_exist(db_name):
|
||||
logger.notify_channel("web-service", LOG_WARNING,
|
||||
'RESTORE DB: %s already exists' % (db_name,))
|
||||
raise Exception, "Database already exists"
|
||||
|
||||
if tools.CONFIG['db_password']:
|
||||
logger.notify_channel("web-service", LOG_ERROR,
|
||||
'RESTORE DB: %s doesn\'t work with password' % (db_name,))
|
||||
raise Exception, "Couldn't restore database with password"
|
||||
|
||||
database = sql_db.db_connect('template1', serialize=1)
|
||||
database.truedb.autocommit()
|
||||
cursor = database.cursor()
|
||||
cursor.execute('CREATE DATABASE ' + db_name + ' ENCODING \'unicode\'')
|
||||
cursor.close()
|
||||
|
||||
cmd = ['pg_restore']
|
||||
if tools.CONFIG['db_user']:
|
||||
cmd.append('--username=' + tools.CONFIG['db_user'])
|
||||
if tools.CONFIG['db_host']:
|
||||
cmd.append('--host=' + tools.CONFIG['db_host'])
|
||||
if tools.CONFIG['db_port']:
|
||||
cmd.append('--port=' + tools.CONFIG['db_port'])
|
||||
cmd.append('--dbname=' + db_name)
|
||||
args2 = tuple(cmd)
|
||||
|
||||
buf = base64.decodestring(data)
|
||||
if os.name == "nt":
|
||||
tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
|
||||
file(tmpfile, 'wb').write(buf)
|
||||
args2 = list(args2)
|
||||
args2.append(' ' + tmpfile)
|
||||
args2 = tuple(args2)
|
||||
stdin, stdout = tools.exec_pg_command_pipe(*args2)
|
||||
if not os.name == "nt":
|
||||
stdin.write(base64.decodestring(data))
|
||||
stdin.close()
|
||||
res = stdout.close()
|
||||
if res:
|
||||
raise Exception, "Couldn't restore database"
|
||||
logger.notify_channel("web-services", LOG_INFO,
|
||||
'RESTORE DB: %s' % (db_name))
|
||||
return True
|
||||
|
||||
def db_exist(self, db_name):
|
||||
try:
|
||||
database = sql_db.db_connect(db_name)
|
||||
database.truedb.close()
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def list(self):
|
||||
database = sql_db.db_connect('template1')
|
||||
try:
|
||||
cursor = database.cursor()
|
||||
db_user = tools.CONFIG["db_user"]
|
||||
if not db_user and os.name == 'posix':
|
||||
import pwd
|
||||
db_user = pwd.getpwuid(os.getuid())[0]
|
||||
if not db_user:
|
||||
cursor.execute("SELECT usename " \
|
||||
"FROM pg_user " \
|
||||
"WHERE usesysid = (" \
|
||||
"SELECT datdba " \
|
||||
"FROM pg_database " \
|
||||
"WHERE datname = %s)",
|
||||
(tools.CONFIG["db_name"],))
|
||||
res = cursor.fetchone()
|
||||
db_user = res and res[0]
|
||||
if db_user:
|
||||
cursor.execute("SELECT datname " \
|
||||
"FROM pg_database " \
|
||||
"WHERE datdba = (" \
|
||||
"SELECT usesysid " \
|
||||
"FROM pg_user " \
|
||||
"WHERE usename=%s) " \
|
||||
"AND datname not in " \
|
||||
"('template0', 'template1', 'postgres')",
|
||||
(db_user,))
|
||||
else:
|
||||
cursor.execute("SELECT datname " \
|
||||
"FROM pg_database " \
|
||||
"WHERE datname not in " \
|
||||
"('template0', 'template1','postgres')")
|
||||
res = [name for (name,) in cursor.fetchall()]
|
||||
cursor.close()
|
||||
except:
|
||||
res = []
|
||||
database.truedb.close()
|
||||
return res
|
||||
|
||||
def change_admin_password(self, old_password, new_password):
|
||||
security.check_super(old_password)
|
||||
tools.CONFIG['admin_passwd'] = new_password
|
||||
tools.CONFIG.save()
|
||||
return True
|
||||
|
||||
def list_lang(self):
|
||||
return tools.scan_languages()
|
||||
@@ -0,0 +1,31 @@
|
||||
from trytond.netsvc import Service, LocalService
|
||||
from trytond import security
|
||||
|
||||
class Object(Service):
|
||||
|
||||
def __init__(self, name="object"):
|
||||
Service.__init__(self, name)
|
||||
Service.join_group(self, 'web-services')
|
||||
Service.export_method(self, self.execute)
|
||||
Service.export_method(self, self.exec_workflow)
|
||||
Service.export_method(self, self.obj_list)
|
||||
|
||||
def exec_workflow(self, database, user, passwd, object_name, method,
|
||||
object_id):
|
||||
security.check(database, user, passwd)
|
||||
service = LocalService("object_proxy")
|
||||
res = service.exec_workflow(database, user, object_name, method,
|
||||
object_id)
|
||||
return res
|
||||
|
||||
def execute(self, database, user, passwd, object_name, method, *args):
|
||||
security.check(database, user, passwd)
|
||||
service = LocalService("object_proxy")
|
||||
res = service.execute(database, user, object_name, method, *args)
|
||||
return res
|
||||
|
||||
def obj_list(self, database, user, passwd):
|
||||
security.check(database, user, passwd)
|
||||
service = LocalService("object_proxy")
|
||||
res = service.obj_list()
|
||||
return res
|
||||
@@ -0,0 +1,82 @@
|
||||
from trytond.netsvc import Service, LocalService
|
||||
from trytond import security
|
||||
import threading
|
||||
import thread
|
||||
from trytond import pooler
|
||||
from trytond.config import CONFIG
|
||||
import base64
|
||||
|
||||
|
||||
class Report(Service):
|
||||
|
||||
def __init__(self, name='report'):
|
||||
Service.__init__(self, name)
|
||||
Service.join_group(self, 'web-services')
|
||||
Service.export_method(self, self.report)
|
||||
Service.export_method(self, self.report_get)
|
||||
self._reports = {}
|
||||
self.max_id = 0
|
||||
self.id_protect = threading.Semaphore()
|
||||
|
||||
def report(self, database, user, passwd, object_name, ids, datas=None,
|
||||
context=None):
|
||||
if datas is None:
|
||||
datas = {}
|
||||
if context is None:
|
||||
context = {}
|
||||
security.check(database, user, passwd)
|
||||
|
||||
self.id_protect.acquire()
|
||||
self.max_id += 1
|
||||
report_id = self.max_id
|
||||
self.id_protect.release()
|
||||
|
||||
self._reports[report_id] = {
|
||||
'user': user,
|
||||
'result': False,
|
||||
'state': False,
|
||||
}
|
||||
|
||||
def _go(report_id, user, ids, datas, context):
|
||||
cursor = pooler.get_db(database).cursor()
|
||||
obj = LocalService('report.' + object_name)
|
||||
(result, format) = obj.create(cursor, user, ids, datas, context)
|
||||
cursor.close()
|
||||
self._reports[report_id]['result'] = result
|
||||
self._reports[report_id]['format'] = format
|
||||
self._reports[report_id]['state'] = True
|
||||
return True
|
||||
|
||||
thread.start_new_thread(_go, (report_id, user, ids, datas, context))
|
||||
return report_id
|
||||
|
||||
def _check_report(self, report_id):
|
||||
result = self._reports[report_id]
|
||||
res = {'state': result['state']}
|
||||
if res['state']:
|
||||
if CONFIG['reportgz']:
|
||||
import zlib
|
||||
res2 = zlib.compress(result['result'])
|
||||
res['code'] = 'zlib'
|
||||
else:
|
||||
#CHECKME: why is this needed???
|
||||
if isinstance(result['result'], unicode):
|
||||
res2 = result['result'].encode('latin1', 'replace')
|
||||
else:
|
||||
res2 = result['result']
|
||||
if res2:
|
||||
res['result'] = base64.encodestring(res2)
|
||||
res['format'] = result['format']
|
||||
del self._reports[report_id]
|
||||
return res
|
||||
|
||||
def report_get(self, database, user, passwd, report_id):
|
||||
security.check(database, user, passwd)
|
||||
|
||||
if report_id in self._reports:
|
||||
if self._reports[report_id]['uid'] == user:
|
||||
return self._check_report(report_id)
|
||||
else:
|
||||
raise Exception, 'AccessDenied'
|
||||
else:
|
||||
raise Exception, 'ReportNotFound'
|
||||
@@ -0,0 +1,42 @@
|
||||
from trytond.netsvc import Service, LocalService
|
||||
from trytond import security
|
||||
|
||||
|
||||
class Wizard(Service):
|
||||
|
||||
def __init__(self, name='wizard'):
|
||||
Service.__init__(self, name)
|
||||
Service.join_group(self, 'web-services')
|
||||
Service.export_method(self, self.execute)
|
||||
Service.export_method(self, self.create)
|
||||
self.max_id = 0
|
||||
self.wiz_datas = {}
|
||||
self.wiz_name = {}
|
||||
self.wiz_uid = {}
|
||||
|
||||
def _execute(self, database, user, wiz_id, datas, action, context):
|
||||
self.wiz_datas[wiz_id].update(datas)
|
||||
wiz = LocalService('wizard.'+self.wiz_name[wiz_id])
|
||||
return wiz.execute(database, user, self.wiz_datas[wiz_id], action,
|
||||
context)
|
||||
|
||||
def create(self, database, user, passwd, wiz_name, datas=None):
|
||||
if not datas:
|
||||
datas = {}
|
||||
security.check(database, user, passwd)
|
||||
# FIXME: this is not thread-safe
|
||||
self.max_id += 1
|
||||
self.wiz_datas[self.max_id] = {}
|
||||
self.wiz_name[self.max_id] = wiz_name
|
||||
self.wiz_uid[self.max_id] = user
|
||||
return self.max_id
|
||||
|
||||
def execute(self, database, user, passwd, wiz_id, datas, *args):
|
||||
security.check(database, user, passwd)
|
||||
if wiz_id in self.wiz_uid:
|
||||
if self.wiz_uid[wiz_id] == user:
|
||||
return self._execute(database, user, wiz_id, datas, *args)
|
||||
else:
|
||||
raise Exception, 'AccessDenied'
|
||||
else:
|
||||
raise Exception, 'WizardNotFound'
|
||||
@@ -0,0 +1 @@
|
||||
from wkf_service import *
|
||||
@@ -0,0 +1,64 @@
|
||||
import workitem
|
||||
|
||||
def create(cursor, ident, wkf_id):
|
||||
(user, res_type, res_id) = ident
|
||||
cursor.execute("SELECT NEXTVAL('wkf_instance_id_seq')")
|
||||
(id_new,) = cursor.fetchone()
|
||||
cursor.execute('INSERT INTO wkf_instance ' \
|
||||
'(id, res_type, res_id, uid, wkf_id) VALUES (%d,%s,%s,%s,%s)',
|
||||
(id_new, res_type, res_id, user, wkf_id))
|
||||
cursor.execute('SELECT * FROM wkf_activity ' \
|
||||
'WHERE flow_start = True and wkf_id = %d', (wkf_id,))
|
||||
res = cursor.dictfetchall()
|
||||
workitem.create(cursor, res, id_new, ident)
|
||||
update(cursor, id_new, ident)
|
||||
return id_new
|
||||
|
||||
def delete(cursor, ident):
|
||||
(user, res_type, res_id) = ident
|
||||
cursor.execute('DELETE FROM wkf_instance ' \
|
||||
'WHERE res_id = %d AND res_type = %s', (res_id, res_type))
|
||||
|
||||
def validate(cursor, inst_id, ident, signal, force_running=False):
|
||||
cursor.execute("SELECT * FROM wkf_workitem WHERE inst_id = %d", (inst_id,))
|
||||
for witem in cursor.dictfetchall():
|
||||
workitem.process(cursor, witem, ident, signal, force_running)
|
||||
return _update_end(cursor, inst_id, ident)
|
||||
|
||||
def update(cursor, inst_id, ident):
|
||||
cursor.execute("SELECT * FROM wkf_workitem WHERE inst_id = %d", (inst_id,))
|
||||
for witem in cursor.dictfetchall():
|
||||
workitem.process(cursor, witem, ident)
|
||||
return _update_end(cursor, inst_id, ident)
|
||||
|
||||
def _update_end(cursor, inst_id, ident):
|
||||
cursor.execute('SELECT state, flow_stop FROM wkf_workitem w ' \
|
||||
'LEFT JOIN wkf_activity a ' \
|
||||
'ON (a.id = w.act_id) WHERE w.inst_id = %d', (inst_id,))
|
||||
res = True
|
||||
for row in cursor.fetchall():
|
||||
if (row[0] != 'complete') or not row[1]:
|
||||
res = False
|
||||
break
|
||||
if res:
|
||||
cursor.execute('SELECT DISTINCT a.name FROM wkf_activity a ' \
|
||||
'LEFT JOIN wkf_workitem w ' \
|
||||
'ON (a.id = w.act_id) ' \
|
||||
'WHERE w.inst_id = %d', (inst_id,))
|
||||
act_names = cursor.fetchall()
|
||||
cursor.execute("UPDATE wkf_instance " \
|
||||
"SET state = 'complete' WHERE id = %d", (inst_id,))
|
||||
cursor.execute("UPDATE wkf_workitem " \
|
||||
"SET state = 'complete' WHERE subflow_id = %d", (inst_id,))
|
||||
# TODO remove the subquery
|
||||
cursor.execute("SELECT i.id, w.osv, i.res_id " \
|
||||
"FROM wkf_instance i " \
|
||||
"LEFT JOIN wkf w ON (i.wkf_id = w.id) " \
|
||||
"WHERE i.id IN (" \
|
||||
"SELECT inst_id FROM wkf_workitem " \
|
||||
"WHERE subflow_id = %d)", (inst_id,))
|
||||
for i in cursor.fetchall():
|
||||
for act_name in act_names:
|
||||
validate(cursor, i[0], (ident[0], i[1], i[2]),
|
||||
'subflow.' + act_name[0])
|
||||
return res
|
||||
@@ -0,0 +1,73 @@
|
||||
from trytond import netsvc
|
||||
from trytond import pooler
|
||||
|
||||
|
||||
class EnvCall(object):
|
||||
|
||||
def __init__(self, wf_service, d_arg):
|
||||
self.wf_service = wf_service
|
||||
self.d_arg = d_arg
|
||||
|
||||
def __call__(self, *args):
|
||||
arg = self.d_arg + args
|
||||
return self.wf_service.execute_cr(*arg)
|
||||
|
||||
|
||||
class Env(dict):
|
||||
|
||||
def __init__(self, wf_service, cursor, user, model, ids):
|
||||
super(Env, self).__init__()
|
||||
self.wf_service = wf_service
|
||||
self.cursor = cursor
|
||||
self.user = user
|
||||
self.model = model
|
||||
self.ids = ids
|
||||
self.obj = pooler.get_pool(cursor.dbname).get(model)
|
||||
self.columns = self.obj._columns.keys() + \
|
||||
self.obj._inherit_fields.keys()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if (key in self.columns) and (not super(Env, self).__contains__(key)):
|
||||
res = self.wf_service.execute_cr(self.cursor, self.user,
|
||||
self.model, 'read', self.ids, [key])[0][key]
|
||||
super(Env, self).__setitem__(key, res)
|
||||
return res
|
||||
elif key in dir(self.obj):
|
||||
return EnvCall(self.wf_service, (self.cursor, self.user, self.model,
|
||||
key, self.ids))
|
||||
else:
|
||||
return super(Env, self).__getitem__(key)
|
||||
|
||||
def eval_expr(cursor, ident, action):
|
||||
res = False
|
||||
for line in action.split('\n'):
|
||||
user = ident[0]
|
||||
model = ident[1]
|
||||
ids = [ident[2]]
|
||||
if line == 'True':
|
||||
res = True
|
||||
elif line =='False':
|
||||
res = False
|
||||
else:
|
||||
wf_service = netsvc.LocalService("object_proxy")
|
||||
env = Env(wf_service, cursor, user, model, ids)
|
||||
res = eval(line, env)
|
||||
return res
|
||||
|
||||
def execute(cursor, ident, activity):
|
||||
return eval_expr(cursor, ident, activity['action'])
|
||||
|
||||
def check(cursor, ident, transition, signal):
|
||||
res = True
|
||||
if transition['signal']:
|
||||
res = (signal == transition['signal'])
|
||||
|
||||
if transition['role_id']:
|
||||
user = ident[0]
|
||||
serv = netsvc.LocalService('object_proxy')
|
||||
user_roles = serv.execute_cr(cursor, user, 'res.users', 'read', [user],
|
||||
['roles_id'])[0]['roles_id']
|
||||
res = res and serv.execute_cr(cursor, user, 'res.roles', 'check',
|
||||
user_roles, transition['role_id'])
|
||||
res = res and eval_expr(cursor, ident, transition['condition'])
|
||||
return res
|
||||
@@ -0,0 +1,108 @@
|
||||
"Workflow service"
|
||||
from trytond.netsvc import Service
|
||||
import instance
|
||||
|
||||
|
||||
class WorkflowService(Service):
|
||||
"Workflow service"
|
||||
|
||||
def __init__(self, name='workflow'):
|
||||
Service.__init__(self, name)
|
||||
Service.export_method(self, self.clear_cache)
|
||||
Service.export_method(self, self.trg_write)
|
||||
Service.export_method(self, self.trg_delete)
|
||||
Service.export_method(self, self.trg_create)
|
||||
Service.export_method(self, self.trg_validate)
|
||||
Service.export_method(self, self.trg_redirect)
|
||||
Service.export_method(self, self.trg_trigger)
|
||||
self.wkf_on_create_cache = {}
|
||||
|
||||
def clear_cache(self, cursor):
|
||||
"Clear workflow cache"
|
||||
self.wkf_on_create_cache[cursor.dbname] = {}
|
||||
|
||||
@staticmethod
|
||||
def trg_write(user, res_type, res_id, cursor):
|
||||
"Trigger write"
|
||||
ident = (user, res_type, res_id)
|
||||
cursor.execute('SELECT id FROM wkf_instance ' \
|
||||
'WHERE res_id = %d AND res_type = %s AND state = %s',
|
||||
(res_id, res_type, 'active'))
|
||||
for (instance_id,) in cursor.fetchall():
|
||||
instance.update(cursor, instance_id, ident)
|
||||
|
||||
@staticmethod
|
||||
def trg_trigger(user, res_type, res_id, cursor):
|
||||
"Trigger trigger"
|
||||
cursor.execute('SELECT instance_id FROM wkf_triggers ' \
|
||||
'WHERE res_id = %d AND model = %s', (res_id, res_type))
|
||||
# TODO remove the query from for statement
|
||||
for (instance_id,) in cursor.fetchall():
|
||||
cursor.execute('SELECT user, res_type, res_id FROM wkf_instance ' \
|
||||
'WHERE id = %d', (instance_id,))
|
||||
ident = cursor.fetchone()
|
||||
instance.update(cursor, instance_id, ident)
|
||||
|
||||
@staticmethod
|
||||
def trg_delete(user, res_type, res_id, cursor):
|
||||
"Trigger delete"
|
||||
ident = (user, res_type, res_id)
|
||||
instance.delete(cursor, ident)
|
||||
|
||||
def trg_create(self, user, res_type, res_id, cursor):
|
||||
"Trigger create"
|
||||
ident = (user, res_type, res_id)
|
||||
self.wkf_on_create_cache.setdefault(cursor.dbname, {})
|
||||
if res_type in self.wkf_on_create_cache[cursor.dbname]:
|
||||
wkf_ids = self.wkf_on_create_cache[cursor.dbname][res_type]
|
||||
else:
|
||||
cursor.execute('SELECT id FROM wkf ' \
|
||||
'WHERE osv = %s AND on_create = True', (res_type,))
|
||||
wkf_ids = cursor.fetchall()
|
||||
self.wkf_on_create_cache[cursor.dbname][res_type] = wkf_ids
|
||||
for (wkf_id,) in wkf_ids:
|
||||
instance.create(cursor, ident, wkf_id)
|
||||
|
||||
@staticmethod
|
||||
def trg_validate(user, res_type, res_id, signal, cursor):
|
||||
"Trigger validate"
|
||||
ident = (user, res_type, res_id)
|
||||
# ids of all active workflow instances
|
||||
# for a corresponding resource (id, model_name)
|
||||
cursor.execute('SELECT id FROM wkf_instance ' \
|
||||
'WHERE res_id = %d AND res_type = %s AND state = %s',
|
||||
(res_id, res_type, 'active'))
|
||||
for (instance_id,) in cursor.fetchall():
|
||||
instance.validate(cursor, instance_id, ident, signal)
|
||||
|
||||
@staticmethod
|
||||
def trg_redirect(user, res_type, res_id, new_rid, cursor):
|
||||
"""
|
||||
Trigger redirect
|
||||
make all workitems which are waiting for a (subflow) workflow instance
|
||||
for the old resource point to the (first active) workflow instance for
|
||||
the new resource
|
||||
"""
|
||||
# get ids of wkf instances for the old resource (res_id)
|
||||
# XXX shouldn't we get only active instances?
|
||||
cursor.execute('SELECT id, wkf_id FROM wkf_instance ' \
|
||||
'WHERE res_id = %d AND res_type = %s', (res_id, res_type))
|
||||
for old_inst_id, wkf_id in cursor.fetchall():
|
||||
# first active instance for new resource (new_rid), using same wkf
|
||||
cursor.execute(
|
||||
'SELECT id '\
|
||||
'FROM wkf_instance '\
|
||||
'WHERE res_id = %d AND res_type = %s ' \
|
||||
'AND wkf_id = %d AND state = %s',
|
||||
(new_rid, res_type, wkf_id, 'active'))
|
||||
new_id = cursor.fetchone()
|
||||
if new_id:
|
||||
# select all workitems which "wait" for the old instance
|
||||
cursor.execute('SELECT id FROM wkf_workitem ' \
|
||||
'WHERE subflow_id = %d', (old_inst_id,))
|
||||
for (item_id,) in cursor.fetchall():
|
||||
# redirect all those workitems
|
||||
# to the wkf instance of the new resource
|
||||
cursor.execute('UPDATE wkf_workitem ' \
|
||||
'SET subflow_id = %d ' \
|
||||
'WHERE id = %d', (new_id[0], item_id))
|
||||
@@ -0,0 +1,184 @@
|
||||
import wkf_expr
|
||||
import instance
|
||||
|
||||
# TODO: remove triggers
|
||||
|
||||
def create(cursor, act_datas, inst_id, ident):
|
||||
for act in act_datas:
|
||||
cursor.execute("SELECT NEXTVAL('wkf_workitem_id_seq')")
|
||||
(id_new,) = cursor.fetchone()
|
||||
cursor.execute("INSERT INTO wkf_workitem " \
|
||||
"(id, act_id, inst_id, state) VALUES (%d, %s, %s, 'active')",
|
||||
(id_new, act['id'], inst_id))
|
||||
cursor.execute('SELECT * FROM wkf_workitem WHERE id=%d', (id_new,))
|
||||
res = cursor.dictfetchone()
|
||||
process(cursor, res, ident)
|
||||
|
||||
def process(cursor, workitem, ident, signal=None, force_running=False):
|
||||
cursor.execute('SELECT * FROM wkf_activity WHERE id = %d',
|
||||
(workitem['act_id'],))
|
||||
activity = cursor.dictfetchone()
|
||||
triggers = False
|
||||
if workitem['state'] == 'active':
|
||||
triggers = True
|
||||
if not _execute(cursor, workitem, activity, ident):
|
||||
return False
|
||||
|
||||
if workitem['state'] == 'running':
|
||||
pass
|
||||
|
||||
if workitem['state'] == 'complete' or force_running:
|
||||
res = _split_test(cursor, workitem, activity['split_mode'], ident,
|
||||
signal)
|
||||
triggers = triggers and not res
|
||||
|
||||
if triggers:
|
||||
cursor.execute('SELECT * FROM wkf_transition ' \
|
||||
'WHERE act_from = %d', (workitem['act_id'],))
|
||||
alltrans = cursor.dictfetchall()
|
||||
for trans in alltrans:
|
||||
if trans['trigger_model']:
|
||||
ids = wkf_expr.eval_expr(cursor, ident,
|
||||
trans['trigger_expr_id'])
|
||||
for res_id in ids:
|
||||
cursor.execute('SELECT NEXTVAL(\'wkf_triggers_id_seq\')')
|
||||
(new_id,) = cursor.fetchone()
|
||||
cursor.execute('INSERT INTO wkf_triggers ' \
|
||||
'(model, res_id, instance_id, workitem_id, id) ' \
|
||||
'VALUES (%s, %d, %d, %d, %d)',
|
||||
(trans['trigger_model'], res_id,
|
||||
workitem['inst_id'], workitem['id'], new_id))
|
||||
return True
|
||||
|
||||
def _state_set(cursor, workitem, state):
|
||||
cursor.execute('UPDATE wkf_workitem ' \
|
||||
'SET state = %s WHERE id = %d', (state, workitem['id']))
|
||||
workitem['state'] = state
|
||||
|
||||
def _execute(cursor, workitem, activity, ident):
|
||||
"send a signal to parent workflow (signal: subflow.signal_name)"
|
||||
|
||||
if (workitem['state'] == 'active') and activity['signal_send']:
|
||||
# TODO remove subquery
|
||||
cursor.execute("SELECT i.id, w.osv, i.res_id " \
|
||||
"FROM wkf_instance i " \
|
||||
"LEFT JOIN wkf w " \
|
||||
"ON (i.wkf_id = w.id) " \
|
||||
"WHERE i.id in (" \
|
||||
"SELECT inst_id FROM wkf_workitem " \
|
||||
"WHERE subflow_id = %d)", (workitem['inst_id'],))
|
||||
for i in cursor.fetchall():
|
||||
instance.validate(cursor, i[0], (ident[0], i[1], i[2]),
|
||||
activity['signal_send'], force_running=True)
|
||||
|
||||
if activity['kind'] == 'dummy':
|
||||
if workitem['state'] == 'active':
|
||||
_state_set(cursor, workitem, 'complete')
|
||||
elif activity['kind'] == 'function':
|
||||
if workitem['state'] == 'active':
|
||||
_state_set(cursor, workitem, 'running')
|
||||
wkf_expr.execute(cursor, ident, workitem, activity)
|
||||
_state_set(cursor, workitem, 'complete')
|
||||
elif activity['kind'] == 'stopall':
|
||||
if workitem['state'] == 'active':
|
||||
_state_set(cursor, workitem, 'running')
|
||||
cursor.execute('DELECT FROM wkf_workitem ' \
|
||||
'WHERE inst_id = %d AND id <> %d',
|
||||
(workitem['inst_id'], workitem['id']))
|
||||
if activity['action']:
|
||||
wkf_expr.execute(cursor, ident, workitem, activity)
|
||||
_state_set(cursor, workitem, 'complete')
|
||||
elif activity['kind'] == 'subflow':
|
||||
if workitem['state'] == 'active':
|
||||
_state_set(cursor, workitem, 'running')
|
||||
if activity.get('action', False):
|
||||
id_new = wkf_expr.execute(cursor, ident, workitem, activity)
|
||||
if not (id_new):
|
||||
cursor.execute('DELETE FROM wkf_workitem ' \
|
||||
'WHERE id = %s', (workitem['id'],))
|
||||
return False
|
||||
assert type(id_new) == type(1) or type(id_new) == type(1L), \
|
||||
'Wrong return value: ' + str(id_new) + ' ' + \
|
||||
str(type(id_new))
|
||||
cursor.execute('SELECT id FROM wkf_instance ' \
|
||||
'WHERE res_id = %d AND wkf_id = %d',
|
||||
(id_new, activity['subflow_id']))
|
||||
(id_new,) = cursor.fetchone()
|
||||
else:
|
||||
id_new = instance.create(cursor, ident, activity['subflow_id'])
|
||||
cursor.execute('UPDATE wkf_workitem ' \
|
||||
'SET subflow_id = %d WHERE id = %d',
|
||||
(id_new, workitem['id']))
|
||||
workitem['subflow_id'] = id_new
|
||||
if workitem['state'] == 'running':
|
||||
cursor.execute("SELECT state FROM wkf_instance " \
|
||||
"WHERE id = %d", (workitem['subflow_id'],))
|
||||
(state,) = cursor.fetchone()
|
||||
if state == 'complete':
|
||||
_state_set(cursor, workitem, 'complete')
|
||||
return True
|
||||
|
||||
def _split_test(cursor, workitem, split_mode, ident, signal=None):
|
||||
test = False
|
||||
transitions = []
|
||||
cursor.execute('SELECT * FROM wkf_transition ' \
|
||||
'WHERE act_from = %d', (workitem['act_id'],))
|
||||
alltrans = cursor.dictfetchall()
|
||||
if split_mode == 'XOR' or split_mode == 'OR':
|
||||
for transition in alltrans:
|
||||
if wkf_expr.check(cursor, workitem, ident, transition, signal):
|
||||
test = True
|
||||
transitions.append((transition['id'], workitem['inst_id']))
|
||||
if split_mode == 'XOR':
|
||||
break
|
||||
else:
|
||||
test = True
|
||||
for transition in alltrans:
|
||||
if not wkf_expr.check(cursor, workitem, ident, transition, signal):
|
||||
test = False
|
||||
break
|
||||
cursor.execute('SELECT COUNT(*) ' \
|
||||
'FROM wkf_witm_trans WHERE trans_id = %d AND inst_id = %d',
|
||||
(transition['id'], workitem['inst_id']))
|
||||
(count,) = cursor.fetchone()
|
||||
if not count:
|
||||
transitions.append((transition['id'], workitem['inst_id']))
|
||||
if test and len(transitions):
|
||||
cursor.executemany('INSERT INTO wkf_witm_trans ' \
|
||||
'(trans_id, inst_id) values (%d, %d)', transitions)
|
||||
cursor.execute('DELETE FROM wkf_workitem WHERE id = %d',
|
||||
(workitem['id'],))
|
||||
for transition in transitions:
|
||||
_join_test(cursor, transition[0], transition[1], ident)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _join_test(cursor, trans_id, inst_id, ident):
|
||||
# TODO remove the subquery
|
||||
cursor.execute('SELECT * FROM wkf_activity ' \
|
||||
'WHERE id = (SELECT act_to FROM wkf_transition WHERE id = %d)',
|
||||
(trans_id,))
|
||||
activity = cursor.dictfetchone()
|
||||
if activity['join_mode'] == 'XOR':
|
||||
create(cursor, [activity], inst_id, ident)
|
||||
cursor.execute('DELETE FROM wkf_witm_trans ' \
|
||||
'WHERE inst_id = %d AND trans_id = %d', (inst_id, trans_id))
|
||||
else:
|
||||
cursor.execute('SELECT id FROM wkf_transition ' \
|
||||
'WHERE act_to = %d', (activity['id'],))
|
||||
trans_ids = cursor.fetchall()
|
||||
delete = True
|
||||
for (trans_id,) in trans_ids:
|
||||
cursor.execute('SELECT COUNT(*) FROM wkf_witm_trans ' \
|
||||
'WHERE trans_id = %d AND inst_id = %d',
|
||||
(trans_id, inst_id))
|
||||
(count,) = cursor.fetchone()
|
||||
if not count:
|
||||
delete = False
|
||||
break
|
||||
if delete:
|
||||
for (trans_id,) in trans_ids:
|
||||
cursor.execute('DELETE FROM wkf_witm_trans ' \
|
||||
'WHERE trans_id = %d AND inst_id = %d',
|
||||
(trans_id, inst_id))
|
||||
create(cursor, [activity], inst_id, ident)
|
||||
Reference in New Issue
Block a user