Browse Source

[environment_checkup] Inicjalizacja

v12_initial_fix
Maciej Wawro 6 years ago
parent
commit
93f2514fcf
  1. 1
      .gitignore
  2. 88
      galicea_environment_checkup/README.rst
  3. 7
      galicea_environment_checkup/__init__.py
  4. 27
      galicea_environment_checkup/__manifest__.py
  5. 3
      galicea_environment_checkup/controllers/__init__.py
  6. 26
      galicea_environment_checkup/controllers/dashboard.py
  7. 0
      galicea_environment_checkup/environment_checkup/__init__.py
  8. 49
      galicea_environment_checkup/environment_checkup/core.py
  9. 30
      galicea_environment_checkup/environment_checkup/custom.py
  10. 181
      galicea_environment_checkup/environment_checkup/dependencies.py
  11. 24
      galicea_environment_checkup/environment_checkup/runtime.py
  12. 0
      galicea_environment_checkup/environment_checkup/utils.py
  13. 1
      galicea_environment_checkup/models/__init__.py
  14. 19
      galicea_environment_checkup/models/ext_module.py
  15. BIN
      galicea_environment_checkup/static/description/custom.png
  16. BIN
      galicea_environment_checkup/static/description/dependencies.png
  17. BIN
      galicea_environment_checkup/static/description/icon.png
  18. 108
      galicea_environment_checkup/static/src/js/environment_checkup.js
  19. 71
      galicea_environment_checkup/static/src/xml/templates.xml
  20. 9
      galicea_environment_checkup/views/data.xml
  21. 17
      galicea_environment_checkup/views/environment_checks.xml
  22. 20
      galicea_environment_checkup/views/views.xml

1
.gitignore

@ -0,0 +1 @@
*.pyc

88
galicea_environment_checkup/README.rst

@ -0,0 +1,88 @@
About
=====
This add-on allows you to:
- programmatically check software dependencies required by your add-on, as well as inform the Administrator as to how to meet them,
- add custom verification for Odoo instance set-up and inform the Administrator about any inconsistencies.
Dependency checks
=================
.. image:: /galicea_environment_checkup/static/description/dependencies.png
How-to
------
Just add ``environment_checkup`` entry to ``__manifest__.py``
.. code::
{
...
'environment_checkup': {
'dependencies': {
'python': [
{
'name': 'Crypto',
'version': '>=2.6.2',
'install': "pip install 'PyCrypto>=2.6.1'"
},
],
'external': [
{
'name': 'wkhtmltopdf',
'install': "apt install wkhtmltopdf"
},
{
'name': 'git',
'version': '^3.0.0',
'install': "apt install git"
}
],
'internal': [
{
'name': 'web',
'version': '~10.0.1.0'
}
]
}
}
}
Custom in-code checks
=====================
.. image:: /galicea_environment_checkup/static/description/custom.png
How-to
------
1. Add the check
``system_checks.py``
.. code::
# -*- coding: utf-8 -*-
import cgi
from odoo.addons.galicea_environment_checkup import custom_check, CheckSuccess, CheckWarning, CheckFail
@custom_check
def check_mail(env):
users_without_emails = env['res.users'].sudo().search([('email', '=', False)])
if users_without_emails:
raise CheckWarning(
'Some users don\'t have their e-mails set up.',
details='See user <tt>{}</tt>.'.format(cgi.escape(users_without_emails[0].name))
)
return CheckSuccess('All users have their e-mails set.')
2. Make sure it's loaded
``__init__.py``
.. code::
# -*- coding: utf-8 -*-
from . import system_checks

7
galicea_environment_checkup/__init__.py

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from . import models
from . import controllers
from environment_checkup.custom import custom_check
from environment_checkup.core import CheckFail, CheckWarning, CheckSuccess

27
galicea_environment_checkup/__manifest__.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
{
'name': "Galicea Enviromnent Check-up",
'summary': """
Programmatically validate environment, including internal and external
dependencies""",
'author': "Maciej Wawro",
'maintainer': "Galicea",
'website': "http://galicea.pl",
'category': 'Technical Settings',
'version': '10.0.1.0',
'depends': ['web'],
'data': [
'views/data.xml',
'views/views.xml',
'views/environment_checks.xml'
],
'qweb': ['static/src/xml/templates.xml'],
'installable': True
}

