CONFIGURATION_TYPES = ((CONFIGURATION_TYPE_RELEASED, 'Released'), (CONFIGURATION_TYPE_WORKING, 'Working'),) CURRENT_UNITS = (
    NO_CHOICE,
    ('A', 'A'),
    ('uA', '\u03BCV'),
    ('mA', 'mA'),
    ('kA', 'kA'),
    ('MA', 'MA'),
    ('Other', 'Other'),
) DISTANCE_UNITS = (
    NO_CHOICE,
    ('mil', 'mil'),
    ('in', 'in'),
    ('ft', 'ft'),
    ('yd', 'yd'),
    ('km', 'km'),
    ('m', 'm'),
    ('cm', 'cm'),
    ('mm', 'mm'),
    ('um', '\u03BCm'),
    ('nm', 'nm'),
    ('Other', 'Other'),
) FREQUENCY_UNITS = (
    NO_CHOICE,
    ('Hz', 'Hz'),
    ('kHz', 'kHz'),
    ('MHz', 'MHz'),
    ('GHz', 'GHz'),
    ('Other', 'Other'),
) INTERFACE_TYPES = (
    NO_CHOICE,
    ('I2C', 'I2C'),
    ('SPI', 'SPI'),
    ('CAN', 'CAN'),
    ('One-Wire', '1-Wire'),
    ('RS485', 'RS-485'),
    ('RS232', 'RS-232'),
    ('WiFi', 'Wi-Fi'),
    ('4G', '4G'),
    ('BT', 'Bluetooth'),
    ('BTLE', 'Bluetooth LE'),
    ('Z_Wave', 'Z-Wave'),
    ('Zigbee', 'Zigbee'),
    ('LAN', 'LAN'),
    ('USB', 'USB'),
    ('HDMI', 'HDMI'),
    ('Other', 'Other'),
) MEMORY_UNITS = (
    NO_CHOICE,
    ('KB', 'KB'),
    ('MB', 'MB'),
    ('GB', 'GB'),
    ('TB', 'TB'),
    ('Other', 'Other'),
) NUMBER_CLASS_CODE_LEN_DEFAULT = 3 NUMBER_CLASS_CODE_LEN_MAX = 16 NUMBER_CLASS_CODE_LEN_MIN = 2 NUMBER_ITEM_LEN_DEFAULT = 4 NUMBER_ITEM_LEN_MAX = 128 NUMBER_ITEM_LEN_MIN = 3 NUMBER_SCHEME_INTELLIGENT = 'I' NUMBER_SCHEME_SEMI_INTELLIGENT = 'S' NUMBER_VARIATION_LEN_DEFAULT = 2 NUMBER_VARIATION_LEN_MAX = 16 NUMBER_VARIATION_LEN_MIN = 0 PACKAGE_TYPES = (
    NO_CHOICE,
    ('0201 smd', '0201 smd'),
    ('0402 smd', '0402 smd'),
    ('0603 smd', '0603 smd'),
    ('0805 smd', '0805 smd'),
    ('1206 smd', '1206 smd'),
    ('1210 smd', '1210 smd'),
    ('1812 smd', '1812 smd'),
    ('2010 smd', '2010 smd'),
    ('2512 smd', '2512 smd'),
    ('1/8 radial', '1/8 radial'),
    ('1/4 radial', '1/4 radial'),
    ('1/2 radial', '1/2 radial'),
    ('Size A', 'Size A'),
    ('Size B', 'Size B'),
    ('Size C', 'Size C'),
    ('Size D', 'Size D'),
    ('Size E', 'Size E'),
    ('SOT-23', 'SOT-23'),
    ('SOT-223', 'SOT-233'),
    ('DIL', 'DIL'),
    ('SOP', 'SOP'),
    ('SOIC', 'SOIC'),
    ('QFN', 'QFN'),
    ('QFP', 'QFP'),
    ('QFT', 'QFT'),
    ('PLCC', 'PLCC'),
    ('VGA', 'VGA'),
    ('Other', 'Other'),
) POWER_UNITS = (
    NO_CHOICE,
    ('W', 'W'),
    ('uW', '\u03BCW'),
    ('mW', 'mW'),
    ('kW', 'kW'),
    ('MW', 'MW'),
    ('Other', 'Other'),
) ROLE_TYPE_VIEWER = 'V' ROLE_TYPES = ((ROLE_TYPE_ADMIN, 'Admin'), (ROLE_TYPE_VIEWER, 'Viewer'),) SUBSCRIPTION_TYPES = ((SUBSCRIPTION_TYPE_FREE, 'Free'), (SUBSCRIPTION_TYPE_PRO, 'Pro'),) TEMPERATURE_UNITS = (
    NO_CHOICE,
    ('C', '\u00B0C'),
    ('F', '\u00B0F'),
    ('Other', 'Other'),
) VALUE_UNITS = (
    NO_CHOICE,
    ('Ohms', '\u03A9'),
    ('mOhms', 'm\u03A9'),
    ('kOhms', 'k\u03A9'),
    ('MOhms', 'M\u03A9'),
    ('F', 'F'),
    ('pF', 'pF'),
    ('nF', 'nF'),
    ('uF', '\u03BCF'),
    ('V', 'V'),
    ('uV', '\u03BCV'),
    ('mV', 'mV'),
    ('A', 'A'),
    ('uA', '\u03BCA'),
    ('mA', 'mA'),
    ('C', '\u00B0C'),
    ('F', '\u00B0F'),
    ('H', 'H'),
    ('nH', 'nH'),
    ('mH', 'mH'),
    ('uH', '\u03BCH'),
    ('Hz', 'Hz'),
    ('kHz', 'kHz'),
    ('MHz', 'MHz'),
    ('GHz', 'GHz'),
    ('Other', 'Other'),
) VOLTAGE_UNITS = (
    NO_CHOICE,
    ('V', 'V'),
    ('uV', '\u03BCV'),
    ('mV', 'mV'),
    ('kV', 'kV'),
    ('MV', 'MV'),
    ('Other', 'Other'),
) WAVELENGTH_UNITS = (
    NO_CHOICE,
    ('km', 'km'),
    ('m', 'm'),
    ('cm', 'cm'),
    ('mm', 'mm'),
    ('um', '\u03BCm'),
    ('nm', 'nm'),
    ('A', '\u212B'),
    ('Other', 'Other'),
) WEIGHT_UNITS = (
    NO_CHOICE,
    ('mg', 'mg'),
    ('g', 'g'),
    ('kg', 'kg'),
    ('oz', 'oz'),
    ('lb', 'lb'),
    ('Other', 'Other'),
) class BOMFlatCSVHeaders(CSVHeaders):
    all_headers_defns = [
        CSVHeader('part_number', name_options=['part number', 'part no', ]),
        CSVHeader('quantity', name_options=['count', 'qty', ]),
        CSVHeader('do_not_load', name_options=['dnl', 'dnp', 'do_not_populate', 'do_not_load', 'do not load', 'do not populate', ]),
        CSVHeader('part_class', name_options=['class', 'part_category']),
        CSVHeader('references', name_options=['designator', 'designators', 'reference', ]),
        CSVHeader('synopsis', name_options=['part_synopsis', ]),
        CSVHeader('revision', name_options=['rev', 'part_revision', 'rev.']),
    ] + ManufacturerPartCSVHeaders.all_headers_defns \
      + SellerPartCSVHeaders.all_headers_defns + [
        CSVHeader('extended_qty', name_options=['extended_quantity', 'part_extended_quantity', 'part_ext_qty', ]),
        CSVHeader('extended_cost', name_options=['part_extended_cost', 'part_ext_cost', ]),
        CSVHeader('order_qty', name_options=['part_order_qty', 'part_order_quantity', 'order_quantity', ]),
        CSVHeader('out_of_pocket_cost', name_options=['part_out_of_pocket_cost', 'cost', ]),
        CSVHeader('lead_time_days', name_options=['part_lead_time_days', ]),
    ] class BOMIndentedCSVHeaders(BOMFlatCSVHeaders):
    all_headers_defns = [CSVHeader('level')] + BOMFlatCSVHeaders.all_headers_defns class CSVHeaderError(Exception):
    def __init__(self, str):
        self.str = str

    def __str__(self):
        return self.str class PartClassesCSVHeaders(CSVHeaders):
    all_headers_defns = [
        CSVHeader('code'),
        CSVHeader('name'),
        CSVHeader('comment', name_options=['description', 'desc', 'desc.']),
        CSVHeader('mouser_enabled'),
    ] class PartsListCSVHeaders(CSVHeaders):
    part_attributes = [
        CSVHeader('value', name_options=['val', 'val.', ]),
        CSVHeader('value_units', name_options=['value units', 'val. units', 'val units', ]),
        CSVHeader('tolerance', name_options=[]),
        CSVHeader('attribute', name_options=[]),
        CSVHeader('package', name_options=[]),
        CSVHeader('pin_count', name_options=[]),
        CSVHeader('frequency', name_options=[]),
        CSVHeader('frequency_units', name_options=[]),
        CSVHeader('wavelength', name_options=[]),
        CSVHeader('wavelength_units', name_options=[]),
        CSVHeader('memory', name_options=[]),
        CSVHeader('memory_units', name_options=[]),
        CSVHeader('interface', name_options=[]),
        CSVHeader('supply_voltage', name_options=[]),
        CSVHeader('supply_voltage_units', name_options=[]),
        CSVHeader('temperature_rating', name_options=[]),
        CSVHeader('temperature_rating_units', name_options=[]),
        CSVHeader('power_rating', name_options=[]),
        CSVHeader('power_rating_units', name_options=[]),
        CSVHeader('voltage_rating', name_options=[]),
        CSVHeader('voltage_rating_units', name_options=[]),
        CSVHeader('current_rating', name_options=[]),
        CSVHeader('current_rating_units', name_options=[]),
        CSVHeader('material', name_options=[]),
        CSVHeader('color', name_options=[]),
        CSVHeader('finish', name_options=[]),
        CSVHeader('length', name_options=[]),
        CSVHeader('length_units', name_options=[]),
        CSVHeader('width', name_options=[]),
        CSVHeader('width_units', name_options=[]),
        CSVHeader('height', name_options=[]),
        CSVHeader('height_units', name_options=[]),
        CSVHeader('weight', name_options=[]),
        CSVHeader('weight_units', name_options=[]),
    ]

    all_headers_defns = [
        CSVHeader('description', name_options=['desc', 'desc.', ]),
        CSVHeader('manufacturer_name', name_options=['mfg_name', 'manufacturer_name', 'part_manufacturer', 'mfg', 'manufacturer', 'manufacturer name', ]),
        CSVHeader('manufacturer_part_number', name_options=['mpn', 'mfg_part_number', 'part_manufacturer_part_number', 'mfg part number', 'manufacturer part number']),
        CSVHeader('part_number', name_options=['part number', 'part no', ]),
        CSVHeader('revision', name_options=['rev', 'part_revision', ]),
    ] + part_attributes \
      + SellerPartCSVHeaders.all_headers_defns class AutocompleteTextInput(forms.TextInput):
    def __init__(self, *args, **kwargs):
        self.queryset = kwargs.pop('queryset')
        self.autocomplete_limit = kwargs.pop('autocomplete_limit', None)
        self.autocomplete_min_length = kwargs.pop('autocomplete_min_length', 0)
        self.autocomplete_submit = kwargs.pop('autocomplete_submit', False)
        self.form_name = kwargs.pop('form_name', 'form')
        self.verbose_string_function = kwargs.pop('verbose_string_function', str)
        super().__init__(*args, **kwargs)

    def render(self, name, value, attrs=None, renderer=None):
        # verbose_string_function is used if we want to show verbose strings in the dropdown,
        # but autocomplete to something simpler

        # Disable chrome autocomplete..we dont want double duty here!
        if attrs is not None:
            attrs.update({'autocomplete': 'off'})
        else:
            attrs['autocomplete'] = "off"

        html = super().render(name, value, attrs)

        autocomplete_dict = {}
        autocomplete_dict_to_fill = {}
        for obj in self.queryset:
            verbose_obj_str = self.verbose_string_function(obj).replace('"', '\\"')
            autocomplete_dict.update({verbose_obj_str: None})
            autocomplete_dict_to_fill.update({verbose_obj_str: str(obj)})

        autocomplete_json = dumps(autocomplete_dict).replace("'", "\\'")
        autocomplete_json_to_fill = dumps(autocomplete_dict_to_fill).replace("'", "\\'")

        # To escape brackets in a Python 3.6 f-string we use double brackets
        inline_code = mark_safe(
            f"""<script>
            const {name}_data = JSON.parse('{autocomplete_json}');
            const {name}_data_to_fill = JSON.parse('{autocomplete_json_to_fill}');
            const {name}_input = document.getElementById("id_{name}");
            const {name}_form = {name}_input.form;
            $(document).ready(function () {{
                $('#id_{name}').autocomplete({{
                    data: {name}_data,
                    limit: {self.autocomplete_limit or 'undefined'}, // The max amount of results that can be shown at once. Default: Infinity.
                    minLength: {self.autocomplete_min_length}, // The minimum length of the input for the autocomplete to start. Default: 1.
                    onAutocomplete: function (val) {{
                        console.log({name}_data_to_fill);
                        $("#id_{name}").val({name}_data_to_fill[val]);
                        {f'{name}_form.submit()' if self.autocomplete_submit else ''}
                    }},
                }});
            }});
            </script>"""
        )
        return html + inline_code class Organization(models.Model):
