[MERGE]: Merged with lp:openobject-server

bzr revid: atp@tinyerp.com-20120612065235-ndmy1xvphxvub0je
bzr revid: atp@tinyerp.com-20120615112550-rkq1qug49ecfm2ig
This commit is contained in:
Atul Patel (OpenERP)
2012-06-15 16:55:50 +05:30
8 changed files with 131 additions and 70 deletions
+63 -38
View File
@@ -73,42 +73,56 @@ class view(osv.osv):
}
_order = "priority,name"
def _check_xml(self, cr, uid, ids, context=None):
def root_view(record):
if not record.inherit_id:
return record
else:
return root_view(record.inherit_id)
def call_view(record):
try:
root = root_view(record)
# Temporarily commented, waiting fixes; it breaks most updates because
# when you update modules, all views are in the database, but only
# fields of loaded objects are on the objects. This breaks the
# fields_view_get calls. (field exists in view but not in the object)
#
# self.pool.get(root.model).fields_view_get(cr, uid, view_id=root.id, view_type=root.type, context=context)
# Holds the RNG schema
_relaxng_validator = None
return True
except:
_logger.exception("Can't obtain view description for model: %s (view id: %s).", record.model, record.id)
return False
for view in self.browse(cr, uid, ids, context):
return call_view(view)
# TODO The following code should be executed (i.e. no early return above).
# TODO The view.rng file can be read only once!
eview = etree.fromstring(view.arch.encode('utf8'))
def _relaxng(self):
if not self._relaxng_validator:
frng = tools.file_open(os.path.join('base','rng','view.rng'))
try:
relaxng_doc = etree.parse(frng)
relaxng = etree.RelaxNG(relaxng_doc)
if not relaxng.validate(eview):
for error in relaxng.error_log:
_logger.error(tools.ustr(error))
return False
self._relaxng_validator = etree.RelaxNG(relaxng_doc)
except Exception:
_logger.exception('Failed to load RelaxNG XML schema for views validation')
finally:
frng.close()
return self._relaxng_validator
def _check_render_view(self, cr, uid, view, context=None):
"""Verify that the given view's hierarchy is valid for rendering, along with all the changes applied by
its inherited views, by rendering it using ``fields_view_get()``.
@param browse_record view: view to validate
@return: the rendered definition (arch) of the view, always utf-8 bytestring (legacy convention)
if no error occurred, else False.
"""
try:
fvg = self.pool.get(view.model).fields_view_get(cr, uid, view_id=view.id, view_type=view.type, context=context)
return fvg['arch']
except:
_logger.exception("Can't render view %s for model: %s", view.xml_id, view.model)
return False
def _check_xml(self, cr, uid, ids, context=None):
for view in self.browse(cr, uid, ids, context):
# Sanity check: the view should not break anything upon rendering!
view_arch_utf8 = self._check_render_view(cr, uid, view, context=context)
# always utf-8 bytestring - legacy convention
if not view_arch_utf8: return False
# RNG-based validation is not possible anymore with 7.0 forms
# TODO 7.0: provide alternative assertion-based validation of view_arch_utf8
view_docs = [etree.fromstring(view_arch_utf8)]
if view_docs[0].tag == 'data':
# A <data> element is a wrapper for multiple root nodes
view_docs = view_docs[0]
validator = self._relaxng()
for view_arch in view_docs:
if (view_arch.get('version') < '7.0') and validator and not validator.validate(view_arch):
for error in validator.error_log:
_logger.error(tools.ustr(error))
return False
return True
_constraints = [
@@ -122,20 +136,31 @@ class view(osv.osv):
cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, type, inherit_id)')
def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None):
"""Retrieves the architecture of views that inherit from the given view.
"""Retrieves the architecture of views that inherit from the given view, from the sets of
views that should currently be used in the system. During the module upgrade phase it
may happen that a view is present in the database but the fields it relies on are not
fully loaded yet. This method only considers views that belong to modules whose code
is already loaded. Custom views defined directly in the database are loaded only
after the module initialization phase is completely finished.
:param int view_id: id of the view whose inheriting views should be retrieved
:param str model: model identifier of the view's related model (for double-checking)
:rtype: list of tuples
:return: [(view_arch,view_id), ...]
"""
cr.execute("""SELECT
arch, id
FROM
ir_ui_view
WHERE
inherit_id=%s AND model=%s
ORDER BY priority""", (view_id, model))
if self.pool._init:
# Module init currently in progress, only consider views from modules whose code was already loaded
query = """SELECT v.arch, v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id)
WHERE v.inherit_id=%s AND v.model=%s AND md.module in %s
ORDER BY priority"""
query_params = (view_id, model, tuple(self.pool._init_modules))
else:
# Modules fully loaded, consider all views
query = """SELECT v.arch, v.id FROM ir_ui_view v
WHERE v.inherit_id=%s AND v.model=%s
ORDER BY priority"""
query_params = (view_id, model)
cr.execute(query, query_params)
return cr.fetchall()
def write(self, cr, uid, ids, vals, context=None):
+15 -1
View File
@@ -23,8 +23,9 @@ import logging
import re
import urllib
import zipimport
import base64
from openerp import modules, pooler, release, tools
from openerp import modules, pooler, release, tools, addons
from openerp.tools.parse_version import parse_version
from openerp.tools.translate import _
from openerp.osv import fields, osv, orm
@@ -160,6 +161,18 @@ class module(osv.osv):
res[key][k] = "\n".join(sorted(v))
return res
def _get_icon_image(self, cr, uid, ids, field_name=None, arg=None, context=None):
res = dict.fromkeys(ids, '')
for module in self.browse(cr, uid, ids, context=context):
path = addons.get_module_resource(module.name, 'static', 'src', 'img', 'icon.png')
if path:
image_file = tools.file_open(path, 'rb')
try:
res[module.id] = image_file.read().encode('base64')
finally:
image_file.close()
return res
_columns = {
'name': fields.char("Technical Name", size=128, readonly=True, required=True, select=True),
'category_id': fields.many2one('ir.module.category', 'Category', readonly=True, select=True),
@@ -211,6 +224,7 @@ class module(osv.osv):
'certificate' : fields.char('Quality Certificate', size=64, readonly=True),
'application': fields.boolean('Application', readonly=True),
'icon': fields.char('Icon URL', size=128),
'icon_image': fields.function(_get_icon_image, string='Icon', type="binary"),
}
_defaults = {
+6 -8
View File
@@ -123,9 +123,8 @@
<header>
<button name="button_install" states="uninstalled" string="Install" icon="terp-gtk-jump-to-ltr" type="object" class="oe_form_button_hi"/>
<button name="button_upgrade" states="installed" string="Upgrade" icon="terp-gtk-go-back-rtl" type="object" class="oe_form_button_hi"/>
<button name="button_uninstall" states="installed" string="Uninstall (beta)"
icon="terp-dialog-close" type="object"
confirm="Do you confirm the uninstallation of this module? This will permanently erase all data currently stored by the module!"/>
<button name="button_uninstall" states="installed" string="Uninstall (beta)" icon="terp-dialog-close" type="object"
confirm="Do you confirm the uninstallation of this module? This will permanently erase all data currently stored by the module!"/>
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" icon="gtk-cancel" type="object"/>
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" icon="gtk-cancel" type="object"/>
<button name="button_install_cancel" states="to install" string="Cancel Install" icon="gtk-cancel" type="object"/>
@@ -133,7 +132,7 @@
</header>
<sheet>
<div class="oe_right oe_form_button_box">
<field name="icon" widget='image'/>
<field name="icon_image" widget="image"/>
</div>
<div class="oe_form_title">
<label for="shortdesc" class="oe_form_readonly_hidden"/>
@@ -158,7 +157,6 @@
</page>
<page string="Technical Data">
<group col="4">
<field name="name"/>
<field name="demo"/>
<field name="application"/>
</group>
@@ -170,11 +168,11 @@
</field>
</page>
<page string="Features" attrs="{'invisible':[('state','!=','installed')]}">
<separator string="Created Menus"/>
<label for="menus_by_module" string="Created Menus"/>
<field name="menus_by_module"/>
<separator string="Created Views"/>
<label for="views_by_module" string="Created Views"/>
<field name="views_by_module"/>
<separator string="Defined Reports"/>
<label for="reports_by_module" string="Defined Reports"/>
<field name="reports_by_module"/>
</page>
</notebook>
+1 -12
View File
@@ -88,17 +88,6 @@ class res_company(osv.osv):
return result
def _get_bank_data(self, cr, uid, ids, field_names, arg, context=None):
""" Read the 'address' functional fields. """
result = {}
for company in self.browse(cr, uid, ids, context=context):
r = []
for bank in company.bank_ids:
if bank.footer:
r.append(bank.name_get(context=context)[0][1])
result[company.id] = ' | '.join(r)
return result
def _set_address_data(self, cr, uid, company_id, name, value, arg, context=None):
""" Write the 'address' functional fields. """
company = self.browse(cr, uid, company_id, context=context)
@@ -120,7 +109,7 @@ class res_company(osv.osv):
'partner_id': fields.many2one('res.partner', 'Partner', required=True),
'rml_header1': fields.char('Report Header / Company Slogan', size=200, help="Appears by default on the top right corner of your printed documents."),
'rml_footer1': fields.char('General Information Footer', size=200),
'rml_footer2': fields.function(_get_bank_data, type="char", string='Bank Accounts Footer', size=250, help="This field is computed automatically based on bank accounts defined, having the display on footer checkbox set."),
'rml_footer2': fields.char('Bank Accounts Footer', size=250, help="Write here your bank accounts for customer payments."),
'rml_header': fields.text('RML Header', required=True),
'rml_header2': fields.text('RML Internal Header', required=True),
'rml_header3': fields.text('RML Internal Header', required=True),
+24
View File
@@ -39,6 +39,19 @@
</rng:optional>
</rng:define>
<rng:define name="modifiable">
<rng:optional>
<!-- @modifiers contains a JSON map unifying the various
modifier attributes: @readonly, @required, @invisible.
Each attribute is a key, mapped to a JSON list representing
a condition expressed as an OpenERP `domain` filter
Only some of the modifier keys make sense on some
elements, for example <filter> and <group> only support
`invisible`. -->
<rng:attribute name="modifiers"/>
</rng:optional>
</rng:define>
<rng:define name="access_rights">
<rng:optional>
<rng:attribute name="groups"/>
@@ -294,6 +307,8 @@
<rng:element name="label">
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:ref name="modifiable"/>
<rng:optional><rng:attribute name="invisible"/></rng:optional>
<rng:optional><rng:attribute name="align"/></rng:optional>
<rng:optional><rng:attribute name="nolabel"/></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
@@ -393,6 +408,7 @@
<rng:element name="page">
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:ref name="modifiable"/>
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:optional><rng:attribute name="name"/></rng:optional>
<rng:optional><rng:attribute name="attrs"/></rng:optional>
@@ -443,6 +459,8 @@
<rng:element name="separator">
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:ref name="modifiable"/>
<rng:optional><rng:attribute name="invisible"/></rng:optional>
<rng:optional><rng:attribute name="name"/></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:optional><rng:attribute name="rowspan"/></rng:optional>
@@ -508,6 +526,7 @@
<rng:attribute name="name" />
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:ref name="modifiable"/>
<rng:optional><rng:attribute name="domain_filter"/></rng:optional>
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="string"/></rng:optional>
@@ -587,6 +606,7 @@
<rng:element name="group">
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:ref name="modifiable"/>
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="colspan"/></rng:optional>
<rng:optional><rng:attribute name="rowspan"/></rng:optional>
@@ -598,6 +618,7 @@
<rng:optional><rng:attribute name="width"/></rng:optional>
<rng:optional><rng:attribute name="name"/></rng:optional>
<rng:optional><rng:attribute name="color" /></rng:optional>
<rng:optional><rng:attribute name="invisible"/></rng:optional>
<rng:ref name="container"/>
</rng:element>
</rng:define>
@@ -640,6 +661,7 @@
<rng:element name="button">
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:ref name="modifiable"/>
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="invisible"/></rng:optional>
<rng:optional><rng:attribute name="name" /></rng:optional>
@@ -684,7 +706,9 @@
<rng:element name="filter">
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:ref name="modifiable"/>
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="invisible"/></rng:optional>
<rng:optional><rng:attribute name="name" /></rng:optional>
<rng:optional><rng:attribute name="separator" /></rng:optional>
<rng:optional><rng:attribute name="icon" /></rng:optional>
+1 -1
View File
@@ -154,7 +154,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
loaded_modules.append(package.name)
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
init_module_models(cr, package.name, models)
pool._init_modules.add(package.name)
status['progress'] = float(index) / len(graph)
# Can't put this line out of the loop: ir.module.module will be
+4
View File
@@ -48,6 +48,10 @@ class Registry(object):
self._store_function = {}
self._init = True
self._init_parent = {}
# modules fully loaded (maintained during init phase by `loading` module)
self._init_modules = set()
self.db_name = db_name
self.db = openerp.sql_db.db_connect(db_name)
+17 -10
View File
@@ -1977,6 +1977,7 @@ class BaseModel(object):
def raise_view_error(error_msg, child_view_id):
view, child_view = self.pool.get('ir.ui.view').browse(cr, user, [view_id, child_view_id], context)
error_msg = error_msg % {'parent_xml_id': view.xml_id}
raise AttributeError("View definition error for inherited view '%s' on model '%s': %s"
% (child_view.xml_id, self._name, error_msg))
@@ -2005,16 +2006,18 @@ class BaseModel(object):
if node.get('name') == spec.get('name'):
return node
return None
else:
for node in source.getiterator(spec.tag):
good = True
for attr in spec.attrib:
if attr != 'position' and (not node.get(attr) or node.get(attr) != spec.get(attr)):
good = False
break
if good:
return node
return None
for node in source.getiterator(spec.tag):
if isinstance(node, SKIPPED_ELEMENT_TYPES):
continue
if all(node.get(attr) == spec.get(attr) \
for attr in spec.attrib
if attr not in ('position','version')):
# Version spec should match parent's root element's version
if spec.get('version') and spec.get('version') != source.get('version'):
return None
return node
return None
def apply_inheritance_specs(source, specs_arch, inherit_id=None):
""" Apply an inheriting view.
@@ -2080,7 +2083,11 @@ class BaseModel(object):
if attr != 'position'
])
tag = "<%s%s>" % (spec.tag, attrs)
if spec.get('version') and spec.get('version') != source.get('version'):
raise_view_error("Mismatching view API version for element '%s': %r vs %r in parent view '%%(parent_xml_id)s'" % \
(tag, spec.get('version'), source.get('version')), inherit_id)
raise_view_error("Element '%s' not found in parent view '%%(parent_xml_id)s'" % tag, inherit_id)
return source
def apply_view_inheritance(cr, user, source, inherit_id):