3
galicea_environment_checkup/controllers/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import dashboard

26
galicea_environment_checkup/controllers/dashboard.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from odoo import http
from odoo.exceptions import AccessError
from odoo.http import request
from ..environment_checkup.runtime import all_installed_checks, display_data
from ..environment_checkup.core import CheckResult
class Dashboard(http.Controller):
@http.route('/galicea_environment_checkup/data', type='json', auth='user')
def data(self, request, **kw):
if not request.env.user.has_group('base.group_erp_manager'):
raise AccessError("Access Denied")
checks = all_installed_checks(request.env)
response = display_data(request.env, checks)
priority = {
CheckResult.FAIL: 0,
CheckResult.WARNING: 1,
CheckResult.SUCCESS: 2
}
response.sort(key=lambda res: (priority[res['result']], res['module']))
return response

0
galicea_environment_checkup/environment_checkup/__init__.py

49
galicea_environment_checkup/environment_checkup/core.py

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
import logging
_logger = logging.getLogger(__name__)
class CheckResult(object):
SUCCESS = 'success'
WARNING = 'warning'
FAIL = 'fail'
def __init__(self, result, message, details = None):
super(CheckResult, self).__init__()
self.result = result
self.message = message
self.details = details
class CheckSuccess(CheckResult):
def __init__(self, message, **kwargs):
super(CheckSuccess, self).__init__(CheckResult.SUCCESS, message, **kwargs)
class CheckIssue(CheckResult, Exception):
def __init__(self, result, message, **kwargs):
Exception.__init__(self, message)
CheckResult.__init__(self, result, message, **kwargs)
class CheckFail(CheckIssue):
def __init__(self, message, **kwargs):
super(CheckFail, self).__init__(CheckResult.FAIL, message, **kwargs)
class CheckWarning(CheckIssue):
def __init__(self, message, **kwargs):
super(CheckWarning, self).__init__(CheckResult.WARNING, message, **kwargs)
class Check(object):
def __init__(self, module):
self.module = module
def run(self, env):
try:
return self._run(env)
except CheckIssue as issue:
return issue
except Exception as ex:
_logger.exception(ex)
return CheckFail('Check failed when processing: {}'.format(ex))
def _run(self, env):
raise NotImplementedError('Should be overriden by the subclass')

30
galicea_environment_checkup/environment_checkup/custom.py

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
import collections
from core import Check
custom_checks_per_module = collections.defaultdict(list)
class CustomCheck(Check):
def __init__(self, module, func):
super(CustomCheck, self).__init__(module)
self.func = func
def _run(self, env):
return self.func(env)
def custom_check(func):
try:
module = func.__module__.split('.')[2]
except IndexError:
module = ''
custom_checks_per_module[module].append(
CustomCheck(module=module, func=func)
)
return func
def get_checks_for_module(module_name):
return custom_checks_per_module[module_name]

181
galicea_environment_checkup/environment_checkup/dependencies.py

