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 CSVHeader:
    def __init__(self, name, *args, **kwargs):
        self.name = name
        self.name_options = kwargs.get('name_options', [])

    def __contains__(self, item):
        if isinstance(item, str):
            return item == self.name
        else:
            return item.name == self.name

    def synonyms(self):
        return [self.name] + self.name_options

    def keys(self):
        return [self.name]

    def __str__(self):
        return self.name class ManufacturerPartCSVHeaders(CSVHeaders):
    all_headers_defns = [
        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']),
    ] 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 PartsListCSVHeadersSemiIntelligent(PartsListCSVHeaders):
    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_class', name_options=['class', 'part_category']),
        CSVHeader('part_number', name_options=['part number', 'part no', ]),
        CSVHeader('revision', name_options=['rev', 'part_revision', ]),
    ] + PartsListCSVHeaders.part_attributes \
      + SellerPartCSVHeaders.all_headers_defns class SellerPartCSVHeaders(CSVHeaders):
    all_headers_defns = [
        CSVHeader('seller', name_options=['part_seller', 'part_seller_name', ]),
        CSVHeader('unit_cost', name_options=['seller_part_unit_cost', 'unit_cost', 'part_cost', ]),
        CSVHeader('minimum_order_quantity', name_options=['minimum_order_quantity', 'moq', 'part_moq', ]),
        CSVHeader('nre_cost', name_options=['part_nre', 'part_nre_cost', 'nre', 'nre_cost', ]),
        CSVHeader('minimum_pack_quantity', name_options=['minimum_pack_quantity', 'mpq', 'part_mpq' ]),
        CSVHeader('lead_time_days', name_options=['lead_time_days', 'lead_time', 'lt']),
    ] class UserModelChoiceField(forms.ModelChoiceField):
class UserCreateForm(UserCreationForm):
class UserForm(forms.ModelForm):
    class Meta:
class UserAddForm(forms.ModelForm):
    class Meta:
class UserMetaForm(forms.ModelForm):
    class Meta:
class OrganizationCreateForm(forms.ModelForm):
    class Meta:
class OrganizationForm(forms.ModelForm):
    class Meta:
class OrganizationFormEditSettings(OrganizationForm):
    class Meta:
class OrganizationNumberLenForm(forms.ModelForm):
    class Meta:
class PartInfoForm(forms.Form):
class ManufacturerForm(forms.ModelForm):
    class Meta:
class ManufacturerPartForm(forms.ModelForm):
    class Meta:
class SellerPartForm(forms.ModelForm):
    class Meta:
class PartClassForm(forms.ModelForm):
    class Meta:
class PartClassSelectionForm(forms.Form):
class PartClassCSVForm(forms.Form):
class PartCSVForm(forms.Form):
class PartFormIntelligent(forms.ModelForm):
    class Meta:
class PartFormSemiIntelligent(forms.ModelForm):
    class Meta:
class PartRevisionForm(forms.ModelForm):
    class Meta:
class PartRevisionNewForm(PartRevisionForm):
class SubpartForm(forms.ModelForm):
    class Meta:
class AddSubpartForm(forms.Form):
class UploadBOMForm(forms.Form):
class BOMCSVForm(forms.Form):
class FileForm(forms.Form):
    def label_from_instance(self, user):
    def clean_email(self):
    def save(self, commit=True):
    def __init__(self, *args, **kwargs):
    def clean_username(self):
    def clean_role(self):
    def save(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def save(self):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def clean(self):
    def __init__(self, *args, **kwargs):
    def clean_name(self):
    def clean_code(self):
    def clean(self):
    def __init__(self, *args, **kwargs):
    def clean_part_class(self):
    def __init__(self, *args, **kwargs):
    def clean(self):
    def __init__(self, *args, **kwargs):
    def clean(self):
                def is_valid_choice(choice, choices):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def clean_number_class(self):
    def clean(self):
    def __init__(self, *args, **kwargs):
    def clean(self):
    def __init__(self, *args, **kwargs):
    def save(self):
    def __init__(self, *args, **kwargs):
    def clean_count(self):
    def clean_reference(self):
    def clean(self):
    def __init__(self, *args, **kwargs):
    def clean_count(self):
    def clean_reference(self):
    def clean_subpart_part_number(self):
    def clean(self):
    def __init__(self, *args, **kwargs):
    def clean_parent_part_number(self):
    def __init__(self, *args, **kwargs):
    def clean(self): 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 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)
import csv
import logging
import operator
import bom.constants as constants
from functools import reduce
from json import dumps, loads
from django.contrib import messages
from django.contrib.auth import authenticate, get_user_model, login
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db import IntegrityError
from django.db.models import ProtectedError, Q, prefetch_related_objects
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.encoding import smart_str
from django.utils.text import smart_split
from django.views.generic.base import TemplateView
from social_django.models import UserSocialAuth
from bom.csv_headers import (
    BOMFlatCSVHeaders,
    BOMIndentedCSVHeaders,
    CSVHeader,
    ManufacturerPartCSVHeaders,
    PartClassesCSVHeaders,
    PartsListCSVHeaders,
    PartsListCSVHeadersSemiIntelligent,
    SellerPartCSVHeaders,
)
from bom.forms import (
    AddSubpartForm,
    BOMCSVForm,
    FileForm,
    ManufacturerForm,
    ManufacturerPartForm,
    OrganizationCreateForm,
    OrganizationForm,
    OrganizationFormEditSettings,
    OrganizationNumberLenForm,
    PartClassCSVForm,
    PartClassForm,
    PartClassFormSet,
    PartClassSelectionForm,
    PartCSVForm,
    PartFormIntelligent,
    PartFormSemiIntelligent,
    PartInfoForm,
    PartRevisionForm,
    PartRevisionNewForm,
    SellerPartForm,
    SubpartForm,
    UploadBOMForm,
    UserAddForm,
    UserCreateForm,
    UserForm,
    UserMetaForm,
)
from bom.models import (
    Assembly,
    AssemblySubparts,
    Manufacturer,
    ManufacturerPart,
    Organization,
    Part,
    PartClass,
    PartRevision,
    SellerPart,
    Subpart,
    User,
    UserMeta,
)
from bom.utils import check_references_for_duplicates, listify_string, prep_for_sorting_nicely, stringify_list
@login_required
def sellerpart_edit(request, sellerpart_id):
    user = request.user
    profile = user.bom_profile()
    organization = profile.organization

    title = "Edit Seller Part"
    action = reverse('bom:sellerpart-edit', kwargs={'sellerpart_id': sellerpart_id})
    sellerpart = get_object_or_404(SellerPart, pk=sellerpart_id)

    if request.method == 'POST':
        form = SellerPartForm(request.POST, instance=sellerpart, organization=organization)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('bom:part-info', kwargs={'part_id': sellerpart.manufacturer_part.part.id}) + '?tab_anchor=sourcing')
    else:
        form = SellerPartForm(instance=sellerpart, organization=organization)

    return TemplateResponse(request, 'bom/bom-form.html', locals())


@login_required
def sellerpart_delete(request, sellerpart_id):
    user = request.user
    profile = user.bom_profile()
    organization = profile.organization

    sellerpart = get_object_or_404(SellerPart, pk=sellerpart_id)
    part = sellerpart.manufacturer_part.part
    sellerpart.delete()
    return HttpResponseRedirect(reverse('bom:part-info', kwargs={'part_id': part.id}) + '?tab_anchor=sourcing')


