diff options
| author | Christophe Simonis <chs@odoo.com> | 2014-09-05 15:28:22 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2014-09-05 15:28:22 +0200 |
| commit | 31bf30d2d0eb78baaa36360b99fe58bf7cd4c9e7 (patch) | |
| tree | d7be61ecd6bb4c8b5552e39b03e2a64eebbbf323 /openerp/cli | |
| parent | afba9e8817c458640ae446ac7aaaab79be4fe1e9 (diff) | |
| parent | ed1c173971968eaa96693cb4e1547efa6e6c34e0 (diff) | |
[MERGE] forward port of branch 8.0 up to ed1c173
Diffstat (limited to 'openerp/cli')
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 |