class UserMeta(models.Model):
class PartClass(models.Model):
    class Meta:
class Manufacturer(models.Model, AsDictModel):
    class Meta:
class Part(models.Model):
    class Meta:
class PartRevision(models.Model):
    class Meta:
class AssemblySubparts(models.Model):
    class Meta:
class Subpart(models.Model):
class Assembly(models.Model):
class ManufacturerPart(models.Model, AsDictModel):
    class Meta:
class Seller(models.Model, AsDictModel):
class SellerPart(models.Model, AsDictModel):
    class Meta():
    def number_cs(self):
    def number_ns(self):
    def number_vs(self):
    def __str__(self):
    def seller_parts(self):
    def part_list_csv_headers(self):
    def email(self):
    def save(self, *args, **kwargs):
    def get_or_create_organization(self):
    def google_authenticated(self) -> bool:
    def is_organization_owner(self) -> bool:
    def _user_meta(self, organization=None):
    def __str__(self):
    def __str__(self):
    def full_part_number(self):
    def verify_format_number_class(number_class, organization):
    def verify_format_number_item(number_item, organization):
    def verify_format_number_variation(number_variation, organization):
    def parse_part_number(part_number, organization):
    def parse_partial_part_number(part_number, organization, validate=True):
    def description(self):
    def latest(self):
    def revisions(self):
    def seller_parts(self, exclude_primary=False):
    def manufacturer_parts(self, exclude_primary=False):
    def where_used(self):
    def where_used_full(self):
        def where_used_given_part(used_in_parts, part):
    def indented(self, part_revision=None):
    def optimal_seller(self, quantity=None):
    def assign_part_number(self):
    def save(self, *args, **kwargs):
    def verbose_str(self):
    def __str__(self):
    def generate_synopsis(self, make_searchable=False):
        def verbosify(val, units=None, pre=None, pre_whitespace=True, post=None, post_whitespace=True):
    def synopsis(self, return_displayable=True):
    def save(self, *args, **kwargs):
    def indented(self, top_level_quantity=100):
        def indented_given_bom(bom, part_revision, parent_id=None, parent=None, qty=1, parent_qty=1, indent_level=0, subpart=None, reference='', do_not_load=False):
    def flat(self, top_level_quantity=100, sort=False):
        def flat_given_bom(bom, part_revision, parent=None, qty=1, parent_qty=1, subpart=None, reference=''):
        def sort_by_references(p):
    def where_used(self):
    def where_used_full(self):
        def where_used_given_part(used_in_parts, part):
    def next_revision(self):
    def __str__(self):
    def save(self, *args, **kwargs):
    def __str__(self):
    def seller_parts(self):
    def optimal_seller(self, quantity=None):
    def as_dict_for_export(self):
    def __str__(self):
    def __str__(self):
    def as_dict(self):
    def as_dict_for_export(self):
    def optimal(sellerparts, quantity):
    def order_quantity(self, extended_quantity):
    def __str__(self):
                FORMATS = {
                    1: '{0:0=1d}', 2: '{0:0=2d}', 3: '{0:0=3d}', 4: '{0:0=4d}', 5: '{0:0=5d}',
                    6: '{0:0=6d}', 7: '{0:0=7d}', 8: '{0:0=8d}', 9: '{0:0=9d}', 10: '{0:0=10d}'
                } def check_references_for_duplicates(new_refs, seen_refs, duplicate_refs):
    new_refs_list = listify_string(new_refs)
    new_refs_set = set()
    for r in new_refs_list:
        if r in new_refs_set:
            duplicate_refs.add(r)
        else:
            new_refs_set.add(r)
            if r in seen_refs:
                duplicate_refs.add(r)
        seen_refs.add(r) def get_from_dict(input_dict, key_options):
    for key in key_options:
        val = input_dict.get(key, None)
        if val:
            return val
    return None def listify_string(st):
    if st is None:
        return []
    ss = re.split(' |:|;|,|\t|\n', st)
    split_st = []
    for s in ss:
        s_strip = s.strip()
        if len(s_strip) != 0:
            split_st.append(s_strip)
    return split_st def prep_for_sorting_nicely(item):
    convert = lambda text: int(text) if text.isdigit() else text
    alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
    return alphanum_key(item) def stringify_list(li):
    return ', '.join(str(x) for x in li) def validate_pct(value):
