<commit_before># -*- coding: utf-8 -*-

from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from ast import literal_eval

# TODO: Implement a default attribute value field/method to load up on wizard


class ProductAttribute(models.Model):
    _inherit = 'product.attribute'

    @api.multi
    def copy(self, default=None):
        for attr in self:
            default.update({'name': attr.name + " (copy)"})
            attr = super(ProductAttribute, attr).copy(default)
            return attr

    @api.model
    def _get_nosearch_fields(self):
        """Return a list of custom field types that do not support searching"""
        return ['binary']

    @api.onchange('custom_type')
    def onchange_custom_type(self):
        if self.custom_type in self._get_nosearch_fields():
            self.search_ok = False

    CUSTOM_TYPES = [
        ('char', 'Char'),
        ('int', 'Integer'),
        ('float', 'Float'),
        ('text', 'Textarea'),
        ('color', 'Color'),
        ('binary', 'Attachment'),
        ('date', 'Date'),
        ('datetime', 'DateTime'),
    ]

    active = fields.Boolean(
        string='Active',
        default=True,
        help='By unchecking the active field you can '
        'disable a attribute without deleting it'
    )

    min_val = fields.Integer(string="Min Value", help="Minimum value allowed")
    max_val = fields.Integer(string="Max Value", help="Minimum value allowed")

    # TODO: Exclude self from result-set of dependency
    val_custom = fields.Boolean(
        string='Custom Value',
        help='Allow custom value for this attribute?'
    )
    custom_type = fields.Selection(
        selection=CUSTOM_TYPES,
        string='Field Type',
        size=64,
        help='The type of the custom field generated in the frontend'
    )

    description = fields.Text(string='Description', translate=True)

    search_ok = fields.Boolean(
        string='Searchable',
        help='When checking for variants with '
        'the same configuration, do we '
        'include this field in the search?'
    )

    required = fields.Boolean(
        string='Required',
        default=True,
        help='Determines the required value of this '
        'attribute though it can be change on '
        'the template level'
    )

    multi = fields.Boolean(
        string="Multi",
        help='Allow selection of multiple values for '
        'this attribute?'
    )

    uom_id = fields.Many2one(
        comodel_name='product.uom',
        string='Unit of Measure'
    )

    image = fields.Binary(string='Image')

    create_on_the_fly = fields.Boolean(
        string='Can create value on the fly',
        help='User can create new value with product configurator'
    )

    # TODO prevent the same attribute from being defined twice on the
    # attribute lines

    _order = 'sequence'

    @api.constrains('custom_type', 'search_ok')
    def check_searchable_field(self):
        nosearch_fields = self._get_nosearch_fields()
        if self.custom_type in nosearch_fields and self.search_ok:
            raise ValidationError(
                _("Selected custom field type '%s' is not searchable" %
                  self.custom_type)
            )

    def validate_custom_val(self, val):
        """ Pass in a desired custom value and ensure it is valid.
        Probaly should check type, etc, but let's assume fine for the moment.
        """
        self.ensure_one()
        if self.custom_type in ('int', 'float'):
            minv = self.min_val
            maxv = self.max_val
            val = literal_eval(val)
            if minv and maxv and (val < minv or val > maxv):
                raise ValidationError(
                    _("Selected custom value '%s' must be between %s and %s"
                        % (self.name, self.min_val, self.max_val))
                )
            elif minv and val < minv:
                raise ValidationError(
                    _("Selected custom value '%s' must be at least %s" %
                        (self.name, self.min_val))
                )
            elif maxv and val > maxv:
                raise ValidationError(
                    _("Selected custom value '%s' must be lower than %s" %
                        (self.name, self.max_val + 1))
                )


class ProductAttributeLine(models.Model):
    _inherit = 'product.attribute.line'

    @api.onchange('attribute_id')
    def onchange_attribute(self):
        self.value_ids = False
        self.required = self.attribute_id.required
        # TODO: Remove all dependencies pointed towards the attribute being
        # changed

    custom = fields.Boolean(
        string='Custom',
        help="Allow custom values for this attribute?"
    )
    required = fields.Boolean(
        string='Required',
        help="Is this attribute required?"
    )

    multi = fields.Boolean(
        string='Multi',
        help='Allow selection of multiple values for this attribute?'
    )

    sequence = fields.Integer(string='Sequence', default=10)

    # TODO: Order by dependencies first and then sequence so dependent fields
    # do not come before master field
    _order = 'product_tmpl_id, sequence, id'

    # TODO: Constraint not allowing introducing dependencies that do not exist
    # on the product.template


