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
    if part_id is not None:
        part = get_object_or_404(Part, pk=part_id)
        part_revision = part.latest()
    elif part_revision_id is not None:
        part_revision = get_object_or_404(PartRevision, pk=part_revision_id)
        part = part_revision.part
    else:
        messages.error(request, "View requires part or part revision.")
        return HttpResponseRedirect(request.META.get('HTTP_REFERER'), '/')

    if part.organization != organization:
        messages.error(request, "Cant export a part that is not yours!")
        return HttpResponseRedirect(request.META.get('HTTP_REFERER'), '/')

    response = HttpResponse(content_type='text/csv')
    filename = f'indabom_export_{part.full_part_number()}_{"flat" if flat else "indented"}'
    response['Content-Disposition'] = f'attachment; filename="{filename}.csv'

    qty_cache_key = str(part_id) + '_qty'
    qty = cache.get(qty_cache_key, 1000)

    try:
        if flat:
            bom = part_revision.flat(top_level_quantity=qty)
        else:
            bom = part_revision.indented(top_level_quantity=qty)
    except (RuntimeError, RecursionError):
        messages.error(request, "Error: infinite recursion in part relationship. Contact info@indabom.com to resolve.")
        bom = []
    except AttributeError as err:
        messages.error(request, err)
        bom = []

    if flat:
        csv_headers = BOMFlatCSVHeaders()
    else:
        csv_headers = BOMIndentedCSVHeaders()

    csv_headers_raw = csv_headers.get_default_all()
    csv_rows = []
    for _, item in bom.parts.items():
        mapped_row = {}
        raw_row = {k: smart_str(v) for k, v in item.as_dict_for_export().items()}
        for kx, vx in raw_row.items():
            if csv_headers.get_default(kx) is None: print ("NONE", kx)
            mapped_row.update({csv_headers.get_default(kx): vx})

        if sourcing_detailed:
            for idx, sp in enumerate(item.seller_parts_for_export()):
                if f'{ManufacturerPartCSVHeaders.all_headers_defns[0]}_{idx + 1}' not in csv_headers_raw:
                    csv_headers_raw.extend([f'{h}_{idx + 1}' for h in ManufacturerPartCSVHeaders.all_headers_defns])
                    csv_headers_raw.extend([f'{h}_{idx + 1}' for h in SellerPartCSVHeaders.all_headers_defns])
                mapped_row.update({f'{k}_{idx + 1}': smart_str(v) for k, v in sp.items()})
        elif sourcing:
            for idx, mp in enumerate(item.manufacturer_parts_for_export()):
                if f'{ManufacturerPartCSVHeaders.all_headers_defns[0]}_{idx + 1}' not in csv_headers_raw:
                    csv_headers_raw.extend([f'{h}_{idx + 1}' for h in ManufacturerPartCSVHeaders.all_headers_defns])
                mapped_row.update({f'{k}_{idx + 1}': smart_str(v) for k, v in mp.items()})

        csv_rows.append(mapped_row)

    writer = csv.DictWriter(response, fieldnames=csv_headers_raw)
    writer.writeheader()
    writer.writerows(csv_rows)

    return response

# @login_required
# def part_export_bom_flat(request, part_revision_id):
#     user = request.user
#     profile = user.bom_profile()
#     organization = profile.organization
#
#     part_revision = get_object_or_404(PartRevision, pk=part_revision_id)
#
#     if part_revision.part.organization != organization:
#         messages.error(request, "Cant export a part that is not yours!")
#         return HttpResponseRedirect(request.META.get('HTTP_REFERER'), '/')
#
#     response = HttpResponse(content_type='text/csv')
#     response['Content-Disposition'] = 'attachment; filename="{}_indabom_parts_flat.csv"'.format(
#         part_revision.part.full_part_number())
#
#     # As compared to indented bom, show all references for a subpart as a single item and
#     # don't show do_not_load status at all because it won't be clear as to which subpart reference
#     # the do_not_load refers to.
#     qty_cache_key = str(part_revision.part.id) + '_qty'
#     qty = cache.get(qty_cache_key, 1000)
#
#     try:
#         bom = part_revision.flat(top_level_quantity=qty)
#     except (RuntimeError, RecursionError):
#         messages.error(request, "Error: infinite recursion in part relationship. Contact info@indabom.com to resolve.")
#         bom = []
#     except AttributeError as err:
#         messages.error(request, err)
#         bom = []
#
#     csv_headers = BOMFlatCSVHeaders()
#     writer = csv.DictWriter(response, fieldnames=csv_headers.get_default_all())
#     writer.writeheader()
#
#     for _, item in bom.parts.items():
#         mapped_row = {}
#         raw_row = {k: smart_str(v) for k, v in item.as_dict_for_export().items()}
#         for kx, vx in raw_row.items():
#             if csv_headers.get_default(kx) is None: print ("NONE", kx)
#             mapped_row.update({csv_headers.get_default(kx): vx})
#         writer.writerow({k: smart_str(v) for k, v in mapped_row.items()})
#
#     return response


@login_required
def upload_bom(request):
    user = request.user
    profile = user.bom_profile()
    organization = profile.organization

    if request.method == 'POST' and 'file' in request.FILES and request.FILES['file'] is not None:

        upload_bom_form = UploadBOMForm(request.POST, organization=organization)