import codecs
import csv
import logging
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.core.exceptions import ValidationError
from django.core.validators import MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator
from django.db import IntegrityError
from django.forms.models import model_to_dict
from django.utils.translation import gettext_lazy as _
from djmoney.money import Money
from .constants import (
    CONFIGURATION_TYPES,
    CURRENT_UNITS,
    DISTANCE_UNITS,
    FREQUENCY_UNITS,
    INTERFACE_TYPES,
    MEMORY_UNITS,
    NUMBER_CLASS_CODE_LEN_DEFAULT,
    NUMBER_CLASS_CODE_LEN_MAX,
    NUMBER_CLASS_CODE_LEN_MIN,
    NUMBER_ITEM_LEN_DEFAULT,
    NUMBER_ITEM_LEN_MAX,
    NUMBER_ITEM_LEN_MIN,
    NUMBER_SCHEME_INTELLIGENT,
    NUMBER_SCHEME_SEMI_INTELLIGENT,
    NUMBER_VARIATION_LEN_DEFAULT,
    NUMBER_VARIATION_LEN_MAX,
    NUMBER_VARIATION_LEN_MIN,
    PACKAGE_TYPES,
    POWER_UNITS,
    ROLE_TYPE_VIEWER,
    ROLE_TYPES,
    SUBSCRIPTION_TYPES,
    TEMPERATURE_UNITS,
    VALUE_UNITS,
    VOLTAGE_UNITS,
    WAVELENGTH_UNITS,
    WEIGHT_UNITS,
)
from .csv_headers import (
    BOMFlatCSVHeaders,
    BOMIndentedCSVHeaders,
    CSVHeaderError,
    PartClassesCSVHeaders,
    PartsListCSVHeaders,
)
from .form_fields import AutocompleteTextInput
from .models import (
    Assembly,
    AssemblySubparts,
    Manufacturer,
    ManufacturerPart,
    Organization,
    Part,
    PartClass,
    PartRevision,
    Seller,
    SellerPart,
    Subpart,
    User,
    UserMeta,
)
from .utils import (
    check_references_for_duplicates,
    get_from_dict,
    listify_string,
    prep_for_sorting_nicely,
    stringify_list,
)
from .validators import alphanumeric, decimal, numeric
                units = cleaned_data.get(key)
                if not value and units:
                    self.add_error(key, forms.ValidationError(f"Cannot specify {units} without an accompanying value", code='invalid'))
                elif units and not units:
                    self.add_error(key, forms.ValidationError(f"Cannot specify value {units} without an accompanying units", code='invalid'))

        return cleaned_data