class ProductAttributeValue(models.Model):
    _inherit = 'product.attribute.value'

    @api.multi
    def copy(self, default=None):
        default.update({'name': self.name + " (copy)"})
        product = super(ProductAttributeValue, self).copy(default)
        return product

    active = fields.Boolean(
        string='Active',
        default=True,
        help='By unchecking the active field you can '
        'disable a attribute value without deleting it'
    )
    product_id = fields.Many2one(
        comodel_name='product.product',
        string='Related Product'
    )

    @api.model
    def name_search(self, name='', args=None, operator='ilike', limit=100):
        """Use name_search as a domain restriction for the frontend to show
        only values set on the product template taking all the configuration
        restrictions into account.

        TODO: This only works when activating the selection not when typing
        """
        product_tmpl_id = self.env.context.get('_cfg_product_tmpl_id')
        if product_tmpl_id:
            # TODO: Avoiding browse here could be a good performance enhancer
            product_tmpl = self.env['product.template'].browse(product_tmpl_id)
            tmpl_vals = product_tmpl.attribute_line_ids.mapped('value_ids')<commit_msg>add attr_value to attr_line domain<commit_after># -*- coding: utf-8 -*-

from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from ast import literal_eval

# TODO: Implement a default attribute value field/method to load up on wizard


class ProductAttribute(models.Model):
    _inherit = 'product.attribute'

    @api.multi
    def copy(self, default=None):
        for attr in self:
            default.update({'name': attr.name + " (copy)"})
            attr = super(ProductAttribute, attr).copy(default)
            return attr

    @api.model
    def _get_nosearch_fields(self):
        """Return a list of custom field types that do not support searching"""
        return ['binary']

    @api.onchange('custom_type')
    def onchange_custom_type(self):
        if self.custom_type in self._get_nosearch_fields():
            self.search_ok = False

    CUSTOM_TYPES = [
        ('char', 'Char'),
        ('int', 'Integer'),
        ('float', 'Float'),
        ('text', 'Textarea'),
        ('color', 'Color'),
        ('binary', 'Attachment'),
        ('date', 'Date'),
        ('datetime', 'DateTime'),
    ]

    active = fields.Boolean(
        string='Active',
        default=True,
        help='By unchecking the active field you can '
        'disable a attribute without deleting it'
    )

    min_val = fields.Integer(string="Min Value", help="Minimum value allowed")
    max_val = fields.Integer(string="Max Value", help="Minimum value allowed")

    # TODO: Exclude self from result-set of dependency
    val_custom = fields.Boolean(
        string='Custom Value',
        help='Allow custom value for this attribute?'
    )
    custom_type = fields.Selection(
        selection=CUSTOM_TYPES,
        string='Field Type',
        size=64,
        help='The type of the custom field generated in the frontend'
    )

    description = fields.Text(string='Description', translate=True)

    search_ok = fields.Boolean(
        string='Searchable',
        help='When checking for variants with '
        'the same configuration, do we '
        'include this field in the search?'
    )

    required = fields.Boolean(
        string='Required',
        default=True,
        help='Determines the required value of this '
        'attribute though it can be change on '
        'the template level'
    )

    multi = fields.Boolean(
        string="Multi",
        help='Allow selection of multiple values for '
        'this attribute?'
    )

    uom_id = fields.Many2one(
        comodel_name='product.uom',
        string='Unit of Measure'
    )

    image = fields.Binary(string='Image')

    create_on_the_fly = fields.Boolean(
        string='Can create value on the fly',
        help='User can create new value with product configurator'
    )

    # TODO prevent the same attribute from being defined twice on the
    # attribute lines

    _order = 'sequence'

    @api.constrains('custom_type', 'search_ok')
    def check_searchable_field(self):
        nosearch_fields = self._get_nosearch_fields()
        if self.custom_type in nosearch_fields and self.search_ok:
            raise ValidationError(
                _("Selected custom field type '%s' is not searchable" %
                  self.custom_type)
            )

    def validate_custom_val(self, val):
        """ Pass in a desired custom value and ensure it is valid.
        Probaly should check type, etc, but let's assume fine for the moment.
        """
        self.ensure_one()
        if self.custom_type in ('int', 'float'):
            minv = self.min_val
            maxv = self.max_val
            val = literal_eval(val)
            if minv and maxv and (val < minv or val > maxv):
                raise ValidationError(
                    _("Selected custom value '%s' must be between %s and %s"
                        % (self.name, self.min_val, self.max_val))
                )
            elif minv and val < minv:
                raise ValidationError(
                    _("Selected custom value '%s' must be at least %s" %
                        (self.name, self.min_val))
                )
            elif maxv and val > maxv:
                raise ValidationError(
                    _("Selected custom value '%s' must be lower than %s" %
                        (self.name, self.max_val + 1))
                )