@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
import subprocess
import re
import cgi
from odoo.modules.module import load_information_from_description_file
from odoo.tools import which
from core import Check, CheckSuccess, CheckWarning, CheckFail
class DependencyCheck(Check):
dependency_type = None
def __init__(self, module, dependency):
super(DependencyCheck, self).__init__(module)
self.dependency = dependency
def _dependency_installed(self, env, name):
raise NotImplementedError('Should be overriden by the subclass')
def _installed_version(self, env, name):
raise NotImplementedError('Should be overriden by the subclass')
def _details(self):
if 'install' in self.dependency:
return 'Install command: <pre>{}</pre>'.format(self.dependency['install'])
return None
def __has_required_version(self, installed_version, version_expression):
version_operator = '='
version = self.dependency['version']
if version[:1] in ['=', '~', '^']:
version_operator = version[:1]
version = version[1:]
elif version[:2] in ['>=']:
version_operator = version[:2]
version = version[2:]
try:
parsed_version = map(int, version.split('.'))
except ValueError:
raise CheckFail(
'Invalid version expression',
details = """
Allowed expressions are <pre>=x.y.z</pre>, <pre>&gt;=x.y.z</pre>, <pre>^x.z.y</pre>,
<pre>~x.y.z. Got <pre>{}</pre>""".format(cgi.escape(self.dependency['version']))
)
parsed_installed_version = map(int, installed_version.split('.'))
parsed_version.extend(0 for _ in range(len(parsed_installed_version) - len(parsed_version)))
parsed_installed_version.extend(0 for _ in range(len(parsed_version) - len(parsed_installed_version)))
if version_operator == '^':
if parsed_installed_version[:1] != parsed_version[:1]:
return False
version_operator = '>='
elif version_operator == '~':
if parsed_installed_version[:2] != parsed_version[:2]:
return False
version_operator = '>='
if version_operator == '>=':
return tuple(parsed_installed_version) >= tuple(parsed_version)
elif version_operator == '=':
return tuple(parsed_installed_version) == tuple(parsed_version)
assert False
def _run(self, env):
name = self.dependency['name']
if not self._dependency_installed(env, name):
raise CheckFail(
'Required {} - {} - is not installed.'.format(self.dependency_type, name),
details=self._details()
)
if 'version' in self.dependency:
version_expression = self.dependency['version']
installed_version = self._installed_version(env, name)
if not self.__has_required_version(installed_version, version_expression):
raise CheckWarning(
'Required {} - {} - has version {}, but {} is needed.'.format(
self.dependency_type,
name,
installed_version,
version_expression
),
details=self._details()
)
return CheckSuccess(
'Required {} - {} - is installed.'.format(self.dependency_type, name),
details=self._details()
)
class InternalDependencyCheck(DependencyCheck):
dependency_type = 'Odoo module'
def _dependency_installed(self, env, name):
return name in env.registry._init_modules
def _installed_version(self, env, name):
return env['ir.module.module'].sudo().search([('name', '=', name)]).latest_version
class PythonDependencyCheck(DependencyCheck):
dependency_type = 'Python module'
def _dependency_installed(self, env, name):
try:
__import__(name)
return True
except ImportError:
return False
def _installed_version(self, env, name):
try:
return __import__(name).__version__
except AttributeError:
raise CheckWarning(
'Could not detect version of the Python module: {}.'.format(name),
details=self._details()
)
class ExternalDependencyCheck(DependencyCheck):
dependency_type = 'system executable'
def _dependency_installed(self, env, name):
try:
which(name)
return True
except IOError:
return False
def _installed_version(self, env, name):
try:
exe = which(name)
out = subprocess.check_output([exe, '--version'])
match = re.search('[\d.]+', out)
if not match:
raise CheckWarning(
'Unable to detect version for executable {}'.format(name),
details="Command {} --version returned <pre>{}</pre>".format(exe, out)
)
return match.group(0)
except subprocess.CalledProcessError as e:
raise CheckWarning(
'Unable to detect version for executable {}: {}'.format(name, e),
details=self._details()
)
def get_checks_for_module(module_name):
result = []
manifest = load_information_from_description_file(module_name)
manifest_checks = manifest.get('environment_checkup') or {}
dependencies = manifest_checks.get('dependencies') or {}
for dependency in dependencies.get('python') or []:
result.append(PythonDependencyCheck(module_name, dependency))
for dependency in dependencies.get('external') or []:
result.append(ExternalDependencyCheck(module_name, dependency))
for dependency in dependencies.get('internal') or []:
result.append(InternalDependencyCheck(module_name, dependency))
return result
def get_checks_for_module_recursive(module):
class ModuleDFS(object):
def __init__(self):
self.visited_modules = set()
self.checks = []
def visit(self, module):
if module.name in self.visited_modules:
return
self.visited_modules.add(module.name)
self.checks += get_checks_for_module(module.name)
for module_dependency in module.dependencies_id:
if module_dependency.depend_id:
self.visit(module_dependency.depend_id)
return self
return ModuleDFS().visit(module).checks

