From 8402c6176bf2713d949ab1bbe23e162e1d402473 Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Tue, 29 Mar 2016 18:33:47 -0300 Subject: [PATCH 01/11] ADD adhoc_modules --- adhoc_modules/__init__.py | 4 + adhoc_modules/__openerp__.py | 43 ++++ adhoc_modules/models/__init__.py | 5 + adhoc_modules/models/adhoc_module.py | 145 +++++++++++ adhoc_modules/models/adhoc_module_category.py | 197 +++++++++++++++ .../models/adhoc_module_dependency.py | 54 ++++ .../models/adhoc_module_repository.py | 237 ++++++++++++++++++ adhoc_modules/octohub/__init__.py | 12 + adhoc_modules/octohub/connection.py | 75 ++++++ adhoc_modules/octohub/exceptions.py | 26 ++ adhoc_modules/octohub/response.py | 103 ++++++++ adhoc_modules/octohub/utils.py | 40 +++ adhoc_modules/security/ir.model.access.csv | 9 + .../views/adhoc_module_category_view.xml | 117 +++++++++ .../views/adhoc_module_repository_view.xml | 69 +++++ adhoc_modules/views/adhoc_module_view.xml | 126 ++++++++++ adhoc_modules_server/README.rst | 27 ++ adhoc_modules_server/__init__.py | 4 + adhoc_modules_server/__openerp__.py | 47 ++++ .../data/mass_editting_data.xml | 13 + adhoc_modules_server/models/__init__.py | 5 + adhoc_modules_server/models/adhoc_module.py | 63 +++++ .../models/adhoc_module_category.py | 197 +++++++++++++++ .../models/adhoc_module_dependency.py | 54 ++++ .../models/adhoc_module_repository.py | 237 ++++++++++++++++++ adhoc_modules_server/octohub/__init__.py | 12 + adhoc_modules_server/octohub/connection.py | 75 ++++++ adhoc_modules_server/octohub/exceptions.py | 26 ++ adhoc_modules_server/octohub/response.py | 103 ++++++++ adhoc_modules_server/octohub/utils.py | 40 +++ .../security/ir.model.access.csv | 9 + .../views/adhoc_module_category_view.xml | 117 +++++++++ .../views/adhoc_module_repository_view.xml | 69 +++++ .../views/adhoc_module_view.xml | 126 ++++++++++ 34 files changed, 2486 insertions(+) create mode 100644 adhoc_modules/__init__.py create mode 100644 adhoc_modules/__openerp__.py create mode 100644 adhoc_modules/models/__init__.py create mode 100644 adhoc_modules/models/adhoc_module.py create mode 100644 adhoc_modules/models/adhoc_module_category.py create mode 100644 adhoc_modules/models/adhoc_module_dependency.py create mode 100644 adhoc_modules/models/adhoc_module_repository.py create mode 100644 adhoc_modules/octohub/__init__.py create mode 100644 adhoc_modules/octohub/connection.py create mode 100644 adhoc_modules/octohub/exceptions.py create mode 100644 adhoc_modules/octohub/response.py create mode 100644 adhoc_modules/octohub/utils.py create mode 100644 adhoc_modules/security/ir.model.access.csv create mode 100644 adhoc_modules/views/adhoc_module_category_view.xml create mode 100644 adhoc_modules/views/adhoc_module_repository_view.xml create mode 100644 adhoc_modules/views/adhoc_module_view.xml create mode 100644 adhoc_modules_server/README.rst create mode 100644 adhoc_modules_server/__init__.py create mode 100644 adhoc_modules_server/__openerp__.py create mode 100644 adhoc_modules_server/data/mass_editting_data.xml create mode 100644 adhoc_modules_server/models/__init__.py create mode 100644 adhoc_modules_server/models/adhoc_module.py create mode 100644 adhoc_modules_server/models/adhoc_module_category.py create mode 100644 adhoc_modules_server/models/adhoc_module_dependency.py create mode 100644 adhoc_modules_server/models/adhoc_module_repository.py create mode 100644 adhoc_modules_server/octohub/__init__.py create mode 100644 adhoc_modules_server/octohub/connection.py create mode 100644 adhoc_modules_server/octohub/exceptions.py create mode 100644 adhoc_modules_server/octohub/response.py create mode 100644 adhoc_modules_server/octohub/utils.py create mode 100644 adhoc_modules_server/security/ir.model.access.csv create mode 100644 adhoc_modules_server/views/adhoc_module_category_view.xml create mode 100644 adhoc_modules_server/views/adhoc_module_repository_view.xml create mode 100644 adhoc_modules_server/views/adhoc_module_view.xml diff --git a/adhoc_modules/__init__.py b/adhoc_modules/__init__.py new file mode 100644 index 0000000..c6a3ab6 --- /dev/null +++ b/adhoc_modules/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +from . import models +# from . import controllers +# from . import wizard diff --git a/adhoc_modules/__openerp__.py b/adhoc_modules/__openerp__.py new file mode 100644 index 0000000..9f70fde --- /dev/null +++ b/adhoc_modules/__openerp__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2015 ADHOC SA (http://www.adhoc.com.ar) +# All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + "name": "ADHOC Modules", + "version": "8.0.0.0.0", + 'author': 'ADHOC SA', + 'website': 'www.adhoc.com.ar', + 'license': 'AGPL-3', + 'depends': [ + 'base', + ], + 'external_dependencies': { + }, + 'data': [ + 'views/adhoc_module_category_view.xml', + 'views/adhoc_module_view.xml', + 'security/ir.model.access.csv', + ], + 'demo': [], + 'test': [], + 'installable': True, + 'active': False, + 'auto_install': True +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/adhoc_modules/models/__init__.py b/adhoc_modules/models/__init__.py new file mode 100644 index 0000000..0c67ef3 --- /dev/null +++ b/adhoc_modules/models/__init__.py @@ -0,0 +1,5 @@ +# -*- encoding: utf-8 -*- +from . import adhoc_module_repository +from . import adhoc_module_dependency +from . import adhoc_module_category +from . import adhoc_module diff --git a/adhoc_modules/models/adhoc_module.py b/adhoc_modules/models/adhoc_module.py new file mode 100644 index 0000000..f87ed65 --- /dev/null +++ b/adhoc_modules/models/adhoc_module.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api, _ +# from openerp.exceptions import Warning +import logging + +_logger = logging.getLogger(__name__) + + +class AdhocModuleModule(models.Model): + _inherit = 'ir.module.module' + + adhoc_category_id = fields.Many2one( + 'adhoc.module.category', + 'ADHOC Category', + auto_join=True, + ) + computed_summary = fields.Char( + compute='get_computed_summary', + inverse='set_adhoc_summary', + ) + adhoc_summary = fields.Char( + ) + adhoc_description_html = fields.Html( + ) + support_type = fields.Selection([ + ('supported', 'Soportado'), + ('unsupported', 'No Soportado'), + # ('unsupport_all', 'No Soporta BD'), + ], + string='Support Type', + ) + review = fields.Selection([ + ('0', 'Not Recommended'), + ('1', 'Only If Necessary'), + ('2', 'Neutral'), + ('3', 'Recomendado'), + ('4', 'Muy Recomendado'), + ], 'Opinion', + select=True + ) + conf_visibility = fields.Selection([ + ('normal', 'Normal'), + ('to_review', 'A Revisar'), + ('future_versions', 'Versiones Futuras'), + ('only_if_depends', 'Solo si dependencias'), + ('auto_install', 'Auto Install'), + ('installed_by_others', 'Instalado por Otro'), + ('on_config_wizard', 'En asistente de configuración'), + ('unusable', 'No Usable'), + ], + 'Visibility', + required=True, + default='normal', + help="Módulos que se pueden instalar:\n" + "* Normal: visible para ser instalado\n" + "* Solo si dependencias: se muestra solo si dependencias instaladas\n" + "* Auto Instalar: auto instalar si se cumplen dependencias\n" + "* Instalado por Otro: algún otro módulo dispara la instalación\n" + "* En asistente de configuración: este módulo esta presente en el " + "asistente de configuración\n" + "\nMódulos en los que se bloquea la instalación:\n" + "* A Revisar: hay que analizar como lo vamos a utilizar\n" + "* Versiones Futuras: se va a incorporar más adelante\n" + "* No Usable: no se usa ni se va a sugerir uso en versiones futuras\n" + ) + visibility_obs = fields.Char( + 'Visibility Observation' + ) + ignored = fields.Boolean( + 'Ignored' + ) + + @api.model + def set_adhoc_summary(self): + self.adhoc_summary = self.computed_summary + + @api.one + def get_computed_summary(self): + self.computed_summary = self.adhoc_summary or self.summary + + @api.multi + def open_module(self): + self.ensure_one() + module_form = self.env.ref( + 'adhoc_modules.view_adhoc_module_module_form', False) + if not module_form: + return False + return { + 'name': _('Module Description'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': self._model, + 'views': [(module_form.id, 'form')], + 'view_id': module_form.id, + 'res_id': self.id, + 'target': 'current', + # 'target': 'new', + 'context': self._context, + # top open in editable form + 'flags': { + 'form': {'action_buttons': True, 'options': {'mode': 'edit'}}} + } + + @api.multi + def button_ignore(self): + return self.write({'ignored': True}) + + @api.multi + def button_set_to_install(self): + """ + Casi igual a "button_install" pero no devuelve ninguna acción, queda + seteado unicamente + """ + # Mark the given modules to be installed. + self.state_update('to install', ['uninstalled']) + + # Mark (recursively) the newly satisfied modules to also be installed + + # Select all auto-installable (but not yet installed) modules. + domain = [('state', '=', 'uninstalled'), ('auto_install', '=', True)] + uninstalled_modules = self.search(domain) + + # Keep those with: + # - all dependencies satisfied (installed or to be installed), + # - at least one dependency being 'to install' + satisfied_states = frozenset(('installed', 'to install', 'to upgrade')) + + def all_depencies_satisfied(m): + states = set(d.state for d in m.dependencies_id) + return states.issubset( + satisfied_states) and ('to install' in states) + to_install_modules = filter( + all_depencies_satisfied, uninstalled_modules) + to_install_ids = map(lambda m: m.id, to_install_modules) + + # Mark them to be installed. + if to_install_ids: + self.browse(to_install_ids).button_install() + + return True diff --git a/adhoc_modules/models/adhoc_module_category.py b/adhoc_modules/models/adhoc_module_category.py new file mode 100644 index 0000000..196b1c9 --- /dev/null +++ b/adhoc_modules/models/adhoc_module_category.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api +import logging + +_logger = logging.getLogger(__name__) + + +class AdhocModuleCategory(models.Model): + _name = 'adhoc.module.category' + # _parent_store = True + _order = 'sequence' + # _rec_name = 'display_name' + + name = fields.Char( + required=True, + ) + count_modules = fields.Integer( + string='# Modules', + compute='get_count_modules', + ) + count_suggested_modules = fields.Integer( + string='# Suggested Modules', + compute='get_count_modules', + ) + count_subcategories_modules = fields.Integer( + string='# Subcategories Modules', + compute='get_count_subcategories_modules', + ) + count_suggested_subcategories_modules = fields.Integer( + string='# Suggested Subcategories Modules', + compute='get_count_subcategories_modules', + ) + count_subcategories = fields.Integer( + string='# Subcategories', + compute='get_count_subcategories', + ) + count_suggested_subcategories = fields.Integer( + string='# Suggested Subcategories', + compute='get_count_subcategories', + ) + color = fields.Integer( + string='Color Index', + compute='get_color', + ) + parent_id = fields.Many2one( + 'adhoc.module.category', + 'Parent Category', + select=True, + ) + child_ids = fields.One2many( + 'adhoc.module.category', + 'parent_id', + 'Child Categories' + ) + module_ids = fields.One2many( + 'adhoc.module.module', + 'adhoc_category_id', + 'Modules' + ) + description = fields.Text( + ) + sequence = fields.Integer( + 'Sequence', + default=10, + ) + display_name = fields.Char( + compute='get_display_name' + ) + + @api.multi + @api.depends('child_ids') + def get_display_name(self): + def get_names(cat): + """ Return the list [cat.name, cat.parent_id.name, ...] """ + res = [] + while cat: + res.append(cat.name) + cat = cat.parent_id + return res + for cat in self: + cat.display_name = " / ".join(reversed(get_names(cat))) + + @api.multi + def name_get(self): + result = [] + for record in self: + result.append((record.id, record.display_name)) + return result + + @api.one + @api.depends('child_ids') + def get_count_subcategories(self): + self.count_subcategories = len(self.child_ids) + self.count_suggested_subcategories = len(self.child_ids.filtered( + lambda x: x.count_suggested_subcategories_modules)) + + @api.multi + def get_subcategories_modules(self): + self.ensure_one() + return self.env['adhoc.module.module'].search([ + ('adhoc_category_id', 'child_of', self.id)]) + + @api.multi + def get_suggested_subcategories_modules(self): + self.ensure_one() + return self.env['adhoc.module.module'].search([ + ('adhoc_category_id', 'child_of', self.id), + ('ignored', '=', False), + ('state', '=', 'uninstalled'), + ]) + + # @api.model + # def search_count_subcategories_modules(self, operator, value): + # sub_modules = self.get_suggested_subcategories_modules() + # if operator == 'like': + # operator = 'ilike' + # return [('name', operator, value)] + + @api.one + def get_count_subcategories_modules(self): + self.count_suggested_subcategories_modules = len( + self.get_suggested_subcategories_modules()) + self.count_subcategories_modules = len( + self.get_subcategories_modules()) + + @api.one + @api.depends('module_ids') + def get_count_modules(self): + self.count_modules = len(self.module_ids) + self.count_suggested_modules = len(self.module_ids.filtered( + lambda x: x.state != 'uninstalled' and not x.ignored)) + + # @api.depends('state') + @api.one + def get_color(self): + color = 4 + # if self.state == 'draft': + # color = 7 + # elif self.state == 'cancel': + # color = 1 + # elif self.state == 'inactive': + # color = 3 + # if self.overall_state != 'ok': + # color = 2 + self.color = color + + @api.multi + def action_subcategories(self): + self.ensure_one() + action = self.env['ir.model.data'].xmlid_to_object( + 'adhoc_modules.action_adhoc_module_category') + + if not action: + return False + res = action.read()[0] + res['context'] = { + 'search_default_parent_id': self.id, + } + return res + + @api.multi + def action_modules(self): + self.ensure_one() + action = self.env['ir.model.data'].xmlid_to_object( + 'adhoc_modules.action_adhoc_module_module') + + if not action: + return False + res = action.read()[0] + res['domain'] = [('adhoc_category_id', '=', self.id)] + res['context'] = { + 'search_default_not_ignored': 1, + 'search_default_state': 'uninstalled', + } + return res + + @api.multi + def action_subcategories_modules(self): + self.ensure_one() + action = self.env['ir.model.data'].xmlid_to_object( + 'adhoc_modules.action_adhoc_module_module') + + if not action: + return False + res = action.read()[0] + modules = self.get_subcategories_modules() + res['domain'] = [('id', 'in', modules.ids)] + res['context'] = { + 'search_default_not_ignored': 1, + 'search_default_state': 'uninstalled', + 'search_default_group_by_adhoc_category': 1 + } + return res diff --git a/adhoc_modules/models/adhoc_module_dependency.py b/adhoc_modules/models/adhoc_module_dependency.py new file mode 100644 index 0000000..6011d99 --- /dev/null +++ b/adhoc_modules/models/adhoc_module_dependency.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api + +DEP_STATES = [ + ('uninstallable', 'Uninstallable'), + ('uninstalled', 'Not Installed'), + ('installed', 'Installed'), + ('to upgrade', 'To be upgraded'), + ('to remove', 'To be removed'), + ('to install', 'To be installed'), + ('unknown', 'Unknown'), +] + + +class AdhocModuleDependency(models.Model): + _name = "adhoc.module.dependency" + _description = "Module dependency" + + # the dependency name + name = fields.Char( + index=True + ) + # the module that depends on it + module_id = fields.Many2one( + 'adhoc.module.module', + 'Module', + ondelete='cascade', + auto_join=True, + ) + # the module corresponding to the dependency, and its status + depend_id = fields.Many2one( + 'adhoc.module.module', + 'Dependency', + compute='_compute_depend' + ) + state = fields.Selection( + DEP_STATES, + string='Status', + compute='_compute_depend' + ) + + @api.one + @api.depends('name') + def _compute_depend(self): + mod = self.env['adhoc.module.module'].search([ + ('name', '=', self.name), + ('repository_id.branch', '=', self.module_id.repository_id.branch), + ], limit=1) + self.depend_id = mod + self.state = self.depend_id.state or 'unknown' diff --git a/adhoc_modules/models/adhoc_module_repository.py b/adhoc_modules/models/adhoc_module_repository.py new file mode 100644 index 0000000..aee99a7 --- /dev/null +++ b/adhoc_modules/models/adhoc_module_repository.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api, modules, tools +from openerp.modules.module import adapt_version +from openerp.exceptions import Warning +from octohub.connection import Connection +from openerp.tools.parse_version import parse_version +import base64 +import logging +import itertools +import os + +_logger = logging.getLogger(__name__) +MANIFEST = '__openerp__.py' +README = ['README.rst', 'README.md', 'README.txt'] + + +def load_information_from_contents( + manifest_content, readme_content=False, index_content=False): + """ + :param module: The name of the module (sale, purchase, ...) + :param mod_path: Physical path of module, if not providedThe name of the + module (sale, purchase, ...) + """ + + # default values for descriptor + info = { + 'application': False, + 'author': '', + 'auto_install': False, + 'category': 'Uncategorized', + 'depends': [], + 'description': '', + # 'icon': get_module_icon(module), + 'installable': True, + 'license': 'AGPL-3', + 'post_load': None, + 'version': '1.0', + 'web': False, + 'website': '', + 'sequence': 100, + 'summary': '', + } + info.update(itertools.izip( + 'depends data demo test init_xml update_xml demo_xml'.split(), + iter(list, None))) + try: + info.update(eval(manifest_content)) + except: + _logger.warning('could not ') + return {} + # if not info.get('description'): + # readme_path = [opj(mod_path, x) for x in README + # if os.path.isfile(opj(mod_path, x))] + # if readme_path: + # readme_text = tools.file_open(readme_path[0]).read() + # info['description'] = readme_text + + if 'active' in info: + # 'active' has been renamed 'auto_install' + info['auto_install'] = info['active'] + + info['version'] = adapt_version(info['version']) + return info + + +class AdhocModuleRepository(models.Model): + _name = 'adhoc.module.repository' + + user = fields.Char( + 'User or Organization', + required=True, + help='eg. "ingadhoc"', + ) + subdirectory = fields.Char( + 'Subdirectory', + help='For eg. "addons"', + ) + name = fields.Char( + 'Repository Name', + required=True, + help='eg. "product"', + ) + branch = fields.Selection( + [('8.0', '8.0'), ('9.0', '9.0')], + 'Branch / Odoo Version', + required=True, + ) + module_ids = fields.One2many( + 'adhoc.module.module', + 'repository_id', + 'Modules', + ) + token = fields.Char( + help='If no token configured, we will try to use a general one setted ' + 'as "github.token" parameter, if none configured, we try connecting ' + 'without token' + ) + auto_update = fields.Boolean( + default=True, + ) + sequence = fields.Integer( + string='Sequence', + default=10, + ) + + @api.multi + def get_token(self): + self.ensure_one() + token = self.token + if not token: + token = self.env['ir.config_parameter'].get_param( + 'github.token') or '' + return token + + @api.multi + def get_connection(self): + self.ensure_one() + token = self.get_token() + return Connection(token) + + @api.multi + def get_modules_paths(self): + """return name of remote modules""" + response = self.read_remote_path(self.subdirectory or '') + paths = [x['path'] for x in response.parsed if x['type'] == 'dir'] + _logger.info('Readed paths %s' % paths) + return paths + + @api.multi + def read_remote_path(self, path=False): + _logger.info('Reading data from remote path %s' % path) + conn = self.get_connection() + # obtener directorios + uri = "/repos/%s/%s/contents/%s" % ( + self.name, self.user, path or '') + try: + response = conn.send( + 'GET', uri, params={'ref': self.branch}) + except Exception, ResponseError: + raise Warning( + 'Could not get modules for:\n' + '* Repository: %s\n' + '* URI: %s\n' + '* Branch: %s\n' + '* Token: %s\n\n' + 'This is what we get:%s' % ( + self.name, uri, self.branch, + self.get_token(), ResponseError)) + return response + + @api.model + def get_module_info(self, name): + info = {} + try: + response = self.read_remote_path("%s/__openerp__.py" % name) + encoded_content = response.parsed['content'] + info = load_information_from_contents( + base64.b64decode(encoded_content)) + except Exception: + _logger.debug('Error when trying to fetch informations for ' + 'module %s', name, exc_info=True) + return info + + @api.multi + def get_module_vals(self, info): + self.ensure_one() + return { + 'description': info.get('description', ''), + 'shortdesc': info.get('name', ''), + 'author': info.get('author', 'Unknown'), + 'maintainer': info.get('maintainer', False), + 'contributors': ', '.join(info.get('contributors', [])) or False, + 'website': info.get('website', ''), + 'license': info.get('license', 'AGPL-3'), + 'sequence': info.get('sequence', 100), + 'application': info.get('application', False), + 'auto_install': info.get('auto_install', False), + 'icon': info.get('icon', False), + 'summary': info.get('summary', ''), + } + + @api.multi + def scan_repository(self): + self.ensure_one() + res = [0, 0] # [update, add] + + default_version = modules.adapt_version('1.0') + + # iterate through detected modules and update/create them in db + for module_path in self.get_modules_paths(): + # sacamos la ultima parte del path como nombre del modulo + mod_name = os.path.basename(module_path) + # search for modules of same name an odoo version + mod = self.env['adhoc.module.module'].search([ + ('name', '=', mod_name), + ('repository_id.branch', '=', self.branch)], limit=1) + module_info = self.get_module_info(module_path) + values = self.get_module_vals(module_info) + + if mod: + _logger.info('Updating data for module %s' % mod_name) + if mod.repository_id.id != self.id: + raise Warning( + 'Module already exist in other repository') + updated_values = {} + for key in values: + old = getattr(mod, key) + updated = isinstance( + values[key], basestring) and tools.ustr( + values[key]) or values[key] + if (old or updated) and updated != old: + updated_values[key] = values[key] + if module_info.get( + 'installable', True) and mod.state == 'uninstallable': + updated_values['state'] = 'uninstalled' + if parse_version(module_info.get( + 'version', default_version)) > parse_version( + mod.latest_version or default_version): + res[0] += 1 + if updated_values: + mod.write(updated_values) + else: + _logger.info('Creating new module %s' % mod_name) + # if not installable, we dont upload + if not module_info or not module_info.get( + 'installable', True): + continue + mod = mod.create(dict( + name=mod_name, state='uninstalled', + repository_id=self.id, **values)) + res[1] += 1 + mod._update_dependencies(module_info.get('depends', [])) + return res diff --git a/adhoc_modules/octohub/__init__.py b/adhoc_modules/octohub/__init__.py new file mode 100644 index 0000000..4ac03a0 --- /dev/null +++ b/adhoc_modules/octohub/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +__version__ = '0.1' +__useragent__ = 'octohub/%s' % __version__ + diff --git a/adhoc_modules/octohub/connection.py b/adhoc_modules/octohub/connection.py new file mode 100644 index 0000000..07f3f7e --- /dev/null +++ b/adhoc_modules/octohub/connection.py @@ -0,0 +1,75 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +import requests + +from octohub import __useragent__ +from octohub.response import parse_response + +class Pager(object): + def __init__(self, conn, uri, params, max_pages=0): + """Iterator object handling pagination of Connection.send (method: GET) + conn (octohub.Connection): Connection object + uri (str): Request URI (e.g., /user/issues) + params (dict): Parameters to include in request + max_pages (int): Maximum amount of pages to get (0 for all) + """ + self.conn = conn + self.uri = uri + self.params = params + self.max_pages = max_pages + self.count = 0 + + def __iter__(self): + while True: + self.count += 1 + response = self.conn.send('GET', self.uri, self.params) + yield response + + if self.count == self.max_pages: + break + + if not 'next' in response.parsed_link.keys(): + break + + # Parsed link is absolute. Connection wants a relative link, + # so remove protocol and GitHub endpoint for the pagination URI. + m = re.match(self.conn.endpoint + '(.*)', response.parsed_link.next.uri) + self.uri = m.groups()[0] + self.params = response.parsed_link.next.params + +class Connection(object): + def __init__(self, token=None): + """OctoHub connection + token (str): GitHub Token (anonymous if not provided) + """ + self.endpoint = 'https://api.github.com' + self.headers = {'User-Agent': __useragent__} + + if token: + self.headers['Authorization'] = 'token %s' % token + + def send(self, method, uri, params={}, data=None): + """Prepare and send request + method (str): Request HTTP method (e.g., GET, POST, DELETE, ...) + uri (str): Request URI (e.g., /user/issues) + params (dict): Parameters to include in request + data (str | file type object): data to include in request + + returns: requests.Response object, including: + response.parsed (AttrDict): parsed response when applicable + response.parsed_link (AttrDict): parsed header link when applicable + http://docs.python-requests.org/en/latest/api/#requests.Response + """ + url = self.endpoint + uri + kwargs = {'headers': self.headers, 'params': params, 'data': data} + response = requests.request(method, url, **kwargs) + + return parse_response(response) + diff --git a/adhoc_modules/octohub/exceptions.py b/adhoc_modules/octohub/exceptions.py new file mode 100644 index 0000000..ccb4f95 --- /dev/null +++ b/adhoc_modules/octohub/exceptions.py @@ -0,0 +1,26 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +import simplejson as json + +class ResponseError(Exception): + """Accessible attributes: error + error (AttrDict): Parsed error response + """ + def __init__(self, error): + Exception.__init__(self, error) + self.error = error + + def __str__(self): + return json.dumps(self.error, indent=1) + + +class OctoHubError(Exception): + pass + diff --git a/adhoc_modules/octohub/response.py b/adhoc_modules/octohub/response.py new file mode 100644 index 0000000..b2ddc42 --- /dev/null +++ b/adhoc_modules/octohub/response.py @@ -0,0 +1,103 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +import re + +from octohub.utils import AttrDict, get_logger +from octohub.exceptions import ResponseError, OctoHubError + +log = get_logger('response') + +def _get_content_type(response): + """Parse response and return content-type""" + try: + content_type = response.headers['Content-Type'] + content_type = content_type.split(';', 1)[0] + except KeyError: + content_type = None + + return content_type + +def _parse_link(header_link): + """Parse header link and return AttrDict[rel].uri|params""" + links = AttrDict() + for s in header_link.split(','): + link = AttrDict() + + m = re.match('<(.*)\?(.*)>', s.split(';')[0].strip()) + link.uri = m.groups()[0] + link.params = {} + for kv in m.groups()[1].split('&'): + key, value = kv.split('=') + link.params[key] = value + + m = re.match('rel="(.*)"', s.split(';')[1].strip()) + rel = m.groups()[0] + + links[rel] = link + log.debug('link-%s-page: %s' % (rel, link.params['page'])) + + return links + +def parse_element(el): + """Parse el recursively, replacing dicts with AttrDicts representation""" + if type(el) == dict: + el_dict = AttrDict() + for key, val in el.items(): + el_dict[key] = parse_element(val) + + return el_dict + + elif type(el) == list: + el_list = [] + for l in el: + el_list.append(parse_element(l)) + + return el_list + + else: + return el + +def parse_response(response): + """Parse request response object and raise exception on response error code + response (requests.Response object): + + returns: requests.Response object, including: + response.parsed (AttrDict) + response.parsed_link (AttrDict) + http://docs.python-requests.org/en/latest/api/#requests.Response + """ + response.parsed = AttrDict() + response.parsed_link = AttrDict() + + if 'link' in response.headers.keys(): + response.parsed_link = _parse_link(response.headers['link']) + + headers = ['status', 'x-ratelimit-limit', 'x-ratelimit-remaining'] + for header in headers: + if header in response.headers.keys(): + log.info('%s: %s' % (header, response.headers[header])) + + content_type = _get_content_type(response) + + if content_type == 'application/json': + json = response.json + if callable(json): + json = json() + response.parsed = parse_element(json) + else: + if not response.status_code == 204: + raise OctoHubError('unhandled content_type: %s' % content_type) + + if not response.status_code in (200, 201, 204): + raise ResponseError(response.parsed) + + return response + + diff --git a/adhoc_modules/octohub/utils.py b/adhoc_modules/octohub/utils.py new file mode 100644 index 0000000..e168472 --- /dev/null +++ b/adhoc_modules/octohub/utils.py @@ -0,0 +1,40 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +import os +import logging + +class AttrDict(dict): + """Attribute Dictionary (set and access attributes 'pythonically')""" + def __getattr__(self, name): + if name in self: + return self[name] + raise AttributeError('no such attribute: %s' % name) + + def __setattr__(self, name, val): + self[name] = val + +def get_logger(name, level=None): + """Returns logging handler based on name and level (stderr) + name (str): name of logging handler + level (str): see logging.LEVEL + """ + logger = logging.getLogger(name) + + if not logger.handlers: + stderr = logging.StreamHandler() + stderr.setFormatter(logging.Formatter( + '%(levelname)s [%(name)s]: %(message)s')) + logger.addHandler(stderr) + + level = level if level else os.environ.get('OCTOHUB_LOGLEVEL', 'CRITICAL') + logger.setLevel(getattr(logging, level)) + + return logger + diff --git a/adhoc_modules/security/ir.model.access.csv b/adhoc_modules/security/ir.model.access.csv new file mode 100644 index 0000000..8d97c03 --- /dev/null +++ b/adhoc_modules/security/ir.model.access.csv @@ -0,0 +1,9 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_adhoc_module_module_all,access_adhoc_module_module_all,model_adhoc_module_module,base.group_system,1,1,1,1 +access_adhoc_module_module_config,access_adhoc_module_module_config,model_adhoc_module_module,,1,0,0,0 +access_adhoc_module_category_all,access_adhoc_module_category_all,model_adhoc_module_category,base.group_system,1,1,1,1 +access_adhoc_module_category_config,access_adhoc_module_category_config,model_adhoc_module_category,,1,0,0,0 +access_adhoc_module_repository_all,access_adhoc_module_repository_all,model_adhoc_module_repository,base.group_system,1,1,1,1 +access_adhoc_module_repository_config,access_adhoc_module_repository_config,model_adhoc_module_repository,,1,0,0,0 +access_adhoc_module_dependency_all,access_adhoc_module_dependency_all,model_adhoc_module_dependency,base.group_system,1,1,1,1 +access_adhoc_module_dependency_config,access_adhoc_module_dependency_config,model_adhoc_module_dependency,,1,0,0,0 diff --git a/adhoc_modules/views/adhoc_module_category_view.xml b/adhoc_modules/views/adhoc_module_category_view.xml new file mode 100644 index 0000000..182b9ab --- /dev/null +++ b/adhoc_modules/views/adhoc_module_category_view.xml @@ -0,0 +1,117 @@ + + + + + + + adhoc.module.category.kanban + adhoc.module.category + + + + + + + + + + + + + + + + + + + adhoc.module.category.search + adhoc.module.category + + + + + + + + + + + adhoc.module.category.tree + adhoc.module.category + + + + + + + + + + + adhoc.module.category.form + adhoc.module.category + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + ADHOC Categories + adhoc.module.category + form + kanban,tree,form + {'search_default_root_categories': 1} + + + + +
+
\ No newline at end of file diff --git a/adhoc_modules/views/adhoc_module_repository_view.xml b/adhoc_modules/views/adhoc_module_repository_view.xml new file mode 100644 index 0000000..0d1ce4c --- /dev/null +++ b/adhoc_modules/views/adhoc_module_repository_view.xml @@ -0,0 +1,69 @@ + + + + + + + adhoc.module.repository.form + adhoc.module.repository + +
+
+
+ + + + + + + + + + + +
+
+
+ + + + adhoc.module.repository.search + adhoc.module.repository + + + + + + + + + + + + + + + + adhoc.module.repository.tree + adhoc.module.repository + + + + + + + + + + + ADHOC Repositories + adhoc.module.repository + form + tree,form + + + + +
+
\ No newline at end of file diff --git a/adhoc_modules/views/adhoc_module_view.xml b/adhoc_modules/views/adhoc_module_view.xml new file mode 100644 index 0000000..e0a0dab --- /dev/null +++ b/adhoc_modules/views/adhoc_module_view.xml @@ -0,0 +1,126 @@ + + + + + + + adhoc.module.module.search + adhoc.module.module + + + + + + + + + + + + + + + + + + + + + + + + + + adhoc.module.module.kanban + adhoc.module.module + + primary + + + + + + + + + + + + + + + adhoc.module.module.tree + adhoc.module.module + + primary + + + top + + + + 1 + + + 1 + + + + + + + + + + + + + + + + ADHOC Modules + adhoc.module.module + form + tree,kanban,form + {'search_default_un_categorized': 1, 'search_default_conf_visibility': 'normal'} + + + + + + \ No newline at end of file diff --git a/adhoc_modules_server/README.rst b/adhoc_modules_server/README.rst new file mode 100644 index 0000000..2d28a16 --- /dev/null +++ b/adhoc_modules_server/README.rst @@ -0,0 +1,27 @@ +TODO +==== + +En clientes: + Agregamos en categorías un campo booleano "Contratado" + +En nuestra bd: + los productos tienen un link a "categoría de módulos" + Entonces al agregar un producto a una bd, se actualiza la bd del cliente seteando que esa categoría está comprada. + + +* Evaluar si es mejor entrar a la kanban de categorías con un default_group_by="parent_id" para que permita drag and drop y de un solo vistazo vez todas las categorías, tipo dashboard. +* implementar suggested subcategories +* Agregar modules required para categorías? y que solo aparezcan si dichos modulos estan instalados) +* Agregamos atributos a los módulos tipo sugerido, normal, skipped. Luego en las kanban de categorías, si no hay ninguno a revisar (todos skipped o instalados) lo mostramos de un color, como para saber que terminaste una configuración +* De hecho solo mostramos de manera predeterminada (por filtro) categorías recomendadas y módulos a revisae +* Implementar sacar descripciones de readme o index, tampoco es tan necesario +* agregar vista particular para configuracion que muestre los desconfigurados +* mejorar button_install_cancel para que desmarque los padres +* Llevar todo lo que podamos al modulo de clientes, y luego que este dependa de aquel + +* Vincular documentos o temas a un módulo para que luego de instalarlo al client lo lleve a la documentación correspondiente +* Traer icono, aunque es renigue sin sentido tal vez +* Agregar version requerida en los modulos o algo por el estilo para que se actualice automáticamente +* Cron para actualizar repos (solo los auto_update) +* Mostrar los que faltan asignar +* Sacar warning de "InsecurePlatformWarning: A true SSLContext object is not available." diff --git a/adhoc_modules_server/__init__.py b/adhoc_modules_server/__init__.py new file mode 100644 index 0000000..c6a3ab6 --- /dev/null +++ b/adhoc_modules_server/__init__.py @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- +from . import models +# from . import controllers +# from . import wizard diff --git a/adhoc_modules_server/__openerp__.py b/adhoc_modules_server/__openerp__.py new file mode 100644 index 0000000..898ebb1 --- /dev/null +++ b/adhoc_modules_server/__openerp__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2015 ADHOC SA (http://www.adhoc.com.ar) +# All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + "name": "ADHOC Modules", + "version": "8.0.0.0.0", + 'author': 'ADHOC SA', + 'website': 'www.adhoc.com.ar', + 'license': 'AGPL-3', + 'depends': [ + 'adhoc_modules', + 'mass_editing', + ], + 'external_dependencies': { + 'python': ['octuhub'] + }, + 'data': [ + 'views/adhoc_module_repository_view.xml', + 'views/adhoc_module_category_view.xml', + 'views/adhoc_module_view.xml', + 'security/ir.model.access.csv', + 'data/mass_editting_data.xml', + ], + 'demo': [], + 'test': [], + 'installable': True, + 'active': False, + 'auto_install': True +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/adhoc_modules_server/data/mass_editting_data.xml b/adhoc_modules_server/data/mass_editting_data.xml new file mode 100644 index 0000000..c2e4fdc --- /dev/null +++ b/adhoc_modules_server/data/mass_editting_data.xml @@ -0,0 +1,13 @@ + + + + + Asignar Categoría + + + + + + + + diff --git a/adhoc_modules_server/models/__init__.py b/adhoc_modules_server/models/__init__.py new file mode 100644 index 0000000..0c67ef3 --- /dev/null +++ b/adhoc_modules_server/models/__init__.py @@ -0,0 +1,5 @@ +# -*- encoding: utf-8 -*- +from . import adhoc_module_repository +from . import adhoc_module_dependency +from . import adhoc_module_category +from . import adhoc_module diff --git a/adhoc_modules_server/models/adhoc_module.py b/adhoc_modules_server/models/adhoc_module.py new file mode 100644 index 0000000..bdb2a20 --- /dev/null +++ b/adhoc_modules_server/models/adhoc_module.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api +# from openerp.exceptions import Warning +import logging + +_logger = logging.getLogger(__name__) + + +class AdhocModuleModule(models.Model): + _inherit = 'ir.module.module' + _name = 'adhoc.module.module' + + repository_id = fields.Many2one( + 'adhoc.module.repository', + 'Repository', + ondelete='cascade', + required=True, + auto_join=True, + ) + dependencies_id = fields.One2many( + 'adhoc.module.dependency', + 'module_id', + 'Dependencies', + readonly=True, + ) + + @api.model + def create(self, vals): + # ir module modifies create, we need default one + create_original = models.BaseModel.create + module = create_original(self, vals) + module_metadata = { + 'name': 'module_%s_%s' % ( + vals['name'], + module.repository_id.branch.replace('.', '_')), + 'model': self._name, + 'module': 'adhoc_module', + 'res_id': module.id, + 'noupdate': True, + } + self.env['ir.model.data'].create(module_metadata) + return module + + @api.multi + def _update_dependencies(self, depends=None): + self.ensure_one() + if depends is None: + depends = [] + existing = set(x.name for x in self.dependencies_id) + needed = set(depends) + for dep in (needed - existing): + self._cr.execute( + 'INSERT INTO adhoc_module_dependency (module_id, name) ' + 'values (%s, %s)', (self.id, dep)) + for dep in (existing - needed): + self._cr.execute( + 'DELETE FROM adhoc_module_dependency WHERE module_id = %s ' + 'and name = %s', (self.id, dep)) + self.invalidate_cache(['dependencies_id']) diff --git a/adhoc_modules_server/models/adhoc_module_category.py b/adhoc_modules_server/models/adhoc_module_category.py new file mode 100644 index 0000000..196b1c9 --- /dev/null +++ b/adhoc_modules_server/models/adhoc_module_category.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api +import logging + +_logger = logging.getLogger(__name__) + + +class AdhocModuleCategory(models.Model): + _name = 'adhoc.module.category' + # _parent_store = True + _order = 'sequence' + # _rec_name = 'display_name' + + name = fields.Char( + required=True, + ) + count_modules = fields.Integer( + string='# Modules', + compute='get_count_modules', + ) + count_suggested_modules = fields.Integer( + string='# Suggested Modules', + compute='get_count_modules', + ) + count_subcategories_modules = fields.Integer( + string='# Subcategories Modules', + compute='get_count_subcategories_modules', + ) + count_suggested_subcategories_modules = fields.Integer( + string='# Suggested Subcategories Modules', + compute='get_count_subcategories_modules', + ) + count_subcategories = fields.Integer( + string='# Subcategories', + compute='get_count_subcategories', + ) + count_suggested_subcategories = fields.Integer( + string='# Suggested Subcategories', + compute='get_count_subcategories', + ) + color = fields.Integer( + string='Color Index', + compute='get_color', + ) + parent_id = fields.Many2one( + 'adhoc.module.category', + 'Parent Category', + select=True, + ) + child_ids = fields.One2many( + 'adhoc.module.category', + 'parent_id', + 'Child Categories' + ) + module_ids = fields.One2many( + 'adhoc.module.module', + 'adhoc_category_id', + 'Modules' + ) + description = fields.Text( + ) + sequence = fields.Integer( + 'Sequence', + default=10, + ) + display_name = fields.Char( + compute='get_display_name' + ) + + @api.multi + @api.depends('child_ids') + def get_display_name(self): + def get_names(cat): + """ Return the list [cat.name, cat.parent_id.name, ...] """ + res = [] + while cat: + res.append(cat.name) + cat = cat.parent_id + return res + for cat in self: + cat.display_name = " / ".join(reversed(get_names(cat))) + + @api.multi + def name_get(self): + result = [] + for record in self: + result.append((record.id, record.display_name)) + return result + + @api.one + @api.depends('child_ids') + def get_count_subcategories(self): + self.count_subcategories = len(self.child_ids) + self.count_suggested_subcategories = len(self.child_ids.filtered( + lambda x: x.count_suggested_subcategories_modules)) + + @api.multi + def get_subcategories_modules(self): + self.ensure_one() + return self.env['adhoc.module.module'].search([ + ('adhoc_category_id', 'child_of', self.id)]) + + @api.multi + def get_suggested_subcategories_modules(self): + self.ensure_one() + return self.env['adhoc.module.module'].search([ + ('adhoc_category_id', 'child_of', self.id), + ('ignored', '=', False), + ('state', '=', 'uninstalled'), + ]) + + # @api.model + # def search_count_subcategories_modules(self, operator, value): + # sub_modules = self.get_suggested_subcategories_modules() + # if operator == 'like': + # operator = 'ilike' + # return [('name', operator, value)] + + @api.one + def get_count_subcategories_modules(self): + self.count_suggested_subcategories_modules = len( + self.get_suggested_subcategories_modules()) + self.count_subcategories_modules = len( + self.get_subcategories_modules()) + + @api.one + @api.depends('module_ids') + def get_count_modules(self): + self.count_modules = len(self.module_ids) + self.count_suggested_modules = len(self.module_ids.filtered( + lambda x: x.state != 'uninstalled' and not x.ignored)) + + # @api.depends('state') + @api.one + def get_color(self): + color = 4 + # if self.state == 'draft': + # color = 7 + # elif self.state == 'cancel': + # color = 1 + # elif self.state == 'inactive': + # color = 3 + # if self.overall_state != 'ok': + # color = 2 + self.color = color + + @api.multi + def action_subcategories(self): + self.ensure_one() + action = self.env['ir.model.data'].xmlid_to_object( + 'adhoc_modules.action_adhoc_module_category') + + if not action: + return False + res = action.read()[0] + res['context'] = { + 'search_default_parent_id': self.id, + } + return res + + @api.multi + def action_modules(self): + self.ensure_one() + action = self.env['ir.model.data'].xmlid_to_object( + 'adhoc_modules.action_adhoc_module_module') + + if not action: + return False + res = action.read()[0] + res['domain'] = [('adhoc_category_id', '=', self.id)] + res['context'] = { + 'search_default_not_ignored': 1, + 'search_default_state': 'uninstalled', + } + return res + + @api.multi + def action_subcategories_modules(self): + self.ensure_one() + action = self.env['ir.model.data'].xmlid_to_object( + 'adhoc_modules.action_adhoc_module_module') + + if not action: + return False + res = action.read()[0] + modules = self.get_subcategories_modules() + res['domain'] = [('id', 'in', modules.ids)] + res['context'] = { + 'search_default_not_ignored': 1, + 'search_default_state': 'uninstalled', + 'search_default_group_by_adhoc_category': 1 + } + return res diff --git a/adhoc_modules_server/models/adhoc_module_dependency.py b/adhoc_modules_server/models/adhoc_module_dependency.py new file mode 100644 index 0000000..6011d99 --- /dev/null +++ b/adhoc_modules_server/models/adhoc_module_dependency.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api + +DEP_STATES = [ + ('uninstallable', 'Uninstallable'), + ('uninstalled', 'Not Installed'), + ('installed', 'Installed'), + ('to upgrade', 'To be upgraded'), + ('to remove', 'To be removed'), + ('to install', 'To be installed'), + ('unknown', 'Unknown'), +] + + +class AdhocModuleDependency(models.Model): + _name = "adhoc.module.dependency" + _description = "Module dependency" + + # the dependency name + name = fields.Char( + index=True + ) + # the module that depends on it + module_id = fields.Many2one( + 'adhoc.module.module', + 'Module', + ondelete='cascade', + auto_join=True, + ) + # the module corresponding to the dependency, and its status + depend_id = fields.Many2one( + 'adhoc.module.module', + 'Dependency', + compute='_compute_depend' + ) + state = fields.Selection( + DEP_STATES, + string='Status', + compute='_compute_depend' + ) + + @api.one + @api.depends('name') + def _compute_depend(self): + mod = self.env['adhoc.module.module'].search([ + ('name', '=', self.name), + ('repository_id.branch', '=', self.module_id.repository_id.branch), + ], limit=1) + self.depend_id = mod + self.state = self.depend_id.state or 'unknown' diff --git a/adhoc_modules_server/models/adhoc_module_repository.py b/adhoc_modules_server/models/adhoc_module_repository.py new file mode 100644 index 0000000..aee99a7 --- /dev/null +++ b/adhoc_modules_server/models/adhoc_module_repository.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api, modules, tools +from openerp.modules.module import adapt_version +from openerp.exceptions import Warning +from octohub.connection import Connection +from openerp.tools.parse_version import parse_version +import base64 +import logging +import itertools +import os + +_logger = logging.getLogger(__name__) +MANIFEST = '__openerp__.py' +README = ['README.rst', 'README.md', 'README.txt'] + + +def load_information_from_contents( + manifest_content, readme_content=False, index_content=False): + """ + :param module: The name of the module (sale, purchase, ...) + :param mod_path: Physical path of module, if not providedThe name of the + module (sale, purchase, ...) + """ + + # default values for descriptor + info = { + 'application': False, + 'author': '', + 'auto_install': False, + 'category': 'Uncategorized', + 'depends': [], + 'description': '', + # 'icon': get_module_icon(module), + 'installable': True, + 'license': 'AGPL-3', + 'post_load': None, + 'version': '1.0', + 'web': False, + 'website': '', + 'sequence': 100, + 'summary': '', + } + info.update(itertools.izip( + 'depends data demo test init_xml update_xml demo_xml'.split(), + iter(list, None))) + try: + info.update(eval(manifest_content)) + except: + _logger.warning('could not ') + return {} + # if not info.get('description'): + # readme_path = [opj(mod_path, x) for x in README + # if os.path.isfile(opj(mod_path, x))] + # if readme_path: + # readme_text = tools.file_open(readme_path[0]).read() + # info['description'] = readme_text + + if 'active' in info: + # 'active' has been renamed 'auto_install' + info['auto_install'] = info['active'] + + info['version'] = adapt_version(info['version']) + return info + + +class AdhocModuleRepository(models.Model): + _name = 'adhoc.module.repository' + + user = fields.Char( + 'User or Organization', + required=True, + help='eg. "ingadhoc"', + ) + subdirectory = fields.Char( + 'Subdirectory', + help='For eg. "addons"', + ) + name = fields.Char( + 'Repository Name', + required=True, + help='eg. "product"', + ) + branch = fields.Selection( + [('8.0', '8.0'), ('9.0', '9.0')], + 'Branch / Odoo Version', + required=True, + ) + module_ids = fields.One2many( + 'adhoc.module.module', + 'repository_id', + 'Modules', + ) + token = fields.Char( + help='If no token configured, we will try to use a general one setted ' + 'as "github.token" parameter, if none configured, we try connecting ' + 'without token' + ) + auto_update = fields.Boolean( + default=True, + ) + sequence = fields.Integer( + string='Sequence', + default=10, + ) + + @api.multi + def get_token(self): + self.ensure_one() + token = self.token + if not token: + token = self.env['ir.config_parameter'].get_param( + 'github.token') or '' + return token + + @api.multi + def get_connection(self): + self.ensure_one() + token = self.get_token() + return Connection(token) + + @api.multi + def get_modules_paths(self): + """return name of remote modules""" + response = self.read_remote_path(self.subdirectory or '') + paths = [x['path'] for x in response.parsed if x['type'] == 'dir'] + _logger.info('Readed paths %s' % paths) + return paths + + @api.multi + def read_remote_path(self, path=False): + _logger.info('Reading data from remote path %s' % path) + conn = self.get_connection() + # obtener directorios + uri = "/repos/%s/%s/contents/%s" % ( + self.name, self.user, path or '') + try: + response = conn.send( + 'GET', uri, params={'ref': self.branch}) + except Exception, ResponseError: + raise Warning( + 'Could not get modules for:\n' + '* Repository: %s\n' + '* URI: %s\n' + '* Branch: %s\n' + '* Token: %s\n\n' + 'This is what we get:%s' % ( + self.name, uri, self.branch, + self.get_token(), ResponseError)) + return response + + @api.model + def get_module_info(self, name): + info = {} + try: + response = self.read_remote_path("%s/__openerp__.py" % name) + encoded_content = response.parsed['content'] + info = load_information_from_contents( + base64.b64decode(encoded_content)) + except Exception: + _logger.debug('Error when trying to fetch informations for ' + 'module %s', name, exc_info=True) + return info + + @api.multi + def get_module_vals(self, info): + self.ensure_one() + return { + 'description': info.get('description', ''), + 'shortdesc': info.get('name', ''), + 'author': info.get('author', 'Unknown'), + 'maintainer': info.get('maintainer', False), + 'contributors': ', '.join(info.get('contributors', [])) or False, + 'website': info.get('website', ''), + 'license': info.get('license', 'AGPL-3'), + 'sequence': info.get('sequence', 100), + 'application': info.get('application', False), + 'auto_install': info.get('auto_install', False), + 'icon': info.get('icon', False), + 'summary': info.get('summary', ''), + } + + @api.multi + def scan_repository(self): + self.ensure_one() + res = [0, 0] # [update, add] + + default_version = modules.adapt_version('1.0') + + # iterate through detected modules and update/create them in db + for module_path in self.get_modules_paths(): + # sacamos la ultima parte del path como nombre del modulo + mod_name = os.path.basename(module_path) + # search for modules of same name an odoo version + mod = self.env['adhoc.module.module'].search([ + ('name', '=', mod_name), + ('repository_id.branch', '=', self.branch)], limit=1) + module_info = self.get_module_info(module_path) + values = self.get_module_vals(module_info) + + if mod: + _logger.info('Updating data for module %s' % mod_name) + if mod.repository_id.id != self.id: + raise Warning( + 'Module already exist in other repository') + updated_values = {} + for key in values: + old = getattr(mod, key) + updated = isinstance( + values[key], basestring) and tools.ustr( + values[key]) or values[key] + if (old or updated) and updated != old: + updated_values[key] = values[key] + if module_info.get( + 'installable', True) and mod.state == 'uninstallable': + updated_values['state'] = 'uninstalled' + if parse_version(module_info.get( + 'version', default_version)) > parse_version( + mod.latest_version or default_version): + res[0] += 1 + if updated_values: + mod.write(updated_values) + else: + _logger.info('Creating new module %s' % mod_name) + # if not installable, we dont upload + if not module_info or not module_info.get( + 'installable', True): + continue + mod = mod.create(dict( + name=mod_name, state='uninstalled', + repository_id=self.id, **values)) + res[1] += 1 + mod._update_dependencies(module_info.get('depends', [])) + return res diff --git a/adhoc_modules_server/octohub/__init__.py b/adhoc_modules_server/octohub/__init__.py new file mode 100644 index 0000000..4ac03a0 --- /dev/null +++ b/adhoc_modules_server/octohub/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +__version__ = '0.1' +__useragent__ = 'octohub/%s' % __version__ + diff --git a/adhoc_modules_server/octohub/connection.py b/adhoc_modules_server/octohub/connection.py new file mode 100644 index 0000000..07f3f7e --- /dev/null +++ b/adhoc_modules_server/octohub/connection.py @@ -0,0 +1,75 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +import requests + +from octohub import __useragent__ +from octohub.response import parse_response + +class Pager(object): + def __init__(self, conn, uri, params, max_pages=0): + """Iterator object handling pagination of Connection.send (method: GET) + conn (octohub.Connection): Connection object + uri (str): Request URI (e.g., /user/issues) + params (dict): Parameters to include in request + max_pages (int): Maximum amount of pages to get (0 for all) + """ + self.conn = conn + self.uri = uri + self.params = params + self.max_pages = max_pages + self.count = 0 + + def __iter__(self): + while True: + self.count += 1 + response = self.conn.send('GET', self.uri, self.params) + yield response + + if self.count == self.max_pages: + break + + if not 'next' in response.parsed_link.keys(): + break + + # Parsed link is absolute. Connection wants a relative link, + # so remove protocol and GitHub endpoint for the pagination URI. + m = re.match(self.conn.endpoint + '(.*)', response.parsed_link.next.uri) + self.uri = m.groups()[0] + self.params = response.parsed_link.next.params + +class Connection(object): + def __init__(self, token=None): + """OctoHub connection + token (str): GitHub Token (anonymous if not provided) + """ + self.endpoint = 'https://api.github.com' + self.headers = {'User-Agent': __useragent__} + + if token: + self.headers['Authorization'] = 'token %s' % token + + def send(self, method, uri, params={}, data=None): + """Prepare and send request + method (str): Request HTTP method (e.g., GET, POST, DELETE, ...) + uri (str): Request URI (e.g., /user/issues) + params (dict): Parameters to include in request + data (str | file type object): data to include in request + + returns: requests.Response object, including: + response.parsed (AttrDict): parsed response when applicable + response.parsed_link (AttrDict): parsed header link when applicable + http://docs.python-requests.org/en/latest/api/#requests.Response + """ + url = self.endpoint + uri + kwargs = {'headers': self.headers, 'params': params, 'data': data} + response = requests.request(method, url, **kwargs) + + return parse_response(response) + diff --git a/adhoc_modules_server/octohub/exceptions.py b/adhoc_modules_server/octohub/exceptions.py new file mode 100644 index 0000000..ccb4f95 --- /dev/null +++ b/adhoc_modules_server/octohub/exceptions.py @@ -0,0 +1,26 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +import simplejson as json + +class ResponseError(Exception): + """Accessible attributes: error + error (AttrDict): Parsed error response + """ + def __init__(self, error): + Exception.__init__(self, error) + self.error = error + + def __str__(self): + return json.dumps(self.error, indent=1) + + +class OctoHubError(Exception): + pass + diff --git a/adhoc_modules_server/octohub/response.py b/adhoc_modules_server/octohub/response.py new file mode 100644 index 0000000..b2ddc42 --- /dev/null +++ b/adhoc_modules_server/octohub/response.py @@ -0,0 +1,103 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +import re + +from octohub.utils import AttrDict, get_logger +from octohub.exceptions import ResponseError, OctoHubError + +log = get_logger('response') + +def _get_content_type(response): + """Parse response and return content-type""" + try: + content_type = response.headers['Content-Type'] + content_type = content_type.split(';', 1)[0] + except KeyError: + content_type = None + + return content_type + +def _parse_link(header_link): + """Parse header link and return AttrDict[rel].uri|params""" + links = AttrDict() + for s in header_link.split(','): + link = AttrDict() + + m = re.match('<(.*)\?(.*)>', s.split(';')[0].strip()) + link.uri = m.groups()[0] + link.params = {} + for kv in m.groups()[1].split('&'): + key, value = kv.split('=') + link.params[key] = value + + m = re.match('rel="(.*)"', s.split(';')[1].strip()) + rel = m.groups()[0] + + links[rel] = link + log.debug('link-%s-page: %s' % (rel, link.params['page'])) + + return links + +def parse_element(el): + """Parse el recursively, replacing dicts with AttrDicts representation""" + if type(el) == dict: + el_dict = AttrDict() + for key, val in el.items(): + el_dict[key] = parse_element(val) + + return el_dict + + elif type(el) == list: + el_list = [] + for l in el: + el_list.append(parse_element(l)) + + return el_list + + else: + return el + +def parse_response(response): + """Parse request response object and raise exception on response error code + response (requests.Response object): + + returns: requests.Response object, including: + response.parsed (AttrDict) + response.parsed_link (AttrDict) + http://docs.python-requests.org/en/latest/api/#requests.Response + """ + response.parsed = AttrDict() + response.parsed_link = AttrDict() + + if 'link' in response.headers.keys(): + response.parsed_link = _parse_link(response.headers['link']) + + headers = ['status', 'x-ratelimit-limit', 'x-ratelimit-remaining'] + for header in headers: + if header in response.headers.keys(): + log.info('%s: %s' % (header, response.headers[header])) + + content_type = _get_content_type(response) + + if content_type == 'application/json': + json = response.json + if callable(json): + json = json() + response.parsed = parse_element(json) + else: + if not response.status_code == 204: + raise OctoHubError('unhandled content_type: %s' % content_type) + + if not response.status_code in (200, 201, 204): + raise ResponseError(response.parsed) + + return response + + diff --git a/adhoc_modules_server/octohub/utils.py b/adhoc_modules_server/octohub/utils.py new file mode 100644 index 0000000..e168472 --- /dev/null +++ b/adhoc_modules_server/octohub/utils.py @@ -0,0 +1,40 @@ +# Copyright (c) 2013 Alon Swartz +# +# This file is part of OctoHub. +# +# OctoHub is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +import os +import logging + +class AttrDict(dict): + """Attribute Dictionary (set and access attributes 'pythonically')""" + def __getattr__(self, name): + if name in self: + return self[name] + raise AttributeError('no such attribute: %s' % name) + + def __setattr__(self, name, val): + self[name] = val + +def get_logger(name, level=None): + """Returns logging handler based on name and level (stderr) + name (str): name of logging handler + level (str): see logging.LEVEL + """ + logger = logging.getLogger(name) + + if not logger.handlers: + stderr = logging.StreamHandler() + stderr.setFormatter(logging.Formatter( + '%(levelname)s [%(name)s]: %(message)s')) + logger.addHandler(stderr) + + level = level if level else os.environ.get('OCTOHUB_LOGLEVEL', 'CRITICAL') + logger.setLevel(getattr(logging, level)) + + return logger + diff --git a/adhoc_modules_server/security/ir.model.access.csv b/adhoc_modules_server/security/ir.model.access.csv new file mode 100644 index 0000000..8d97c03 --- /dev/null +++ b/adhoc_modules_server/security/ir.model.access.csv @@ -0,0 +1,9 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_adhoc_module_module_all,access_adhoc_module_module_all,model_adhoc_module_module,base.group_system,1,1,1,1 +access_adhoc_module_module_config,access_adhoc_module_module_config,model_adhoc_module_module,,1,0,0,0 +access_adhoc_module_category_all,access_adhoc_module_category_all,model_adhoc_module_category,base.group_system,1,1,1,1 +access_adhoc_module_category_config,access_adhoc_module_category_config,model_adhoc_module_category,,1,0,0,0 +access_adhoc_module_repository_all,access_adhoc_module_repository_all,model_adhoc_module_repository,base.group_system,1,1,1,1 +access_adhoc_module_repository_config,access_adhoc_module_repository_config,model_adhoc_module_repository,,1,0,0,0 +access_adhoc_module_dependency_all,access_adhoc_module_dependency_all,model_adhoc_module_dependency,base.group_system,1,1,1,1 +access_adhoc_module_dependency_config,access_adhoc_module_dependency_config,model_adhoc_module_dependency,,1,0,0,0 diff --git a/adhoc_modules_server/views/adhoc_module_category_view.xml b/adhoc_modules_server/views/adhoc_module_category_view.xml new file mode 100644 index 0000000..182b9ab --- /dev/null +++ b/adhoc_modules_server/views/adhoc_module_category_view.xml @@ -0,0 +1,117 @@ + + + + + + + adhoc.module.category.kanban + adhoc.module.category + + + + + + + + + + + + + + + + + + + adhoc.module.category.search + adhoc.module.category + + + + + + + + + + + adhoc.module.category.tree + adhoc.module.category + + + + + + + + + + + adhoc.module.category.form + adhoc.module.category + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + ADHOC Categories + adhoc.module.category + form + kanban,tree,form + {'search_default_root_categories': 1} + + + + +
+
\ No newline at end of file diff --git a/adhoc_modules_server/views/adhoc_module_repository_view.xml b/adhoc_modules_server/views/adhoc_module_repository_view.xml new file mode 100644 index 0000000..0d1ce4c --- /dev/null +++ b/adhoc_modules_server/views/adhoc_module_repository_view.xml @@ -0,0 +1,69 @@ + + + + + + + adhoc.module.repository.form + adhoc.module.repository + +
+
+
+ + + + + + + + + + + +
+
+
+ + + + adhoc.module.repository.search + adhoc.module.repository + + + + + + + + + + + + + + + + adhoc.module.repository.tree + adhoc.module.repository + + + + + + + + + + + ADHOC Repositories + adhoc.module.repository + form + tree,form + + + + +
+
\ No newline at end of file diff --git a/adhoc_modules_server/views/adhoc_module_view.xml b/adhoc_modules_server/views/adhoc_module_view.xml new file mode 100644 index 0000000..e0a0dab --- /dev/null +++ b/adhoc_modules_server/views/adhoc_module_view.xml @@ -0,0 +1,126 @@ + + + + + + + adhoc.module.module.search + adhoc.module.module + + + + + + + + + + + + + + + + + + + + + + + + + + adhoc.module.module.kanban + adhoc.module.module + + primary + + + + + + + + + + + + + + + adhoc.module.module.tree + adhoc.module.module + + primary + + + top + + + + 1 + + + 1 + + + + + + + + + + + + + + + + ADHOC Modules + adhoc.module.module + form + tree,kanban,form + {'search_default_un_categorized': 1, 'search_default_conf_visibility': 'normal'} + + + + + + \ No newline at end of file From 504d0a4cc0067dea62e3db4f138b9bf4239864b3 Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Thu, 14 Apr 2016 08:16:00 -0300 Subject: [PATCH 02/11] IMP --- adhoc_modules/models/__init__.py | 2 - adhoc_modules/models/adhoc_module_category.py | 2 +- .../models/adhoc_module_dependency.py | 54 ---- .../models/adhoc_module_repository.py | 237 ------------------ adhoc_modules/octohub/__init__.py | 12 - adhoc_modules/octohub/connection.py | 75 ------ adhoc_modules/octohub/exceptions.py | 26 -- adhoc_modules/octohub/response.py | 103 -------- adhoc_modules/octohub/utils.py | 40 --- adhoc_modules/security/ir.model.access.csv | 7 - .../views/adhoc_module_category_view.xml | 6 +- .../views/adhoc_module_repository_view.xml | 69 ----- adhoc_modules/views/adhoc_module_view.xml | 84 ++++--- web_support_client/security/groups.xml | 12 + 14 files changed, 63 insertions(+), 666 deletions(-) delete mode 100644 adhoc_modules/models/adhoc_module_dependency.py delete mode 100644 adhoc_modules/models/adhoc_module_repository.py delete mode 100644 adhoc_modules/octohub/__init__.py delete mode 100644 adhoc_modules/octohub/connection.py delete mode 100644 adhoc_modules/octohub/exceptions.py delete mode 100644 adhoc_modules/octohub/response.py delete mode 100644 adhoc_modules/octohub/utils.py delete mode 100644 adhoc_modules/views/adhoc_module_repository_view.xml create mode 100644 web_support_client/security/groups.xml diff --git a/adhoc_modules/models/__init__.py b/adhoc_modules/models/__init__.py index 0c67ef3..7aae00b 100644 --- a/adhoc_modules/models/__init__.py +++ b/adhoc_modules/models/__init__.py @@ -1,5 +1,3 @@ # -*- encoding: utf-8 -*- -from . import adhoc_module_repository -from . import adhoc_module_dependency from . import adhoc_module_category from . import adhoc_module diff --git a/adhoc_modules/models/adhoc_module_category.py b/adhoc_modules/models/adhoc_module_category.py index 196b1c9..bf13bec 100644 --- a/adhoc_modules/models/adhoc_module_category.py +++ b/adhoc_modules/models/adhoc_module_category.py @@ -57,7 +57,7 @@ class AdhocModuleCategory(models.Model): 'Child Categories' ) module_ids = fields.One2many( - 'adhoc.module.module', + 'ir.module.module', 'adhoc_category_id', 'Modules' ) diff --git a/adhoc_modules/models/adhoc_module_dependency.py b/adhoc_modules/models/adhoc_module_dependency.py deleted file mode 100644 index 6011d99..0000000 --- a/adhoc_modules/models/adhoc_module_dependency.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# For copyright and license notices, see __openerp__.py file in module root -# directory -############################################################################## -from openerp import models, fields, api - -DEP_STATES = [ - ('uninstallable', 'Uninstallable'), - ('uninstalled', 'Not Installed'), - ('installed', 'Installed'), - ('to upgrade', 'To be upgraded'), - ('to remove', 'To be removed'), - ('to install', 'To be installed'), - ('unknown', 'Unknown'), -] - - -class AdhocModuleDependency(models.Model): - _name = "adhoc.module.dependency" - _description = "Module dependency" - - # the dependency name - name = fields.Char( - index=True - ) - # the module that depends on it - module_id = fields.Many2one( - 'adhoc.module.module', - 'Module', - ondelete='cascade', - auto_join=True, - ) - # the module corresponding to the dependency, and its status - depend_id = fields.Many2one( - 'adhoc.module.module', - 'Dependency', - compute='_compute_depend' - ) - state = fields.Selection( - DEP_STATES, - string='Status', - compute='_compute_depend' - ) - - @api.one - @api.depends('name') - def _compute_depend(self): - mod = self.env['adhoc.module.module'].search([ - ('name', '=', self.name), - ('repository_id.branch', '=', self.module_id.repository_id.branch), - ], limit=1) - self.depend_id = mod - self.state = self.depend_id.state or 'unknown' diff --git a/adhoc_modules/models/adhoc_module_repository.py b/adhoc_modules/models/adhoc_module_repository.py deleted file mode 100644 index aee99a7..0000000 --- a/adhoc_modules/models/adhoc_module_repository.py +++ /dev/null @@ -1,237 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# For copyright and license notices, see __openerp__.py file in module root -# directory -############################################################################## -from openerp import models, fields, api, modules, tools -from openerp.modules.module import adapt_version -from openerp.exceptions import Warning -from octohub.connection import Connection -from openerp.tools.parse_version import parse_version -import base64 -import logging -import itertools -import os - -_logger = logging.getLogger(__name__) -MANIFEST = '__openerp__.py' -README = ['README.rst', 'README.md', 'README.txt'] - - -def load_information_from_contents( - manifest_content, readme_content=False, index_content=False): - """ - :param module: The name of the module (sale, purchase, ...) - :param mod_path: Physical path of module, if not providedThe name of the - module (sale, purchase, ...) - """ - - # default values for descriptor - info = { - 'application': False, - 'author': '', - 'auto_install': False, - 'category': 'Uncategorized', - 'depends': [], - 'description': '', - # 'icon': get_module_icon(module), - 'installable': True, - 'license': 'AGPL-3', - 'post_load': None, - 'version': '1.0', - 'web': False, - 'website': '', - 'sequence': 100, - 'summary': '', - } - info.update(itertools.izip( - 'depends data demo test init_xml update_xml demo_xml'.split(), - iter(list, None))) - try: - info.update(eval(manifest_content)) - except: - _logger.warning('could not ') - return {} - # if not info.get('description'): - # readme_path = [opj(mod_path, x) for x in README - # if os.path.isfile(opj(mod_path, x))] - # if readme_path: - # readme_text = tools.file_open(readme_path[0]).read() - # info['description'] = readme_text - - if 'active' in info: - # 'active' has been renamed 'auto_install' - info['auto_install'] = info['active'] - - info['version'] = adapt_version(info['version']) - return info - - -class AdhocModuleRepository(models.Model): - _name = 'adhoc.module.repository' - - user = fields.Char( - 'User or Organization', - required=True, - help='eg. "ingadhoc"', - ) - subdirectory = fields.Char( - 'Subdirectory', - help='For eg. "addons"', - ) - name = fields.Char( - 'Repository Name', - required=True, - help='eg. "product"', - ) - branch = fields.Selection( - [('8.0', '8.0'), ('9.0', '9.0')], - 'Branch / Odoo Version', - required=True, - ) - module_ids = fields.One2many( - 'adhoc.module.module', - 'repository_id', - 'Modules', - ) - token = fields.Char( - help='If no token configured, we will try to use a general one setted ' - 'as "github.token" parameter, if none configured, we try connecting ' - 'without token' - ) - auto_update = fields.Boolean( - default=True, - ) - sequence = fields.Integer( - string='Sequence', - default=10, - ) - - @api.multi - def get_token(self): - self.ensure_one() - token = self.token - if not token: - token = self.env['ir.config_parameter'].get_param( - 'github.token') or '' - return token - - @api.multi - def get_connection(self): - self.ensure_one() - token = self.get_token() - return Connection(token) - - @api.multi - def get_modules_paths(self): - """return name of remote modules""" - response = self.read_remote_path(self.subdirectory or '') - paths = [x['path'] for x in response.parsed if x['type'] == 'dir'] - _logger.info('Readed paths %s' % paths) - return paths - - @api.multi - def read_remote_path(self, path=False): - _logger.info('Reading data from remote path %s' % path) - conn = self.get_connection() - # obtener directorios - uri = "/repos/%s/%s/contents/%s" % ( - self.name, self.user, path or '') - try: - response = conn.send( - 'GET', uri, params={'ref': self.branch}) - except Exception, ResponseError: - raise Warning( - 'Could not get modules for:\n' - '* Repository: %s\n' - '* URI: %s\n' - '* Branch: %s\n' - '* Token: %s\n\n' - 'This is what we get:%s' % ( - self.name, uri, self.branch, - self.get_token(), ResponseError)) - return response - - @api.model - def get_module_info(self, name): - info = {} - try: - response = self.read_remote_path("%s/__openerp__.py" % name) - encoded_content = response.parsed['content'] - info = load_information_from_contents( - base64.b64decode(encoded_content)) - except Exception: - _logger.debug('Error when trying to fetch informations for ' - 'module %s', name, exc_info=True) - return info - - @api.multi - def get_module_vals(self, info): - self.ensure_one() - return { - 'description': info.get('description', ''), - 'shortdesc': info.get('name', ''), - 'author': info.get('author', 'Unknown'), - 'maintainer': info.get('maintainer', False), - 'contributors': ', '.join(info.get('contributors', [])) or False, - 'website': info.get('website', ''), - 'license': info.get('license', 'AGPL-3'), - 'sequence': info.get('sequence', 100), - 'application': info.get('application', False), - 'auto_install': info.get('auto_install', False), - 'icon': info.get('icon', False), - 'summary': info.get('summary', ''), - } - - @api.multi - def scan_repository(self): - self.ensure_one() - res = [0, 0] # [update, add] - - default_version = modules.adapt_version('1.0') - - # iterate through detected modules and update/create them in db - for module_path in self.get_modules_paths(): - # sacamos la ultima parte del path como nombre del modulo - mod_name = os.path.basename(module_path) - # search for modules of same name an odoo version - mod = self.env['adhoc.module.module'].search([ - ('name', '=', mod_name), - ('repository_id.branch', '=', self.branch)], limit=1) - module_info = self.get_module_info(module_path) - values = self.get_module_vals(module_info) - - if mod: - _logger.info('Updating data for module %s' % mod_name) - if mod.repository_id.id != self.id: - raise Warning( - 'Module already exist in other repository') - updated_values = {} - for key in values: - old = getattr(mod, key) - updated = isinstance( - values[key], basestring) and tools.ustr( - values[key]) or values[key] - if (old or updated) and updated != old: - updated_values[key] = values[key] - if module_info.get( - 'installable', True) and mod.state == 'uninstallable': - updated_values['state'] = 'uninstalled' - if parse_version(module_info.get( - 'version', default_version)) > parse_version( - mod.latest_version or default_version): - res[0] += 1 - if updated_values: - mod.write(updated_values) - else: - _logger.info('Creating new module %s' % mod_name) - # if not installable, we dont upload - if not module_info or not module_info.get( - 'installable', True): - continue - mod = mod.create(dict( - name=mod_name, state='uninstalled', - repository_id=self.id, **values)) - res[1] += 1 - mod._update_dependencies(module_info.get('depends', [])) - return res diff --git a/adhoc_modules/octohub/__init__.py b/adhoc_modules/octohub/__init__.py deleted file mode 100644 index 4ac03a0..0000000 --- a/adhoc_modules/octohub/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013 Alon Swartz -# -# This file is part of OctoHub. -# -# OctoHub is free software; you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. - -__version__ = '0.1' -__useragent__ = 'octohub/%s' % __version__ - diff --git a/adhoc_modules/octohub/connection.py b/adhoc_modules/octohub/connection.py deleted file mode 100644 index 07f3f7e..0000000 --- a/adhoc_modules/octohub/connection.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2013 Alon Swartz -# -# This file is part of OctoHub. -# -# OctoHub is free software; you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. - -import requests - -from octohub import __useragent__ -from octohub.response import parse_response - -class Pager(object): - def __init__(self, conn, uri, params, max_pages=0): - """Iterator object handling pagination of Connection.send (method: GET) - conn (octohub.Connection): Connection object - uri (str): Request URI (e.g., /user/issues) - params (dict): Parameters to include in request - max_pages (int): Maximum amount of pages to get (0 for all) - """ - self.conn = conn - self.uri = uri - self.params = params - self.max_pages = max_pages - self.count = 0 - - def __iter__(self): - while True: - self.count += 1 - response = self.conn.send('GET', self.uri, self.params) - yield response - - if self.count == self.max_pages: - break - - if not 'next' in response.parsed_link.keys(): - break - - # Parsed link is absolute. Connection wants a relative link, - # so remove protocol and GitHub endpoint for the pagination URI. - m = re.match(self.conn.endpoint + '(.*)', response.parsed_link.next.uri) - self.uri = m.groups()[0] - self.params = response.parsed_link.next.params - -class Connection(object): - def __init__(self, token=None): - """OctoHub connection - token (str): GitHub Token (anonymous if not provided) - """ - self.endpoint = 'https://api.github.com' - self.headers = {'User-Agent': __useragent__} - - if token: - self.headers['Authorization'] = 'token %s' % token - - def send(self, method, uri, params={}, data=None): - """Prepare and send request - method (str): Request HTTP method (e.g., GET, POST, DELETE, ...) - uri (str): Request URI (e.g., /user/issues) - params (dict): Parameters to include in request - data (str | file type object): data to include in request - - returns: requests.Response object, including: - response.parsed (AttrDict): parsed response when applicable - response.parsed_link (AttrDict): parsed header link when applicable - http://docs.python-requests.org/en/latest/api/#requests.Response - """ - url = self.endpoint + uri - kwargs = {'headers': self.headers, 'params': params, 'data': data} - response = requests.request(method, url, **kwargs) - - return parse_response(response) - diff --git a/adhoc_modules/octohub/exceptions.py b/adhoc_modules/octohub/exceptions.py deleted file mode 100644 index ccb4f95..0000000 --- a/adhoc_modules/octohub/exceptions.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2013 Alon Swartz -# -# This file is part of OctoHub. -# -# OctoHub is free software; you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. - -import simplejson as json - -class ResponseError(Exception): - """Accessible attributes: error - error (AttrDict): Parsed error response - """ - def __init__(self, error): - Exception.__init__(self, error) - self.error = error - - def __str__(self): - return json.dumps(self.error, indent=1) - - -class OctoHubError(Exception): - pass - diff --git a/adhoc_modules/octohub/response.py b/adhoc_modules/octohub/response.py deleted file mode 100644 index b2ddc42..0000000 --- a/adhoc_modules/octohub/response.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright (c) 2013 Alon Swartz -# -# This file is part of OctoHub. -# -# OctoHub is free software; you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. - -import re - -from octohub.utils import AttrDict, get_logger -from octohub.exceptions import ResponseError, OctoHubError - -log = get_logger('response') - -def _get_content_type(response): - """Parse response and return content-type""" - try: - content_type = response.headers['Content-Type'] - content_type = content_type.split(';', 1)[0] - except KeyError: - content_type = None - - return content_type - -def _parse_link(header_link): - """Parse header link and return AttrDict[rel].uri|params""" - links = AttrDict() - for s in header_link.split(','): - link = AttrDict() - - m = re.match('<(.*)\?(.*)>', s.split(';')[0].strip()) - link.uri = m.groups()[0] - link.params = {} - for kv in m.groups()[1].split('&'): - key, value = kv.split('=') - link.params[key] = value - - m = re.match('rel="(.*)"', s.split(';')[1].strip()) - rel = m.groups()[0] - - links[rel] = link - log.debug('link-%s-page: %s' % (rel, link.params['page'])) - - return links - -def parse_element(el): - """Parse el recursively, replacing dicts with AttrDicts representation""" - if type(el) == dict: - el_dict = AttrDict() - for key, val in el.items(): - el_dict[key] = parse_element(val) - - return el_dict - - elif type(el) == list: - el_list = [] - for l in el: - el_list.append(parse_element(l)) - - return el_list - - else: - return el - -def parse_response(response): - """Parse request response object and raise exception on response error code - response (requests.Response object): - - returns: requests.Response object, including: - response.parsed (AttrDict) - response.parsed_link (AttrDict) - http://docs.python-requests.org/en/latest/api/#requests.Response - """ - response.parsed = AttrDict() - response.parsed_link = AttrDict() - - if 'link' in response.headers.keys(): - response.parsed_link = _parse_link(response.headers['link']) - - headers = ['status', 'x-ratelimit-limit', 'x-ratelimit-remaining'] - for header in headers: - if header in response.headers.keys(): - log.info('%s: %s' % (header, response.headers[header])) - - content_type = _get_content_type(response) - - if content_type == 'application/json': - json = response.json - if callable(json): - json = json() - response.parsed = parse_element(json) - else: - if not response.status_code == 204: - raise OctoHubError('unhandled content_type: %s' % content_type) - - if not response.status_code in (200, 201, 204): - raise ResponseError(response.parsed) - - return response - - diff --git a/adhoc_modules/octohub/utils.py b/adhoc_modules/octohub/utils.py deleted file mode 100644 index e168472..0000000 --- a/adhoc_modules/octohub/utils.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) 2013 Alon Swartz -# -# This file is part of OctoHub. -# -# OctoHub is free software; you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. - -import os -import logging - -class AttrDict(dict): - """Attribute Dictionary (set and access attributes 'pythonically')""" - def __getattr__(self, name): - if name in self: - return self[name] - raise AttributeError('no such attribute: %s' % name) - - def __setattr__(self, name, val): - self[name] = val - -def get_logger(name, level=None): - """Returns logging handler based on name and level (stderr) - name (str): name of logging handler - level (str): see logging.LEVEL - """ - logger = logging.getLogger(name) - - if not logger.handlers: - stderr = logging.StreamHandler() - stderr.setFormatter(logging.Formatter( - '%(levelname)s [%(name)s]: %(message)s')) - logger.addHandler(stderr) - - level = level if level else os.environ.get('OCTOHUB_LOGLEVEL', 'CRITICAL') - logger.setLevel(getattr(logging, level)) - - return logger - diff --git a/adhoc_modules/security/ir.model.access.csv b/adhoc_modules/security/ir.model.access.csv index 8d97c03..da0d9a0 100644 --- a/adhoc_modules/security/ir.model.access.csv +++ b/adhoc_modules/security/ir.model.access.csv @@ -1,9 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_adhoc_module_module_all,access_adhoc_module_module_all,model_adhoc_module_module,base.group_system,1,1,1,1 -access_adhoc_module_module_config,access_adhoc_module_module_config,model_adhoc_module_module,,1,0,0,0 -access_adhoc_module_category_all,access_adhoc_module_category_all,model_adhoc_module_category,base.group_system,1,1,1,1 access_adhoc_module_category_config,access_adhoc_module_category_config,model_adhoc_module_category,,1,0,0,0 -access_adhoc_module_repository_all,access_adhoc_module_repository_all,model_adhoc_module_repository,base.group_system,1,1,1,1 -access_adhoc_module_repository_config,access_adhoc_module_repository_config,model_adhoc_module_repository,,1,0,0,0 -access_adhoc_module_dependency_all,access_adhoc_module_dependency_all,model_adhoc_module_dependency,base.group_system,1,1,1,1 -access_adhoc_module_dependency_config,access_adhoc_module_dependency_config,model_adhoc_module_dependency,,1,0,0,0 diff --git a/adhoc_modules/views/adhoc_module_category_view.xml b/adhoc_modules/views/adhoc_module_category_view.xml index 182b9ab..dba4ae1 100644 --- a/adhoc_modules/views/adhoc_module_category_view.xml +++ b/adhoc_modules/views/adhoc_module_category_view.xml @@ -7,7 +7,7 @@ adhoc.module.category.kanban adhoc.module.category - + @@ -69,7 +69,7 @@ adhoc.module.category.tree adhoc.module.category - + @@ -81,7 +81,7 @@ adhoc.module.category.form adhoc.module.category -
+
diff --git a/adhoc_modules/views/adhoc_module_repository_view.xml b/adhoc_modules/views/adhoc_module_repository_view.xml deleted file mode 100644 index 0d1ce4c..0000000 --- a/adhoc_modules/views/adhoc_module_repository_view.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - adhoc.module.repository.form - adhoc.module.repository - - -
-
- - - - - - - - - - - - -
-
- - - - adhoc.module.repository.search - adhoc.module.repository - - - - - - - - - - - - - - - - adhoc.module.repository.tree - adhoc.module.repository - - - - - - - - - - - ADHOC Repositories - adhoc.module.repository - form - tree,form - - - - -
-
\ No newline at end of file diff --git a/adhoc_modules/views/adhoc_module_view.xml b/adhoc_modules/views/adhoc_module_view.xml index e0a0dab..8521a30 100644 --- a/adhoc_modules/views/adhoc_module_view.xml +++ b/adhoc_modules/views/adhoc_module_view.xml @@ -3,41 +3,33 @@ - - adhoc.module.module.search - adhoc.module.module + + adhoc.ir.module.module.search + ir.module.module + + - - - - + - - - - - - - - - - - + + + + - adhoc.module.module.kanban - adhoc.module.module + adhoc.ir.module.module.kanban + ir.module.module primary + - @@ -96,7 +98,7 @@
@@ -106,29 +108,29 @@ ir.module.module form tree,kanban,form - ['adhoc_category_id', '!=', False)] + [('adhoc_category_id', '!=', False)] - + - + - + - - + + - + diff --git a/adhoc_modules/views/db_configuration_view.xml b/adhoc_modules/views/db_configuration_view.xml new file mode 100644 index 0000000..ca996e4 --- /dev/null +++ b/adhoc_modules/views/db_configuration_view.xml @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/adhoc_modules/views/support_view.xml b/adhoc_modules/views/support_view.xml new file mode 100644 index 0000000..6f36225 --- /dev/null +++ b/adhoc_modules/views/support_view.xml @@ -0,0 +1,26 @@ + + + + + + + + support.contract.form + support.contract + + +
+
+
+
+
+
diff --git a/adhoc_modules_server/__openerp__.py b/adhoc_modules_server/__openerp__.py index 898ebb1..1368a3a 100644 --- a/adhoc_modules_server/__openerp__.py +++ b/adhoc_modules_server/__openerp__.py @@ -28,9 +28,9 @@ 'adhoc_modules', 'mass_editing', ], - 'external_dependencies': { - 'python': ['octuhub'] - }, + # 'external_dependencies': { + # 'python': ['octuhub'] + # }, 'data': [ 'views/adhoc_module_repository_view.xml', 'views/adhoc_module_category_view.xml', diff --git a/adhoc_modules_server/data/mass_editting_data.xml b/adhoc_modules_server/data/mass_editting_data.xml index c2e4fdc..f3e24d1 100644 --- a/adhoc_modules_server/data/mass_editting_data.xml +++ b/adhoc_modules_server/data/mass_editting_data.xml @@ -3,8 +3,8 @@ Asignar Categoría - - + + diff --git a/adhoc_modules_server/models/__init__.py b/adhoc_modules_server/models/__init__.py index 0c67ef3..ec746b6 100644 --- a/adhoc_modules_server/models/__init__.py +++ b/adhoc_modules_server/models/__init__.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- from . import adhoc_module_repository from . import adhoc_module_dependency -from . import adhoc_module_category +# from . import adhoc_module_category from . import adhoc_module diff --git a/adhoc_modules_server/models/adhoc_module.py b/adhoc_modules_server/models/adhoc_module.py index bdb2a20..7f23633 100644 --- a/adhoc_modules_server/models/adhoc_module.py +++ b/adhoc_modules_server/models/adhoc_module.py @@ -38,7 +38,7 @@ def create(self, vals): vals['name'], module.repository_id.branch.replace('.', '_')), 'model': self._name, - 'module': 'adhoc_module', + 'module': 'adhoc_module_module', 'res_id': module.id, 'noupdate': True, } diff --git a/adhoc_modules_server/models/adhoc_module_repository.py b/adhoc_modules_server/models/adhoc_module_repository.py index aee99a7..0431416 100644 --- a/adhoc_modules_server/models/adhoc_module_repository.py +++ b/adhoc_modules_server/models/adhoc_module_repository.py @@ -136,7 +136,7 @@ def read_remote_path(self, path=False): conn = self.get_connection() # obtener directorios uri = "/repos/%s/%s/contents/%s" % ( - self.name, self.user, path or '') + self.user, self.name, path or '') try: response = conn.send( 'GET', uri, params={'ref': self.branch}) diff --git a/adhoc_modules_server/security/.~lock.ir.model.access.csv# b/adhoc_modules_server/security/.~lock.ir.model.access.csv# new file mode 100644 index 0000000..426d311 --- /dev/null +++ b/adhoc_modules_server/security/.~lock.ir.model.access.csv# @@ -0,0 +1 @@ +,chosco,localhost,15.04.2016 17:31,file:///home/chosco/.config/libreoffice/4; \ No newline at end of file diff --git a/adhoc_modules_server/security/ir.model.access.csv b/adhoc_modules_server/security/ir.model.access.csv index 8d97c03..8dd8244 100644 --- a/adhoc_modules_server/security/ir.model.access.csv +++ b/adhoc_modules_server/security/ir.model.access.csv @@ -1,9 +1,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_adhoc_module_module_all,access_adhoc_module_module_all,model_adhoc_module_module,base.group_system,1,1,1,1 -access_adhoc_module_module_config,access_adhoc_module_module_config,model_adhoc_module_module,,1,0,0,0 -access_adhoc_module_category_all,access_adhoc_module_category_all,model_adhoc_module_category,base.group_system,1,1,1,1 -access_adhoc_module_category_config,access_adhoc_module_category_config,model_adhoc_module_category,,1,0,0,0 -access_adhoc_module_repository_all,access_adhoc_module_repository_all,model_adhoc_module_repository,base.group_system,1,1,1,1 -access_adhoc_module_repository_config,access_adhoc_module_repository_config,model_adhoc_module_repository,,1,0,0,0 -access_adhoc_module_dependency_all,access_adhoc_module_dependency_all,model_adhoc_module_dependency,base.group_system,1,1,1,1 -access_adhoc_module_dependency_config,access_adhoc_module_dependency_config,model_adhoc_module_dependency,,1,0,0,0 +access_adhoc_module_module_all,access_adhoc_module_module_all,model_adhoc_module_module,,1,0,0,0 +access_adhoc_module_module_config,access_adhoc_module_module_config,model_adhoc_module_module,base.group_system,1,1,1,1 +access_adhoc_module_category_config,access_adhoc_module_category_config,adhoc_modules.model_adhoc_module_category,base.group_system,1,1,1,1 +access_adhoc_module_repository_all,access_adhoc_module_repository_all,model_adhoc_module_repository,,1,0,0,0 +access_adhoc_module_repository_config,access_adhoc_module_repository_config,model_adhoc_module_repository,base.group_system,1,1,1,1 +access_adhoc_module_dependency_all,access_adhoc_module_dependency_all,model_adhoc_module_dependency,,1,0,0,0 +access_adhoc_module_dependency_config,access_adhoc_module_dependency_config,model_adhoc_module_dependency,base.group_system,1,1,1,1 diff --git a/adhoc_modules_server/views/adhoc_module_category_view.xml b/adhoc_modules_server/views/adhoc_module_category_view.xml index 182b9ab..9ddf1b3 100644 --- a/adhoc_modules_server/views/adhoc_module_category_view.xml +++ b/adhoc_modules_server/views/adhoc_module_category_view.xml @@ -2,65 +2,16 @@ - - - adhoc.module.category.kanban - adhoc.module.category - - - - - - - - - - - - - - - - - - - adhoc.module.category.search + + + adhoc.module.category.kanban adhoc.module.category + + - - - - + + true + @@ -68,10 +19,11 @@ adhoc.module.category.tree adhoc.module.category + + - - - + + true @@ -80,25 +32,13 @@ adhoc.module.category.form adhoc.module.category + + -
-
-
- - - - - - - - - - - - - - - + + true + true + true
@@ -111,7 +51,7 @@ {'search_default_root_categories': 1}
- +
\ No newline at end of file diff --git a/adhoc_modules_server/views/adhoc_module_repository_view.xml b/adhoc_modules_server/views/adhoc_module_repository_view.xml index 0d1ce4c..17edb97 100644 --- a/adhoc_modules_server/views/adhoc_module_repository_view.xml +++ b/adhoc_modules_server/views/adhoc_module_repository_view.xml @@ -57,13 +57,15 @@
- ADHOC Repositories - adhoc.module.repository - form - tree,form + ADHOC Repositories + adhoc.module.repository + form + tree,form - + + + \ No newline at end of file diff --git a/adhoc_modules_server/views/adhoc_module_view.xml b/adhoc_modules_server/views/adhoc_module_view.xml index e0a0dab..3c4b272 100644 --- a/adhoc_modules_server/views/adhoc_module_view.xml +++ b/adhoc_modules_server/views/adhoc_module_view.xml @@ -120,7 +120,7 @@ {'search_default_un_categorized': 1, 'search_default_conf_visibility': 'normal'} - + \ No newline at end of file diff --git a/web_support_client/README.RST b/web_support_client/README.RST new file mode 100644 index 0000000..c1a17c9 --- /dev/null +++ b/web_support_client/README.RST @@ -0,0 +1,7 @@ +Functionalities to interact with support provider +================================================= + +It adds a new modle "support.contract" with some useful methods: +* First you should get active contract with "contract = self.get_active_contract()" +* then you can cal different methods as: + * \ No newline at end of file diff --git a/web_support_client/models/support.py b/web_support_client/models/support.py index caa4254..2663071 100755 --- a/web_support_client/models/support.py +++ b/web_support_client/models/support.py @@ -100,4 +100,16 @@ def get_active_contract(self): raise Warning(_('No active contract configured')) return active_contract + @api.multi + def check_modules_installed(self, modules=[]): + """ + where modules should be a list of modules names + for eg. modules = ['database_tools'] + """ + self.ensure_one() + client = self.get_connection() + for module in modules: + if client.modules(name=module, installed=True) is None: + return False + return True # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 7b5f8176dbf126b65b783e277f1d9697fb499627 Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Wed, 20 Apr 2016 17:03:17 -0300 Subject: [PATCH 04/11] IMp --- adhoc_modules/models/__init__.py | 1 + adhoc_modules/models/adhoc_module_category.py | 186 +++++++++++------ adhoc_modules/models/db_configuration.py | 10 +- adhoc_modules/models/ir_module.py | 109 +++++----- adhoc_modules/models/module_dependency.py | 24 +++ adhoc_modules/models/support.py | 30 ++- .../views/adhoc_module_category_view.xml | 95 +++++---- adhoc_modules/views/adhoc_module_view.xml | 56 +++--- adhoc_modules_server/__openerp__.py | 1 + adhoc_modules_server/models/__init__.py | 3 +- adhoc_modules_server/models/adhoc_module.py | 28 ++- .../models/adhoc_module_category.py | 190 +++--------------- .../models/product_template.py | 20 ++ .../views/adhoc_module_category_view.xml | 14 +- .../views/adhoc_module_view.xml | 2 + web_support_client/models/support.py | 1 + 16 files changed, 421 insertions(+), 349 deletions(-) create mode 100644 adhoc_modules/models/module_dependency.py create mode 100644 adhoc_modules_server/models/product_template.py diff --git a/adhoc_modules/models/__init__.py b/adhoc_modules/models/__init__.py index 517dbd5..cc18067 100644 --- a/adhoc_modules/models/__init__.py +++ b/adhoc_modules/models/__init__.py @@ -3,3 +3,4 @@ from . import ir_module from . import support from . import db_configuration +from . import module_dependency diff --git a/adhoc_modules/models/adhoc_module_category.py b/adhoc_modules/models/adhoc_module_category.py index a8fd4b6..e1bdfed 100644 --- a/adhoc_modules/models/adhoc_module_category.py +++ b/adhoc_modules/models/adhoc_module_category.py @@ -5,7 +5,7 @@ ############################################################################## from openerp import models, fields, api import logging - +import string _logger = logging.getLogger(__name__) @@ -19,31 +19,53 @@ class AdhocModuleCategory(models.Model): _parent_order = "sequence" # _rec_name = 'display_name' + visibility = fields.Selection([ + ('normal', 'Normal'), + ('product_required', 'Product Required'), + ], + required=True, + readonly=True, + default='normal', + ) + contracted_product = fields.Char( + readonly=True, + ) name = fields.Char( + readonly=True, required=True, ) + code = fields.Char( + readonly=True, + # required=True, + # readonly=True, + # default='/', + ) count_modules = fields.Integer( string='# Modules', compute='get_count_modules', ) - count_suggested_modules = fields.Integer( - string='# Suggested Modules', + count_pending_modules = fields.Integer( + string='# Revised Modules', compute='get_count_modules', ) - count_subcategories_modules = fields.Integer( - string='# Subcategories Modules', - compute='get_count_subcategories_modules', - ) - count_suggested_subcategories_modules = fields.Integer( - string='# Suggested Subcategories Modules', - compute='get_count_subcategories_modules', + count_revised_modules = fields.Integer( + string='# Revised Modules', + compute='get_count_modules', ) + # count_subcategories_modules = fields.Integer( + # string='# Subcategories Modules', + # compute='get_count_subcategories_modules', + # ) + # count_suggested_subcategories_modules = fields.Integer( + # string='# Suggested Subcategories Modules', + # compute='get_count_subcategories_modules', + # ) count_subcategories = fields.Integer( string='# Subcategories', compute='get_count_subcategories', ) - count_suggested_subcategories = fields.Integer( - string='# Suggested Subcategories', + count_revised_subcategories = fields.Integer( + string='# Revised Subcategories', compute='get_count_subcategories', ) color = fields.Integer( @@ -55,6 +77,7 @@ class AdhocModuleCategory(models.Model): 'Parent Category', select=True, ondelete='restrict', + readonly=True, ) parent_left = fields.Integer( 'Parent Left', @@ -67,30 +90,73 @@ class AdhocModuleCategory(models.Model): child_ids = fields.One2many( 'adhoc.module.category', 'parent_id', - 'Child Categories' + 'Child Categories', + readonly=True, ) module_ids = fields.One2many( 'ir.module.module', 'adhoc_category_id', - 'Modules' + 'Modules', + domain=[('visible', '=', True)], + readonly=True, ) description = fields.Text( + readonly=True, ) sequence = fields.Integer( 'Sequence', default=10, + readonly=True, + ) + to_revise = fields.Boolean( + compute='get_to_revise', + search='search_to_revise', ) display_name = fields.Char( - compute='get_display_name' + compute='get_display_name', + # store=True ) _sql_constraints = [ - ('name_uniq', 'unique(name)', + ('code_uniq', 'unique(code)', 'Category name must be unique'), ] + @api.one + @api.depends() + def get_to_revise(self): + if 'uninstalled' in self.module_ids.mapped('state'): + self.to_revise = True + elif True in self.child_ids.mapped('to_revise'): + self.to_revise = True + else: + self.to_revise = False + + @api.model + def search_to_revise(self, operator, value): + """Se tiene que revisar si hay modulos o categorías a revisar""" + # TODO mejorar, en teoria esta soportando solo dos niveles de + # anidamiento con esta form + # intente child_ids.to_revise = True pero dio max recursion + # una alternativa es buscar todos los modulos uninstalled y visible + # agruparlos por categoria y buscar las categorías padres de esas + return [ + '|', ('module_ids.state', 'in', ['uninstalled']), + ('child_ids.module_ids.state', 'in', ['uninstalled']), + ] + + @api.one + @api.constrains('child_ids', 'name', 'parent_id') + def set_code(self): + # if not self.code: + code = self.display_name + valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits) + code = ''.join(c for c in code if c in valid_chars) + code = code.replace(' ', '').replace('.', '').lower() + self.code = code + @api.multi - @api.depends('child_ids') + @api.depends('child_ids', 'name', 'parent_id') def get_display_name(self): def get_names(cat): """ Return the list [cat.name, cat.parent_id.name, ...] """ @@ -102,21 +168,19 @@ def get_names(cat): for cat in self: cat.display_name = " / ".join(reversed(get_names(cat))) - # IMPORTANTE si usamos esto entonces tenemos cambiar en support para que - # busque el nombre de cada registro - # @api.multi - # def name_get(self): - # result = [] - # for record in self: - # result.append((record.id, record.display_name)) - # return result + @api.multi + def name_get(self): + result = [] + for record in self: + result.append((record.id, record.display_name)) + return result @api.one @api.depends('child_ids') def get_count_subcategories(self): self.count_subcategories = len(self.child_ids) - self.count_suggested_subcategories = len(self.child_ids.filtered( - lambda x: x.count_suggested_subcategories_modules)) + self.count_revised_subcategories = len(self.child_ids.filtered( + lambda x: not x.to_revise)) @api.multi def get_subcategories_modules(self): @@ -129,7 +193,6 @@ def get_suggested_subcategories_modules(self): self.ensure_one() return self.module_ids.search([ ('adhoc_category_id', 'child_of', self.id), - ('ignored', '=', False), ('state', '=', 'uninstalled'), ]) @@ -140,26 +203,33 @@ def get_suggested_subcategories_modules(self): # operator = 'ilike' # return [('name', operator, value)] - @api.one - def get_count_subcategories_modules(self): - self.count_suggested_subcategories_modules = len( - self.get_suggested_subcategories_modules()) - self.count_subcategories_modules = len( - self.get_subcategories_modules()) + # @api.one + # def get_count_subcategories_modules(self): + # self.count_suggested_subcategories_modules = len( + # self.get_suggested_subcategories_modules()) + # self.count_subcategories_modules = len( + # self.get_subcategories_modules()) @api.one @api.depends('module_ids') def get_count_modules(self): - self.count_modules = len(self.module_ids) - self.count_suggested_modules = len(self.module_ids.filtered( - lambda x: x.state != 'uninstalled' and not x.ignored)) + count_modules = len(self.module_ids) + count_pending_modules = len(self.module_ids.filtered( + lambda x: x.state == 'uninstalled')) + self.count_modules = count_modules + self.count_pending_modules = count_pending_modules + self.count_revised_modules = count_modules - count_pending_modules # @api.depends('state') @api.one def get_color(self): color = 4 - # if self.state == 'draft': - # color = 7 + # TODO implementar color de las no contratadas + # if self.count_pending_modules: + if self.visibility != 'normal' and not self.contracted_product: + color = 1 + elif self.to_revise: + color = 7 # elif self.state == 'cancel': # color = 1 # elif self.state == 'inactive': @@ -179,6 +249,8 @@ def action_subcategories(self): res = action.read()[0] res['context'] = { 'search_default_parent_id': self.id, + 'search_default_to_revise': 1, + 'search_default_not_contracted': 1 } return res @@ -186,32 +258,32 @@ def action_subcategories(self): def action_modules(self): self.ensure_one() action = self.env['ir.model.data'].xmlid_to_object( - 'adhoc_modules.action_adhoc_module_module') + 'adhoc_modules.action_adhoc_ir_module_module') if not action: return False res = action.read()[0] res['domain'] = [('adhoc_category_id', '=', self.id)] res['context'] = { - 'search_default_not_ignored': 1, + # 'search_default_not_ignored': 1, 'search_default_state': 'uninstalled', } return res - @api.multi - def action_subcategories_modules(self): - self.ensure_one() - action = self.env['ir.model.data'].xmlid_to_object( - 'adhoc_modules.action_adhoc_module_module') + # @api.multi + # def action_subcategories_modules(self): + # self.ensure_one() + # action = self.env['ir.model.data'].xmlid_to_object( + # 'adhoc_modules.action_adhoc_ir_module_module') - if not action: - return False - res = action.read()[0] - modules = self.get_subcategories_modules() - res['domain'] = [('id', 'in', modules.ids)] - res['context'] = { - 'search_default_not_ignored': 1, - 'search_default_state': 'uninstalled', - 'search_default_group_by_adhoc_category': 1 - } - return res + # if not action: + # return False + # res = action.read()[0] + # modules = self.get_subcategories_modules() + # res['domain'] = [('id', 'in', modules.ids)] + # res['context'] = { + # # 'search_default_not_ignored': 1, + # 'search_default_state': 'uninstalled', + # 'search_default_group_by_adhoc_category': 1 + # } + # return res diff --git a/adhoc_modules/models/db_configuration.py b/adhoc_modules/models/db_configuration.py index bd2b7a1..ad66a15 100644 --- a/adhoc_modules/models/db_configuration.py +++ b/adhoc_modules/models/db_configuration.py @@ -3,12 +3,12 @@ # For copyright and license notices, see __openerp__.py file in module root # directory ############################################################################## -from openerp import models, fields, api, _ +from openerp import models # from openerp import pooler -from openerp.exceptions import Warning -from datetime import datetime -from datetime import date -from dateutil.relativedelta import relativedelta +# from openerp.exceptions import Warning +# from datetime import datetime +# from datetime import date +# from dateutil.relativedelta import relativedelta class database_tools_configuration(models.TransientModel): diff --git a/adhoc_modules/models/ir_module.py b/adhoc_modules/models/ir_module.py index a3e327b..c5b251d 100644 --- a/adhoc_modules/models/ir_module.py +++ b/adhoc_modules/models/ir_module.py @@ -47,6 +47,7 @@ class AdhocModuleModule(models.Model): ('normal', 'Normal'), ('only_if_depends', 'Solo si dependencias'), ('auto_install', 'Auto Install'), + ('auto_install_by_module', 'Auto Install by Module'), ('installed_by_others', 'Instalado por Otro'), ('on_config_wizard', 'En asistente de configuración'), # no instalable @@ -61,6 +62,7 @@ class AdhocModuleModule(models.Model): "* Normal: visible para ser instalado\n" "* Solo si dependencias: se muestra solo si dependencias instaladas\n" "* Auto Instalar: auto instalar si se cumplen dependencias\n" + "* Auto Instalado Por Módulo: se instala si se cumplen dependencias\n" "* Instalado por Otro: algún otro módulo dispara la instalación\n" "* En asistente de configuración: este módulo esta presente en el " "asistente de configuración\n" @@ -72,47 +74,63 @@ class AdhocModuleModule(models.Model): visibility_obs = fields.Char( 'Visibility Observation' ) - ignored = fields.Boolean( - 'Ignored' + visible = fields.Boolean( + compute='get_visible', + search='search_visible', ) - to_check = fields.Boolean( - compute='get_to_check', - search='search_to_check', - string='To Check', + # ignored = fields.Boolean( + # 'Ignored' + # ) + state = fields.Selection( + selection_add=[('ignored', 'Ignored')] ) + # to_check = fields.Boolean( + # compute='get_to_check', + # # search='search_to_check', + # string='To Check', + # ) + + # @api.one + # def get_to_check(self): + # to_check = True + # if self.state != 'uninstalled': + # to_check = False + # elif (self.conf_visibility == 'only_if_depends' and not self.depends): + # to_check = False + # elif self.conf_visibility != 'normal': + # to_check = False + # self.to_check = to_check @api.one - def get_to_check(self): - to_check = True - if self.ignored: - to_check = False - elif (self.conf_visibility == 'only_if_depends' and not self.depends): - to_check = False + @api.depends('adhoc_category_id', 'conf_visibility') + def get_visible(self): + visible = True + # si esta en estos estados, no importa el resto, queremos verlo + if self.state in ['installed', 'to install']: + visible = True + elif not self.adhoc_category_id: + visible = False + elif self.conf_visibility == 'only_if_depends': + uninstalled_dependencies = self.dependencies_id.mapped( + 'depend_id').filtered( + lambda x: x.state not in ['installed', 'to install']) + if uninstalled_dependencies: + visible = False elif self.conf_visibility != 'normal': - to_check = False - self.to_check = to_check + visible = False + self.visible = visible @api.model - def search_to_check(self, operator, value): - # if operator - # normal_modules = self.search([ - # ('ignored', '!=', True), - # ('conf_visibility', '=', 'normal'), - # # ('depends.dependencies_id', '=', 'only_if_depends'), - # ]) - # only_if_depends_modules = self.search([ - # ('ignored', '!=', True), - # ('conf_visibility', '=', 'only_if_depends'), - # ('dependencies_id.depend_id.state', '=', 'installed'), - # ]) + def search_visible(self, operator, value): + installed_modules_names = self.search([ + ('state', 'in', ['installed', 'to install'])]).mapped('name') return [ - ('ignored', '!=', True), - ('conf_visibility', '=', 'normal'), - '|', ('conf_visibility', '=', 'only_if_depends'), - ('dependencies_id.depend_id.state', '=', 'installed'), + '|', ('state', 'in', ['installed', 'to install']), + '&', ('adhoc_category_id', '!=', False), + '|', ('conf_visibility', '=', 'normal'), + '&', ('conf_visibility', '=', 'only_if_depends'), + ('dependencies_id.name', 'in', installed_modules_names), ] - # ('conf_visibility', '=', 'only_if_depends'), - # self.search(['conf_']) @api.model def set_adhoc_summary(self): @@ -123,32 +141,13 @@ def get_computed_summary(self): self.computed_summary = self.adhoc_summary or self.summary @api.multi - def open_module(self): - self.ensure_one() - module_form = self.env.ref( - 'adhoc_modules.view_adhoc_module_module_form', False) - if not module_form: - return False - return { - 'name': _('Module Description'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': self._model, - 'views': [(module_form.id, 'form')], - 'view_id': module_form.id, - 'res_id': self.id, - 'target': 'current', - # 'target': 'new', - 'context': self._context, - # top open in editable form - 'flags': { - 'form': {'action_buttons': True, 'options': {'mode': 'edit'}}} - } + def button_un_ignore(self): + return self.write({'state': 'uninstalled'}) @api.multi def button_ignore(self): - return self.write({'ignored': True}) + return self.write({'state': 'ignored'}) + # return self.write({'ignored': True}) @api.multi def button_set_to_install(self): diff --git a/adhoc_modules/models/module_dependency.py b/adhoc_modules/models/module_dependency.py new file mode 100644 index 0000000..2f3b184 --- /dev/null +++ b/adhoc_modules/models/module_dependency.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields +# from openerp.exceptions import Warning +import logging + +_logger = logging.getLogger(__name__) + + +class module_dependency(models.Model): + _inherit = "ir.module.module.dependency" + + state = fields.Selection( + selection_add=[('ignored', 'Ignored')] + ) + # state = fields.Selection(DEP_STATES, string='Status', compute='_compute_state') + # depend_id = fields.Many2one(search='search_depend_id') + + # @api.model + # def search_depend_id(self): + # return diff --git a/adhoc_modules/models/support.py b/adhoc_modules/models/support.py index 0f1aa91..ad36b5d 100644 --- a/adhoc_modules/models/support.py +++ b/adhoc_modules/models/support.py @@ -24,6 +24,9 @@ def _cron_update_adhoc_modules(self): @api.multi def get_adhoc_modules_data(self): + # we send contract_id so it can be used in other functions + self = self.with_context( + contract_id=self.contract_id) self.ensure_one() _logger.info( "Updating Updating ADHOC Modules Data For Contract %s" % self.name) @@ -38,9 +41,12 @@ def get_adhoc_modules_data(self): @api.model def update_adhoc_categories(self, client): + contract_id = self._context.get('contract_id') fields = [ 'name', + 'code', 'parent_id', + 'visibility', 'description', 'sequence', ] @@ -48,17 +54,23 @@ def update_adhoc_categories(self, client): remote_model = client.model('adhoc.module.category') remote_datas = remote_model.search_read( - [], fields, order='parent_left') + [], fields, 0, None, 'parent_left') for remote_data in remote_datas: # we dont wont or need id - remote_data.pop('id') + category_id = remote_data.pop('id') parent_data = remote_data.pop('parent_id') if parent_data: + parent_code = remote_model.search_read( + [('id', '=', parent_data[0])], ['code'])[0]['code'] parent = local_model.search([ - ('name', '=', parent_data[1])], limit=1) + ('code', '=', parent_code)], limit=1) remote_data['parent_id'] = parent.id local_record = local_model.search([ ('name', '=', remote_data.get('name'))], limit=1) + if remote_data['visibility'] == 'product_required': + remote_data['contracted_product'] = ( + remote_model.get_related_contracted_product( + category_id, contract_id)) if local_record: local_record.write(remote_data) else: @@ -89,12 +101,20 @@ def update_adhoc_modules(self, client): remote_data.pop('id') category_data = remote_data.pop('adhoc_category_id') if category_data: + category_code = client.model( + 'adhoc.module.category').search_read( + [('id', '=', category_data[0])], ['code'])[0]['code'] adhoc_category = self.env['adhoc.module.category'].search([ - ('name', '=', category_data[1])], limit=1) - remote_data['adhoc_category_id'] = adhoc_category.id + ('code', '=', category_code)], limit=1) + remote_data['adhoc_category_id'] = ( + adhoc_category and adhoc_category.id or False) local_record = local_model.search([ ('name', '=', remote_data.get('name'))], limit=1) if local_record: local_record.write(remote_data) + else: + _logger.warning( + 'Module %s not found on database, you can try updating db' + ' list' % remote_data.get('name')) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/adhoc_modules/views/adhoc_module_category_view.xml b/adhoc_modules/views/adhoc_module_category_view.xml index dba4ae1..1948039 100644 --- a/adhoc_modules/views/adhoc_module_category_view.xml +++ b/adhoc_modules/views/adhoc_module_category_view.xml @@ -13,38 +13,61 @@ + + -
-
-