class ProductAttributeLine(models.Model):
    _inherit = 'product.attribute.line'

    @api.onchange('attribute_id')
    def onchange_attribute(self):
        self.value_ids = False
        self.required = self.attribute_id.required
        # TODO: Remove all dependencies pointed towards the attribute being
        # changed

    custom = fields.Boolean(
        string='Custom',
        help="Allow custom values for this attribute?"
    )
    required = fields.Boolean(
        string='Required',
        help="Is this attribute required?"
    )

    multi = fields.Boolean(
        string='Multi',
        help='Allow selection of multiple values for this attribute?'
    )

    sequence = fields.Integer(string='Sequence', default=10)

    # TODO: Order by dependencies first and then sequence so dependent fields
    # do not come before master field
    _order = 'product_tmpl_id, sequence, id'

    # TODO: Constraint not allowing introducing dependencies that do not exist
    # on the product.template


class ProductAttributeValue(models.Model):
    _inherit = 'product.attribute.value'

    @api.model
    def create(self, vals):
        if self.env.context.get('product_tmpl_id'):
            # creation from wizard allowed by create_on_the_fly
            # we must add the attr_value to the attr_line or it won't show in domain
            product_tmpl_id = self.env.context.get('product_tmpl_id')
            attribute_id = vals.get('attribute_id',
                                    self.env.context.get('default_attribute_id'))
            line = self.env['product.attribute.line'].search([
                ('product_tmpl_id', '=', product_tmpl_id),
                ('attribute_id', '=', attribute_id)])
            match = line.attribute_id.value_ids.filtered(lambda rec: rec.name == vals['name'])
            if match:
                record = match[0]
            else:
                # default behavior
                record = super(ProductAttributeValue, self).create(vals)
            # create related line
            line.value_ids += record
        else:
            record = super(ProductAttributeValue, self).create(vals)
        return record

    @api.multi
    def copy(self, default=None):
        default.update({'name': self.name + " (copy)"})
        product = super(ProductAttributeValue, self).copy(default)
        return product

    active = fields.Boolean(
        string='Active',
        default=True,
        help='By unchecking the active field you can '
        'disable a attribute value without deleting it'
    )
    product_id = fields.Many2one(
        comodel_name='product.product',
        string='Related Product'
    )

    @api.model
    def name_search(self, name='', args=None, operator='ilike', limit=100):
        """Use name_search as a domain restriction for the frontend to show
        only values set on the product template taking all the configuration
        restrictions into account.

        TODO: This only works when activating the selection not when typing
        """
        product_tmpl_id = self.env.context.get('_cfg_product_tmpl_id')
        if product_tmpl_id:
            # TODO: Avoiding browse here could be a good performance enhancer
            product_tmpl = self.env['product.template'].browse(product_tmpl_id)
            tmpl_vals = product_tmpl.attribute_line_ids.mapped('value_ids')
{'fraction_non_alphanumeric': 0.09642236887808267, 'fraction_numerical': 0.0015977770059048282, 'mean_word_length': 2.2614408699592206, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1453660', 'n_tokens_mistral': 3938, 'n_tokens_neox': 3789, 'n_words': 1174}