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/utils.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# This file is to have no project dependencies
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def increment_char(c):
|
|
5
|
+
"""
|
|
6
|
+
Increment an uppercase character, returning 'A' if 'Z' is given
|
|
7
|
+
"""
|
|
8
|
+
return chr(ord(c) + 1) if c != 'Z' else 'A'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def increment_str(s):
|
|
12
|
+
lpart = s.rstrip('Z')
|
|
13
|
+
num_replacements = len(s) - len(lpart)
|
|
14
|
+
new_s = lpart[:-1] + increment_char(lpart[-1]) if lpart else 'A'
|
|
15
|
+
new_s += 'A' * num_replacements
|
|
16
|
+
return new_s
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# The following function is based upon code from Jeff Atwood, see:
|
|
20
|
+
#
|
|
21
|
+
# https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
|
|
22
|
+
#
|
|
23
|
+
# Code has been adapted for for use as sort function for Python sorted(). Enables sorting an
|
|
24
|
+
# iterable whose items are strings represented by a mix of alphanumeric characters. For the
|
|
25
|
+
# default sort for {'R14', 'R5'} is:
|
|
26
|
+
#
|
|
27
|
+
# R14 R5
|
|
28
|
+
#
|
|
29
|
+
# but with prep_for_sorting_nicely the sort will be what is more naturally expected:
|
|
30
|
+
#
|
|
31
|
+
# R5 R14
|
|
32
|
+
#
|
|
33
|
+
|
|
34
|
+
import re
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def prep_for_sorting_nicely(item):
|
|
38
|
+
convert = lambda text: int(text) if text.isdigit() else text
|
|
39
|
+
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
|
|
40
|
+
return alphanum_key(item)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Convert a string with delimited fields into a list of fields. Delimiters are comma,
|
|
44
|
+
# semi-colon, colon, tab, or blank space. Fields may contain any printable character.
|
|
45
|
+
def listify_string(st):
|
|
46
|
+
if st is None:
|
|
47
|
+
return []
|
|
48
|
+
ss = re.split(' |:|;|,|\t|\n', st)
|
|
49
|
+
split_st = []
|
|
50
|
+
for s in ss:
|
|
51
|
+
s_strip = s.strip()
|
|
52
|
+
if len(s_strip) != 0:
|
|
53
|
+
split_st.append(s_strip)
|
|
54
|
+
return split_st
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Convert a list of items into a comma-separated string without any surrounding brackets,
|
|
58
|
+
# for example:
|
|
59
|
+
#
|
|
60
|
+
# list = [1, 2, 3, 4]
|
|
61
|
+
#
|
|
62
|
+
# becomes '1, 2, 3, 4'
|
|
63
|
+
#
|
|
64
|
+
# as compared to str(list) which
|
|
65
|
+
#
|
|
66
|
+
# becomes '[1, 2, 3 4]'
|
|
67
|
+
def stringify_list(li):
|
|
68
|
+
return ', '.join(str(x) for x in li)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Check a string reference designator for duplicates as compared to a running set of
|
|
72
|
+
# reference already seen. A reference designator may contain multiple delimited references,
|
|
73
|
+
# so need to check the new designator for duplicates before checking against references
|
|
74
|
+
# already seen. All duplicate references are added to the set duplicate_refs.
|
|
75
|
+
def check_references_for_duplicates(new_refs, seen_refs, duplicate_refs):
|
|
76
|
+
new_refs_list = listify_string(new_refs)
|
|
77
|
+
new_refs_set = set()
|
|
78
|
+
for r in new_refs_list:
|
|
79
|
+
if r in new_refs_set:
|
|
80
|
+
duplicate_refs.add(r)
|
|
81
|
+
else:
|
|
82
|
+
new_refs_set.add(r)
|
|
83
|
+
if r in seen_refs:
|
|
84
|
+
duplicate_refs.add(r)
|
|
85
|
+
seen_refs.add(r)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Given a string that represents a number, returns a string that eliminates trailing zeros
|
|
89
|
+
# and decimal point if any from the input. For example, 25.000 become 25. If the input
|
|
90
|
+
# string that does not represent a number then the original string is returned.
|
|
91
|
+
def strip_trailing_zeros(num):
|
|
92
|
+
found = False
|
|
93
|
+
for c in num:
|
|
94
|
+
if c.isdigit():
|
|
95
|
+
found = True
|
|
96
|
+
elif c not in ['-', '+', '.']:
|
|
97
|
+
found = False
|
|
98
|
+
break
|
|
99
|
+
return ('%f' % float(num)).rstrip('0').rstrip('.') if found else num
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Input a dict with a list of key options, return the value if it exists, else None
|
|
103
|
+
def get_from_dict(input_dict, key_options):
|
|
104
|
+
for key in key_options:
|
|
105
|
+
val = input_dict.get(key, None)
|
|
106
|
+
if val:
|
|
107
|
+
return val
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
# via https://github.com/hayj/SystemTools/blob/master/systemtools/number.py
|
|
111
|
+
def parse_number(text):
|
|
112
|
+
"""
|
|
113
|
+
Return the first number in the given text for any locale.
|
|
114
|
+
TODO we actually don't take into account spaces for only
|
|
115
|
+
3-digited numbers (like "1 000") so, for now, "1 0" is 10.
|
|
116
|
+
TODO parse cases like "125,000.1,0.2" (125000.1).
|
|
117
|
+
:example:
|
|
118
|
+
>>> parseNumber("a 125,00 €")
|
|
119
|
+
125
|
|
120
|
+
>>> parseNumber("100.000,000")
|
|
121
|
+
100000
|
|
122
|
+
>>> parseNumber("100 000,000")
|
|
123
|
+
100000
|
|
124
|
+
>>> parseNumber("100,000,000")
|
|
125
|
+
100000000
|
|
126
|
+
>>> parseNumber("100 000 000")
|
|
127
|
+
100000000
|
|
128
|
+
>>> parseNumber("100.001 001")
|
|
129
|
+
100.001
|
|
130
|
+
>>> parseNumber("$.3")
|
|
131
|
+
0.3
|
|
132
|
+
>>> parseNumber(".003")
|
|
133
|
+
0.003
|
|
134
|
+
>>> parseNumber(".003 55")
|
|
135
|
+
0.003
|
|
136
|
+
>>> parseNumber("3 005")
|
|
137
|
+
3005
|
|
138
|
+
>>> parseNumber("1.190,00 €")
|
|
139
|
+
1190
|
|
140
|
+
>>> parseNumber("1190,00 €")
|
|
141
|
+
1190
|
|
142
|
+
>>> parseNumber("1,190.00 €")
|
|
143
|
+
1190
|
|
144
|
+
>>> parseNumber("$1190.00")
|
|
145
|
+
1190
|
|
146
|
+
>>> parseNumber("$1 190.99")
|
|
147
|
+
1190.99
|
|
148
|
+
>>> parseNumber("$-1 190.99")
|
|
149
|
+
-1190.99
|
|
150
|
+
>>> parseNumber("1 000 000.3")
|
|
151
|
+
1000000.3
|
|
152
|
+
>>> parseNumber('-151.744122')
|
|
153
|
+
-151.744122
|
|
154
|
+
>>> parseNumber('-1')
|
|
155
|
+
-1
|
|
156
|
+
>>> parseNumber("1 0002,1.2")
|
|
157
|
+
10002.1
|
|
158
|
+
>>> parseNumber("")
|
|
159
|
+
>>> parseNumber(None)
|
|
160
|
+
>>> parseNumber(1)
|
|
161
|
+
1
|
|
162
|
+
>>> parseNumber(1.1)
|
|
163
|
+
1.1
|
|
164
|
+
>>> parseNumber("rrr1,.2o")
|
|
165
|
+
1
|
|
166
|
+
>>> parseNumber("rrr1rrr")
|
|
167
|
+
1
|
|
168
|
+
>>> parseNumber("rrr ,.o")
|
|
169
|
+
"""
|
|
170
|
+
try:
|
|
171
|
+
# First we return None if we don't have something in the text:
|
|
172
|
+
if text is None:
|
|
173
|
+
return None
|
|
174
|
+
if isinstance(text, int) or isinstance(text, float):
|
|
175
|
+
return text
|
|
176
|
+
text = text.strip()
|
|
177
|
+
if text == "":
|
|
178
|
+
return None
|
|
179
|
+
# Next we get the first "[0-9,. ]+":
|
|
180
|
+
n = re.search("-?[0-9]*([,. ]?[0-9]+)+", text).group(0)
|
|
181
|
+
n = n.strip()
|
|
182
|
+
if not re.match(".*[0-9]+.*", text):
|
|
183
|
+
return None
|
|
184
|
+
# Then we cut to keep only 2 symbols:
|
|
185
|
+
while " " in n and "," in n and "." in n:
|
|
186
|
+
index = max(n.rfind(','), n.rfind(' '), n.rfind('.'))
|
|
187
|
+
n = n[0:index]
|
|
188
|
+
n = n.strip()
|
|
189
|
+
# We count the number of symbols:
|
|
190
|
+
symbolsCount = 0
|
|
191
|
+
for current in [" ", ",", "."]:
|
|
192
|
+
if current in n:
|
|
193
|
+
symbolsCount += 1
|
|
194
|
+
# If we don't have any symbol, we do nothing:
|
|
195
|
+
if symbolsCount == 0:
|
|
196
|
+
pass
|
|
197
|
+
# With one symbol:
|
|
198
|
+
elif symbolsCount == 1:
|
|
199
|
+
# If this is a space, we just remove all:
|
|
200
|
+
if " " in n:
|
|
201
|
+
n = n.replace(" ", "")
|
|
202
|
+
# Else we set it as a "." if one occurence, or remove it:
|
|
203
|
+
else:
|
|
204
|
+
theSymbol = "," if "," in n else "."
|
|
205
|
+
if n.count(theSymbol) > 1:
|
|
206
|
+
n = n.replace(theSymbol, "")
|
|
207
|
+
else:
|
|
208
|
+
n = n.replace(theSymbol, ".")
|
|
209
|
+
else:
|
|
210
|
+
# Now replace symbols so the right symbol is "." and all left are "":
|
|
211
|
+
rightSymbolIndex = max(n.rfind(','), n.rfind(' '), n.rfind('.'))
|
|
212
|
+
rightSymbol = n[rightSymbolIndex:rightSymbolIndex+1]
|
|
213
|
+
if rightSymbol == " ":
|
|
214
|
+
return parseNumber(n.replace(" ", "_"))
|
|
215
|
+
n = n.replace(rightSymbol, "R")
|
|
216
|
+
leftSymbolIndex = max(n.rfind(','), n.rfind(' '), n.rfind('.'))
|
|
217
|
+
leftSymbol = n[leftSymbolIndex:leftSymbolIndex+1]
|
|
218
|
+
n = n.replace(leftSymbol, "L")
|
|
219
|
+
n = n.replace("L", "")
|
|
220
|
+
n = n.replace("R", ".")
|
|
221
|
+
# And we cast the text to float or int:
|
|
222
|
+
n = float(n)
|
|
223
|
+
if n.is_integer():
|
|
224
|
+
return int(n)
|
|
225
|
+
else:
|
|
226
|
+
return n
|
|
227
|
+
except: pass
|
|
228
|
+
return None
|
bom/validators.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from django.core.validators import MaxValueValidator, RegexValidator
|
|
2
|
+
from django.core.exceptions import ValidationError
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', 'Only alphanumeric characters are allowed.')
|
|
7
|
+
numeric = RegexValidator(r'^[0-9]*$', 'Only numeric characters are allowed.')
|
|
8
|
+
decimal = RegexValidator(r'^[0-9]\d*(\.\d+)?$', 'Only decimal number characters are allowed.')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def validate_pct(value):
|
|
12
|
+
if value is not None and len(value) > 1:
|
|
13
|
+
try:
|
|
14
|
+
if value.endswith("%"):
|
|
15
|
+
return float(value[:-1]) / 100
|
|
16
|
+
else:
|
|
17
|
+
return float(value)
|
|
18
|
+
except (TypeError, ValueError):
|
|
19
|
+
raise ValidationError(
|
|
20
|
+
_('%(value)s is not a valid pct'),
|
|
21
|
+
params={'value': value},
|
|
22
|
+
)
|
|
23
|
+
return None
|
bom/views/__init__.py
ADDED
|
File without changes
|
bom/views/json_views.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from django.contrib.auth.decorators import login_required
|
|
2
|
+
from django.core.cache import cache
|
|
3
|
+
from django.http import JsonResponse
|
|
4
|
+
from django.shortcuts import get_object_or_404
|
|
5
|
+
from django.utils.decorators import method_decorator
|
|
6
|
+
from django.views import View
|
|
7
|
+
|
|
8
|
+
from bom.models import PartRevision
|
|
9
|
+
from bom.third_party_apis.base_api import BaseApiError
|
|
10
|
+
from bom.third_party_apis.mouser import Mouser
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BomJsonResponse(View):
|
|
14
|
+
response = {'errors': [], 'content': {}}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@method_decorator(login_required, name='dispatch')
|
|
18
|
+
class MouserPartMatchBOM(BomJsonResponse):
|
|
19
|
+
def get(self, request, part_revision_id):
|
|
20
|
+
part_revision = get_object_or_404(PartRevision, pk=part_revision_id) # get all of the pricing for manufacturer parts, marked with mouser in this part
|
|
21
|
+
user = request.user
|
|
22
|
+
profile = user.bom_profile()
|
|
23
|
+
organization = profile.organization
|
|
24
|
+
|
|
25
|
+
# Goal is to search mouser for anything that we want from mouser, then update the part revision in the bom with that
|
|
26
|
+
# To do that we can just get the manufacturer parts in this BOM
|
|
27
|
+
part = part_revision.part
|
|
28
|
+
qty_cache_key = str(part.id) + '_qty'
|
|
29
|
+
assy_quantity = cache.get(qty_cache_key, 100)
|
|
30
|
+
|
|
31
|
+
flat_bom = part_revision.flat(assy_quantity)
|
|
32
|
+
|
|
33
|
+
mouser = Mouser()
|
|
34
|
+
manufacturer_parts = flat_bom.mouser_parts()
|
|
35
|
+
# Quantity is the same on flat and indented bom WRT sourcing, so we should only need to look up by part revision, or even part
|
|
36
|
+
for bom_id, mp in manufacturer_parts.items():
|
|
37
|
+
bom_part = flat_bom.parts[bom_id]
|
|
38
|
+
bom_part_quantity = bom_part.total_extended_quantity
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
part_seller_info = mouser.search_and_match(mp, quantity=bom_part_quantity, currency=organization.currency)
|
|
42
|
+
except BaseApiError as err:
|
|
43
|
+
self.response['errors'].append(str(err))
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
bom_part.seller_part = part_seller_info['optimal_seller_part']
|
|
48
|
+
bom_part.api_info = part_seller_info['mouser_parts'][0]
|
|
49
|
+
except (KeyError, IndexError):
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
flat_bom.update()
|
|
53
|
+
flat_bom_dict = flat_bom.as_dict()
|
|
54
|
+
self.response['content'].update({'flat_bom': flat_bom_dict})
|
|
55
|
+
return JsonResponse(self.response)
|