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.

Files changed (191) hide show
  1. bom/__init__.py +1 -0
  2. bom/admin.py +207 -0
  3. bom/apps.py +8 -0
  4. bom/auth_backends.py +47 -0
  5. bom/base_classes.py +31 -0
  6. bom/constants.py +217 -0
  7. bom/context_processors.py +9 -0
  8. bom/csv_headers.py +252 -0
  9. bom/decorators.py +32 -0
  10. bom/form_fields.py +59 -0
  11. bom/forms.py +1328 -0
  12. bom/helpers.py +367 -0
  13. bom/local_settings.py +35 -0
  14. bom/migrations/0001_initial.py +135 -0
  15. bom/migrations/0002_auto_20180908_2151.py +24 -0
  16. bom/migrations/0003_sellerpart_data_source.py +18 -0
  17. bom/migrations/0004_auto_20180911_0011.py +18 -0
  18. bom/migrations/0005_auto_20181007_1934.py +56 -0
  19. bom/migrations/0006_auto_20181007_1949.py +41 -0
  20. bom/migrations/0007_auto_20181009_0256.py +19 -0
  21. bom/migrations/0008_auto_20181030_0427.py +19 -0
  22. bom/migrations/0009_subpart_reference.py +18 -0
  23. bom/migrations/0010_auto_20181202_0733.py +23 -0
  24. bom/migrations/0011_auto_20181202_2113.py +22 -0
  25. bom/migrations/0012_partchangehistory.py +30 -0
  26. bom/migrations/0013_auto_20190222_1631.py +19 -0
  27. bom/migrations/0014_auto_20190223_2353.py +18 -0
  28. bom/migrations/0015_auto_20190303_1915.py +136 -0
  29. bom/migrations/0016_auto_20190405_2308.py +58 -0
  30. bom/migrations/0017_auto_20190616_1912.py +19 -0
  31. bom/migrations/0018_auto_20190616_2143.py +24 -0
  32. bom/migrations/0019_auto_20190624_1246.py +45 -0
  33. bom/migrations/0020_auto_20190627_0207.py +38 -0
  34. bom/migrations/0021_auto_20190627_0428.py +23 -0
  35. bom/migrations/0022_auto_20190811_2140.py +35 -0
  36. bom/migrations/0023_auto_20191205_2351.py +255 -0
  37. bom/migrations/0024_auto_20191214_1342.py +89 -0
  38. bom/migrations/0025_auto_20191221_1907.py +38 -0
  39. bom/migrations/0026_auto_20191222_2258.py +22 -0
  40. bom/migrations/0027_auto_20191222_2347.py +17 -0
  41. bom/migrations/0028_partrevision_displayable_synopsis.py +74 -0
  42. bom/migrations/0029_auto_20191231_1630.py +23 -0
  43. bom/migrations/0030_auto_20200101_2253.py +22 -0
  44. bom/migrations/0031_auto_20200104_1352.py +38 -0
  45. bom/migrations/0032_auto_20200126_1806.py +27 -0
  46. bom/migrations/0033_auto_20200203_0618.py +29 -0
  47. bom/migrations/0034_auto_20200222_0359.py +30 -0
  48. bom/migrations/0035_auto_20200303_0111.py +34 -0
  49. bom/migrations/0036_auto_20200303_0538.py +17 -0
  50. bom/migrations/0037_auto_20200405_1642.py +44 -0
  51. bom/migrations/0038_auto_20200422_0504.py +19 -0
  52. bom/migrations/0039_auto_20200929_2315.py +41 -0
  53. bom/migrations/0040_alter_organization_currency.py +19 -0
  54. bom/migrations/0041_organization_subscription_quantity.py +18 -0
  55. bom/migrations/0042_auto_20210720_2137.py +23 -0
  56. bom/migrations/0043_auto_20211123_0157.py +24 -0
  57. bom/migrations/0044_auto_20220831_1241.py +23 -0
  58. bom/migrations/0045_sellerpart_link.py +18 -0
  59. bom/migrations/0046_alter_sellerpart_unique_together.py +17 -0
  60. bom/migrations/0047_sellerpart_seller_part_number.py +18 -0
  61. bom/migrations/0048_rename_part_organization_number_class_bom_part_organiz_b333d6_idx_and_more.py +1017 -0
  62. bom/migrations/0049_alter_assembly_id_alter_assemblysubparts_id_and_more.py +99 -0
  63. bom/migrations/0050_alter_organization_options.py +17 -0
  64. bom/migrations/0051_alter_manufacturer_organization_and_more.py +41 -0
  65. bom/migrations/0052_remove_partrevision_attribute_and_more.py +584 -0
  66. bom/migrations/__init__.py +0 -0
  67. bom/models.py +886 -0
  68. bom/part_bom.py +192 -0
  69. bom/settings.py +262 -0
  70. bom/static/bom/css/dashboard.css +17 -0
  71. bom/static/bom/css/jquery.treetable.css +28 -0
  72. bom/static/bom/css/materialize.min.css +13 -0
  73. bom/static/bom/css/part-info.css +15 -0
  74. bom/static/bom/css/style.css +482 -0
  75. bom/static/bom/css/tablesorter-theme.materialize.css +176 -0
  76. bom/static/bom/css/treetable-theme.css +42 -0
  77. bom/static/bom/doc/sample_part_classes.csv +38 -0
  78. bom/static/bom/doc/test_bom.csv +6 -0
  79. bom/static/bom/doc/test_bom_5_intelligent.csv +4 -0
  80. bom/static/bom/doc/test_full_bom.csv +37 -0
  81. bom/static/bom/doc/test_new_parts.csv +5 -0
  82. bom/static/bom/doc/test_new_parts_5_intelligent.csv +5 -0
  83. bom/static/bom/img/_ionicons_svg_md-arrow-dropdown.svg +1 -0
  84. bom/static/bom/img/_ionicons_svg_md-arrow-dropright.svg +1 -0
  85. bom/static/bom/img/favicon.ico +0 -0
  86. bom/static/bom/img/google/web/1x/btn_google_signin_dark_disabled_web.png +0 -0
  87. bom/static/bom/img/google/web/1x/btn_google_signin_dark_focus_web.png +0 -0
  88. bom/static/bom/img/google/web/1x/btn_google_signin_dark_normal_web.png +0 -0
  89. bom/static/bom/img/google/web/1x/btn_google_signin_dark_pressed_web.png +0 -0
  90. bom/static/bom/img/google/web/1x/btn_google_signin_light_disabled_web.png +0 -0
  91. bom/static/bom/img/google/web/1x/btn_google_signin_light_focus_web.png +0 -0
  92. bom/static/bom/img/google/web/1x/btn_google_signin_light_normal_web.png +0 -0
  93. bom/static/bom/img/google/web/1x/btn_google_signin_light_pressed_web.png +0 -0
  94. bom/static/bom/img/google/web/2x/btn_google_signin_dark_disabled_web@2x.png +0 -0
  95. bom/static/bom/img/google/web/2x/btn_google_signin_dark_focus_web@2x.png +0 -0
  96. bom/static/bom/img/google/web/2x/btn_google_signin_dark_normal_web@2x.png +0 -0
  97. bom/static/bom/img/google/web/2x/btn_google_signin_dark_pressed_web@2x.png +0 -0
  98. bom/static/bom/img/google/web/2x/btn_google_signin_light_disabled_web@2x.png +0 -0
  99. bom/static/bom/img/google/web/2x/btn_google_signin_light_focus_web@2x.png +0 -0
  100. bom/static/bom/img/google/web/2x/btn_google_signin_light_normal_web@2x.png +0 -0
  101. bom/static/bom/img/google/web/2x/btn_google_signin_light_pressed_web@2x.png +0 -0
  102. bom/static/bom/img/google/web/vector/btn_google_dark_disabled_ios.eps +814 -0
  103. bom/static/bom/img/google/web/vector/btn_google_dark_disabled_ios.svg +24 -0
  104. bom/static/bom/img/google/web/vector/btn_google_dark_focus_ios.eps +1866 -0
  105. bom/static/bom/img/google/web/vector/btn_google_dark_focus_ios.svg +51 -0
  106. bom/static/bom/img/google/web/vector/btn_google_dark_normal_ios.eps +1031 -0
  107. bom/static/bom/img/google/web/vector/btn_google_dark_normal_ios.svg +50 -0
  108. bom/static/bom/img/google/web/vector/btn_google_dark_pressed_ios.eps +1031 -0
  109. bom/static/bom/img/google/web/vector/btn_google_dark_pressed_ios.svg +50 -0
  110. bom/static/bom/img/google/web/vector/btn_google_light_disabled_ios.eps +814 -0
  111. bom/static/bom/img/google/web/vector/btn_google_light_disabled_ios.svg +24 -0
  112. bom/static/bom/img/google/web/vector/btn_google_light_focus_ios.eps +1837 -0
  113. bom/static/bom/img/google/web/vector/btn_google_light_focus_ios.svg +44 -0
  114. bom/static/bom/img/google/web/vector/btn_google_light_normal_ios.eps +1002 -0
  115. bom/static/bom/img/google/web/vector/btn_google_light_normal_ios.svg +43 -0
  116. bom/static/bom/img/google/web/vector/btn_google_light_pressed_ios.eps +1002 -0
  117. bom/static/bom/img/google/web/vector/btn_google_light_pressed_ios.svg +43 -0
  118. bom/static/bom/img/google_drive_logo.svg +1 -0
  119. bom/static/bom/img/indabom.png +0 -0
  120. bom/static/bom/img/mouser.png +0 -0
  121. bom/static/bom/img/octopart_blue.svg +19 -0
  122. bom/static/bom/js/formset-handler.js +65 -0
  123. bom/static/bom/js/jquery-3.4.1.min.js +2 -0
  124. bom/static/bom/js/jquery.ba-floatingscrollbar.min.js +10 -0
  125. bom/static/bom/js/jquery.treetable.js +629 -0
  126. bom/static/bom/js/materialize.min.js +6 -0
  127. bom/templates/bom/account-delete.html +23 -0
  128. bom/templates/bom/add-manufacturer-part.html +66 -0
  129. bom/templates/bom/add-sellerpart.html +93 -0
  130. bom/templates/bom/base-menu.html +16 -0
  131. bom/templates/bom/base.html +129 -0
  132. bom/templates/bom/bom-action-btn.html +23 -0
  133. bom/templates/bom/bom-action-table.html +57 -0
  134. bom/templates/bom/bom-base-menu.html +6 -0
  135. bom/templates/bom/bom-base.html +24 -0
  136. bom/templates/bom/bom-form-modal.html +36 -0
  137. bom/templates/bom/bom-form.html +30 -0
  138. bom/templates/bom/bom-modal-add-users.html +49 -0
  139. bom/templates/bom/bom-signup.html +12 -0
  140. bom/templates/bom/components/bom-flat.html +131 -0
  141. bom/templates/bom/components/bom-indented.html +237 -0
  142. bom/templates/bom/components/manufacturer-part-list.html +270 -0
  143. bom/templates/bom/components/seller-part-list.html +62 -0
  144. bom/templates/bom/create-part.html +65 -0
  145. bom/templates/bom/dashboard-menu.html +15 -0
  146. bom/templates/bom/dashboard.html +303 -0
  147. bom/templates/bom/edit-manufacturer-part.html +72 -0
  148. bom/templates/bom/edit-part-class.html +120 -0
  149. bom/templates/bom/edit-part.html +67 -0
  150. bom/templates/bom/edit-quantity-of-measure.html +119 -0
  151. bom/templates/bom/edit-user-meta.html +70 -0
  152. bom/templates/bom/help.html +1356 -0
  153. bom/templates/bom/manufacturer-info.html +82 -0
  154. bom/templates/bom/manufacturers.html +97 -0
  155. bom/templates/bom/nothing-to-see.html +15 -0
  156. bom/templates/bom/organization-create.html +135 -0
  157. bom/templates/bom/part-info.html +448 -0
  158. bom/templates/bom/part-revision-display.html +50 -0
  159. bom/templates/bom/part-revision-edit.html +39 -0
  160. bom/templates/bom/part-revision-manage-bom.html +115 -0
  161. bom/templates/bom/part-revision-new.html +57 -0
  162. bom/templates/bom/part-revision-release.html +41 -0
  163. bom/templates/bom/search-help.html +101 -0
  164. bom/templates/bom/seller-info.html +82 -0
  165. bom/templates/bom/sellers.html +97 -0
  166. bom/templates/bom/settings.html +734 -0
  167. bom/templates/bom/signup.html +28 -0
  168. bom/templates/bom/subscription_panel.html +16 -0
  169. bom/templates/bom/table_of_contents.html +47 -0
  170. bom/templates/bom/upload-bom.html +111 -0
  171. bom/templates/bom/upload-parts-help.html +103 -0
  172. bom/templates/bom/upload-parts.html +50 -0
  173. bom/templates/registration/login.html +39 -0
  174. bom/tests.py +1592 -0
  175. bom/third_party_apis/__init__.py +0 -0
  176. bom/third_party_apis/base_api.py +51 -0
  177. bom/third_party_apis/google_drive.py +166 -0
  178. bom/third_party_apis/mouser.py +132 -0
  179. bom/third_party_apis/test_apis.py +24 -0
  180. bom/urls.py +100 -0
  181. bom/utils.py +228 -0
  182. bom/validators.py +23 -0
  183. bom/views/__init__.py +0 -0
  184. bom/views/json_views.py +55 -0
  185. bom/views/views.py +1773 -0
  186. bom/wsgi.py +16 -0
  187. django_bom-1.262.dist-info/METADATA +206 -0
  188. django_bom-1.262.dist-info/RECORD +191 -0
  189. django_bom-1.262.dist-info/WHEEL +5 -0
  190. django_bom-1.262.dist-info/licenses/LICENSE +674 -0
  191. 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