django-bom 1.262__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-bom might be problematic. Click here for more details.
- bom/__init__.py +1 -0
- bom/admin.py +207 -0
- bom/apps.py +8 -0
- bom/auth_backends.py +47 -0
- bom/base_classes.py +31 -0
- bom/constants.py +217 -0
- bom/context_processors.py +9 -0
- bom/csv_headers.py +252 -0
- bom/decorators.py +32 -0
- bom/form_fields.py +59 -0
- bom/forms.py +1328 -0
- bom/helpers.py +367 -0
- bom/local_settings.py +35 -0
- bom/migrations/0001_initial.py +135 -0
- bom/migrations/0002_auto_20180908_2151.py +24 -0
- bom/migrations/0003_sellerpart_data_source.py +18 -0
- bom/migrations/0004_auto_20180911_0011.py +18 -0
- bom/migrations/0005_auto_20181007_1934.py +56 -0
- bom/migrations/0006_auto_20181007_1949.py +41 -0
- bom/migrations/0007_auto_20181009_0256.py +19 -0
- bom/migrations/0008_auto_20181030_0427.py +19 -0
- bom/migrations/0009_subpart_reference.py +18 -0
- bom/migrations/0010_auto_20181202_0733.py +23 -0
- bom/migrations/0011_auto_20181202_2113.py +22 -0
- bom/migrations/0012_partchangehistory.py +30 -0
- bom/migrations/0013_auto_20190222_1631.py +19 -0
- bom/migrations/0014_auto_20190223_2353.py +18 -0
- bom/migrations/0015_auto_20190303_1915.py +136 -0
- bom/migrations/0016_auto_20190405_2308.py +58 -0
- bom/migrations/0017_auto_20190616_1912.py +19 -0
- bom/migrations/0018_auto_20190616_2143.py +24 -0
- bom/migrations/0019_auto_20190624_1246.py +45 -0
- bom/migrations/0020_auto_20190627_0207.py +38 -0
- bom/migrations/0021_auto_20190627_0428.py +23 -0
- bom/migrations/0022_auto_20190811_2140.py +35 -0
- bom/migrations/0023_auto_20191205_2351.py +255 -0
- bom/migrations/0024_auto_20191214_1342.py +89 -0
- bom/migrations/0025_auto_20191221_1907.py +38 -0
- bom/migrations/0026_auto_20191222_2258.py +22 -0
- bom/migrations/0027_auto_20191222_2347.py +17 -0
- bom/migrations/0028_partrevision_displayable_synopsis.py +74 -0
- bom/migrations/0029_auto_20191231_1630.py +23 -0
- bom/migrations/0030_auto_20200101_2253.py +22 -0
- bom/migrations/0031_auto_20200104_1352.py +38 -0
- bom/migrations/0032_auto_20200126_1806.py +27 -0
- bom/migrations/0033_auto_20200203_0618.py +29 -0
- bom/migrations/0034_auto_20200222_0359.py +30 -0
- bom/migrations/0035_auto_20200303_0111.py +34 -0
- bom/migrations/0036_auto_20200303_0538.py +17 -0
- bom/migrations/0037_auto_20200405_1642.py +44 -0
- bom/migrations/0038_auto_20200422_0504.py +19 -0
- bom/migrations/0039_auto_20200929_2315.py +41 -0
- bom/migrations/0040_alter_organization_currency.py +19 -0
- bom/migrations/0041_organization_subscription_quantity.py +18 -0
- bom/migrations/0042_auto_20210720_2137.py +23 -0
- bom/migrations/0043_auto_20211123_0157.py +24 -0
- bom/migrations/0044_auto_20220831_1241.py +23 -0
- bom/migrations/0045_sellerpart_link.py +18 -0
- bom/migrations/0046_alter_sellerpart_unique_together.py +17 -0
- bom/migrations/0047_sellerpart_seller_part_number.py +18 -0
- bom/migrations/0048_rename_part_organization_number_class_bom_part_organiz_b333d6_idx_and_more.py +1017 -0
- bom/migrations/0049_alter_assembly_id_alter_assemblysubparts_id_and_more.py +99 -0
- bom/migrations/0050_alter_organization_options.py +17 -0
- bom/migrations/0051_alter_manufacturer_organization_and_more.py +41 -0
- bom/migrations/0052_remove_partrevision_attribute_and_more.py +584 -0
- bom/migrations/__init__.py +0 -0
- bom/models.py +886 -0
- bom/part_bom.py +192 -0
- bom/settings.py +262 -0
- bom/static/bom/css/dashboard.css +17 -0
- bom/static/bom/css/jquery.treetable.css +28 -0
- bom/static/bom/css/materialize.min.css +13 -0
- bom/static/bom/css/part-info.css +15 -0
- bom/static/bom/css/style.css +482 -0
- bom/static/bom/css/tablesorter-theme.materialize.css +176 -0
- bom/static/bom/css/treetable-theme.css +42 -0
- bom/static/bom/doc/sample_part_classes.csv +38 -0
- bom/static/bom/doc/test_bom.csv +6 -0
- bom/static/bom/doc/test_bom_5_intelligent.csv +4 -0
- bom/static/bom/doc/test_full_bom.csv +37 -0
- bom/static/bom/doc/test_new_parts.csv +5 -0
- bom/static/bom/doc/test_new_parts_5_intelligent.csv +5 -0
- bom/static/bom/img/_ionicons_svg_md-arrow-dropdown.svg +1 -0
- bom/static/bom/img/_ionicons_svg_md-arrow-dropright.svg +1 -0
- bom/static/bom/img/favicon.ico +0 -0
- bom/static/bom/img/google/web/1x/btn_google_signin_dark_disabled_web.png +0 -0
- bom/static/bom/img/google/web/1x/btn_google_signin_dark_focus_web.png +0 -0
- bom/static/bom/img/google/web/1x/btn_google_signin_dark_normal_web.png +0 -0
- bom/static/bom/img/google/web/1x/btn_google_signin_dark_pressed_web.png +0 -0
- bom/static/bom/img/google/web/1x/btn_google_signin_light_disabled_web.png +0 -0
- bom/static/bom/img/google/web/1x/btn_google_signin_light_focus_web.png +0 -0
- bom/static/bom/img/google/web/1x/btn_google_signin_light_normal_web.png +0 -0
- bom/static/bom/img/google/web/1x/btn_google_signin_light_pressed_web.png +0 -0
- bom/static/bom/img/google/web/2x/btn_google_signin_dark_disabled_web@2x.png +0 -0
- bom/static/bom/img/google/web/2x/btn_google_signin_dark_focus_web@2x.png +0 -0
- bom/static/bom/img/google/web/2x/btn_google_signin_dark_normal_web@2x.png +0 -0
- bom/static/bom/img/google/web/2x/btn_google_signin_dark_pressed_web@2x.png +0 -0
- bom/static/bom/img/google/web/2x/btn_google_signin_light_disabled_web@2x.png +0 -0
- bom/static/bom/img/google/web/2x/btn_google_signin_light_focus_web@2x.png +0 -0
- bom/static/bom/img/google/web/2x/btn_google_signin_light_normal_web@2x.png +0 -0
- bom/static/bom/img/google/web/2x/btn_google_signin_light_pressed_web@2x.png +0 -0
- bom/static/bom/img/google/web/vector/btn_google_dark_disabled_ios.eps +814 -0
- bom/static/bom/img/google/web/vector/btn_google_dark_disabled_ios.svg +24 -0
- bom/static/bom/img/google/web/vector/btn_google_dark_focus_ios.eps +1866 -0
- bom/static/bom/img/google/web/vector/btn_google_dark_focus_ios.svg +51 -0
- bom/static/bom/img/google/web/vector/btn_google_dark_normal_ios.eps +1031 -0
- bom/static/bom/img/google/web/vector/btn_google_dark_normal_ios.svg +50 -0
- bom/static/bom/img/google/web/vector/btn_google_dark_pressed_ios.eps +1031 -0
- bom/static/bom/img/google/web/vector/btn_google_dark_pressed_ios.svg +50 -0
- bom/static/bom/img/google/web/vector/btn_google_light_disabled_ios.eps +814 -0
- bom/static/bom/img/google/web/vector/btn_google_light_disabled_ios.svg +24 -0
- bom/static/bom/img/google/web/vector/btn_google_light_focus_ios.eps +1837 -0
- bom/static/bom/img/google/web/vector/btn_google_light_focus_ios.svg +44 -0
- bom/static/bom/img/google/web/vector/btn_google_light_normal_ios.eps +1002 -0
- bom/static/bom/img/google/web/vector/btn_google_light_normal_ios.svg +43 -0
- bom/static/bom/img/google/web/vector/btn_google_light_pressed_ios.eps +1002 -0
- bom/static/bom/img/google/web/vector/btn_google_light_pressed_ios.svg +43 -0
- bom/static/bom/img/google_drive_logo.svg +1 -0
- bom/static/bom/img/indabom.png +0 -0
- bom/static/bom/img/mouser.png +0 -0
- bom/static/bom/img/octopart_blue.svg +19 -0
- bom/static/bom/js/formset-handler.js +65 -0
- bom/static/bom/js/jquery-3.4.1.min.js +2 -0
- bom/static/bom/js/jquery.ba-floatingscrollbar.min.js +10 -0
- bom/static/bom/js/jquery.treetable.js +629 -0
- bom/static/bom/js/materialize.min.js +6 -0
- bom/templates/bom/account-delete.html +23 -0
- bom/templates/bom/add-manufacturer-part.html +66 -0
- bom/templates/bom/add-sellerpart.html +93 -0
- bom/templates/bom/base-menu.html +16 -0
- bom/templates/bom/base.html +129 -0
- bom/templates/bom/bom-action-btn.html +23 -0
- bom/templates/bom/bom-action-table.html +57 -0
- bom/templates/bom/bom-base-menu.html +6 -0
- bom/templates/bom/bom-base.html +24 -0
- bom/templates/bom/bom-form-modal.html +36 -0
- bom/templates/bom/bom-form.html +30 -0
- bom/templates/bom/bom-modal-add-users.html +49 -0
- bom/templates/bom/bom-signup.html +12 -0
- bom/templates/bom/components/bom-flat.html +131 -0
- bom/templates/bom/components/bom-indented.html +237 -0
- bom/templates/bom/components/manufacturer-part-list.html +270 -0
- bom/templates/bom/components/seller-part-list.html +62 -0
- bom/templates/bom/create-part.html +65 -0
- bom/templates/bom/dashboard-menu.html +15 -0
- bom/templates/bom/dashboard.html +303 -0
- bom/templates/bom/edit-manufacturer-part.html +72 -0
- bom/templates/bom/edit-part-class.html +120 -0
- bom/templates/bom/edit-part.html +67 -0
- bom/templates/bom/edit-quantity-of-measure.html +119 -0
- bom/templates/bom/edit-user-meta.html +70 -0
- bom/templates/bom/help.html +1356 -0
- bom/templates/bom/manufacturer-info.html +82 -0
- bom/templates/bom/manufacturers.html +97 -0
- bom/templates/bom/nothing-to-see.html +15 -0
- bom/templates/bom/organization-create.html +135 -0
- bom/templates/bom/part-info.html +448 -0
- bom/templates/bom/part-revision-display.html +50 -0
- bom/templates/bom/part-revision-edit.html +39 -0
- bom/templates/bom/part-revision-manage-bom.html +115 -0
- bom/templates/bom/part-revision-new.html +57 -0
- bom/templates/bom/part-revision-release.html +41 -0
- bom/templates/bom/search-help.html +101 -0
- bom/templates/bom/seller-info.html +82 -0
- bom/templates/bom/sellers.html +97 -0
- bom/templates/bom/settings.html +734 -0
- bom/templates/bom/signup.html +28 -0
- bom/templates/bom/subscription_panel.html +16 -0
- bom/templates/bom/table_of_contents.html +47 -0
- bom/templates/bom/upload-bom.html +111 -0
- bom/templates/bom/upload-parts-help.html +103 -0
- bom/templates/bom/upload-parts.html +50 -0
- bom/templates/registration/login.html +39 -0
- bom/tests.py +1592 -0
- bom/third_party_apis/__init__.py +0 -0
- bom/third_party_apis/base_api.py +51 -0
- bom/third_party_apis/google_drive.py +166 -0
- bom/third_party_apis/mouser.py +132 -0
- bom/third_party_apis/test_apis.py +24 -0
- bom/urls.py +100 -0
- bom/utils.py +228 -0
- bom/validators.py +23 -0
- bom/views/__init__.py +0 -0
- bom/views/json_views.py +55 -0
- bom/views/views.py +1773 -0
- bom/wsgi.py +16 -0
- django_bom-1.262.dist-info/METADATA +206 -0
- django_bom-1.262.dist-info/RECORD +191 -0
- django_bom-1.262.dist-info/WHEEL +5 -0
- django_bom-1.262.dist-info/licenses/LICENSE +674 -0
- django_bom-1.262.dist-info/top_level.txt +1 -0
bom/csv_headers.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
|
|
3
|
+
from .utils import get_from_dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CSVHeaderError(Exception):
|
|
7
|
+
def __init__(self, str):
|
|
8
|
+
self.str = str
|
|
9
|
+
|
|
10
|
+
def __str__(self):
|
|
11
|
+
return self.str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CSVHeader:
|
|
15
|
+
def __init__(self, name, *args, **kwargs):
|
|
16
|
+
self.name = name
|
|
17
|
+
self.name_options = kwargs.get('name_options', [])
|
|
18
|
+
|
|
19
|
+
def __contains__(self, item):
|
|
20
|
+
if isinstance(item, str):
|
|
21
|
+
return item == self.name
|
|
22
|
+
else:
|
|
23
|
+
return item.name == self.name
|
|
24
|
+
|
|
25
|
+
def synonyms(self):
|
|
26
|
+
return [self.name] + self.name_options
|
|
27
|
+
|
|
28
|
+
def keys(self):
|
|
29
|
+
return [self.name]
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return self.name
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CSVHeaders(ABC):
|
|
36
|
+
all_headers_defns = []
|
|
37
|
+
|
|
38
|
+
def __init__(self, *args, **kwargs):
|
|
39
|
+
self.headers_defns = list(self.all_headers_defns)
|
|
40
|
+
|
|
41
|
+
def get_synoynms(self, hdr_name):
|
|
42
|
+
for defn in self.headers_defns:
|
|
43
|
+
if hdr_name in defn:
|
|
44
|
+
return defn.synonyms()
|
|
45
|
+
else:
|
|
46
|
+
for syn in defn.name_options:
|
|
47
|
+
if hdr_name == syn:
|
|
48
|
+
k = defn.keys()
|
|
49
|
+
return k + defn.name_options
|
|
50
|
+
|
|
51
|
+
# If header name does not have a default (i.e., it is not a valid header name) then
|
|
52
|
+
# returns None.
|
|
53
|
+
def get_default(self, hdr_name):
|
|
54
|
+
synonyms = self.get_synoynms(hdr_name)
|
|
55
|
+
return self.get_synoynms(hdr_name)[0] if synonyms is not None else None
|
|
56
|
+
|
|
57
|
+
# Preserves order of definitions as listed in all_header_defns:
|
|
58
|
+
def get_default_all(self):
|
|
59
|
+
return [d.name for d in self.headers_defns]
|
|
60
|
+
|
|
61
|
+
# Given a list of header names returns the default name for each. The return list
|
|
62
|
+
# matches the order of the input list. If a name is not recognized, then returns
|
|
63
|
+
# None as its default:
|
|
64
|
+
def get_defaults_list(self, hdr_names):
|
|
65
|
+
defaults_list = []
|
|
66
|
+
for hdr_name in hdr_names:
|
|
67
|
+
defaults_list.append(self.get_default(hdr_name))
|
|
68
|
+
return defaults_list
|
|
69
|
+
|
|
70
|
+
def is_valid(self, hdr_name):
|
|
71
|
+
return self.get_synoynms(hdr_name) is not None
|
|
72
|
+
|
|
73
|
+
def get_val_from_row(self, input_dict, hdr_name):
|
|
74
|
+
synonyms = self.get_synoynms(hdr_name)
|
|
75
|
+
return get_from_dict(input_dict, synonyms) if synonyms is not None else None
|
|
76
|
+
|
|
77
|
+
def count_matches(self, header, hdr_name):
|
|
78
|
+
c = 0
|
|
79
|
+
syns = self.get_synoynms(hdr_name)
|
|
80
|
+
if syns is not None:
|
|
81
|
+
for h in header:
|
|
82
|
+
c += 1 if h in syns else 0
|
|
83
|
+
return c
|
|
84
|
+
|
|
85
|
+
def validate_header_names(self, headers):
|
|
86
|
+
unrecognized_list = []
|
|
87
|
+
for hdr in headers:
|
|
88
|
+
if not self.is_valid(hdr):
|
|
89
|
+
unrecognized_list.append(hdr)
|
|
90
|
+
if len(unrecognized_list) == 1:
|
|
91
|
+
raise CSVHeaderError(("Unrecognized column header \'{}\'").format(unrecognized_list[0]))
|
|
92
|
+
elif len(unrecognized_list) > 1:
|
|
93
|
+
raise CSVHeaderError(("Unrecognized column headers \'{}\'").format(unrecognized_list))
|
|
94
|
+
|
|
95
|
+
# Each assertion is expressed in reverse-polish notation with no precendence in order of evaluation. Operands are
|
|
96
|
+
# header names, operators are:
|
|
97
|
+
#
|
|
98
|
+
# 'in' means contains
|
|
99
|
+
# 'and' means logical AND
|
|
100
|
+
# 'or' means logical OR
|
|
101
|
+
# 'mex' means mutually exclusive (one or the other but not both)
|
|
102
|
+
#
|
|
103
|
+
# For example:
|
|
104
|
+
# 'up', 'down', 'and', 'left', 'or'
|
|
105
|
+
# Means that 'up' and 'down' must be present or just 'left' must be present.
|
|
106
|
+
# All assertions in list must be true or will raise an exception.
|
|
107
|
+
def validate_header_assertions(self, headers, assertion_list):
|
|
108
|
+
|
|
109
|
+
def evaluate(headers, operand, operator, prev_count, report):
|
|
110
|
+
c = 0
|
|
111
|
+
if operator == '____count____':
|
|
112
|
+
return self.count_matches(headers, operand)
|
|
113
|
+
elif operator == 'in':
|
|
114
|
+
c = self.count_matches(headers, operand)
|
|
115
|
+
if c == 0:
|
|
116
|
+
if report: raise CSVHeaderError(("Missing column named \'{}\'").format(operand))
|
|
117
|
+
elif c > 1:
|
|
118
|
+
if report: raise CSVHeaderError(("Multiple columns with same or synonymous name \'{}\'").format(operand))
|
|
119
|
+
elif operator == 'and':
|
|
120
|
+
c = self.count_matches(headers, operand)
|
|
121
|
+
if c == 0 or prev_count == 0:
|
|
122
|
+
if report: raise CSVHeaderError(("Missing column named \'{}\'").format(self.get_default(operand)))
|
|
123
|
+
elif operator == 'or':
|
|
124
|
+
c = self.count_matches(headers, operand)
|
|
125
|
+
if c == 0 and prev_count == 0:
|
|
126
|
+
if report: raise CSVHeaderError(("Missing column named \'{}\'").format(self.get_default(operand)))
|
|
127
|
+
elif operator == 'me':
|
|
128
|
+
c = self.count_matches(headers, operand)
|
|
129
|
+
if c > 1 and prev_count > 1:
|
|
130
|
+
if report: raise CSVHeaderError(("Conflicting column named \'{}\'").format(self.get_default(operand)))
|
|
131
|
+
return c
|
|
132
|
+
|
|
133
|
+
c = 0
|
|
134
|
+
for assertion in assertion_list:
|
|
135
|
+
if (len(assertion) > 1):
|
|
136
|
+
i = 0
|
|
137
|
+
num_asserts = len(assertion)
|
|
138
|
+
while (i < num_asserts):
|
|
139
|
+
operand = assertion[i]
|
|
140
|
+
operand_or_operator = assertion[i + 1]
|
|
141
|
+
if operand_or_operator not in ['in', 'and', 'or', 'mex']:
|
|
142
|
+
operator = '____count____'
|
|
143
|
+
c = evaluate(headers, operand, operator, c, i + 1 == num_asserts)
|
|
144
|
+
i += 1
|
|
145
|
+
else:
|
|
146
|
+
operator = operand_or_operator
|
|
147
|
+
c = evaluate(headers, operand, operator, c, i + 1 == num_asserts)
|
|
148
|
+
i += 2
|
|
149
|
+
|
|
150
|
+
def validate_header_in(self, headers, header):
|
|
151
|
+
header_synonyms = self.get_synoynms(header)
|
|
152
|
+
for hs in header_synonyms:
|
|
153
|
+
if hs in headers:
|
|
154
|
+
return True
|
|
155
|
+
raise CSVHeaderError(f'Missing column named {header}')
|
|
156
|
+
|
|
157
|
+
def add_header(self, header):
|
|
158
|
+
self.headers_defns.append(header)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
# For each CSV header class, a static data member dictionary uses the key as the default name for the header while the
|
|
163
|
+
# value is the list of synonyms for the header.
|
|
164
|
+
#
|
|
165
|
+
# In the derive class initialization of the base class member all_headers_defns, the order in which data members are
|
|
166
|
+
# listed may be used to prescribe the order in which CVS file columns will be created. So doing depends upon how the
|
|
167
|
+
# CSV generation code is written, but as long as it iterates over the list returned by get_default_all() then the
|
|
168
|
+
# order will be preserved.
|
|
169
|
+
#
|
|
170
|
+
|
|
171
|
+
class ManufacturerPartCSVHeaders(CSVHeaders):
|
|
172
|
+
all_headers_defns = [
|
|
173
|
+
CSVHeader('manufacturer_name', name_options=['mfg_name', 'manufacturer_name', 'part_manufacturer', 'mfg', 'manufacturer', 'manufacturer name', ]),
|
|
174
|
+
CSVHeader('manufacturer_part_number', name_options=['mpn', 'mfg_part_number', 'part_manufacturer_part_number', 'mfg part number', 'manufacturer part number']),
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class SellerPartCSVHeaders(CSVHeaders):
|
|
179
|
+
all_headers_defns = [
|
|
180
|
+
CSVHeader('seller', name_options=['part_seller', 'part_seller_name', ]),
|
|
181
|
+
CSVHeader('seller_part_number', name_options=['seller_pn', 'spn', 'pn', 'part_seller_part_number', 'seller_part_number', ]),
|
|
182
|
+
CSVHeader('unit_cost', name_options=['seller_part_unit_cost', 'unit_cost', 'part_cost', ]),
|
|
183
|
+
CSVHeader('minimum_order_quantity', name_options=['minimum_order_quantity', 'moq', 'part_moq', ]),
|
|
184
|
+
CSVHeader('nre_cost', name_options=['part_nre', 'part_nre_cost', 'nre', 'nre_cost', ]),
|
|
185
|
+
CSVHeader('minimum_pack_quantity', name_options=['minimum_pack_quantity', 'mpq', 'part_mpq' ]),
|
|
186
|
+
CSVHeader('lead_time_days', name_options=['lead_time_days', 'lead_time', 'lt']),
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class PartClassesCSVHeaders(CSVHeaders):
|
|
191
|
+
all_headers_defns = [
|
|
192
|
+
CSVHeader('code'),
|
|
193
|
+
CSVHeader('name'),
|
|
194
|
+
CSVHeader('comment', name_options=['description', 'desc', 'desc.']),
|
|
195
|
+
CSVHeader('mouser_enabled'),
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class PartRevisionPropertyCSVHeaders(CSVHeaders):
|
|
200
|
+
def add_dynamic_headers(self, definitions):
|
|
201
|
+
for defn in definitions:
|
|
202
|
+
self.add_header(
|
|
203
|
+
CSVHeader(defn.form_field_name,
|
|
204
|
+
name_options=[defn.name, f'property_{defn.code}', f'property_{defn.name}', 'p']))
|
|
205
|
+
if defn.quantity_of_measure:
|
|
206
|
+
self.add_header(
|
|
207
|
+
CSVHeader(defn.form_unit_field_name, name_options=[f'{defn.name} Units', f'{defn.code}_units']))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class PartsListCSVHeaders(PartRevisionPropertyCSVHeaders):
|
|
211
|
+
all_headers_defns = [
|
|
212
|
+
CSVHeader('description', name_options=['desc', 'desc.', ]),
|
|
213
|
+
CSVHeader('manufacturer_name', name_options=['mfg_name', 'manufacturer_name', 'part_manufacturer', 'mfg', 'manufacturer', 'manufacturer name', ]),
|
|
214
|
+
CSVHeader('manufacturer_part_number', name_options=['mpn', 'mfg_part_number', 'part_manufacturer_part_number', 'mfg part number', 'manufacturer part number']),
|
|
215
|
+
CSVHeader('part_number', name_options=['part number', 'part no', ]),
|
|
216
|
+
CSVHeader('revision', name_options=['rev', 'part_revision', ]),
|
|
217
|
+
] + SellerPartCSVHeaders.all_headers_defns
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class PartsListCSVHeadersSemiIntelligent(PartsListCSVHeaders):
|
|
221
|
+
all_headers_defns = [
|
|
222
|
+
CSVHeader('description', name_options=['desc', 'desc.', ]),
|
|
223
|
+
CSVHeader('manufacturer_name', name_options=['mfg_name', 'manufacturer_name', 'part_manufacturer', 'mfg', 'manufacturer', 'manufacturer name', ]),
|
|
224
|
+
CSVHeader('manufacturer_part_number', name_options=['mpn', 'mfg_part_number', 'part_manufacturer_part_number', 'mfg part number', 'manufacturer part number']),
|
|
225
|
+
CSVHeader('part_class', name_options=['class', 'part_category']),
|
|
226
|
+
CSVHeader('part_number', name_options=['part number', 'part no', ]),
|
|
227
|
+
CSVHeader('revision', name_options=['rev', 'part_revision', ]),
|
|
228
|
+
] + SellerPartCSVHeaders.all_headers_defns
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class BOMFlatCSVHeaders(PartRevisionPropertyCSVHeaders):
|
|
232
|
+
all_headers_defns = [
|
|
233
|
+
CSVHeader('part_number', name_options=['part number', 'part no', ]),
|
|
234
|
+
CSVHeader('quantity', name_options=['count', 'qty', 'quantity', ]),
|
|
235
|
+
CSVHeader('do_not_load', name_options=['dnl', 'dnp', 'do_not_populate', 'do_not_load', 'do not load', 'do not populate', ]),
|
|
236
|
+
CSVHeader('part_class', name_options=['class', 'part_category']),
|
|
237
|
+
CSVHeader('references', name_options=['designator', 'designators', 'reference', ]),
|
|
238
|
+
CSVHeader('synopsis', name_options=['part_synopsis', ]),
|
|
239
|
+
CSVHeader('description', name_options=['part_description', 'desc', ]),
|
|
240
|
+
CSVHeader('revision', name_options=['rev', 'part_revision', 'rev.']),
|
|
241
|
+
] + ManufacturerPartCSVHeaders.all_headers_defns \
|
|
242
|
+
+ SellerPartCSVHeaders.all_headers_defns + [
|
|
243
|
+
CSVHeader('extended_qty', name_options=['extended_quantity', 'part_extended_quantity', 'part_ext_qty', ]),
|
|
244
|
+
CSVHeader('extended_cost', name_options=['part_extended_cost', 'part_ext_cost', ]),
|
|
245
|
+
CSVHeader('order_qty', name_options=['part_order_qty', 'part_order_quantity', 'order_quantity', ]),
|
|
246
|
+
CSVHeader('out_of_pocket_cost', name_options=['part_out_of_pocket_cost', 'cost', ]),
|
|
247
|
+
CSVHeader('lead_time_days', name_options=['part_lead_time_days', ]),
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class BOMIndentedCSVHeaders(BOMFlatCSVHeaders):
|
|
252
|
+
all_headers_defns = [CSVHeader('level')] + BOMFlatCSVHeaders.all_headers_defns
|
bom/decorators.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from django.contrib import messages
|
|
2
|
+
from django.core.exceptions import PermissionDenied
|
|
3
|
+
from django.http import HttpResponseRedirect
|
|
4
|
+
from django.urls import reverse
|
|
5
|
+
|
|
6
|
+
from social_django.models import UserSocialAuth
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def google_authenticated(function):
|
|
10
|
+
def wrap(request, *args, **kwargs):
|
|
11
|
+
user = request.user
|
|
12
|
+
try:
|
|
13
|
+
user.social_auth.get(provider='google-oauth2')
|
|
14
|
+
return function(request, *args, **kwargs)
|
|
15
|
+
except UserSocialAuth.DoesNotExist as e:
|
|
16
|
+
messages.error(request, "You must Sign in with Google to access this feature.")
|
|
17
|
+
return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'organization'}))
|
|
18
|
+
|
|
19
|
+
wrap.__doc__ = function.__doc__
|
|
20
|
+
wrap.__name__ = function.__name__
|
|
21
|
+
return wrap
|
|
22
|
+
|
|
23
|
+
def organization_admin(function):
|
|
24
|
+
def wrap(request, *args, **kwargs):
|
|
25
|
+
if request.user.bom_profile().role != 'A':
|
|
26
|
+
messages.error(request, "You don't have permission to perform this action.")
|
|
27
|
+
return HttpResponseRedirect(request.META.get('HTTP_REFERER'), reverse('bom:home'))
|
|
28
|
+
return function(request, *args, **kwargs)
|
|
29
|
+
|
|
30
|
+
wrap.__doc__ = function.__doc__
|
|
31
|
+
wrap.__name__ = function.__name__
|
|
32
|
+
return wrap
|
bom/form_fields.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from django import forms
|
|
2
|
+
from django.utils.safestring import mark_safe
|
|
3
|
+
from json import dumps
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AutocompleteTextInput(forms.TextInput):
|
|
7
|
+
def __init__(self, *args, **kwargs):
|
|
8
|
+
self.queryset = kwargs.pop('queryset')
|
|
9
|
+
self.autocomplete_limit = kwargs.pop('autocomplete_limit', None)
|
|
10
|
+
self.autocomplete_min_length = kwargs.pop('autocomplete_min_length', 0)
|
|
11
|
+
self.autocomplete_submit = kwargs.pop('autocomplete_submit', False)
|
|
12
|
+
self.form_name = kwargs.pop('form_name', 'form')
|
|
13
|
+
self.verbose_string_function = kwargs.pop('verbose_string_function', str)
|
|
14
|
+
super().__init__(*args, **kwargs)
|
|
15
|
+
|
|
16
|
+
def render(self, name, value, attrs=None, renderer=None):
|
|
17
|
+
# verbose_string_function is used if we want to show verbose strings in the dropdown,
|
|
18
|
+
# but autocomplete to something simpler
|
|
19
|
+
|
|
20
|
+
# Disable chrome autocomplete..we dont want double duty here!
|
|
21
|
+
if attrs is not None:
|
|
22
|
+
attrs.update({'autocomplete': 'off'})
|
|
23
|
+
else:
|
|
24
|
+
attrs['autocomplete'] = "off"
|
|
25
|
+
|
|
26
|
+
html = super().render(name, value, attrs)
|
|
27
|
+
|
|
28
|
+
autocomplete_dict = {}
|
|
29
|
+
autocomplete_dict_to_fill = {}
|
|
30
|
+
for obj in self.queryset:
|
|
31
|
+
verbose_obj_str = self.verbose_string_function(obj).replace('"', '\\"')
|
|
32
|
+
autocomplete_dict.update({verbose_obj_str: None})
|
|
33
|
+
autocomplete_dict_to_fill.update({verbose_obj_str: str(obj)})
|
|
34
|
+
|
|
35
|
+
autocomplete_json = dumps(autocomplete_dict).replace("'", "\\'")
|
|
36
|
+
autocomplete_json_to_fill = dumps(autocomplete_dict_to_fill).replace("'", "\\'")
|
|
37
|
+
|
|
38
|
+
# To escape brackets in a Python 3.6 f-string we use double brackets
|
|
39
|
+
inline_code = mark_safe(
|
|
40
|
+
f"""<script>
|
|
41
|
+
const {name}_data = JSON.parse('{autocomplete_json}');
|
|
42
|
+
const {name}_data_to_fill = JSON.parse('{autocomplete_json_to_fill}');
|
|
43
|
+
const {name}_input = document.getElementById("id_{name}");
|
|
44
|
+
const {name}_form = {name}_input.form;
|
|
45
|
+
$(document).ready(function () {{
|
|
46
|
+
$('#id_{name}').autocomplete({{
|
|
47
|
+
data: {name}_data,
|
|
48
|
+
limit: {self.autocomplete_limit or 'undefined'}, // The max amount of results that can be shown at once. Default: Infinity.
|
|
49
|
+
minLength: {self.autocomplete_min_length}, // The minimum length of the input for the autocomplete to start. Default: 1.
|
|
50
|
+
onAutocomplete: function (val) {{
|
|
51
|
+
console.log({name}_data_to_fill);
|
|
52
|
+
$("#id_{name}").val({name}_data_to_fill[val]);
|
|
53
|
+
{f'{name}_form.submit()' if self.autocomplete_submit else ''}
|
|
54
|
+
}},
|
|
55
|
+
}});
|
|
56
|
+
}});
|
|
57
|
+
</script>"""
|
|
58
|
+
)
|
|
59
|
+
return html + inline_code
|