class PartRevisionNewForm(PartRevisionForm):
    copy_assembly = forms.BooleanField(label='Copy assembly from latest revision', initial=True, required=False)

    def __init__(self, *args, **kwargs):
        self.part = kwargs.pop('part', None)
        self.revision = kwargs.pop('revision', None)
        self.assembly = kwargs.pop('assembly', None)
        super(PartRevisionNewForm, self).__init__(*args, **kwargs)
        for _, value in self.fields.items():
            value.widget.attrs['placeholder'] = value.help_text
            value.help_text = ''

    def save(self):
        cleaned_data = super(PartRevisionNewForm, self).clean()
        self.instance.part = self.part
        self.instance.revision = self.revision
        self.instance.assembly = self.assembly
        self.instance.save()
        return self.instance


class SubpartForm(forms.ModelForm):
    class Meta:
        model = Subpart
        fields = ['part_revision', 'reference', 'count', 'do_not_load']

    def __init__(self, *args, **kwargs):
        self.organization = kwargs.pop('organization', None)
        self.part_id = kwargs.pop('part_id', None)
        super(SubpartForm, self).__init__(*args, **kwargs)
        if self.part_id is None:
            self.Meta.exclude = ['part_revision']
        else:
            self.fields['part_revision'].queryset = PartRevision.objects.filter(
                part__id=self.part_id).order_by('-timestamp')

        if self.part_id:
            part = Part.objects.get(id=self.part_id)
            unusable_part_ids = [p.id for p in part.where_used_full()]
            unusable_part_ids.append(part.id)

    def clean_count(self):
        count = self.cleaned_data['count']
        if not count:
            count = 1
        if count < 1:
            validation_error = forms.ValidationError(
                ("Subpart quantity must be > 0."),
                code='invalid')
            self.add_error('count', validation_error)
        return count

    def clean_reference(self):
        reference = self.cleaned_data['reference']
        reference = stringify_list(listify_string(reference))
        return reference

    def clean(self):
        cleaned_data = super(SubpartForm, self).clean()
        reference_list = listify_string(cleaned_data.get('reference'))
        count = cleaned_data.get('count')

        if len(reference_list) > 0 and len(reference_list) != count:
            raise forms.ValidationError(
                ("The number of reference designators ({0}) did not match the subpart quantity ({1}).".format(len(reference_list), count)),
                code='invalid')

        return cleaned_data


