summaryrefslogtreecommitdiff
path: root/odoo/cli
diff options
context:
space:
mode:
Diffstat (limited to 'odoo/cli')
-rw-r--r--odoo/cli/__init__.py13
-rw-r--r--odoo/cli/command.py64
-rw-r--r--odoo/cli/deploy.py99
-rw-r--r--odoo/cli/scaffold.py139
-rw-r--r--odoo/cli/server.py162
-rw-r--r--odoo/cli/shell.py65
-rw-r--r--odoo/cli/start.py75
-rw-r--r--odoo/cli/templates/default/__init__.py.template4
-rw-r--r--odoo/cli/templates/default/__openerp__.py.template35
-rw-r--r--odoo/cli/templates/default/controllers/__init__.py.template3
-rw-r--r--odoo/cli/templates/default/controllers/controllers.py.template23
-rw-r--r--odoo/cli/templates/default/demo/demo.xml.template12
-rw-r--r--odoo/cli/templates/default/models/__init__.py.template3
-rw-r--r--odoo/cli/templates/default/models/models.py.template15
-rw-r--r--odoo/cli/templates/default/security/ir.model.access.csv.template6
-rw-r--r--odoo/cli/templates/default/views/templates.xml.template22
-rw-r--r--odoo/cli/templates/default/views/views.xml.template61
-rw-r--r--odoo/cli/templates/theme/__init__.py.template1
-rw-r--r--odoo/cli/templates/theme/__openerp__.py.template24
-rw-r--r--odoo/cli/templates/theme/demo/pages.xml.template0
-rw-r--r--odoo/cli/templates/theme/static/src/less/custom.less.template0
-rw-r--r--odoo/cli/templates/theme/views/options.xml.template0
-rw-r--r--odoo/cli/templates/theme/views/snippets.xml.template0
23 files changed, 826 insertions, 0 deletions
diff --git a/odoo/cli/__init__.py b/odoo/cli/__init__.py
new file mode 100644
index 00000000000..2ba56ac4cf1
--- /dev/null
+++ b/odoo/cli/__init__.py
@@ -0,0 +1,13 @@
+import logging
+import sys
+import os
+
+import openerp
+
+from command import Command, main
+
+import deploy
+import scaffold
+import server
+import shell
+import start
diff --git a/odoo/cli/command.py b/odoo/cli/command.py
new file mode 100644
index 00000000000..2f2fffa18b4
--- /dev/null
+++ b/odoo/cli/command.py
@@ -0,0 +1,64 @@
+import logging
+import sys
+import os
+from os.path import join as joinpath, isdir
+
+import openerp
+from openerp.modules import get_modules, get_module_path
+
+commands = {}
+
+class CommandType(type):
+ def __init__(cls, name, bases, attrs):
+ super(CommandType, cls).__init__(name, bases, attrs)
+ name = getattr(cls, name, cls.__name__.lower())
+ cls.name = name
+ if name != 'command':
+ commands[name] = cls
+
+class Command(object):
+ """Subclass this class to define new openerp subcommands """
+ __metaclass__ = CommandType
+
+ def run(self, args):
+ pass
+
+class Help(Command):
+ """Display the list of available commands"""
+ def run(self, args):
+ print "Available commands:\n"
+ names = commands.keys()
+ padding = max([len(k) for k in names]) + 2
+ for k in sorted(names):
+ name = k.ljust(padding, ' ')
+ doc = (commands[k].__doc__ or '').strip()
+ print " %s%s" % (name, doc)
+ print "\nUse '%s <command> --help' for individual command help." % sys.argv[0].split(os.path.sep)[-1]
+
+def main():
+ args = sys.argv[1:]
+
+ # The only shared option is '--addons-path=' needed to discover additional
+ # commands from modules
+ if len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith("-"):
+ # parse only the addons-path, do not setup the logger...
+ openerp.tools.config._parse_config([args[0]])
+ args = args[1:]
+
+ # Default legacy command
+ command = "server"
+
+ # TODO: find a way to properly discover addons subcommands without importing the world
+ # Subcommand discovery
+ if len(args) and not args[0].startswith("-"):
+ logging.disable(logging.CRITICAL)
+ for module in get_modules():
+ if isdir(joinpath(get_module_path(module), 'cli')):
+ __import__('openerp.addons.' + module)
+ logging.disable(logging.NOTSET)
+ command = args[0]
+ args = args[1:]
+
+ if command in commands:
+ o = commands[command]()
+ o.run(args)
diff --git a/odoo/cli/deploy.py b/odoo/cli/deploy.py
new file mode 100644
index 00000000000..2e97d838ec2
--- /dev/null
+++ b/odoo/cli/deploy.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import argparse
+import os
+import requests
+import sys
+import tempfile
+import zipfile
+
+from . import Command
+
+class Deploy(Command):
+ """Deploy a module on an Odoo instance"""
+ def __init__(self):
+ super(Deploy, self).__init__()
+ self.session = requests.session()
+
+ def deploy_module(self, module_path, url, login, password, db='', force=False):
+ url = url.rstrip('/')
+ csrf_token = self.authenticate(url, login, password, db)
+ module_file = self.zip_module(module_path)
+ try:
+ return self.upload_module(url, module_file, force=force, csrf_token=csrf_token)
+ finally:
+ os.remove(module_file)
+
+ def upload_module(self, server, module_file, force=False, csrf_token=None):
+ print("Uploading module file...")
+ url = server + '/base_import_module/upload'
+
+ post_data = {'force': '1' if force else ''}
+ if csrf_token: post_data['csrf_token'] = csrf_token
+
+ with open(module_file, 'rb') as f:
+ res = self.session.post(url, files={'mod_file': f}, data=post_data)
+ res.raise_for_status()
+
+ return res.text
+
+ def authenticate(self, server, login, password, db=''):
+ print("Authenticating on server '%s' ..." % server)
+
+ # Fixate session with a given db if any
+ self.session.get(server + '/web/login', params=dict(db=db))
+
+ args = dict(login=login, password=password, db=db)
+ res = self.session.post(server + '/base_import_module/login', args)
+ if res.status_code == 404:
+ raise Exception("The server '%s' does not have the 'base_import_module' installed." % server)
+ elif res.status_code != 200:
+ raise Exception(res.text)
+
+ return res.headers.get('x-csrf-token')
+
+ def zip_module(self, path):
+ path = os.path.abspath(path)
+ if not os.path.isdir(path):
+ raise Exception("Could not find module directory '%s'" % path)
+ container, module_name = os.path.split(path)
+ temp = tempfile.mktemp(suffix='.zip')
+ try:
+ print("Zipping module directory...")
+ with zipfile.ZipFile(temp, 'w') as zfile:
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ file_path = os.path.join(root, file)
+ zfile.write(file_path, file_path.split(container).pop())
+ return temp
+ except Exception:
+ os.remove(temp)
+ raise
+
+ def run(self, cmdargs):
+ parser = argparse.ArgumentParser(
+ prog="%s deploy" % sys.argv[0].split(os.path.sep)[-1],
+ description=self.__doc__
+ )
+ parser.add_argument('path', help="Path of the module to deploy")
+ parser.add_argument('url', nargs='?', help='Url of the server (default=http://localhost:8069)', default="http://localhost:8069")
+ parser.add_argument('--db', dest='db', help='Database to use if server does not use db-filter.')
+ parser.add_argument('--login', dest='login', default="admin", help='Login (default=admin)')
+ parser.add_argument('--password', dest='password', default="admin", help='Password (default=admin)')
+ parser.add_argument('--verify-ssl', action='store_true', help='Verify SSL certificate')
+ parser.add_argument('--force', action='store_true', help='Force init even if module is already installed. (will update `noupdate="1"` records)')
+ if not cmdargs:
+ sys.exit(parser.print_help())
+
+ args = parser.parse_args(args=cmdargs)
+
+ if not args.verify_ssl:
+ self.session.verify = False
+
+ try:
+ if not args.url.startswith(('http://', 'https://')):
+ args.url = 'https://%s' % args.url
+ result = self.deploy_module(args.path, args.url, args.login, args.password, args.db, force=args.force)
+ print(result)
+ except Exception, e:
+ sys.exit("ERROR: %s" % e)
diff --git a/odoo/cli/scaffold.py b/odoo/cli/scaffold.py
new file mode 100644
index 00000000000..221f192347e
--- /dev/null
+++ b/odoo/cli/scaffold.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import argparse
+import os
+import re
+import sys
+
+import jinja2
+
+from . import Command
+
+from openerp.modules.module import (get_module_root, MANIFEST, load_information_from_description_file as load_manifest)
+
+
+class Scaffold(Command):
+ """ Generates an Odoo module skeleton. """
+
+ def run(self, cmdargs):
+ # TODO: bash completion file
+ parser = argparse.ArgumentParser(
+ prog="%s scaffold" % sys.argv[0].split(os.path.sep)[-1],
+ description=self.__doc__,
+ epilog=self.epilog(),
+ )
+ parser.add_argument(
+ '-t', '--template', type=template, default=template('default'),
+ help="Use a custom module template, can be a template name or the"
+ " path to a module template (default: %(default)s)")
+ parser.add_argument('name', help="Name of the module to create")
+ parser.add_argument(
+ 'dest', default='.', nargs='?',
+ help="Directory to create the module in (default: %(default)s)")
+
+ if not cmdargs:
+ sys.exit(parser.print_help())
+ args = parser.parse_args(args=cmdargs)
+
+ args.template.render_to(
+ snake(args.name),
+ directory(args.dest, create=True),
+ {'name': args.name})
+
+ def epilog(self):
+ return "Built-in templates available are: %s" % ', '.join(
+ d for d in os.listdir(builtins())
+ if d != 'base'
+ )
+
+builtins = lambda *args: os.path.join(
+ os.path.abspath(os.path.dirname(__file__)),
+ 'templates',
+ *args)
+
+def snake(s):
+ """ snake cases ``s``
+
+ :param str s:
+ :return: str
+ """
+ # insert a space before each uppercase character preceded by a
+ # non-uppercase letter
+ s = re.sub(r'(?<=[^A-Z])\B([A-Z])', r' \1', s)
+ # lowercase everything, split on whitespace and join
+ return '_'.join(s.lower().split())
+def pascal(s):
+ return ''.join(
+ ss.capitalize()
+ for ss in re.sub('[_\s]+', ' ', s).split()
+ )
+
+def directory(p, create=False):
+ expanded = os.path.abspath(
+ os.path.expanduser(
+ os.path.expandvars(p)))
+ if create and not os.path.exists(expanded):
+ os.makedirs(expanded)
+ if not os.path.isdir(expanded):
+ die("%s is not a directory" % p)
+ return expanded
+
+env = jinja2.Environment()
+env.filters['snake'] = snake
+env.filters['pascal'] = pascal
+class template(object):
+ def __init__(self, identifier):
+ # TODO: archives (zipfile, tarfile)
+ self.id = identifier
+ # is identifier a builtin?
+ self.path = builtins(identifier)
+ if os.path.isdir(self.path):
+ return
+ # is identifier a directory?
+ self.path = identifier
+ if os.path.isdir(self.path):
+ return
+ die("{} is not a valid module template".format(identifier))
+
+ def __str__(self):
+ return self.id
+
+ def files(self):
+ """ Lists the (local) path and content of all files in the template
+ """
+ for root, _, files in os.walk(self.path):
+ for f in files:
+ path = os.path.join(root, f)
+ yield path, open(path, 'rb').read()
+
+ def render_to(self, modname, directory, params=None):
+ """ Render this module template to ``dest`` with the provided
+ rendering parameters
+ """
+ # overwrite with local
+ for path, content in self.files():
+ local = os.path.relpath(path, self.path)
+ # strip .template extension
+ root, ext = os.path.splitext(local)
+ if ext == '.template':
+ local = root
+ dest = os.path.join(directory, modname, local)
+ destdir = os.path.dirname(dest)
+ if not os.path.exists(destdir):
+ os.makedirs(destdir)
+
+ with open(dest, 'wb') as f:
+ if ext not in ('.py', '.xml', '.csv', '.js', '.rst', '.html', '.template'):
+ f.write(content)
+ else:
+ env.from_string(content.decode('utf-8'))\
+ .stream(params or {})\
+ .dump(f, encoding='utf-8')
+
+def die(message, code=1):
+ print >>sys.stderr, message
+ sys.exit(code)
+
+def warn(message):
+ # ASK: shall we use logger ?
+ print "WARNING: " + message
diff --git a/odoo/cli/server.py b/odoo/cli/server.py
new file mode 100644
index 00000000000..314072b69a8
--- /dev/null
+++ b/odoo/cli/server.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+"""
+OpenERP - Server
+OpenERP is an ERP+CRM program for small and medium businesses.
+
+The whole source code is distributed under the terms of the
+GNU Public Licence.
+
+(c) 2003-TODAY, Fabien Pinckaers - OpenERP SA
+"""
+
+import atexit
+import csv
+import logging
+import os
+import signal
+import sys
+import threading
+import traceback
+import time
+
+import openerp
+
+from . import Command
+
+__author__ = openerp.release.author
+__version__ = openerp.release.version
+
+# Also use the `openerp` logger for the main script.
+_logger = logging.getLogger('openerp')
+
+def check_root_user():
+ """Warn if the process's user is 'root' (on POSIX system)."""
+ if os.name == 'posix':
+ import pwd
+ if pwd.getpwuid(os.getuid())[0] == 'root':
+ sys.stderr.write("Running as user 'root' is a security risk.\n")
+
+def check_postgres_user():
+ """ Exit if the configured database user is 'postgres'.
+
+ This function assumes the configuration has been initialized.
+ """
+ config = openerp.tools.config
+ if config['db_user'] == 'postgres':
+ sys.stderr.write("Using the database user 'postgres' is a security risk, aborting.")
+ sys.exit(1)
+
+def report_configuration():
+ """ Log the server version and some configuration values.
+
+ This function assumes the configuration has been initialized.
+ """
+ config = openerp.tools.config
+ _logger.info("OpenERP version %s", __version__)
+ _logger.info('addons paths: %s', openerp.modules.module.ad_paths)
+ host = config['db_host'] or os.environ.get('PGHOST', 'default')
+ port = config['db_port'] or os.environ.get('PGPORT', 'default')
+ user = config['db_user'] or os.environ.get('PGUSER', 'default')
+ _logger.info('database: %s@%s:%s', user, host, port)
+
+def rm_pid_file(main_pid):
+ config = openerp.tools.config
+ if config['pidfile'] and main_pid == os.getpid():
+ try:
+ os.unlink(config['pidfile'])
+ except OSError:
+ pass
+
+def setup_pid_file():
+ """ Create a file with the process id written in it.
+
+ This function assumes the configuration has been initialized.
+ """
+ config = openerp.tools.config
+ if not openerp.evented and config['pidfile']:
+ pid = os.getpid()
+ with open(config['pidfile'], 'w') as fd:
+ fd.write(str(pid))
+ atexit.register(rm_pid_file, pid)
+
+def export_translation():
+ config = openerp.tools.config
+ dbname = config['db_name']
+
+ if config["language"]:
+ msg = "language %s" % (config["language"],)
+ else:
+ msg = "new language"
+ _logger.info('writing translation file for %s to %s', msg,
+ config["translate_out"])
+
+ fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
+
+ with open(config["translate_out"], "w") as buf:
+ registry = openerp.modules.registry.Registry.new(dbname)
+ with openerp.api.Environment.manage():
+ with registry.cursor() as cr:
+ openerp.tools.trans_export(config["language"],
+ config["translate_modules"] or ["all"], buf, fileformat, cr)
+
+ _logger.info('translation file written successfully')
+
+def import_translation():
+ config = openerp.tools.config
+ context = {'overwrite': config["overwrite_existing_translations"]}
+ dbname = config['db_name']
+
+ registry = openerp.modules.registry.Registry.new(dbname)
+ with openerp.api.Environment.manage():
+ with registry.cursor() as cr:
+ openerp.tools.trans_load(
+ cr, config["translate_in"], config["language"], context=context,
+ )
+
+def main(args):
+ check_root_user()
+ openerp.tools.config.parse_config(args)
+ check_postgres_user()
+ report_configuration()
+
+ config = openerp.tools.config
+
+ # the default limit for CSV fields in the module is 128KiB, which is not
+ # quite sufficient to import images to store in attachment. 500MiB is a
+ # bit overkill, but better safe than sorry I guess
+ csv.field_size_limit(500 * 1024 * 1024)
+
+ preload = []
+ if config['db_name']:
+ preload = config['db_name'].split(',')
+ for db_name in preload:
+ try:
+ openerp.service.db._create_empty_database(db_name)
+ except openerp.service.db.DatabaseExists:
+ pass
+
+ if config["translate_out"]:
+ export_translation()
+ sys.exit(0)
+
+ if config["translate_in"]:
+ import_translation()
+ sys.exit(0)
+
+ # This needs to be done now to ensure the use of the multiprocessing
+ # signaling mecanism for registries loaded with -d
+ if config['workers']:
+ openerp.multi_process = True
+
+ stop = config["stop_after_init"]
+
+ setup_pid_file()
+ rc = openerp.service.server.start(preload=preload, stop=stop)
+ sys.exit(rc)
+
+class Server(Command):
+ """Start the odoo server (default command)"""
+ def run(self, args):
+ main(args)
diff --git a/odoo/cli/shell.py b/odoo/cli/shell.py
new file mode 100644
index 00000000000..0d1ed59813e
--- /dev/null
+++ b/odoo/cli/shell.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import code
+import os
+import signal
+import sys
+
+import openerp
+from . import Command
+
+def raise_keyboard_interrupt(*a):
+ raise KeyboardInterrupt()
+
+class Console(code.InteractiveConsole):
+ def __init__(self, locals=None, filename="<console>"):
+ code.InteractiveConsole.__init__(self, locals, filename)
+ try:
+ import readline
+ import rlcompleter
+ except ImportError:
+ print 'readline or rlcompleter not available, autocomplete disabled.'
+ else:
+ readline.set_completer(rlcompleter.Completer(locals).complete)
+ readline.parse_and_bind("tab: complete")
+
+class Shell(Command):
+ """Start odoo in an interactive shell"""
+ def init(self, args):
+ openerp.tools.config.parse_config(args)
+ openerp.cli.server.report_configuration()
+ openerp.service.server.start(preload=[], stop=True)
+ signal.signal(signal.SIGINT, raise_keyboard_interrupt)
+
+ def console(self, local_vars):
+ if not os.isatty(sys.stdin.fileno()):
+ exec sys.stdin in local_vars
+ else:
+ if 'env' not in local_vars:
+ print 'No environment set, use `odoo.py shell -d dbname` to get one.'
+ for i in sorted(local_vars):
+ print '%s: %s' % (i, local_vars[i])
+ Console(locals=local_vars).interact()
+
+ def shell(self, dbname):
+ local_vars = {
+ 'openerp': openerp
+ }
+ with openerp.api.Environment.manage():
+ if dbname:
+ registry = openerp.registry(dbname)
+ with registry.cursor() as cr:
+ uid = openerp.SUPERUSER_ID
+ ctx = openerp.api.Environment(cr, uid, {})['res.users'].context_get()
+ env = openerp.api.Environment(cr, uid, ctx)
+ local_vars['env'] = env
+ local_vars['self'] = env.user
+ self.console(local_vars)
+ else:
+ self.console(local_vars)
+
+ def run(self, args):
+ self.init(args)
+ self.shell(openerp.tools.config['db_name'])
+ return 0
diff --git a/odoo/cli/start.py b/odoo/cli/start.py
new file mode 100644
index 00000000000..b0cc932e19d
--- /dev/null
+++ b/odoo/cli/start.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import argparse
+import glob
+import os
+import sys
+
+from . import Command
+from .server import main
+from openerp.modules.module import get_module_root, MANIFEST
+from openerp.service.db import _create_empty_database, DatabaseExists
+
+
+class Start(Command):
+ """Quick start the Odoo server for your project"""
+
+ def get_module_list(self, path):
+ mods = glob.glob(os.path.join(path, '*/%s' % MANIFEST))
+ return [mod.split(os.path.sep)[-2] for mod in mods]
+
+
+ def run(self, cmdargs):
+ parser = argparse.ArgumentParser(
+ prog="%s start" % sys.argv[0].split(os.path.sep)[-1],
+ description=self.__doc__
+ )
+ parser.add_argument('--path', default=".",
+ help="Directory where your project's modules are stored (will autodetect from current dir)")
+ parser.add_argument("-d", "--database", dest="db_name", default=None,
+ help="Specify the database name (default to project's directory name")
+
+
+ args, unknown = parser.parse_known_args(args=cmdargs)
+
+ project_path = os.path.abspath(os.path.expanduser(os.path.expandvars(args.path)))
+ module_root = get_module_root(project_path)
+ db_name = None
+ if module_root:
+ # started in a module so we choose this module name for database
+ db_name = project_path.split(os.path.sep)[-1]
+ # go to the parent's directory of the module root
+ project_path = os.path.abspath(os.path.join(project_path, os.pardir))
+
+ # check if one of the subfolders has at least one module
+ mods = self.get_module_list(project_path)
+ if mods and '--addons-path' not in cmdargs:
+ cmdargs.append('--addons-path=%s' % project_path)
+
+ if not args.db_name:
+ args.db_name = db_name or project_path.split(os.path.sep)[-1]
+ cmdargs.extend(('-d', args.db_name))
+
+ # TODO: forbid some database names ? eg template1, ...
+ try:
+ _create_empty_database(args.db_name)
+ except DatabaseExists, e:
+ pass
+ except Exception, e:
+ die("Could not create database `%s`. (%s)" % (args.db_name, e))
+
+ if '--db-filter' not in cmdargs:
+ cmdargs.append('--db-filter=^%s$' % args.db_name)
+
+ # Remove --path /-p options from the command arguments
+ def to_remove(i, l):
+ return l[i] == '-p' or l[i].startswith('--path') or \
+ (i > 0 and l[i-1] in ['-p', '--path'])
+ cmdargs = [v for i, v in enumerate(cmdargs)
+ if not to_remove(i, cmdargs)]
+
+ main(cmdargs)
+
+def die(message, code=1):
+ print >>sys.stderr, message
+ sys.exit(code)
diff --git a/odoo/cli/templates/default/__init__.py.template b/odoo/cli/templates/default/__init__.py.template
new file mode 100644
index 00000000000..aa4d0fd63a9
--- /dev/null
+++ b/odoo/cli/templates/default/__init__.py.template
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import controllers
+from . import models
diff --git a/odoo/cli/templates/default/__openerp__.py.template b/odoo/cli/templates/default/__openerp__.py.template
new file mode 100644
index 00000000000..97b8372007c
--- /dev/null
+++ b/odoo/cli/templates/default/__openerp__.py.template
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "{{ name }}",
+
+ 'summary': """
+ Short (1 phrase/line) summary of the module's purpose, used as
+ subtitle on modules listing or apps.openerp.com""",
+
+ 'description': """
+ Long description of module's purpose
+ """,
+
+ 'author': "My Company",
+ 'website': "http://www.yourcompany.com",
+
+ # Categories can be used to filter modules in modules listing
+ # Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml
+ # for the full list
+ 'category': 'Uncategorized',
+ 'version': '0.1',
+
+ # any module necessary for this one to work correctly
+ 'depends': ['base'],
+
+ # always loaded
+ 'data': [
+ # 'security/ir.model.access.csv',
+ 'views/views.xml',
+ 'views/templates.xml',
+ ],
+ # only loaded in demonstration mode
+ 'demo': [
+ 'demo/demo.xml',
+ ],
+}
diff --git a/odoo/cli/templates/default/controllers/__init__.py.template b/odoo/cli/templates/default/controllers/__init__.py.template
new file mode 100644
index 00000000000..b0f26a9a602
--- /dev/null
+++ b/odoo/cli/templates/default/controllers/__init__.py.template
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import controllers
diff --git a/odoo/cli/templates/default/controllers/controllers.py.template b/odoo/cli/templates/default/controllers/controllers.py.template
new file mode 100644
index 00000000000..505208171fc
--- /dev/null
+++ b/odoo/cli/templates/default/controllers/controllers.py.template
@@ -0,0 +1,23 @@
+{%- set mod = name|snake -%}
+{%- set model = "%s.%s"|format(mod, mod) -%}
+{%- set root = "/%s/%s"|format(mod, mod) -%}
+# -*- coding: utf-8 -*-
+from openerp import http
+
+# class {{ mod|pascal }}(http.Controller):
+# @http.route('{{ root }}/', auth='public')
+# def index(self, **kw):
+# return "Hello, world"
+
+# @http.route('{{ root }}/objects/', auth='public')
+# def list(self, **kw):
+# return http.request.render('{{ mod }}.listing', {
+# 'root': '{{ root }}',
+# 'objects': http.request.env['{{ model }}'].search([]),
+# })
+
+# @http.route('{{ root }}/objects/<model("{{ model }}"):obj>/', auth='public')
+# def object(self, obj, **kw):
+# return http.request.render('{{ mod }}.object', {
+# 'object': obj
+# })
diff --git a/odoo/cli/templates/default/demo/demo.xml.template b/odoo/cli/templates/default/demo/demo.xml.template
new file mode 100644
index 00000000000..5ca25672404
--- /dev/null
+++ b/odoo/cli/templates/default/demo/demo.xml.template
@@ -0,0 +1,12 @@
+{%- set mod= name|snake -%}
+{%- set model = "%s.%s"|format(mod, mod) -%}
+<openerp>
+ <data>
+ <!-- {% for item in range(5) %} -->
+ <!-- <record id="object{{ item }}" model="{{ model }}"> -->
+ <!-- <field name="name">Object {{ item }}</field> -->
+ <!-- <field name="value">{{ item * 10 }}</field> -->
+ <!-- </record> -->
+ <!-- {% endfor %} -->
+ </data>
+</openerp>
diff --git a/odoo/cli/templates/default/models/__init__.py.template b/odoo/cli/templates/default/models/__init__.py.template
new file mode 100644
index 00000000000..cde864bae21
--- /dev/null
+++ b/odoo/cli/templates/default/models/__init__.py.template
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import models
diff --git a/odoo/cli/templates/default/models/models.py.template b/odoo/cli/templates/default/models/models.py.template
new file mode 100644
index 00000000000..562aa3a851b
--- /dev/null
+++ b/odoo/cli/templates/default/models/models.py.template
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from openerp import models, fields, api
+
+# class {{ name|snake }}(models.Model):
+# _name = '{{ name|snake }}.{{ name|snake }}'
+
+# name = fields.Char()
+# value = fields.Integer()
+# value2 = fields.Float(compute="_value_pc", store=True)
+# description = fields.Text()
+#
+# @api.depends('value')
+# def _value_pc(self):
+# self.value2 = float(self.value) / 100
diff --git a/odoo/cli/templates/default/security/ir.model.access.csv.template b/odoo/cli/templates/default/security/ir.model.access.csv.template
new file mode 100644
index 00000000000..71a2352fedc
--- /dev/null
+++ b/odoo/cli/templates/default/security/ir.model.access.csv.template
@@ -0,0 +1,6 @@
+{%- set snake_name = name|snake -%}
+{%- set id = "access_%s_%s"|format(snake_name, snake_name) -%}
+{%- set name = "%s.%s"|format(snake_name, snake_name) -%}
+{%- set model_id = "model_%s_%s"|format(snake_name, snake_name) -%}
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+{{ id }},{{ name }},{{ model_id }},,1,0,0,0
diff --git a/odoo/cli/templates/default/views/templates.xml.template b/odoo/cli/templates/default/views/templates.xml.template
new file mode 100644
index 00000000000..b9166501678
--- /dev/null
+++ b/odoo/cli/templates/default/views/templates.xml.template
@@ -0,0 +1,22 @@
+<openerp>
+ <data>
+ <!-- <template id="listing"> -->
+ <!-- <ul> -->
+ <!-- <li t-foreach="objects" t-as="object"> -->
+ <!-- <a t-attf-href="#{ root }/objects/#{ object.id }"> -->
+ <!-- <t t-esc="object.display_name"/> -->
+ <!-- </a> -->
+ <!-- </li> -->
+ <!-- </ul> -->
+ <!-- </template> -->
+ <!-- <template id="object"> -->
+ <!-- <h1><t t-esc="object.display_name"/></h1> -->
+ <!-- <dl> -->
+ <!-- <t t-foreach="object._fields" t-as="field"> -->
+ <!-- <dt><t t-esc="field"/></dt> -->
+ <!-- <dd><t t-esc="object[field]"/></dd> -->
+ <!-- </t> -->
+ <!-- </dl> -->
+ <!-- </template> -->
+ </data>
+</openerp>
diff --git a/odoo/cli/templates/default/views/views.xml.template b/odoo/cli/templates/default/views/views.xml.template
new file mode 100644
index 00000000000..b19f7f7067b
--- /dev/null
+++ b/odoo/cli/templates/default/views/views.xml.template
@@ -0,0 +1,61 @@
+{%- set mod = name|snake -%}
+{%- set model = "%s.%s"|format(mod, mod) -%}
+<openerp>
+ <data>
+ <!-- explicit list view definition -->
+ <!--
+ <record model="ir.ui.view" id="{{mod}}.list">
+ <field name="name">{{name}} list</field>
+ <field name="model">{{model}}</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="name"/>
+ <field name="value"/>
+ <field name="value2"/>
+ </tree>
+ </field>
+ </record>
+ -->
+
+ <!-- actions opening views on models -->
+ <!--
+ <record model="ir.actions.act_window" id="{{mod}}.action_window">
+ <field name="name">{{name}} window</field>
+ <field name="res_model">{{model}}</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ -->
+
+ <!-- server action to the one above -->
+ <!--
+ <record model="ir.actions.server" id="{{mod}}.action_server">
+ <field name="name">{{name}} server</field>
+ <field name="model_id" ref="model_{{mod}}_{{mod}}"/>
+ <field name="code">
+ action = {
+ "type": "ir.actions.act_window",
+ "view_mode": "tree,form",
+ "res_model": self._name,
+ }
+ </field>
+ </record>
+ -->
+
+ <!-- Top menu item -->
+ <!--
+ <menuitem name="{{name}}" id="{{mod}}.menu_root"/>
+ -->
+ <!-- menu categories -->
+ <!--
+ <menuitem name="Menu 1" id="{{mod}}.menu_1" parent="{{mod}}.menu_root"/>
+ <menuitem name="Menu 2" id="{{mod}}.menu_2" parent="{{mod}}.menu_root"/>
+ -->
+ <!-- actions -->
+ <!--
+ <menuitem name="List" id="{{mod}}.menu_1_list" parent="{{mod}}.menu_1"
+ action="{{mod}}.action_window"/>
+ <menuitem name="Server to list" id="{{mod}}" parent="{{mod}}.menu_2"
+ action="{{mod}}.action_server"/>
+ -->
+ </data>
+</openerp>
diff --git a/odoo/cli/templates/theme/__init__.py.template b/odoo/cli/templates/theme/__init__.py.template
new file mode 100644
index 00000000000..40a96afc6ff
--- /dev/null
+++ b/odoo/cli/templates/theme/__init__.py.template
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/odoo/cli/templates/theme/__openerp__.py.template b/odoo/cli/templates/theme/__openerp__.py.template
new file mode 100644
index 00000000000..f6d99c2be45
--- /dev/null
+++ b/odoo/cli/templates/theme/__openerp__.py.template
@@ -0,0 +1,24 @@
+{
+ # Theme information
+ 'name': "{{name}}",
+ 'description': """
+ """,
+ 'category': 'Theme',
+ 'version': '1.0',
+ 'depends': ['website'],
+
+ # templates
+ 'data': [
+ 'views/options.xml',
+ 'views/snippets.xml',
+ ],
+
+ # demo pages
+ 'demo': [
+ 'demo/pages.xml',
+ ],
+
+ # Your information
+ 'author': "My Company",
+ 'website': "",
+}
diff --git a/odoo/cli/templates/theme/demo/pages.xml.template b/odoo/cli/templates/theme/demo/pages.xml.template
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/odoo/cli/templates/theme/demo/pages.xml.template
diff --git a/odoo/cli/templates/theme/static/src/less/custom.less.template b/odoo/cli/templates/theme/static/src/less/custom.less.template
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/odoo/cli/templates/theme/static/src/less/custom.less.template
diff --git a/odoo/cli/templates/theme/views/options.xml.template b/odoo/cli/templates/theme/views/options.xml.template
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/odoo/cli/templates/theme/views/options.xml.template
diff --git a/odoo/cli/templates/theme/views/snippets.xml.template b/odoo/cli/templates/theme/views/snippets.xml.template
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/odoo/cli/templates/theme/views/snippets.xml.template