24
galicea_environment_checkup/environment_checkup/runtime.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
import custom, dependencies
def all_installed_checks(env):
result = []
installed_modules = env.registry._init_modules
for module_name in installed_modules:
result += custom.get_checks_for_module(module_name)
result += dependencies.get_checks_for_module(module_name)
return result
def display_data(env, checks):
response = []
for check in checks:
result = check.run(env)
response.append({
'module': check.module,
'message': result.message,
'details': result.details,
'result': result.result
})
return response

0
galicea_environment_checkup/environment_checkup/utils.py

1
galicea_environment_checkup/models/__init__.py

@ -0,0 +1 @@
from . import ext_module

19
galicea_environment_checkup/models/ext_module.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
import json
from odoo import api, fields, models
from ..environment_checkup import dependencies
from ..environment_checkup.runtime import display_data
class Module(models.Model):
_inherit = 'ir.module.module'
dependency_checks = fields.Text(
compute='_compute_dependency_checks'
)
@api.one
def _compute_dependency_checks(self):
checks = dependencies.get_checks_for_module_recursive(self)
self.dependency_checks = json.dumps(display_data(self.env, checks))

BIN
galicea_environment_checkup/static/description/custom.png

After

Width: 625  |  Height: 198  |  Size: 24 KiB

BIN
galicea_environment_checkup/static/description/dependencies.png

After

Width: 966  |  Height: 755  |  Size: 92 KiB

BIN
galicea_environment_checkup/static/description/icon.png

After

Width: 600  |  Height: 600  |  Size: 40 KiB

108
galicea_environment_checkup/static/src/js/environment_checkup.js

@ -0,0 +1,108 @@
odoo.define('galicea_environment_checkup', function(require) {
"use strict";
var core = require('web.core');
var form_common = require('web.form_common');
var Widget = require('web.Widget');
var session = require('web.session');
var QWeb = core.qweb;
var SystrayMenu = require('web.SystrayMenu');
var Model = require('web.Model');
var Users = new Model('res.users');
var SystrayIcon = Widget.extend({
tagName: 'li',
events: {
"click": "on_click",
},
start: function(){
this.load(this.all_dashboards);
return this._super();
},
load: function(dashboards){
var self = this;
var loading_done = new $.Deferred();
Users.call('has_group', ['base.group_erp_manager']).then(function(is_admin) {
if (is_admin) {
session.rpc('/galicea_environment_checkup/data', {})
.then(function (data) {
var counts = { 'success': 0, 'warning': 0, 'fail': 0 };
data.forEach(function (check) { ++counts[check.result]; });
var result;
if (counts['fail']) {
result = 'fail';
} else if (counts['warning']) {
result = 'warning';
} else {
result = 'success';
}
self.replaceElement(QWeb.render('GaliceaEnvironmentCheckupIcon', {
'result': result,
'count': counts['warning'] + counts['fail']
}));
loading_done.resolve();
});
} else {
loading_done.resolve();
}
});
return loading_done;
},
on_click: function (event) {
event.preventDefault();
this.do_action('galicea_environment_checkup.dashboard_action', {clear_breadcrumbs: true});
},
});
SystrayMenu.Items.push(SystrayIcon);
var Dashboard = Widget.extend({
start: function(){
return this.load(this.all_dashboards);
},
load: function(dashboards) {
var self = this;
var loading_done = new $.Deferred();
session.rpc('/galicea_environment_checkup/data', {})
.then(function (data) {
self.replaceElement(QWeb.render('GaliceaEnvironmentCheckupDashboard', {'data': data}));
loading_done.resolve();
});
return loading_done;
},
});
core.action_registry.add('galicea_environment_checkup.dashboard', Dashboard);
var FormWidget = form_common.AbstractField.extend({
init: function() {
this._super.apply(this, arguments);
this.set("value", "[]");
},
render_value: function() {
var data = JSON.parse(this.get('value'));
if (data.length == 0) {
this.replaceElement('<div />');
return;
}
this.replaceElement(QWeb.render('GaliceaEnvironmentCheckupFormWidget', {'data': data}));
},
});
core.form_widget_registry.add('environment_checks', FormWidget);
return {
SystrayIcon: SystrayIcon,
Dashboard: Dashboard,
FormWidget: FormWidget
};
});