class AddSubpartForm(forms.Form):
    subpart_part_number = forms.CharField(required=True, label="Subpart part number")
    count = forms.IntegerField(required=False, label='Quantity')
    reference = forms.CharField(required=False, label="Reference")
    do_not_load = forms.BooleanField(required=False, label="do_not_load")

    def __init__(self, *args, **kwargs):
        self.organization = kwargs.pop('organization', None)
        self.part_id = kwargs.pop('part_id', None)
        self.part = Part.objects.get(id=self.part_id)
        self.part_revision = self.part.latest()
        self.unusable_part_rev_ids = [pr.id for pr in self.part_revision.where_used_full()]
        self.unusable_part_rev_ids.append(self.part_revision.id)
        super(AddSubpartForm, self).__init__(*args, **kwargs)
        self.fields['subpart_part_number'] = forms.CharField(required=True, label="Subpart part number",
                                                    widget=AutocompleteTextInput(attrs={'placeholder': 'Select a part.'},
                                                                                 queryset=Part.objects.filter(organization=self.organization).exclude(id=self.part_id),
                                                                                 verbose_string_function=Part.verbose_str))

    def clean_count(self):
        count = self.cleaned_data['count']
        if not count:
            count = 1
        elif count < 1:
            validation_error = forms.ValidationError("Subpart quantity must be > 0.", code='invalid')
            self.add_error('count', validation_error)
        return count

    def clean_reference(self):
        reference = self.cleaned_data['reference']
        reference = stringify_list(listify_string(reference))
        return reference

    def clean_subpart_part_number(self):
        subpart_part_number = self.cleaned_data['subpart_part_number']

        if not subpart_part_number:
            validation_error = forms.ValidationError("Must specify a part number.", code='invalid')
            self.add_error('subpart_part_number', validation_error)

        try:

            if self.organization.number_scheme == NUMBER_SCHEME_INTELLIGENT: