summaryrefslogtreecommitdiff
path: root/openerp/cli
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2014-09-05 15:28:22 +0200
committerChristophe Simonis <chs@odoo.com>2014-09-05 15:28:22 +0200
commit31bf30d2d0eb78baaa36360b99fe58bf7cd4c9e7 (patch)
treed7be61ecd6bb4c8b5552e39b03e2a64eebbbf323 /openerp/cli
parentafba9e8817c458640ae446ac7aaaab79be4fe1e9 (diff)
parented1c173971968eaa96693cb4e1547efa6e6c34e0 (diff)
[MERGE] forward port of branch 8.0 up to ed1c173
Diffstat (limited to 'openerp/cli')
-rw-r--r--openerp/cli/scaffold.py274
-rw-r--r--openerp/cli/scaffold/__init__.jinja23
-rw-r--r--openerp/cli/scaffold/controllers.jinja212
-rw-r--r--openerp/cli/scaffold/ir.model.access.jinja26
-rw-r--r--openerp/cli/scaffold/models.jinja29
-rw-r--r--openerp/cli/scaffold/record.jinja24
-rw-r--r--openerp/cli/scaffold/theme_css.jinja24
-rw-r--r--openerp/cli/scaffold/theme_xml.jinja226
-rw-r--r--openerp/cli/scaffold/webclient_css.jinja24
-rw-r--r--openerp/cli/scaffold/webclient_js.jinja214
-rw-r--r--openerp/cli/scaffold/webclient_xml.jinja27
-rw-r--r--openerp/cli/scaffold/xmldata.jinja25
-rw-r--r--openerp/cli/templates/default/__init__.py3
-rw-r--r--openerp/cli/templates/default/__openerp__.py (renamed from openerp/cli/scaffold/__openerp__.jinja2)17
-rw-r--r--openerp/cli/templates/default/controllers.py23
-rw-r--r--openerp/cli/templates/default/demo.xml11
-rw-r--r--openerp/cli/templates/default/models.py8
-rw-r--r--openerp/cli/templates/default/security/ir.model.access.csv6
-rw-r--r--openerp/cli/templates/default/templates.xml22
-rw-r--r--openerp/cli/templates/theme/__init__.py1
-rw-r--r--openerp/cli/templates/theme/__openerp__.py20
-rw-r--r--openerp/cli/templates/theme/static/style/custom.less0
-rw-r--r--openerp/cli/templates/theme/views/options.xml0
-rw-r--r--openerp/cli/templates/theme/views/pages.xml0
-rw-r--r--openerp/cli/templates/theme/views/snippets.xml0
25 files changed, 183 insertions, 296 deletions
diff --git a/openerp/cli/scaffold.py b/openerp/cli/scaffold.py
index 5999aad02a1..c4f2c44db43 100644
--- a/openerp/cli/scaffold.py
+++ b/openerp/cli/scaffold.py
@@ -1,13 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
-import ast
-import functools
-import keyword
-import logging
import os
import re
-import simplejson
import sys
import jinja2
@@ -18,199 +13,43 @@ from openerp.modules.module import (get_module_root, MANIFEST, load_information_
class Scaffold(Command):
- "Generate an Odoo module skeleton."
+ """ 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__
+ description=self.__doc__,
+ epilog=self.epilog(),
)
- parser.add_argument('--init', type=identifier, help='Initialize a new Odoo module')
-
- parser.add_argument('--dest', default=".",
- help='Directory where the module should be created/updated (default to current directory)')
-
- parser.add_argument('--model', type=identifier, help="Name of the model to add")
-
- parser.add_argument('--controller', type=identifier, help="Name of the controller to add")
-
- parser.add_argument('--web', action='store_true', default=False,
- help="Generate structure for a webclient module")
-
- parser.add_argument('--theme', action='store_true', default=False,
- help="Generate structure for a Website theme")
+ 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)
- dest = directory(args.dest)
- if args.init:
- dest = os.path.join(dest, args.init)
- if os.path.exists(dest):
- die("Can't initialize module in `%s`: Directory already exists." % dest)
- if get_module_root(dest):
- die("Can't init a new module in another Odoo module, you probably want to run this "
- "command from your project's root")
- else:
- mroot = get_module_root(dest)
- if not mroot:
- die("The path `%s` provided does not point to an existing Odoo module. "
- "Forgot to `--init` ?" % dest)
- dest = mroot
-
- logging.disable(logging.CRITICAL)
- scaffold = ScaffoldModule(dest)
- if args.model:
- scaffold.add_model(snake(args.model))
- if args.controller:
- scaffold.add_controller(args.controller)
- if args.web:
- scaffold.add_webclient_structure()
- if args.theme:
- scaffold.add_theme_structure()
-
-class ScaffoldModule(object):
- """
- Object for scaffolding existing or new Odoo modules
+ args.template.render_to(
+ snake(args.name),
+ directory(args.dest, create=True),
+ {'name': args.name})
- @param path: Path of an existing module or path of module to create
- """
- def __init__(self, path):
- env = jinja2.Environment(loader=jinja2.PackageLoader(
- 'openerp.cli', 'scaffold'), keep_trailing_newline=True)
- env.filters['snake'] = snake
- self.env = env
- self.path = functools.partial(os.path.join, directory(path))
- self.created = not os.path.exists(self.path())
- directory(path, create=True)
- self.module = self.path().split(os.path.sep)[-1]
- if self.created:
- manifest_base = os.path.splitext(MANIFEST)[0]
- self.render_file('%s.jinja2' % manifest_base, self.path('%s.py' % manifest_base))
- # Create an empty __init__.py so the module can be imported
- open(self.path('__init__.py'), 'a').close()
-
- def add_model(self, model):
- model_module = snake(model)
- model_file = self.path('models', '%s.py' % model_module)
- if os.path.exists(model_file):
- die("Model `%s` already exists !" % model_file)
- self.add_init_import(self.path('__init__.py'), 'models')
- self.add_init_import(self.path('models', '__init__.py'), model_module)
-
- self.render_file('models.jinja2', model_file, model=model)
- self.render_file('ir.model.access.jinja2', self.path('security', 'ir.model.access.csv'),
- if_exists='append', model=model)
- self.append_manifest_list('data', 'security/ir.model.access.csv')
-
- demo_file = 'data/%s_demo.xml' % self.module
- self.append_xml_data('record.jinja2', self.path(demo_file),
- model=model)
- self.append_manifest_list('demo', demo_file)
-
- def add_controller(self, controller):
- controller_module = snake(controller)
- controller_file = self.path('controllers', '%s.py' % controller_module)
- if os.path.exists(controller_file):
- die("Controller `%s` already exists !" % controller_file)
- self.add_init_import(self.path('__init__.py'), 'controllers')
- # Check if the controller name correspond to a model and expose result to templates
- has_model = self.has_import(self.path('models', '__init__.py'), controller_module)
- self.add_init_import(self.path('controllers', '__init__.py'), controller_module)
- self.render_file('controllers.jinja2', controller_file, controller=controller,
- has_model=has_model)
-
- def add_webclient_structure(self):
- self.append_manifest_list('depends', 'web')
- prefix = '%s.%%s' % self.module
- for ext in ('js', 'css', 'xml'):
- self.render_file('webclient_%s.jinja2' % ext,
- self.path('static', 'src', ext, prefix % ext))
-
- def add_theme_structure(self):
- self.append_manifest_list('depends', 'website')
- css_file = '%s_theme.css' % self.module
- self.render_file('theme_css.jinja2', self.path('static', 'src', 'css', css_file))
- self.append_xml_data('theme_xml.jinja2', self.path('views', 'templates.xml'), skip_if_exist=True)
- self.append_manifest_list('data', 'views/templates.xml')
-
- def has_import(self, initfile, module):
- if not os.path.isfile(initfile):
- return False
- with open(initfile, 'r') as f:
- for imp in ast.parse(f.read()).body:
- if isinstance(imp, ast.Import):
- if module in [mod.name for mod in imp.names]:
- return True
- return False
-
- def get_manifest(self, key=None, default=None):
- manifest = load_manifest(self.module, self.path())
- if key:
- return manifest.get(key, default)
- else:
- return manifest
-
- def append_manifest_list(self, key, value, unique=True):
- # TODO: append value without altering serialized formatting
- vals = self.get_manifest(key, [])
- if unique and value in vals:
- return
- vals.append(value)
- self.change_manifest_key(key, vals)
-
- def change_manifest_key(self, key, value):
- value = simplejson.dumps(value)
- with open(self.path(MANIFEST), 'r') as f:
- data = f.read()
- sdata = re.split('["\']%s["\']\s?:\s?\[[^\]]*\]' % key, data)
- add = "'%s': %s" % (key, value)
- if len(sdata) != 2:
- warn("Could not update `%s` key in manifest. You should add this by yourself:"
- "\n\n%s\n" % (key, add))
- else:
- with open(self.path(MANIFEST), 'w') as f:
- f.write(add.join(sdata))
-
- def add_init_import(self, initfile, module):
- if not(os.path.exists(initfile) and self.has_import(initfile, module)):
- self.render_file('__init__.jinja2', initfile, if_exists='append', modules=[module])
-
- def render_file(self, template, dest, if_exists='skip', **kwargs):
- mode = 'a'
- if os.path.exists(dest):
- if if_exists == 'replace':
- mode = 'w'
- elif if_exists != 'append':
- warn("File `%s` already exists. Skipping it..." % dest)
- return
- else:
- kwargs['file_created'] = True
- outdir = os.path.dirname(dest)
- if not os.path.exists(outdir):
- os.makedirs(outdir)
- content = self.env.get_template(template).render(module=self.module, **kwargs)
- with open(dest, mode) as f:
- f.write(content)
-
- def append_xml_data(self, template, dest, skip_if_exist=False, **kwargs):
- if not os.path.exists(dest):
- self.render_file('xmldata.jinja2', dest, **kwargs)
- elif skip_if_exist:
- warn("File `%s` already exists. Skipping it..." % dest)
- with open(dest, 'r') as f:
- data = f.read()
- m = re.search('(^\s*)?</data>', data, re.MULTILINE)
- content = self.env.get_template(template).render(module=self.module, **kwargs)
- if not m:
- warn("Could not add data in `%s`. You should add this by yourself:"
- "\n\n%s\n" % (dest, content))
- else:
- data = data[:m.start()] + content + m.group() + data[m.end():]
- with open(self.path(dest), 'w') as f:
- f.write(data)
+ 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``
@@ -223,13 +62,11 @@ def snake(s):
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 identifier(s):
- if keyword.iskeyword(s):
- die("%s is a Python keyword and can not be used as a name" % s)
- if not re.match('[A-Za-z_][A-Za-z0-9_]*', s):
- die("%s is not a valid Python identifier" % s)
- return s
+def pascal(s):
+ return ''.join(
+ ss.capitalize()
+ for ss in re.sub('[_\s]+', ' ', s).split()
+ )
def directory(p, create=False):
expanded = os.path.abspath(
@@ -237,10 +74,57 @@ def directory(p, create=False):
os.path.expandvars(p)))
if create and not os.path.exists(expanded):
os.makedirs(expanded)
- if create and not os.path.isdir(expanded):
- die("%s exists but is not a directory" % p)
+ 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: directories, archives (zipfile, tarfile)
+ self.id = identifier
+ if not os.path.isdir(self.path):
+ die("{} is not a valid module template".format(identifier))
+
+ def __str__(self):
+ return self.id
+
+ @property
+ def path(self):
+ return builtins(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():
+ _, ext = os.path.splitext(path)
+
+ local = os.path.relpath(path, self.path)
+ 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'):
+ f.write(content)
+ else:
+ env.from_string(content)\
+ .stream(params or {})\
+ .dump(f, encoding='utf-8')
+
def die(message, code=1):
print >>sys.stderr, message
sys.exit(code)
diff --git a/openerp/cli/scaffold/__init__.jinja2 b/openerp/cli/scaffold/__init__.jinja2
deleted file mode 100644
index e8b1efb3fc7..00000000000
--- a/openerp/cli/scaffold/__init__.jinja2
+++ /dev/null
@@ -1,3 +0,0 @@
-{% if file_created -%}# -*- coding: utf-8 -*-{%- endif %}
-{% for module in modules if module -%}import {{ module }}
-{%- endfor %}
diff --git a/openerp/cli/scaffold/controllers.jinja2 b/openerp/cli/scaffold/controllers.jinja2
deleted file mode 100644
index cbe0fdaed7a..00000000000
--- a/openerp/cli/scaffold/controllers.jinja2
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-from openerp import http
-
-class {{ controller }}(http.Controller):
- @http.route('/{{ module }}/{{ controller }}', auth='public', website=True)
- def index(self, **kw):
- return "Hello, world!"
-{% if has_model %}
- @http.route('/{{ module }}/{{ controller }}/<model("{{ module }}.{{ controller }}"):{{ controller }}>'], type='http', auth='public', website=True)
- def {{ controller }}(self, {{ controller }}, **kw):
- return "Hello, %r!" % {{ controller }}
-{% endif %}
diff --git a/openerp/cli/scaffold/ir.model.access.jinja2 b/openerp/cli/scaffold/ir.model.access.jinja2
deleted file mode 100644
index af558a3b625..00000000000
--- a/openerp/cli/scaffold/ir.model.access.jinja2
+++ /dev/null
@@ -1,6 +0,0 @@
-{% if file_created -%}id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink{%- endif %}
-{% if model -%}
-access_{{ module }}_{{ model }},{{- '' -}}
-access_{{ module }}_{{ model }},{{- '' -}}
-model_{{ module }}_{{ model }},,1,0,0,0
-{%- endif %}
diff --git a/openerp/cli/scaffold/models.jinja2 b/openerp/cli/scaffold/models.jinja2
deleted file mode 100644
index d18e72a23a1..00000000000
--- a/openerp/cli/scaffold/models.jinja2
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-from openerp.models import Model
-from openerp.fields import Char
-
-
-class {{ model }}(Model):
- _name = "{{ module }}.{{ model }}"
-
- name = Char()
diff --git a/openerp/cli/scaffold/record.jinja2 b/openerp/cli/scaffold/record.jinja2
deleted file mode 100644
index 53e4ff22383..00000000000
--- a/openerp/cli/scaffold/record.jinja2
+++ /dev/null
@@ -1,4 +0,0 @@
- <record id="{{ module }}.{{ model }}_demo" model="{{ model }}">
- <field name="name">My first record for model '{{ module }}.{{ model }}'</field>
- </record>
-
diff --git a/openerp/cli/scaffold/theme_css.jinja2 b/openerp/cli/scaffold/theme_css.jinja2
deleted file mode 100644
index cd42d262c53..00000000000
--- a/openerp/cli/scaffold/theme_css.jinja2
+++ /dev/null
@@ -1,4 +0,0 @@
-@charset "utf-8";
-.{{ module }} {
- background: white;
-}
diff --git a/openerp/cli/scaffold/theme_xml.jinja2 b/openerp/cli/scaffold/theme_xml.jinja2
deleted file mode 100644
index 2e71368a347..00000000000
--- a/openerp/cli/scaffold/theme_xml.jinja2
+++ /dev/null
@@ -1,26 +0,0 @@
- <template id="assets_frontend" inherit_id="website.assets_frontend" name="Theme Zen">
- <xpath expr="." position="inside">
- <link rel="stylesheet" type="text/css" href="/{{ module }}/static/src/css/{{ module }}_theme.css" />
- </xpath>
- </template>
-
- <template id="website.page_{{ module }}" name="Page {{module}}">
- Page {{ module }}
- </template>
-
- <template id="{{module}}_snippets" name="{{ module }} Snippet" inherit_id="website.snippets">
- <xpath expr="//div[@id='snippet_content']" position="inside">
- <div>
- <div class="oe_snippet_thumbnail">
- <div style="background: white;box-shadow:none;-webkit-box-shadow:none;" class="oe_snippet_thumbnail_img">
- <i class="fa fa-file-code-o fa-5x text-muted"></i>
- </div>
- <span class="oe_snippet_thumbnail_title">{{ module }} Snipper</span>
- </div>
- <section class="oe_snippet_body mb16 mt16">
- <h1>{{ module }} snippet</h1>
- </section>
- </div>
- </xpath>
- </template>
-
diff --git a/openerp/cli/scaffold/webclient_css.jinja2 b/openerp/cli/scaffold/webclient_css.jinja2
deleted file mode 100644
index dadce53503b..00000000000
--- a/openerp/cli/scaffold/webclient_css.jinja2
+++ /dev/null
@@ -1,4 +0,0 @@
-@charset "utf-8";
-.openerp .oe_{{ module }} {
- background: white;
-}
diff --git a/openerp/cli/scaffold/webclient_js.jinja2 b/openerp/cli/scaffold/webclient_js.jinja2
deleted file mode 100644
index 9845e6f0662..00000000000
--- a/openerp/cli/scaffold/webclient_js.jinja2
+++ /dev/null
@@ -1,14 +0,0 @@
-openerp.{{ module }} = function (instance) {
-
-var _t = instance.web._t,
- _lt = instance.web._lt;
-var QWeb = instance.web.qweb;
-
-instance.web.WebClient.include({
- show_application: function () {
- console.log("Show application");
- return this._super.apply(this, arguments);
- };
-});
-
-};
diff --git a/openerp/cli/scaffold/webclient_xml.jinja2 b/openerp/cli/scaffold/webclient_xml.jinja2
deleted file mode 100644
index 98da7f73133..00000000000
--- a/openerp/cli/scaffold/webclient_xml.jinja2
+++ /dev/null
@@ -1,7 +0,0 @@
-<template>
-<t t-name="{{ module }}.MyWidget">
- <div class="oe_{{ module }}">
- Hello World!
- </div>
-</t>
-</template>
diff --git a/openerp/cli/scaffold/xmldata.jinja2 b/openerp/cli/scaffold/xmldata.jinja2
deleted file mode 100644
index 9a29774d6bf..00000000000
--- a/openerp/cli/scaffold/xmldata.jinja2
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data{% if noupdate %} noupdate="1"{% endif %}>
- </data>
-</openerp>
diff --git a/openerp/cli/templates/default/__init__.py b/openerp/cli/templates/default/__init__.py
new file mode 100644
index 00000000000..43dbbaa6318
--- /dev/null
+++ b/openerp/cli/templates/default/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import controllers
+import models
diff --git a/openerp/cli/scaffold/__openerp__.jinja2 b/openerp/cli/templates/default/__openerp__.py
index b62a15d024a..dab78e6555e 100644
--- a/openerp/cli/scaffold/__openerp__.jinja2
+++ b/openerp/cli/templates/default/__openerp__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
{
- 'name': "{{ module }}",
+ 'name': "{{ name }}",
'summary': """
Short (1 phrase/line) summary of the module's purpose, used as
@@ -14,18 +14,21 @@
'website': "http://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
- # Check <odoo>/addons/base/module/module_data.xml of the full list
+ # 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', 'website'],
+ 'depends': ['base'],
+
+ # always loaded
'data': [
+ # 'security/ir.model.access.csv',
+ 'templates.xml',
],
-
+ # only loaded in demonstration mode
'demo': [
- ],
-
- 'tests': [
+ 'demo.xml',
],
}
diff --git a/openerp/cli/templates/default/controllers.py b/openerp/cli/templates/default/controllers.py
new file mode 100644
index 00000000000..505208171fc
--- /dev/null
+++ b/openerp/cli/templates/default/controllers.py
@@ -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/openerp/cli/templates/default/demo.xml b/openerp/cli/templates/default/demo.xml
new file mode 100644
index 00000000000..6c84d877784
--- /dev/null
+++ b/openerp/cli/templates/default/demo.xml
@@ -0,0 +1,11 @@
+{%- 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> -->
+ <!-- </record> -->
+ <!-- {% endfor %} -->
+ </data>
+</openerp>
diff --git a/openerp/cli/templates/default/models.py b/openerp/cli/templates/default/models.py
new file mode 100644
index 00000000000..4d76ef9e2d6
--- /dev/null
+++ b/openerp/cli/templates/default/models.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from openerp import models, fields, api
+
+# class {{ name|snake }}(models.Model):
+# _name = '{{ name|snake }}.{{ name|snake }}'
+
+# name = fields.Char()
diff --git a/openerp/cli/templates/default/security/ir.model.access.csv b/openerp/cli/templates/default/security/ir.model.access.csv
new file mode 100644
index 00000000000..71a2352fedc
--- /dev/null
+++ b/openerp/cli/templates/default/security/ir.model.access.csv
@@ -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/openerp/cli/templates/default/templates.xml b/openerp/cli/templates/default/templates.xml
new file mode 100644
index 00000000000..b9166501678
--- /dev/null
+++ b/openerp/cli/templates/default/templates.xml
@@ -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/openerp/cli/templates/theme/__init__.py b/openerp/cli/templates/theme/__init__.py
new file mode 100644
index 00000000000..40a96afc6ff
--- /dev/null
+++ b/openerp/cli/templates/theme/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/openerp/cli/templates/theme/__openerp__.py b/openerp/cli/templates/theme/__openerp__.py
new file mode 100644
index 00000000000..329483fa078
--- /dev/null
+++ b/openerp/cli/templates/theme/__openerp__.py
@@ -0,0 +1,20 @@
+{
+ # Theme information
+ 'name': "{{name}}",
+ 'description': """
+ """,
+ 'category': 'Theme',
+ 'version': '1.0',
+ 'depends': ['website'],
+
+ # templates, pages, and snippets
+ 'data': [
+ 'views/pages.xml',
+ 'views/options.xml',
+ 'views/snippets.xml',
+ ],
+
+ # Your information
+ 'author': "Your Company",
+ 'website': "",
+}
diff --git a/openerp/cli/templates/theme/static/style/custom.less b/openerp/cli/templates/theme/static/style/custom.less
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/openerp/cli/templates/theme/static/style/custom.less
diff --git a/openerp/cli/templates/theme/views/options.xml b/openerp/cli/templates/theme/views/options.xml
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/openerp/cli/templates/theme/views/options.xml
diff --git a/openerp/cli/templates/theme/views/pages.xml b/openerp/cli/templates/theme/views/pages.xml
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/openerp/cli/templates/theme/views/pages.xml
diff --git a/openerp/cli/templates/theme/views/snippets.xml b/openerp/cli/templates/theme/views/snippets.xml
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/openerp/cli/templates/theme/views/snippets.xml