[REF] base: enhance inheritability

split fields_view_get() and used an abstact mixin class in place of of FormatAddress

[IMP] base: added a m2o field (ir.ui.view) on res.country to decide the layout of the form view to input addresses.
This commit is contained in:
qdp-odoo
2016-12-28 11:20:47 +01:00
parent 65a7531c8b
commit 4dd27bba34
6 changed files with 89 additions and 68 deletions
+5 -7
View File
@@ -10,8 +10,6 @@ from odoo.tools.translate import _
from odoo.tools import email_re, email_split
from odoo.exceptions import UserError, AccessError
from odoo.addons.base.res.res_partner import FormatAddress
from . import crm_stage
_logger = logging.getLogger(__name__)
@@ -51,11 +49,11 @@ CRM_LEAD_FIELDS_TO_MERGE = [
'partner_name']
class Lead(FormatAddress, models.Model):
class Lead(models.Model):
_name = "crm.lead"
_description = "Lead/Opportunity"
_order = "priority desc,activity_date_deadline,id desc"
_inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin']
_inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin', 'format.address.mixin']
_mail_mass_mailing = _('Leads / Opportunities')
def _default_probability(self):
@@ -311,15 +309,15 @@ class Lead(FormatAddress, models.Model):
return super(Lead, self.with_context(context)).copy(default=default)
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
if self._context.get('opportunity_id'):
opportunity = self.browse(self._context['opportunity_id'])
action = opportunity.get_formview_action()
if action.get('views') and any(view_id for view_id in action['views'] if view_id[1] == view_type):
view_id = next(view_id[0] for view_id in action['views'] if view_id[1] == view_type)
res = super(Lead, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
res = super(Lead, self)._fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
if view_type == 'form':
res['arch'] = self.fields_view_get_address(res['arch'])
res['arch'] = self._fields_view_get_address(res['arch'])
return res
# ----------------------------------------
+1 -1
View File
@@ -433,7 +433,7 @@
}
}
&.o_form_editable .o_address_format {
input.o_address_city {
.o_address_city {
width: 38%;
}
div.o_address_state {
+21 -11
View File
@@ -32,17 +32,27 @@ class Country(models.Model):
_description = 'Country'
_order = 'name'
name = fields.Char(string='Country Name', required=True, translate=True, help='The full name of the country.')
code = fields.Char(string='Country Code', size=2,
help='The ISO country code in two chars. \nYou can use this field for quick search.')
address_format = fields.Text(help="""You can state here the usual format to use for the \
addresses belonging to this country.\n\nYou can use the python-style string patern with all the field of the address \
(for example, use '%(street)s' to display the field 'street') plus
\n%(state_name)s: the name of the state
\n%(state_code)s: the code of the state
\n%(country_name)s: the name of the country
\n%(country_code)s: the code of the country""",
default='%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s')
name = fields.Char(
string='Country Name', required=True, translate=True, help='The full name of the country.')
code = fields.Char(
string='Country Code', size=2,
help='The ISO country code in two chars. \nYou can use this field for quick search.')
address_format = fields.Text(
help="Display format to use for addresses belonging to this country.\n\n"
"You can use python-style string pattern with all the fields of the address "
"(for example, use '%(street)s' to display the field 'street') plus"
"\n%(state_name)s: the name of the state"
"\n%(state_code)s: the code of the state"
"\n%(country_name)s: the name of the country"
"\n%(country_code)s: the code of the country",
default='%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s')
address_view_id = fields.Many2one(
comodel_name='ir.ui.view', string="Address View",
domain=[('model', '=', 'res.partner'), ('type', '=', 'form')],
help="Use this field if you want to replace the usual way to encode a complete address. "
"Note that the address_format field is used to modify the way to display addresses "
"(in reports for example), while this field is used to modify the input form for "
"addresses.")
currency_id = fields.Many2one('res.currency', string='Currency')
image = fields.Binary(attachment=True)
phone_code = fields.Integer(string='Country Calling Code')
+6 -1
View File
@@ -20,6 +20,8 @@
<field name="model">res.country</field>
<field name="arch" type="xml">
<form>
<div class="oe_button_box">
</div>
<field name="image" widget="image" class="oe_avatar"/>
<group>
<group>
@@ -28,9 +30,12 @@
<field name="code"/>
</group>
<group>
<field name="address_format" groups="base.group_no_one" placeholder="Address format..."/>
<field name="phone_code"/>
</group>
<group string="Advanced Address Formatting" groups="base.group_no_one" >
<field name="address_format" placeholder="Address format..."/>
<field name="address_view_id"/>
</group>
</group>
<label for="state_ids"/>
<field name="state_ids">
+28 -28
View File
@@ -28,11 +28,6 @@ WARNING_MESSAGE = [
WARNING_HELP = _('Selecting the "Warning" option will notify user with the message, Selecting "Blocking Message" will throw an exception with the message and block the flow. The Message has to be written in the next field.')
ADDRESS_FORMAT_CLASSES = {
'%(city)s %(state_code)s\n%(zip)s': 'o_city_state',
'%(zip)s %(city)s': 'o_zip_city'
}
ADDRESS_FIELDS = ('street', 'street2', 'zip', 'city', 'state_id', 'country_id')
@api.model
def _lang_get(self):
@@ -44,26 +39,30 @@ def _tz_get(self):
return [(tz, tz) for tz in sorted(pytz.all_timezones, key=lambda tz: tz if not tz.startswith('Etc/') else '_')]
class FormatAddress(object):
@api.model
def fields_view_get_address(self, arch):
address_format = self.env.user.company_id.country_id.address_format or ''
for format_pattern, format_class in ADDRESS_FORMAT_CLASSES.iteritems():
if format_pattern in address_format:
doc = etree.fromstring(arch)
for address_node in doc.xpath("//div[@class='o_address_format']"):
# add address format class to address block
address_node.attrib['class'] += ' ' + format_class
if format_class.startswith('o_zip'):
zip_fields = address_node.xpath("//field[@name='zip']")
city_fields = address_node.xpath("//field[@name='city']")
if zip_fields and city_fields:
# move zip field before city field
city_fields[0].addprevious(zip_fields[0])
arch = etree.tostring(doc)
break
return arch
class FormatAddressMixin(models.AbstractModel):
_name = "format.address.mixin"
def _fields_view_get_address(self, arch):
# consider the country of the user, not the country of the partner we want to display
address_view_id = self.env.user.company_id.country_id.address_view_id
if address_view_id and not self._context.get('no_address_format'):
#render the partner address accordingly to address_view_id
doc = etree.fromstring(arch)
for address_node in doc.xpath("//div[@class='o_address_format']"):
Partner = self.env['res.partner'].with_context(no_address_format=True)
sub_view = Partner.fields_view_get(
view_id=address_view_id.id, view_type='form', toolbar=False, submenu=False)
sub_view_node = etree.fromstring(sub_view['arch'])
#if the model is different than res.partner, there are chances that the view won't work
#(e.g fields not present on the model). In that case we just return arch
if self._name != 'res.partner':
try:
self.env['ir.ui.view'].postprocess_and_fields(self._name, sub_view_node, None)
except ValueError:
return arch
address_node.getparent().replace(address_node, sub_view_node)
arch = etree.tostring(doc)
return arch
class PartnerCategory(models.Model):
_description = 'Partner Tags'
@@ -127,8 +126,9 @@ class PartnerTitle(models.Model):
_sql_constraints = [('name_uniq', 'unique (name)', "Title name already exists !")]
class Partner(models.Model, FormatAddress):
class Partner(models.Model):
_description = 'Partner'
_inherit = ['format.address.mixin']
_name = "res.partner"
_order = "display_name"
@@ -307,12 +307,12 @@ class Partner(models.Model, FormatAddress):
return tools.image_resize_image_big(image.encode('base64'))
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
if (not view_id) and (view_type == 'form') and self._context.get('force_email'):
view_id = self.env.ref('base.view_partner_simple_form').id
res = super(Partner, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
res = super(Partner, self)._fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
if view_type == 'form':
res['arch'] = self.fields_view_get_address(res['arch'])
res['arch'] = self._fields_view_get_address(res['arch'])
return res
@api.constrains('parent_id')
+28 -20
View File
@@ -1336,23 +1336,8 @@ class BaseModel(object):
return result
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
""" fields_view_get([view_id | view_type='form'])
Get the detailed composition of the requested view like fields, model, view architecture
:param view_id: id of the view or None
:param view_type: type of the view to return if view_id is None ('form', 'tree', ...)
:param toolbar: true to include contextual actions
:param submenu: deprecated
:return: dictionary describing the composition of the requested view (including inherited views and extensions)
:raise AttributeError:
* if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
* if some tag other than 'position' is found in parent view
:raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
"""
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
View = self.env['ir.ui.view']
result = {
'model': self._name,
'field_parent': False,
@@ -1380,7 +1365,6 @@ class BaseModel(object):
# otherwise try to find the lowest priority matching ir.ui.view
view_id = View.default_view(self._name, view_type)
# context for post-processing might be overriden
if view_id:
# read the view with inherited views applied
root_view = View.browse(view_id).read_combined(['id', 'name', 'field_parent', 'type', 'model', 'arch'])
@@ -1389,9 +1373,7 @@ class BaseModel(object):
result['type'] = root_view['type']
result['view_id'] = root_view['id']
result['field_parent'] = root_view['field_parent']
# override context from postprocessing
if root_view['model'] != self._name:
View = View.with_context(base_model_name=root_view['model'])
result['base_model'] = root_view['model']
else:
# fallback on default views methods if no ir.ui.view could be found
try:
@@ -1401,6 +1383,32 @@ class BaseModel(object):
result['name'] = 'default'
except AttributeError:
raise UserError(_("No default view of type '%s' could be found !") % view_type)
return result
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
""" fields_view_get([view_id | view_type='form'])
Get the detailed composition of the requested view like fields, model, view architecture
:param view_id: id of the view or None
:param view_type: type of the view to return if view_id is None ('form', 'tree', ...)
:param toolbar: true to include contextual actions
:param submenu: deprecated
:return: dictionary describing the composition of the requested view (including inherited views and extensions)
:raise AttributeError:
* if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
* if some tag other than 'position' is found in parent view
:raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
"""
View = self.env['ir.ui.view']
# Get the view arch and all other attributes describing the composition of the view
result = self._fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
# Override context for postprocessing
if view_id and result.get('base_model', self._name) != self._name:
View = View.with_context(base_model_name=result['base_model'])
# Apply post processing, groups and modifiers etc...
xarch, xfields = View.postprocess_and_fields(self._name, etree.fromstring(result['arch']), view_id)