71
galicea_environment_checkup/static/src/xml/templates.xml

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="GaliceaEnvironmentCheckupIcon">
<li>
<a href="#" t-att-title="result == 'success' ? 'No system setup issues' : 'Found '+count+' system setup issues'">
<t t-if="result == 'success'">
<i class="fa fa-check" style="opacity:0.5"></i>
</t>
<t t-if="result == 'warning'">
<i class="fa fa-exclamation-triangle" style="color: orange"></i>
</t>
<t t-if="result == 'fail'">
<i class="fa fa-exclamation-circle" style="color: red"></i>
</t>
<t t-if="result != 'success'">
<span><t t-raw="count" /></span>
</t>
</a>
</li>
</t>
<t t-name="GaliceaEnvironmentChecks">
<div class="row">
<t t-foreach="data" t-as="check">
<div class="col-md-12">
<div style="display: flex; background-color: white; padding: 10px; margin: 10px">
<div style="flex-grow: 0; flex-shrink: 0; width:50px; margin-right: 20px">
<t t-if="check.result == 'success'">
<i class="fa fa-check fa-4x" style="color: green" aria-hidden="true"></i>
</t>
<t t-if="check.result == 'warning'">
<i class="fa fa-exclamation-triangle fa-4x" style="color: orange" aria-hidden="true"></i>
</t>
<t t-if="check.result == 'fail'">
<i class="fa fa-exclamation-circle fa-4x" style="color: red" aria-hidden="true"></i>
</t>
</div>
<div style="flex-grow: 1; flex-shrink: 1">
Module: <t t-esc="check.module" />
<h4><t t-esc="check.message" /></h4>
<div>
<t t-raw="check.details" />
</div>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-name="GaliceaEnvironmentCheckupDashboard">
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Environment check-up</h2>
<t t-call="GaliceaEnvironmentChecks">
<t t-set="data" t-value="data" />
</t>
</div>
</div>
</div>
</t>
<t t-name="GaliceaEnvironmentCheckupFormWidget">
<h1>Module dependencies</h1>
<t t-call="GaliceaEnvironmentChecks">
<t t-set="data" t-value="data" />
</t>
</t>
</templates>

9
galicea_environment_checkup/views/data.xml

@ -0,0 +1,9 @@
<odoo>
<data>
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script src="/galicea_environment_checkup/static/src/js/environment_checkup.js" type="text/javascript" />
</xpath>
</template>
</data>
</odoo>

17
galicea_environment_checkup/views/environment_checks.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="stale_modules">
<p>The following modules need to be upgraded:</p>
<ul>
<t t-foreach="modules" t-as="module">
<li>
<a t-att-href="'#id={}&amp;view_type=form&amp;model=ir.module.module'.format(module.id)">
<t t-esc="module.shortdesc" /> (technical name: <t t-esc="module.name" />;
version on disk: <strong><t t-esc="module.installed_version" /></strong>;
version in DB: <strong><t t-esc="module.latest_version" /></strong>)
</a>
</li>
</t>
</ul>
</template>
</odoo>

20
galicea_environment_checkup/views/views.xml

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="dashboard_action" model="ir.actions.client">
<field name="name">Environment check-up dashboard</field>
<field name="tag">galicea_environment_checkup.dashboard</field>
</record>
<menuitem name="Environment check-up" id="dashboard_menu"
action="dashboard_action" parent="base.menu_administration" groups="base.group_erp_manager" />
<record id="module_form" model="ir.ui.view">
<field name="name">module_form.checks</field>
<field name="model">ir.module.module</field>
<field name="inherit_id" ref="base.module_form"/>
<field name="arch" type="xml">
<field name="description_html" position="before">
<field name="dependency_checks" widget="environment_checks" />
</field>
</field>
</record>
</odoo>
Loading…
Cancel
Save