Refactor scaffolding engine so it can be used from outside

This commit is contained in:
Fabien Meghazi 2014-05-29 16:25:30 +02:00
parent fcc854a4e1
commit 14f7d31a5f
6 changed files with 130 additions and 105 deletions

View File

@ -12,94 +12,11 @@ import jinja2
from . import Command
MANIFEST = '__openerp__'
class Scaffold(Command):
"Generate an Odoo module skeleton."
def __init__(self):
super(Scaffold, self).__init__()
env = jinja2.Environment(loader=jinja2.PackageLoader(
'openerp.cli', 'scaffold'))
env.filters['snake'] = snake
self.env = env
self.manifest = '__openerp__'
def scaffold(self, args):
args.dependency = 'base'
# TODO: update dependencies according to --web and --theme
# if args.web:
# args.dependency = 'web'
# elif args.theme:
# args.dependency = 'website'
dest = directory(args.dest)
if args.init:
args.module = snake(args.init)
module = functools.partial(os.path.join, dest, args.module)
if os.path.exists(module()):
die("Can't initialize module in `%s`: Directory already exists." % module())
if self.get_module_root(dest):
die("Can't init a new module in another module, you probably want to run this "
"command from your project's root")
else:
args.module, dest = self.get_module_root(dest)
module = functools.partial(os.path.join, dest)
if args.init:
self.dump('%s.jinja2' % self.manifest, module('%s.py' % self.manifest), config=args)
if args.model:
model_module = snake(args.model)
model_file = module('models', '%s.py' % model_module)
if os.path.exists(model_file):
die("Model `%s` already exists !" % model_file)
self.add_init_import(module('__init__.py'), 'models')
self.add_init_import(module('models', '__init__.py'), model_module)
self.dump('models.jinja2', model_file, config=args)
self.dump('ir.model.access.jinja2', module('security', 'ir.model.access.csv'), config=args)
if args.controller:
controller_module = snake(args.controller)
controller_file = module('controllers', '%s.py' % controller_module)
if os.path.exists(controller_file):
die("Controller `%s` already exists !" % controller_file)
self.add_init_import(module('__init__.py'), 'controllers')
# Check if the controller name correspond to a model and expose result to templates
args.has_model = self.has_import(module('models', '__init__.py'), controller_module)
self.add_init_import(module('controllers', '__init__.py'), controller_module)
self.dump('controllers.jinja2', module('controllers', controller_file), config=args)
def get_module_root(self, path):
module_name = path.split(os.path.sep)[-1]
# find the module's root directory
while not os.path.exists(os.path.join(path, '%s.py' % self.manifest)):
new_path = os.path.abspath(os.path.join(path, os.pardir))
if path == new_path:
return None
module_name = path.split(os.path.sep)[-1]
path = new_path
return (module_name, path)
def has_import(self, initfile, module):
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 add_init_import(self, initfile, module):
if not(os.path.exists(initfile) and self.has_import(initfile, module)):
self.dump('__init__.jinja2', initfile, modules=[module])
def dump(self, template, dest, **kwargs):
outdir = os.path.dirname(dest)
kwargs['create'] = not os.path.exists(dest)
if not os.path.exists(outdir):
os.makedirs(outdir)
content = self.env.get_template(template).render(**kwargs)
with open(dest, 'a') as f:
f.write(content)
def run(self, args):
# TODO: bash completion file
parser = argparse.ArgumentParser(
@ -124,7 +41,99 @@ class Scaffold(Command):
if not args:
sys.exit(parser.print_help())
args = parser.parse_args(args=args)
self.scaffold(args)
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
scaffold = ScaffoldModule(dest)
if args.model:
scaffold.add_model(args.model)
if args.controller:
scaffold.add_controller(args.controller)
class ScaffoldModule(object):
"""
Object for scaffolding existing or new Odoo modules
@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'))
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)
if self.created:
self.module_name = self.path().split(os.path.sep)[-1]
self.dump('%s.jinja2' % MANIFEST, self.path('%s.py' % MANIFEST))
else:
# TODO: get this information from manifest
self.module_name = self.path().split(os.path.sep)[-1]
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.dump('models.jinja2', model_file, model=model)
self.dump('ir.model.access.jinja2', self.path('security', 'ir.model.access.csv'), model=model)
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.dump('controllers.jinja2', self.path('controllers', controller_file),
controller=controller, has_model=has_model)
def has_import(self, initfile, module):
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 ensure_dependency_to(self, module):
# TODO: update dependencies according to --web and --theme
# if args.web:
# args.dependency = 'web'
# elif args.theme:
# args.dependency = 'website'
pass
def add_init_import(self, initfile, module):
if not(os.path.exists(initfile) and self.has_import(initfile, module)):
self.dump('__init__.jinja2', initfile, modules=[module])
def dump(self, template, dest, **kwargs):
outdir = os.path.dirname(dest)
kwargs['file_created'] = not os.path.exists(dest)
if not os.path.exists(outdir):
os.makedirs(outdir)
content = self.env.get_template(template).render(module_name=self.module_name, **kwargs)
with open(dest, 'a') as f:
f.write(content)
def snake(s):
""" snake cases ``s``
@ -145,16 +154,33 @@ def identifier(s):
die("%s is not a valid Python identifier" % s)
return s
def directory(p):
def directory(p, create=False):
expanded = os.path.abspath(
os.path.expanduser(
os.path.expandvars(p)))
if not os.path.exists(expanded):
if create and not os.path.exists(expanded):
os.makedirs(expanded)
if not os.path.isdir(expanded):
if create and not os.path.isdir(expanded):
die("%s exists but is not a directory" % p)
return expanded
def get_module_root(path):
"""
Get closest module's root begining from path
@param path: Path from which the lookup should start
@return: Module root path
"""
# find the module's root directory
while not os.path.exists(os.path.join(path, '%s.py' % MANIFEST)):
new_path = os.path.abspath(os.path.join(path, os.pardir))
if path == new_path:
return None
path = new_path
return path
def die(message, code=1):
print >>sys.stderr, message
sys.exit(code)

View File

@ -1,3 +1,3 @@
{% if create -%}# -*- coding: utf-8 -*-{%- endif %}
{% if file_created -%}# -*- coding: utf-8 -*-{%- endif %}
{% for module in modules if module -%}import {{ module }}
{%- endfor %}

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
{
'name': "{{ config.module }}",
'name': "{{ module_name }}",
'summary': """
Short (1 phrase/line) summary of the module's purpose, used as
@ -21,7 +21,6 @@
# any module necessary for this one to work correctly
'depends': ['base'],
'data': [
# {{- "'security/ir.model.access.csv'" if config.model -}}
],
'demo': [

View File

@ -2,12 +2,12 @@
from openerp import http
from openerp.addons.web.controllers import main
class {{ config.controller }}(main.Home):
@http.route('/{{ config.module }}/{{ config.controller }}', auth='public')
class {{ controller }}(main.Home):
@http.route('/{{ module_name }}/{{ controller }}', auth='public')
def index(self):
return "Hello, world!"
{% if config.has_model %}
@http.route('/{{ config.module }}/{{ config.controller }}/<model("{{ config.module }}.{{ config.controller }}"):{{ config.controller }}>'], type='http', auth='public')
def {{ config.controller }}(self, {{ config.controller }}, **kw):
return "Hello, %r!" % {{ config.controller }}
{% if has_model %}
@http.route('/{{ module_name }}/{{ controller }}/<model("{{ module_name }}.{{ controller }}"):{{ controller }}>'], type='http', auth='public')
def {{ controller }}(self, {{ controller }}, **kw):
return "Hello, %r!" % {{ controller }}
{% endif %}

View File

@ -1,6 +1,6 @@
{% if create -%}id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink{%- endif %}
{% if config.model -%}
access_{{ config.module|snake }}_{{ config.model|snake }},{{- '' -}}
access_{{ config.module|snake }}_{{ config.model|snake }},{{- '' -}}
model_{{ config.module|snake }}_{{ config.model|snake }},,1,0,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_name|snake }}_{{ model|snake }},{{- '' -}}
access_{{ module_name|snake }}_{{ model|snake }},{{- '' -}}
model_{{ module_name|snake }}_{{ model|snake }},,1,0,0,0
{%- endif %}

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from openerp.osv import orm, fields
class {{ config.model }}(orm.Model):
_name = "{{ config.module|snake }}.{{ config.model|snake }}"
class {{ model }}(orm.Model):
_name = "{{ module_name|snake }}.{{ model|snake }}"
_columns = {
'name': fields.char(),