@login_required
def part_revision_release(request, part_id, part_revision_id):
    user = request.user
    profile = user.bom_profile()
    organization = profile.organization

    part = get_object_or_404(Part, pk=part_id)
    part_revision = get_object_or_404(PartRevision, pk=part_revision_id)
    action = reverse('bom:part-revision-release', kwargs={'part_id': part.id, 'part_revision_id': part_revision.id})
    title = 'Promote {} Rev {} {} from <b>Working</b> to <b>Released</b>?'.format(part.full_part_number(), part_revision.revision, part_revision.synopsis())

    subparts = part_revision.assembly.subparts.filter(part_revision__configuration="W")
    release_warning = subparts.count() > 0

    if request.method == 'POST':
        part_revision.configuration = 'R'
        part_revision.save()
        return HttpResponseRedirect(reverse('bom:part-info-history', kwargs={'part_id': part.id, 'part_revision_id': part_revision.id}))

    return TemplateResponse(request, 'bom/part-revision-release.html', locals())


@login_required
def part_revision_revert(request, part_id, part_revision_id):
    user = request.user
    profile = user.bom_profile()
    organization = profile.organization

    part_revision = get_object_or_404(PartRevision, pk=part_revision_id)
    part_revision.configuration = 'W'
    part_revision.save()
    return HttpResponseRedirect(reverse('bom:part-info-history', kwargs={'part_id': part_id, 'part_revision_id': part_revision_id}))


@login_required
def part_revision_new(request, part_id):
    user = request.user
    profile = user.bom_profile()
    organization = profile.organization

    part = get_object_or_404(Part, pk=part_id)
    title = 'New Revision for {}'.format(part.full_part_number())
    action = reverse('bom:part-revision-new', kwargs={'part_id': part_id})

    latest_revision = part.latest()
    next_revision_number = latest_revision.next_revision() if latest_revision else None

    all_part_revisions = part.revisions()
    all_used_part_revisions = PartRevision.objects.filter(part=part)
    used_in_subparts = Subpart.objects.filter(part_revision__in=all_used_part_revisions)
    used_in_assembly_ids = AssemblySubparts.objects.filter(subpart__in=used_in_subparts).values_list('assembly', flat=True)
    all_used_in_prs = PartRevision.objects.filter(assembly__in=used_in_assembly_ids)
    used_part_revisions = all_used_in_prs.filter(configuration='W')

    if request.method == 'POST':
        part_revision_new_form = PartRevisionNewForm(request.POST, part=part, revision=next_revision_number, assembly=latest_revision.assembly)
        if part_revision_new_form.is_valid():
            new_part_revision = part_revision_new_form.save()

            revisions_to_roll = request.POST.getlist('roll')
            # TODO: could optimize this, but probably shouldn't get too crazy so may be fine...
            for r_id in revisions_to_roll:
                subparts = PartRevision.objects.get(id=r_id).assembly.subparts \
                    .filter(part_revision__in=all_part_revisions)
                subparts.update(part_revision=new_part_revision)

            if part_revision_new_form.cleaned_data['copy_assembly']:
                old_subparts = latest_revision.assembly.subparts.all() if latest_revision.assembly is not None else None
                new_assembly = latest_revision.assembly if latest_revision.assembly is not None else Assembly()
                new_assembly.pk = None
                new_assembly.save()

                part_revision_new_form.cleaned_data['assembly'] = new_assembly

                new_part_revision.assembly = new_assembly
                new_part_revision.save()
                for sp in old_subparts:
                    new_sp = sp
                    new_sp.pk = None
                    new_sp.save()
                    AssemblySubparts.objects.create(assembly=new_assembly, subpart=new_sp)
            return HttpResponseRedirect(reverse('bom:part-info', kwargs={'part_id': part_id}))

    else:
        if latest_revision:
            messages.info(request, 'New revision automatically incremented to `{}` from your last revision `{}`.'.format(next_revision_number, latest_revision.revision))
            latest_revision.revision = next_revision_number  # use updated object to populate form but don't save changes

            part_revision_new_form = PartRevisionNewForm(instance=latest_revision)