- - - +
+

+ + +
+

Contactar a ADHOC
para contratar

+ +
@@ -53,24 +76,27 @@ - + adhoc.module.category.search adhoc.module.category + + + + - + adhoc.module.category.tree adhoc.module.category - @@ -89,6 +115,7 @@ + @@ -107,8 +134,8 @@ ADHOC Categories adhoc.module.category form - kanban,tree,form - {'search_default_root_categories': 1} + kanban + {'search_default_root_categories': 1, 'search_default_to_revise': 1, 'search_default_not_contracted': 1} diff --git a/adhoc_modules/views/adhoc_module_view.xml b/adhoc_modules/views/adhoc_module_view.xml index b888438..5b068ec 100644 --- a/adhoc_modules/views/adhoc_module_view.xml +++ b/adhoc_modules/views/adhoc_module_view.xml @@ -9,14 +9,11 @@ - - - - - + + + - - + @@ -37,43 +34,47 @@ + - primary + - - top - - - + 1 - + 1 - - + - - @@ -107,12 +114,13 @@ ADHOC Modules ir.module.module form - tree,kanban,form - [('adhoc_category_id', '!=', False)] - + kanban,tree,form + [('visible', '=', True)] + {'search_default_state': 'uninstalled'} - + + diff --git a/adhoc_modules_server/__openerp__.py b/adhoc_modules_server/__openerp__.py index 1368a3a..48c5b20 100644 --- a/adhoc_modules_server/__openerp__.py +++ b/adhoc_modules_server/__openerp__.py @@ -27,6 +27,7 @@ 'depends': [ 'adhoc_modules', 'mass_editing', + 'web_support_server', ], # 'external_dependencies': { # 'python': ['octuhub'] diff --git a/adhoc_modules_server/models/__init__.py b/adhoc_modules_server/models/__init__.py index ec746b6..4c84557 100644 --- a/adhoc_modules_server/models/__init__.py +++ b/adhoc_modules_server/models/__init__.py @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- from . import adhoc_module_repository from . import adhoc_module_dependency -# from . import adhoc_module_category +from . import adhoc_module_category from . import adhoc_module +from . import product_template diff --git a/adhoc_modules_server/models/adhoc_module.py b/adhoc_modules_server/models/adhoc_module.py index 7f23633..95ace9a 100644 --- a/adhoc_modules_server/models/adhoc_module.py +++ b/adhoc_modules_server/models/adhoc_module.py @@ -3,7 +3,7 @@ # For copyright and license notices, see __openerp__.py file in module root # directory ############################################################################## -from openerp import models, fields, api +from openerp import models, fields, api, _ # from openerp.exceptions import Warning import logging @@ -38,7 +38,7 @@ def create(self, vals): vals['name'], module.repository_id.branch.replace('.', '_')), 'model': self._name, - 'module': 'adhoc_module_module', + 'module': 'adhoc_module_server', 'res_id': module.id, 'noupdate': True, } @@ -61,3 +61,27 @@ def _update_dependencies(self, depends=None): 'DELETE FROM adhoc_module_dependency WHERE module_id = %s ' 'and name = %s', (self.id, dep)) self.invalidate_cache(['dependencies_id']) + + @api.multi + def open_module(self): + self.ensure_one() + module_form = self.env.ref( + 'adhoc_modules.view_adhoc_module_module_form', False) + if not module_form: + return False + return { + 'name': _('Module Description'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': self._model, + 'views': [(module_form.id, 'form')], + 'view_id': module_form.id, + 'res_id': self.id, + 'target': 'current', + # 'target': 'new', + 'context': self._context, + # top open in editable form + 'flags': { + 'form': {'action_buttons': True, 'options': {'mode': 'edit'}}} + } \ No newline at end of file diff --git a/adhoc_modules_server/models/adhoc_module_category.py b/adhoc_modules_server/models/adhoc_module_category.py index 196b1c9..3246086 100644 --- a/adhoc_modules_server/models/adhoc_module_category.py +++ b/adhoc_modules_server/models/adhoc_module_category.py @@ -5,193 +5,55 @@ ############################################################################## from openerp import models, fields, api import logging - _logger = logging.getLogger(__name__) class AdhocModuleCategory(models.Model): - _name = 'adhoc.module.category' - # _parent_store = True - _order = 'sequence' - # _rec_name = 'display_name' + _inherit = 'adhoc.module.category' - name = fields.Char( + product_tmpl_ids = fields.Many2many( + 'product.template', + 'adhoc_module_category_product_rel', + 'adhoca_category_id', 'product_tmpl_id', + 'Products', required=True, ) - count_modules = fields.Integer( - string='# Modules', - compute='get_count_modules', - ) - count_suggested_modules = fields.Integer( - string='# Suggested Modules', - compute='get_count_modules', - ) - count_subcategories_modules = fields.Integer( - string='# Subcategories Modules', - compute='get_count_subcategories_modules', - ) - count_suggested_subcategories_modules = fields.Integer( - string='# Suggested Subcategories Modules', - compute='get_count_subcategories_modules', + visibility = fields.Selection( + readonly=False, ) - count_subcategories = fields.Integer( - string='# Subcategories', - compute='get_count_subcategories', + contracted_product = fields.Char( + readonly=False, ) - count_suggested_subcategories = fields.Integer( - string='# Suggested Subcategories', - compute='get_count_subcategories', + name = fields.Char( + readonly=False, ) - color = fields.Integer( - string='Color Index', - compute='get_color', + code = fields.Char( + readonly=False, ) parent_id = fields.Many2one( - 'adhoc.module.category', - 'Parent Category', - select=True, + readonly=False, ) child_ids = fields.One2many( - 'adhoc.module.category', - 'parent_id', - 'Child Categories' + readonly=False, ) module_ids = fields.One2many( - 'adhoc.module.module', - 'adhoc_category_id', - 'Modules' + readonly=False, ) description = fields.Text( + readonly=False, ) sequence = fields.Integer( - 'Sequence', - default=10, - ) - display_name = fields.Char( - compute='get_display_name' + readonly=False, ) @api.multi - @api.depends('child_ids') - def get_display_name(self): - def get_names(cat): - """ Return the list [cat.name, cat.parent_id.name, ...] """ - res = [] - while cat: - res.append(cat.name) - cat = cat.parent_id - return res - for cat in self: - cat.display_name = " / ".join(reversed(get_names(cat))) - - @api.multi - def name_get(self): - result = [] - for record in self: - result.append((record.id, record.display_name)) - return result - - @api.one - @api.depends('child_ids') - def get_count_subcategories(self): - self.count_subcategories = len(self.child_ids) - self.count_suggested_subcategories = len(self.child_ids.filtered( - lambda x: x.count_suggested_subcategories_modules)) - - @api.multi - def get_subcategories_modules(self): - self.ensure_one() - return self.env['adhoc.module.module'].search([ - ('adhoc_category_id', 'child_of', self.id)]) - - @api.multi - def get_suggested_subcategories_modules(self): + def get_related_contracted_product(self, contract_id): self.ensure_one() - return self.env['adhoc.module.module'].search([ - ('adhoc_category_id', 'child_of', self.id), - ('ignored', '=', False), - ('state', '=', 'uninstalled'), + analytic_lines = self.env['account.analytic.invoice.line'].search([ + ('analytic_account_id.id', '=', contract_id), + ('product_id.product_tmpl_id', 'in', self.product_tmpl_ids.ids), ]) - - # @api.model - # def search_count_subcategories_modules(self, operator, value): - # sub_modules = self.get_suggested_subcategories_modules() - # if operator == 'like': - # operator = 'ilike' - # return [('name', operator, value)] - - @api.one - def get_count_subcategories_modules(self): - self.count_suggested_subcategories_modules = len( - self.get_suggested_subcategories_modules()) - self.count_subcategories_modules = len( - self.get_subcategories_modules()) - - @api.one - @api.depends('module_ids') - def get_count_modules(self): - self.count_modules = len(self.module_ids) - self.count_suggested_modules = len(self.module_ids.filtered( - lambda x: x.state != 'uninstalled' and not x.ignored)) - - # @api.depends('state') - @api.one - def get_color(self): - color = 4 - # if self.state == 'draft': - # color = 7 - # elif self.state == 'cancel': - # color = 1 - # elif self.state == 'inactive': - # color = 3 - # if self.overall_state != 'ok': - # color = 2 - self.color = color - - @api.multi - def action_subcategories(self): - self.ensure_one() - action = self.env['ir.model.data'].xmlid_to_object( - 'adhoc_modules.action_adhoc_module_category') - - if not action: - return False - res = action.read()[0] - res['context'] = { - 'search_default_parent_id': self.id, - } - return res - - @api.multi - def action_modules(self): - self.ensure_one() - action = self.env['ir.model.data'].xmlid_to_object( - 'adhoc_modules.action_adhoc_module_module') - - if not action: - return False - res = action.read()[0] - res['domain'] = [('adhoc_category_id', '=', self.id)] - res['context'] = { - 'search_default_not_ignored': 1, - 'search_default_state': 'uninstalled', - } - return res - - @api.multi - def action_subcategories_modules(self): - self.ensure_one() - action = self.env['ir.model.data'].xmlid_to_object( - 'adhoc_modules.action_adhoc_module_module') - - if not action: + if analytic_lines: + return analytic_lines.mapped('product_id.name') + else: return False - res = action.read()[0] - modules = self.get_subcategories_modules() - res['domain'] = [('id', 'in', modules.ids)] - res['context'] = { - 'search_default_not_ignored': 1, - 'search_default_state': 'uninstalled', - 'search_default_group_by_adhoc_category': 1 - } - return res diff --git a/adhoc_modules_server/models/product_template.py b/adhoc_modules_server/models/product_template.py new file mode 100644 index 0000000..d5c10f8 --- /dev/null +++ b/adhoc_modules_server/models/product_template.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, fields, api +import logging +_logger = logging.getLogger(__name__) + + +class ProductTempalte(models.Model): + _inherit = 'product.template' + + # adhoc_category_ids = fields.Many2many( + # 'product.template', + # 'adhoc_module_category_product_rel', + # 'adhoca_category_id', 'product_tmpl_id', + # 'Products', + # required=True, + # ) diff --git a/adhoc_modules_server/views/adhoc_module_category_view.xml b/adhoc_modules_server/views/adhoc_module_category_view.xml index 9ddf1b3..9c160e6 100644 --- a/adhoc_modules_server/views/adhoc_module_category_view.xml +++ b/adhoc_modules_server/views/adhoc_module_category_view.xml @@ -9,6 +9,9 @@ +
+ oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click +
true @@ -16,12 +19,15 @@ - + adhoc.module.category.tree adhoc.module.category - + + + + true @@ -35,6 +41,10 @@ + + + +
true true diff --git a/adhoc_modules_server/views/adhoc_module_view.xml b/adhoc_modules_server/views/adhoc_module_view.xml index 3c4b272..52f622c 100644 --- a/adhoc_modules_server/views/adhoc_module_view.xml +++ b/adhoc_modules_server/views/adhoc_module_view.xml @@ -60,6 +60,8 @@ top + + 1 diff --git a/web_support_client/models/support.py b/web_support_client/models/support.py index 71b7019..11392ec 100755 --- a/web_support_client/models/support.py +++ b/web_support_client/models/support.py @@ -99,6 +99,7 @@ def get_active_contract(self): active_contract = self.search([], limit=1) if not active_contract: raise Warning(_('No active contract configured')) + # we send contract_id so it can be used in other functions return active_contract @api.multi From b8e15bdfc5b6a67de09375d152d27386ffc717bb Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Fri, 22 Apr 2016 18:49:24 -0300 Subject: [PATCH 05/11] IMp --- adhoc_modules/models/db_configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adhoc_modules/models/db_configuration.py b/adhoc_modules/models/db_configuration.py index ad66a15..b2c8a20 100644 --- a/adhoc_modules/models/db_configuration.py +++ b/adhoc_modules/models/db_configuration.py @@ -11,5 +11,5 @@ # from dateutil.relativedelta import relativedelta -class database_tools_configuration(models.TransientModel): - _inherit = 'db.configuration' +# class database_tools_configuration(models.TransientModel): +# _inherit = 'db.configuration' From a61b658946deebcfb34d3f80a963f082c72d1017 Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Mon, 25 Apr 2016 14:52:35 -0300 Subject: [PATCH 06/11] IMP --- adhoc_modules/models/ir_module.py | 10 ++++++- adhoc_modules/models/support.py | 4 +-- adhoc_modules/views/adhoc_module_view.xml | 4 +-- adhoc_modules_server/models/adhoc_module.py | 5 ++++ .../models/adhoc_module_category.py | 27 +++++++++++++------ .../security/.~lock.ir.model.access.csv# | 1 - .../security/ir.model.access.csv | 3 ++- .../views/adhoc_module_category_view.xml | 26 +++++++++--------- .../views/adhoc_module_repository_view.xml | 6 ++--- .../views/adhoc_module_view.xml | 4 +-- 10 files changed, 58 insertions(+), 32 deletions(-) delete mode 100644 adhoc_modules_server/security/.~lock.ir.model.access.csv# diff --git a/adhoc_modules/models/ir_module.py b/adhoc_modules/models/ir_module.py index c5b251d..9b2d6f1 100644 --- a/adhoc_modules/models/ir_module.py +++ b/adhoc_modules/models/ir_module.py @@ -95,7 +95,7 @@ class AdhocModuleModule(models.Model): # to_check = True # if self.state != 'uninstalled': # to_check = False - # elif (self.conf_visibility == 'only_if_depends' and not self.depends): + # elif (self.conf_visibility == 'only_if_depends' and not self.depend): # to_check = False # elif self.conf_visibility != 'normal': # to_check = False @@ -110,6 +110,11 @@ def get_visible(self): visible = True elif not self.adhoc_category_id: visible = False + elif ( + self.adhoc_category_id.visibility == 'product_required' and + not self.adhoc_category_id.contracted_product + ): + visible = False elif self.conf_visibility == 'only_if_depends': uninstalled_dependencies = self.dependencies_id.mapped( 'depend_id').filtered( @@ -127,6 +132,9 @@ def search_visible(self, operator, value): return [ '|', ('state', 'in', ['installed', 'to install']), '&', ('adhoc_category_id', '!=', False), + '&', '|', ('adhoc_category_id.visibility', '=', 'normal'), + '&', ('adhoc_category_id.visibility', '=', 'product_required'), + ('adhoc_category_id.contracted_product', '!=', False), '|', ('conf_visibility', '=', 'normal'), '&', ('conf_visibility', '=', 'only_if_depends'), ('dependencies_id.name', 'in', installed_modules_names), diff --git a/adhoc_modules/models/support.py b/adhoc_modules/models/support.py index ad36b5d..ba284f5 100644 --- a/adhoc_modules/models/support.py +++ b/adhoc_modules/models/support.py @@ -51,7 +51,7 @@ def update_adhoc_categories(self, client): 'sequence', ] updated_records = local_model = self.env['adhoc.module.category'] - remote_model = client.model('adhoc.module.category') + remote_model = client.model('adhoc.module.category.server') remote_datas = remote_model.search_read( [], fields, 0, None, 'parent_left') @@ -102,7 +102,7 @@ def update_adhoc_modules(self, client): category_data = remote_data.pop('adhoc_category_id') if category_data: category_code = client.model( - 'adhoc.module.category').search_read( + 'adhoc.module.category.server').search_read( [('id', '=', category_data[0])], ['code'])[0]['code'] adhoc_category = self.env['adhoc.module.category'].search([ ('code', '=', category_code)], limit=1) diff --git a/adhoc_modules/views/adhoc_module_view.xml b/adhoc_modules/views/adhoc_module_view.xml index 5b068ec..51e8f26 100644 --- a/adhoc_modules/views/adhoc_module_view.xml +++ b/adhoc_modules/views/adhoc_module_view.xml @@ -119,8 +119,8 @@ {'search_default_state': 'uninstalled'} - - + + diff --git a/adhoc_modules_server/models/adhoc_module.py b/adhoc_modules_server/models/adhoc_module.py index 95ace9a..c2914bb 100644 --- a/adhoc_modules_server/models/adhoc_module.py +++ b/adhoc_modules_server/models/adhoc_module.py @@ -14,6 +14,11 @@ class AdhocModuleModule(models.Model): _inherit = 'ir.module.module' _name = 'adhoc.module.module' + adhoc_category_id = fields.Many2one( + 'adhoc.module.category.server', + 'ADHOC Category', + auto_join=True, + ) repository_id = fields.Many2one( 'adhoc.module.repository', 'Repository', diff --git a/adhoc_modules_server/models/adhoc_module_category.py b/adhoc_modules_server/models/adhoc_module_category.py index 3246086..d456834 100644 --- a/adhoc_modules_server/models/adhoc_module_category.py +++ b/adhoc_modules_server/models/adhoc_module_category.py @@ -10,33 +10,44 @@ class AdhocModuleCategory(models.Model): _inherit = 'adhoc.module.category' + _name = 'adhoc.module.category.server' product_tmpl_ids = fields.Many2many( 'product.template', 'adhoc_module_category_product_rel', 'adhoca_category_id', 'product_tmpl_id', 'Products', - required=True, ) - visibility = fields.Selection( + module_ids = fields.One2many( + 'adhoc.module.module', + 'adhoc_category_id', + 'Modules', + # domain=[('visible', '=', True)], readonly=False, ) - contracted_product = fields.Char( + parent_id = fields.Many2one( + 'adhoc.module.category.server', + 'Parent Category', + select=True, + ondelete='restrict', readonly=False, ) - name = fields.Char( + child_ids = fields.One2many( + 'adhoc.module.category.server', + 'parent_id', + 'Child Categories', readonly=False, ) - code = fields.Char( + visibility = fields.Selection( readonly=False, ) - parent_id = fields.Many2one( + contracted_product = fields.Char( readonly=False, ) - child_ids = fields.One2many( + name = fields.Char( readonly=False, ) - module_ids = fields.One2many( + code = fields.Char( readonly=False, ) description = fields.Text( diff --git a/adhoc_modules_server/security/.~lock.ir.model.access.csv# b/adhoc_modules_server/security/.~lock.ir.model.access.csv# deleted file mode 100644 index 426d311..0000000 --- a/adhoc_modules_server/security/.~lock.ir.model.access.csv# +++ /dev/null @@ -1 +0,0 @@ -,chosco,localhost,15.04.2016 17:31,file:///home/chosco/.config/libreoffice/4; \ No newline at end of file diff --git a/adhoc_modules_server/security/ir.model.access.csv b/adhoc_modules_server/security/ir.model.access.csv index 8dd8244..00e0f44 100644 --- a/adhoc_modules_server/security/ir.model.access.csv +++ b/adhoc_modules_server/security/ir.model.access.csv @@ -1,7 +1,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_adhoc_module_module_all,access_adhoc_module_module_all,model_adhoc_module_module,,1,0,0,0 access_adhoc_module_module_config,access_adhoc_module_module_config,model_adhoc_module_module,base.group_system,1,1,1,1 -access_adhoc_module_category_config,access_adhoc_module_category_config,adhoc_modules.model_adhoc_module_category,base.group_system,1,1,1,1 +access_adhoc_module_category_server_config,access_adhoc_module_category_server_config,model_adhoc_module_category_server,base.group_system,1,1,1,1 +access_adhoc_module_category_server_all,access_adhoc_module_category_server_all,model_adhoc_module_category_server,,1,0,0,0 access_adhoc_module_repository_all,access_adhoc_module_repository_all,model_adhoc_module_repository,,1,0,0,0 access_adhoc_module_repository_config,access_adhoc_module_repository_config,model_adhoc_module_repository,base.group_system,1,1,1,1 access_adhoc_module_dependency_all,access_adhoc_module_dependency_all,model_adhoc_module_dependency,,1,0,0,0 diff --git a/adhoc_modules_server/views/adhoc_module_category_view.xml b/adhoc_modules_server/views/adhoc_module_category_view.xml index 9c160e6..987d786 100644 --- a/adhoc_modules_server/views/adhoc_module_category_view.xml +++ b/adhoc_modules_server/views/adhoc_module_category_view.xml @@ -3,11 +3,11 @@ - + + primary
oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click @@ -16,14 +16,14 @@ true - + --> adhoc.module.category.tree - adhoc.module.category + adhoc.module.category.server - + primary @@ -37,12 +37,13 @@ adhoc.module.category.form - adhoc.module.category + adhoc.module.category.server - + primary + @@ -55,13 +56,14 @@ ADHOC Categories - adhoc.module.category + adhoc.module.category.server form - kanban,tree,form + tree,form + {'search_default_root_categories': 1} - + - \ No newline at end of file + diff --git a/adhoc_modules_server/views/adhoc_module_repository_view.xml b/adhoc_modules_server/views/adhoc_module_repository_view.xml index 17edb97..07183d4 100644 --- a/adhoc_modules_server/views/adhoc_module_repository_view.xml +++ b/adhoc_modules_server/views/adhoc_module_repository_view.xml @@ -63,9 +63,9 @@ tree,form - + - + - \ No newline at end of file + diff --git a/adhoc_modules_server/views/adhoc_module_view.xml b/adhoc_modules_server/views/adhoc_module_view.xml index 52f622c..7acebad 100644 --- a/adhoc_modules_server/views/adhoc_module_view.xml +++ b/adhoc_modules_server/views/adhoc_module_view.xml @@ -122,7 +122,7 @@ {'search_default_un_categorized': 1, 'search_default_conf_visibility': 'normal'} - + - \ No newline at end of file + From 56aff350ac748a107e54112922863f3cec60c9f4 Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Mon, 25 Apr 2016 16:13:31 -0300 Subject: [PATCH 07/11] FIX lint --- adhoc_modules/__init__.py | 2 +- adhoc_modules/__openerp__.py | 1 + adhoc_modules/models/adhoc_module_category.py | 54 +++++++++---------- adhoc_modules/models/ir_module.py | 44 ++++++++------- adhoc_modules/models/module_dependency.py | 2 +- adhoc_modules/models/support.py | 4 +- 6 files changed, 58 insertions(+), 49 deletions(-) diff --git a/adhoc_modules/__init__.py b/adhoc_modules/__init__.py index c6a3ab6..b8f0d04 100644 --- a/adhoc_modules/__init__.py +++ b/adhoc_modules/__init__.py @@ -1,4 +1,4 @@ # -*- encoding: utf-8 -*- from . import models +from . import wizard # from . import controllers -# from . import wizard diff --git a/adhoc_modules/__openerp__.py b/adhoc_modules/__openerp__.py index 803a33a..d6ae121 100644 --- a/adhoc_modules/__openerp__.py +++ b/adhoc_modules/__openerp__.py @@ -36,6 +36,7 @@ 'views/adhoc_module_view.xml', 'views/support_view.xml', 'views/db_configuration_view.xml', + 'wizard/module_upgrade_view.xml', 'security/ir.model.access.csv', ], 'demo': [], diff --git a/adhoc_modules/models/adhoc_module_category.py b/adhoc_modules/models/adhoc_module_category.py index e1bdfed..8773f10 100644 --- a/adhoc_modules/models/adhoc_module_category.py +++ b/adhoc_modules/models/adhoc_module_category.py @@ -22,100 +22,100 @@ class AdhocModuleCategory(models.Model): visibility = fields.Selection([ ('normal', 'Normal'), ('product_required', 'Product Required'), - ], + ], required=True, readonly=True, default='normal', - ) + ) contracted_product = fields.Char( readonly=True, - ) + ) name = fields.Char( readonly=True, required=True, - ) + ) code = fields.Char( readonly=True, # required=True, # readonly=True, # default='/', - ) + ) count_modules = fields.Integer( string='# Modules', compute='get_count_modules', - ) + ) count_pending_modules = fields.Integer( string='# Revised Modules', compute='get_count_modules', - ) + ) count_revised_modules = fields.Integer( string='# Revised Modules', compute='get_count_modules', - ) + ) # count_subcategories_modules = fields.Integer( - # string='# Subcategories Modules', + # string='# Subcategories Modules', # compute='get_count_subcategories_modules', # ) # count_suggested_subcategories_modules = fields.Integer( - # string='# Suggested Subcategories Modules', + # string='# Suggested Subcategories Modules', # compute='get_count_subcategories_modules', # ) count_subcategories = fields.Integer( string='# Subcategories', compute='get_count_subcategories', - ) + ) count_revised_subcategories = fields.Integer( string='# Revised Subcategories', compute='get_count_subcategories', - ) + ) color = fields.Integer( string='Color Index', compute='get_color', - ) + ) parent_id = fields.Many2one( 'adhoc.module.category', 'Parent Category', select=True, ondelete='restrict', readonly=True, - ) + ) parent_left = fields.Integer( 'Parent Left', select=1 - ) + ) parent_right = fields.Integer( 'Parent Right', select=1 - ) + ) child_ids = fields.One2many( 'adhoc.module.category', 'parent_id', 'Child Categories', readonly=True, - ) + ) module_ids = fields.One2many( 'ir.module.module', 'adhoc_category_id', 'Modules', domain=[('visible', '=', True)], readonly=True, - ) + ) description = fields.Text( readonly=True, - ) + ) sequence = fields.Integer( 'Sequence', default=10, readonly=True, - ) + ) to_revise = fields.Boolean( compute='get_to_revise', search='search_to_revise', - ) + ) display_name = fields.Char( compute='get_display_name', # store=True - ) + ) _sql_constraints = [ ('code_uniq', 'unique(code)', @@ -143,7 +143,7 @@ def search_to_revise(self, operator, value): return [ '|', ('module_ids.state', 'in', ['uninstalled']), ('child_ids.module_ids.state', 'in', ['uninstalled']), - ] + ] @api.one @api.constrains('child_ids', 'name', 'parent_id') @@ -194,7 +194,7 @@ def get_suggested_subcategories_modules(self): return self.module_ids.search([ ('adhoc_category_id', 'child_of', self.id), ('state', '=', 'uninstalled'), - ]) + ]) # @api.model # def search_count_subcategories_modules(self, operator, value): @@ -251,7 +251,7 @@ def action_subcategories(self): 'search_default_parent_id': self.id, 'search_default_to_revise': 1, 'search_default_not_contracted': 1 - } + } return res @api.multi @@ -267,7 +267,7 @@ def action_modules(self): res['context'] = { # 'search_default_not_ignored': 1, 'search_default_state': 'uninstalled', - } + } return res # @api.multi @@ -282,7 +282,7 @@ def action_modules(self): # modules = self.get_subcategories_modules() # res['domain'] = [('id', 'in', modules.ids)] # res['context'] = { - # # 'search_default_not_ignored': 1, + # 'search_default_not_ignored': 1, # 'search_default_state': 'uninstalled', # 'search_default_group_by_adhoc_category': 1 # } diff --git a/adhoc_modules/models/ir_module.py b/adhoc_modules/models/ir_module.py index 9b2d6f1..a42eb5a 100644 --- a/adhoc_modules/models/ir_module.py +++ b/adhoc_modules/models/ir_module.py @@ -17,31 +17,37 @@ class AdhocModuleModule(models.Model): 'adhoc.module.category', 'ADHOC Category', auto_join=True, - ) + readonly=True, + ) computed_summary = fields.Char( compute='get_computed_summary', inverse='set_adhoc_summary', - ) + readonly=True, + ) adhoc_summary = fields.Char( - ) + readonly=True, + ) adhoc_description_html = fields.Html( - ) + readonly=True, + ) support_type = fields.Selection([ ('supported', 'Soportado'), ('unsupported', 'No Soportado'), # ('unsupport_all', 'No Soporta BD'), - ], + ], string='Support Type', - ) + readonly=True, + ) review = fields.Selection([ ('0', 'Not Recommended'), ('1', 'Only If Necessary'), ('2', 'Neutral'), ('3', 'Recomendado'), ('4', 'Muy Recomendado'), - ], 'Opinion', - select=True - ) + ], 'Opinion', + select=True, + readonly=True, + ) conf_visibility = fields.Selection([ # instalables ('normal', 'Normal'), @@ -54,9 +60,10 @@ class AdhocModuleModule(models.Model): ('to_review', 'A Revisar'), ('future_versions', 'Versiones Futuras'), ('unusable', 'No Usable'), - ], + ], 'Visibility', required=True, + readonly=True, default='normal', help="Módulos que se pueden instalar:\n" "* Normal: visible para ser instalado\n" @@ -70,23 +77,24 @@ class AdhocModuleModule(models.Model): "* A Revisar: hay que analizar como lo vamos a utilizar\n" "* Versiones Futuras: se va a incorporar más adelante\n" "* No Usable: no se usa ni se va a sugerir uso en versiones futuras\n" - ) + ) visibility_obs = fields.Char( - 'Visibility Observation' - ) + 'Visibility Observation', + readonly=True, + ) visible = fields.Boolean( compute='get_visible', search='search_visible', - ) + ) # ignored = fields.Boolean( # 'Ignored' # ) state = fields.Selection( selection_add=[('ignored', 'Ignored')] - ) + ) # to_check = fields.Boolean( # compute='get_to_check', - # # search='search_to_check', + # search='search_to_check', # string='To Check', # ) @@ -113,7 +121,7 @@ def get_visible(self): elif ( self.adhoc_category_id.visibility == 'product_required' and not self.adhoc_category_id.contracted_product - ): + ): visible = False elif self.conf_visibility == 'only_if_depends': uninstalled_dependencies = self.dependencies_id.mapped( @@ -138,7 +146,7 @@ def search_visible(self, operator, value): '|', ('conf_visibility', '=', 'normal'), '&', ('conf_visibility', '=', 'only_if_depends'), ('dependencies_id.name', 'in', installed_modules_names), - ] + ] @api.model def set_adhoc_summary(self): diff --git a/adhoc_modules/models/module_dependency.py b/adhoc_modules/models/module_dependency.py index 2f3b184..c779dcf 100644 --- a/adhoc_modules/models/module_dependency.py +++ b/adhoc_modules/models/module_dependency.py @@ -15,7 +15,7 @@ class module_dependency(models.Model): state = fields.Selection( selection_add=[('ignored', 'Ignored')] - ) + ) # state = fields.Selection(DEP_STATES, string='Status', compute='_compute_state') # depend_id = fields.Many2one(search='search_depend_id') diff --git a/adhoc_modules/models/support.py b/adhoc_modules/models/support.py index ba284f5..4fc7cb0 100644 --- a/adhoc_modules/models/support.py +++ b/adhoc_modules/models/support.py @@ -49,7 +49,7 @@ def update_adhoc_categories(self, client): 'visibility', 'description', 'sequence', - ] + ] updated_records = local_model = self.env['adhoc.module.category'] remote_model = client.model('adhoc.module.category.server') @@ -90,7 +90,7 @@ def update_adhoc_modules(self, client): 'review', 'conf_visibility', 'visibility_obs', - ] + ] local_model = self.env['ir.module.module'] remote_model = client.model('adhoc.module.module') From bec42281fa112925975a0e0246e7455c14e604eb Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Mon, 25 Apr 2016 19:34:26 -0300 Subject: [PATCH 08/11] IMP --- adhoc_modules/__openerp__.py | 8 +-- adhoc_modules/models/db_configuration.py | 2 +- adhoc_modules/models/ir_module.py | 35 +++++------ adhoc_modules/models/module_dependency.py | 7 --- .../views/adhoc_module_category_view.xml | 18 ------ adhoc_modules/views/adhoc_module_view.xml | 1 - adhoc_modules/views/support_view.xml | 9 --- adhoc_modules/wizard/__init__.py | 2 + adhoc_modules/wizard/module_upgrade.py | 47 ++++++++++++++ adhoc_modules/wizard/module_upgrade_view.xml | 28 +++++++++ adhoc_modules_server/models/adhoc_module.py | 37 ++++++++--- .../models/adhoc_module_category.py | 54 ++++++++++++---- .../models/adhoc_module_dependency.py | 10 +-- .../models/adhoc_module_repository.py | 14 ++--- .../models/product_template.py | 24 ++++---- .../views/adhoc_module_category_view.xml | 61 ++++++++++++++++++- .../views/adhoc_module_view.xml | 9 ++- database_tools/models/database.py | 50 +++++++-------- 18 files changed, 284 insertions(+), 132 deletions(-) create mode 100644 adhoc_modules/wizard/__init__.py create mode 100644 adhoc_modules/wizard/module_upgrade.py create mode 100644 adhoc_modules/wizard/module_upgrade_view.xml diff --git a/adhoc_modules/__openerp__.py b/adhoc_modules/__openerp__.py index d6ae121..121f9f8 100644 --- a/adhoc_modules/__openerp__.py +++ b/adhoc_modules/__openerp__.py @@ -21,16 +21,16 @@ { "name": "ADHOC Modules", "version": "8.0.0.0.0", - 'author': 'ADHOC SA', + 'author': 'ADHOC SA', 'website': 'www.adhoc.com.ar', 'license': 'AGPL-3', 'depends': [ # module to fetch modules info 'web_support_client', 'database_tools', - ], + ], 'external_dependencies': { - }, + }, 'data': [ 'views/adhoc_module_category_view.xml', 'views/adhoc_module_view.xml', @@ -38,7 +38,7 @@ 'views/db_configuration_view.xml', 'wizard/module_upgrade_view.xml', 'security/ir.model.access.csv', - ], + ], 'demo': [], 'test': [], 'installable': True, diff --git a/adhoc_modules/models/db_configuration.py b/adhoc_modules/models/db_configuration.py index b2c8a20..8b48bff 100644 --- a/adhoc_modules/models/db_configuration.py +++ b/adhoc_modules/models/db_configuration.py @@ -3,7 +3,7 @@ # For copyright and license notices, see __openerp__.py file in module root # directory ############################################################################## -from openerp import models +# from openerp import models # from openerp import pooler # from openerp.exceptions import Warning # from datetime import datetime diff --git a/adhoc_modules/models/ir_module.py b/adhoc_modules/models/ir_module.py index a42eb5a..4aa80fb 100644 --- a/adhoc_modules/models/ir_module.py +++ b/adhoc_modules/models/ir_module.py @@ -4,7 +4,7 @@ # directory ############################################################################## from openerp import models, fields, api, _ -# from openerp.exceptions import Warning +from openerp.exceptions import Warning import logging _logger = logging.getLogger(__name__) @@ -53,7 +53,8 @@ class AdhocModuleModule(models.Model): ('normal', 'Normal'), ('only_if_depends', 'Solo si dependencias'), ('auto_install', 'Auto Install'), - ('auto_install_by_module', 'Auto Install by Module'), + # los auto install por defecto los estamos filtrando y no categorizando + # ('auto_install_by_module', 'Auto Install by Module'), ('installed_by_others', 'Instalado por Otro'), ('on_config_wizard', 'En asistente de configuración'), # no instalable @@ -86,28 +87,20 @@ class AdhocModuleModule(models.Model): compute='get_visible', search='search_visible', ) - # ignored = fields.Boolean( - # 'Ignored' - # ) state = fields.Selection( selection_add=[('ignored', 'Ignored')] ) - # to_check = fields.Boolean( - # compute='get_to_check', - # search='search_to_check', - # string='To Check', - # ) - - # @api.one - # def get_to_check(self): - # to_check = True - # if self.state != 'uninstalled': - # to_check = False - # elif (self.conf_visibility == 'only_if_depends' and not self.depend): - # to_check = False - # elif self.conf_visibility != 'normal': - # to_check = False - # self.to_check = to_check + + @api.one + @api.constrains('state') + def check_module_is_installable(self): + uninstallables = ['to_review', 'future_versions', 'unusable'] + if ( + self.state == 'to install' and + self.conf_visibility in uninstallables): + raise Warning(_( + 'You can not install module %s as is %s') % ( + self.name, self.conf_visibility)) @api.one @api.depends('adhoc_category_id', 'conf_visibility') diff --git a/adhoc_modules/models/module_dependency.py b/adhoc_modules/models/module_dependency.py index c779dcf..8f669f8 100644 --- a/adhoc_modules/models/module_dependency.py +++ b/adhoc_modules/models/module_dependency.py @@ -4,7 +4,6 @@ # directory ############################################################################## from openerp import models, fields -# from openerp.exceptions import Warning import logging _logger = logging.getLogger(__name__) @@ -16,9 +15,3 @@ class module_dependency(models.Model): state = fields.Selection( selection_add=[('ignored', 'Ignored')] ) - # state = fields.Selection(DEP_STATES, string='Status', compute='_compute_state') - # depend_id = fields.Many2one(search='search_depend_id') - - # @api.model - # def search_depend_id(self): - # return diff --git a/adhoc_modules/views/adhoc_module_category_view.xml b/adhoc_modules/views/adhoc_module_category_view.xml index 1948039..b7f7ea8 100644 --- a/adhoc_modules/views/adhoc_module_category_view.xml +++ b/adhoc_modules/views/adhoc_module_category_view.xml @@ -50,24 +50,6 @@ >Subcategories
-
diff --git a/adhoc_modules/views/adhoc_module_view.xml b/adhoc_modules/views/adhoc_module_view.xml index 51e8f26..ea592ee 100644 --- a/adhoc_modules/views/adhoc_module_view.xml +++ b/adhoc_modules/views/adhoc_module_view.xml @@ -48,7 +48,6 @@ adhoc.ir.module.module.tree ir.module.module - primary diff --git a/adhoc_modules/views/support_view.xml b/adhoc_modules/views/support_view.xml index 6f36225..3a27d79 100644 --- a/adhoc_modules/views/support_view.xml +++ b/adhoc_modules/views/support_view.xml @@ -3,15 +3,6 @@ - support.contract.form support.contract diff --git a/adhoc_modules/wizard/__init__.py b/adhoc_modules/wizard/__init__.py new file mode 100644 index 0000000..fbea60a --- /dev/null +++ b/adhoc_modules/wizard/__init__.py @@ -0,0 +1,2 @@ +# -*- encoding: utf-8 -*- +from . import module_upgrade diff --git a/adhoc_modules/wizard/module_upgrade.py b/adhoc_modules/wizard/module_upgrade.py new file mode 100644 index 0000000..0a834fd --- /dev/null +++ b/adhoc_modules/wizard/module_upgrade.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# For copyright and license notices, see __openerp__.py file in module root +# directory +############################################################################## +from openerp import models, api, fields, _ +from openerp.exceptions import Warning + + +class BaseModulePreUpgrade(models.TransientModel): + """ Module Pre Upgrade """ + + _inherit = 'base.module.upgrade' + + recent_backup = fields.Boolean( + readonly=True, + ) + low_review_module_ids = fields.Many2many( + 'ir.module.module', + compute='get_low_review_modules', + string='Low Review Modules', + ) + + @api.one + @api.depends('module_info') + def get_low_review_modules(self): + low_review_modules = self.env['ir.module.module'].search([ + ('state', '=', 'to install'), + ('review', 'in', ['0', '1']), + ]) + self.low_review_module_ids = low_review_modules + + @api.multi + def backup_now(self): + db = self.env['db.database'].search([('type', '=', 'self')], limit=1) + if not db: + raise Warning(_( + 'No Database "Self" found on Database Tools Databses')) + db.action_database_backup() + self.recent_backup = True + return { + 'type': 'ir.actions.act_window', + 'res_model': self._name, + 'res_id': self.id, + 'view_mode': 'form', + 'target': 'new', + } diff --git a/adhoc_modules/wizard/module_upgrade_view.xml b/adhoc_modules/wizard/module_upgrade_view.xml new file mode 100644 index 0000000..65d4882 --- /dev/null +++ b/adhoc_modules/wizard/module_upgrade_view.xml @@ -0,0 +1,28 @@ + + + + + + base.module.upgrade + base.module.upgrade + + + + + + + + + + diff --git a/adhoc_modules_server/models/adhoc_module.py b/adhoc_modules_server/models/adhoc_module.py index c2914bb..d4fc3c4 100644 --- a/adhoc_modules_server/models/adhoc_module.py +++ b/adhoc_modules_server/models/adhoc_module.py @@ -18,20 +18,43 @@ class AdhocModuleModule(models.Model): 'adhoc.module.category.server', 'ADHOC Category', auto_join=True, - ) + readonly=False, + ) repository_id = fields.Many2one( 'adhoc.module.repository', 'Repository', ondelete='cascade', required=True, auto_join=True, - ) + readonly=True, + ) dependencies_id = fields.One2many( 'adhoc.module.dependency', 'module_id', 'Dependencies', readonly=True, - ) + ) + computed_summary = fields.Char( + readonly=False, + ) + adhoc_summary = fields.Char( + readonly=False, + ) + adhoc_description_html = fields.Html( + readonly=False, + ) + support_type = fields.Selection( + readonly=False, + ) + review = fields.Selection( + readonly=False, + ) + conf_visibility = fields.Selection( + readonly=False, + ) + visibility_obs = fields.Char( + readonly=False, + ) @api.model def create(self, vals): @@ -71,7 +94,7 @@ def _update_dependencies(self, depends=None): def open_module(self): self.ensure_one() module_form = self.env.ref( - 'adhoc_modules.view_adhoc_module_module_form', False) + 'adhoc_modules_server.view_adhoc_module_module_form', False) if not module_form: return False return { @@ -79,14 +102,14 @@ def open_module(self): 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', - 'res_model': self._model, + 'res_model': 'adhoc.module.module', 'views': [(module_form.id, 'form')], 'view_id': module_form.id, 'res_id': self.id, 'target': 'current', - # 'target': 'new', + 'target': 'new', 'context': self._context, # top open in editable form 'flags': { 'form': {'action_buttons': True, 'options': {'mode': 'edit'}}} - } \ No newline at end of file + } diff --git a/adhoc_modules_server/models/adhoc_module_category.py b/adhoc_modules_server/models/adhoc_module_category.py index d456834..b660300 100644 --- a/adhoc_modules_server/models/adhoc_module_category.py +++ b/adhoc_modules_server/models/adhoc_module_category.py @@ -17,45 +17,45 @@ class AdhocModuleCategory(models.Model): 'adhoc_module_category_product_rel', 'adhoca_category_id', 'product_tmpl_id', 'Products', - ) + ) module_ids = fields.One2many( 'adhoc.module.module', 'adhoc_category_id', 'Modules', # domain=[('visible', '=', True)], readonly=False, - ) + ) parent_id = fields.Many2one( 'adhoc.module.category.server', 'Parent Category', select=True, ondelete='restrict', readonly=False, - ) + ) child_ids = fields.One2many( 'adhoc.module.category.server', 'parent_id', 'Child Categories', readonly=False, - ) + ) visibility = fields.Selection( readonly=False, - ) + ) contracted_product = fields.Char( readonly=False, - ) + ) name = fields.Char( readonly=False, - ) + ) code = fields.Char( readonly=False, - ) + ) description = fields.Text( readonly=False, - ) + ) sequence = fields.Integer( readonly=False, - ) + ) @api.multi def get_related_contracted_product(self, contract_id): @@ -63,8 +63,40 @@ def get_related_contracted_product(self, contract_id): analytic_lines = self.env['account.analytic.invoice.line'].search([ ('analytic_account_id.id', '=', contract_id), ('product_id.product_tmpl_id', 'in', self.product_tmpl_ids.ids), - ]) + ]) if analytic_lines: return analytic_lines.mapped('product_id.name') else: return False + + @api.multi + def action_subcategories(self): + self.ensure_one() + action = self.env['ir.model.data'].xmlid_to_object( + 'adhoc_modules_server.action_adhoc_module_category') + + if not action: + return False + res = action.read()[0] + res['context'] = { + 'search_default_parent_id': self.id, + # 'search_default_to_revise': 1, + # 'search_default_not_contracted': 1 + } + return res + + @api.multi + def action_modules(self): + self.ensure_one() + action = self.env['ir.model.data'].xmlid_to_object( + 'adhoc_modules_server.action_adhoc_module_module') + + if not action: + return False + res = action.read()[0] + res['domain'] = [('adhoc_category_id', '=', self.id)] + res['context'] = { + # 'search_default_not_ignored': 1, + # 'search_default_state': 'uninstalled', + } + return res diff --git a/adhoc_modules_server/models/adhoc_module_dependency.py b/adhoc_modules_server/models/adhoc_module_dependency.py index 6011d99..c1f5df5 100644 --- a/adhoc_modules_server/models/adhoc_module_dependency.py +++ b/adhoc_modules_server/models/adhoc_module_dependency.py @@ -23,25 +23,25 @@ class AdhocModuleDependency(models.Model): # the dependency name name = fields.Char( index=True - ) + ) # the module that depends on it module_id = fields.Many2one( 'adhoc.module.module', 'Module', ondelete='cascade', auto_join=True, - ) + ) # the module corresponding to the dependency, and its status depend_id = fields.Many2one( 'adhoc.module.module', 'Dependency', compute='_compute_depend' - ) + ) state = fields.Selection( DEP_STATES, string='Status', compute='_compute_depend' - ) + ) @api.one @api.depends('name') @@ -49,6 +49,6 @@ def _compute_depend(self): mod = self.env['adhoc.module.module'].search([ ('name', '=', self.name), ('repository_id.branch', '=', self.module_id.repository_id.branch), - ], limit=1) + ], limit=1) self.depend_id = mod self.state = self.depend_id.state or 'unknown' diff --git a/adhoc_modules_server/models/adhoc_module_repository.py b/adhoc_modules_server/models/adhoc_module_repository.py index 0431416..ecf8cd3 100644 --- a/adhoc_modules_server/models/adhoc_module_repository.py +++ b/adhoc_modules_server/models/adhoc_module_repository.py @@ -74,34 +74,34 @@ class AdhocModuleRepository(models.Model): 'User or Organization', required=True, help='eg. "ingadhoc"', - ) + ) subdirectory = fields.Char( 'Subdirectory', help='For eg. "addons"', - ) + ) name = fields.Char( 'Repository Name', required=True, help='eg. "product"', - ) + ) branch = fields.Selection( [('8.0', '8.0'), ('9.0', '9.0')], 'Branch / Odoo Version', required=True, - ) + ) module_ids = fields.One2many( 'adhoc.module.module', 'repository_id', 'Modules', - ) + ) token = fields.Char( help='If no token configured, we will try to use a general one setted ' 'as "github.token" parameter, if none configured, we try connecting ' 'without token' - ) + ) auto_update = fields.Boolean( default=True, - ) + ) sequence = fields.Integer( string='Sequence', default=10, diff --git a/adhoc_modules_server/models/product_template.py b/adhoc_modules_server/models/product_template.py index d5c10f8..773854b 100644 --- a/adhoc_modules_server/models/product_template.py +++ b/adhoc_modules_server/models/product_template.py @@ -3,18 +3,18 @@ # For copyright and license notices, see __openerp__.py file in module root # directory ############################################################################## -from openerp import models, fields, api -import logging -_logger = logging.getLogger(__name__) +# from openerp import models, fields, api +# import logging +# _logger = logging.getLogger(__name__) -class ProductTempalte(models.Model): - _inherit = 'product.template' +# class ProductTempalte(models.Model): +# _inherit = 'product.template' - # adhoc_category_ids = fields.Many2many( - # 'product.template', - # 'adhoc_module_category_product_rel', - # 'adhoca_category_id', 'product_tmpl_id', - # 'Products', - # required=True, - # ) +# adhoc_category_ids = fields.Many2many( +# 'product.template', +# 'adhoc_module_category_product_rel', +# 'adhoca_category_id', 'product_tmpl_id', +# 'Products', +# required=True, +# ) diff --git a/adhoc_modules_server/views/adhoc_module_category_view.xml b/adhoc_modules_server/views/adhoc_module_category_view.xml index 987d786..6424714 100644 --- a/adhoc_modules_server/views/adhoc_module_category_view.xml +++ b/adhoc_modules_server/views/adhoc_module_category_view.xml @@ -17,6 +17,63 @@ --> + + adhoc.module.category.server.kanban + adhoc.module.category.server + + + + + + + + + + + + + + + + + + + + + + adhoc.module.category.search + adhoc.module.category.server + + primary + + + + + @@ -58,8 +115,8 @@ ADHOC Categories adhoc.module.category.server form - tree,form - + + kanban,tree,form {'search_default_root_categories': 1} diff --git a/adhoc_modules_server/views/adhoc_module_view.xml b/adhoc_modules_server/views/adhoc_module_view.xml index 7acebad..3e256c0 100644 --- a/adhoc_modules_server/views/adhoc_module_view.xml +++ b/adhoc_modules_server/views/adhoc_module_view.xml @@ -14,6 +14,10 @@ + + + + @@ -75,7 +79,7 @@ - +
-
- --> + diff --git a/oca_dependencies.txt b/oca_dependencies.txt index 5601eaa..57239e6 100644 --- a/oca_dependencies.txt +++ b/oca_dependencies.txt @@ -1,3 +1,4 @@ +oca-server-tools https://github.com/oca/server-tools.git odoo-infrastructure https://github.com/ingadhoc/odoo-infrastructure.git odoo-web https://github.com/ingadhoc/odoo-web.git adhoc-miscellaneous https://github.com/ingadhoc/miscellaneous.git From 6fc032b5f1a6b05f6be107bd373741160b630f4b Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Mon, 25 Apr 2016 20:15:16 -0300 Subject: [PATCH 10/11] FIX --- adhoc_modules/models/db_configuration.py | 35 ++++++++++++++----- adhoc_modules/models/ir_module.py | 5 ++- adhoc_modules/views/db_configuration_view.xml | 8 +++-- .../models/adhoc_module_repository.py | 2 +- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/adhoc_modules/models/db_configuration.py b/adhoc_modules/models/db_configuration.py index 2e07b05..42faace 100644 --- a/adhoc_modules/models/db_configuration.py +++ b/adhoc_modules/models/db_configuration.py @@ -4,7 +4,7 @@ # directory ############################################################################## from openerp import models, fields, api -# from openerp import pooler +from openerp.addons.adhoc_modules.models.ir_module import uninstallables # from openerp.exceptions import Warning # from datetime import datetime # from datetime import date @@ -14,18 +14,37 @@ class database_tools_configuration(models.TransientModel): _inherit = 'db.configuration' - @api.model - def _get_adhoc_modules_state(self): - return self.env['ir.module.module'].get_overall_adhoc_modules_state()['state'] + # @api.model + # def _get_adhoc_modules_state(self): + # return self.env[ + # 'ir.module.module'].get_overall_adhoc_modules_state()['state'] @api.one - def get_adhoc_modules_to_install(self): - print '1111111111' - self.adhoc_modules_to_install = self.env['ir.module.module'].search([], limit=10) + # dummy depends son computed field is computed + @api.depends('backups_state') + def get_adhoc_modules_data(self): + uninstalled_modules_names = self.env['ir.module.module'].search([ + ('state', 'not in', ['installed', 'to install'])]).mapped('name') + auto_install_modules = self.env['ir.module.module'].search([ + # '|', ('dependencies_id', '=', False), + ('dependencies_id.name', 'not in', uninstalled_modules_names), + ('conf_visibility', '=', 'auto_install'), + ('state', '=', 'uninstalled'), + ]) + self.adhoc_modules_to_install = auto_install_modules + self.adhoc_modules_to_uninstall = self.env['ir.module.module'].search([ + ('conf_visibility', 'in', uninstallables), + # ('conf_visibility', 'in', []), + ('state', '=', 'installed'), + ]) + adhoc_modules_to_uninstall = fields.Many2many( + 'ir.module.module', + compute='get_adhoc_modules_data', + ) adhoc_modules_to_install = fields.Many2many( 'ir.module.module', - compute='get_adhoc_modules_to_install', + compute='get_adhoc_modules_data', ) # adhoc_modules_state = fields.Selection([ # ('should_not_be_installed', 'Should Not be Installed'), diff --git a/adhoc_modules/models/ir_module.py b/adhoc_modules/models/ir_module.py index 4aa80fb..221c639 100644 --- a/adhoc_modules/models/ir_module.py +++ b/adhoc_modules/models/ir_module.py @@ -8,6 +8,7 @@ import logging _logger = logging.getLogger(__name__) +uninstallables = ['to_review', 'future_versions', 'unusable'] class AdhocModuleModule(models.Model): @@ -94,7 +95,6 @@ class AdhocModuleModule(models.Model): @api.one @api.constrains('state') def check_module_is_installable(self): - uninstallables = ['to_review', 'future_versions', 'unusable'] if ( self.state == 'to install' and self.conf_visibility in uninstallables): @@ -138,6 +138,9 @@ def search_visible(self, operator, value): ('adhoc_category_id.contracted_product', '!=', False), '|', ('conf_visibility', '=', 'normal'), '&', ('conf_visibility', '=', 'only_if_depends'), + # puede llegar a ser necesario si no tiene dependencias pero + # no tendria sentido + # '|', ('dependencies_id', '=', False), ('dependencies_id.name', 'in', installed_modules_names), ] diff --git a/adhoc_modules/views/db_configuration_view.xml b/adhoc_modules/views/db_configuration_view.xml index 67b3a15..cba7e2b 100644 --- a/adhoc_modules/views/db_configuration_view.xml +++ b/adhoc_modules/views/db_configuration_view.xml @@ -9,9 +9,13 @@