django-bom 1.243__py3-none-any.whl → 1.257__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.
- bom/admin.py +60 -14
- bom/auth_backends.py +3 -1
- bom/constants.py +7 -0
- bom/csv_headers.py +22 -44
- bom/forms.py +971 -1043
- bom/helpers.py +79 -4
- bom/migrations/0051_alter_manufacturer_organization_and_more.py +41 -0
- bom/migrations/0052_remove_partrevision_attribute_and_more.py +576 -0
- bom/models.py +253 -99
- bom/settings.py +2 -0
- bom/static/bom/css/style.css +19 -0
- bom/static/bom/js/formset-handler.js +65 -0
- bom/templates/bom/edit-part-class.html +98 -11
- bom/templates/bom/edit-quantity-of-measure.html +119 -0
- bom/templates/bom/edit-user-meta.html +58 -26
- bom/templates/bom/organization-create.html +6 -3
- bom/templates/bom/part-info.html +10 -1
- bom/templates/bom/part-revision-display.html +22 -159
- bom/templates/bom/settings.html +142 -15
- bom/tests.py +117 -31
- bom/urls.py +6 -0
- bom/views/json_views.py +2 -2
- bom/views/views.py +194 -46
- {django_bom-1.243.dist-info → django_bom-1.257.dist-info}/METADATA +1 -1
- {django_bom-1.243.dist-info → django_bom-1.257.dist-info}/RECORD +28 -24
- {django_bom-1.243.dist-info → django_bom-1.257.dist-info}/WHEEL +0 -0
- {django_bom-1.243.dist-info → django_bom-1.257.dist-info}/licenses/LICENSE +0 -0
- {django_bom-1.243.dist-info → django_bom-1.257.dist-info}/top_level.txt +0 -0
bom/templates/bom/settings.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
{% block content %}
|
|
9
9
|
<div class="row container-app">
|
|
10
|
-
<div class="col
|
|
10
|
+
<div class="col s12 l8 offset-l2">
|
|
11
11
|
<ul id="tabs" class="tabs tabs-fixed-width">
|
|
12
12
|
<li class="tab"><a id="user-tab" href="#user">User</a></li>
|
|
13
13
|
<li class="tab"><a id="indabom-tab" href="#indabom">IndaBOM</a></li>
|
|
@@ -45,10 +45,9 @@
|
|
|
45
45
|
</form>
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
|
-
<div class="section"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class="material-icons red-text text-darken-2">warning</i>Danger Zone</h5>
|
|
48
|
+
<div class="section" style="padding: 16px; border-radius: 6px;">
|
|
49
|
+
<h4 class="section-title red-text text-darken-2" style="margin-top:0;"><i
|
|
50
|
+
class="material-icons red-text text-darken-2">warning</i>Danger Zone</h4>
|
|
52
51
|
<p>Deleting your account is permanent. If you are the organization owner, your organization will also be
|
|
53
52
|
deleted unless you transfer ownership before proceeding.</p>
|
|
54
53
|
<div class="right-align" style="margin-top: 8px;">
|
|
@@ -120,7 +119,7 @@
|
|
|
120
119
|
title="Via Mouser.com"
|
|
121
120
|
src="{% static 'bom/img/mouser.png' %}">{% endif %}</td>
|
|
122
121
|
<td>
|
|
123
|
-
<a class="
|
|
122
|
+
<a class="table-action"
|
|
124
123
|
href="{% url 'bom:part-class-edit' part_class_id=part_class.id %}"><i
|
|
125
124
|
class="material-icons left">edit</i>Edit</a>
|
|
126
125
|
</td>
|
|
@@ -144,7 +143,134 @@
|
|
|
144
143
|
</div>
|
|
145
144
|
|
|
146
145
|
<div class="divider"></div>
|
|
146
|
+
{% endif %}
|
|
147
|
+
|
|
148
|
+
<div class="section">
|
|
149
|
+
<h4 class="section-title"><i class="material-icons teal-text text-darken-1">list_alt</i>Property
|
|
150
|
+
Definitions</h4>
|
|
151
|
+
<p>Define part properties (e.g. Capacitance, Length, Sheen), their data type, and their
|
|
152
|
+
associated quantity of measure.</p>
|
|
153
|
+
{% if property_definitions.count > 0 %}
|
|
154
|
+
<table class="striped tight">
|
|
155
|
+
<thead>
|
|
156
|
+
<tr>
|
|
157
|
+
<th class="text-normal">Name</th>
|
|
158
|
+
<th class="text-normal">Type</th>
|
|
159
|
+
<th class="text-normal">Required</th>
|
|
160
|
+
<th class="text-normal">Quantity of Measure</th>
|
|
161
|
+
<th class="text-normal">Options</th>
|
|
162
|
+
</tr>
|
|
163
|
+
</thead>
|
|
164
|
+
<tbody>
|
|
165
|
+
{% for prop in property_definitions %}
|
|
166
|
+
<tr>
|
|
167
|
+
<td class="text-normal">{{ prop.name }}</td>
|
|
168
|
+
<td class="text-normal">{{ prop.get_type_display }}</td>
|
|
169
|
+
<td class="text-normal">{% if prop.required %}Yes{% else %}No{% endif %}</td>
|
|
170
|
+
<td class="text-normal">{{ prop.quantity_of_measure.name|default:"-" }}</td>
|
|
171
|
+
<td>
|
|
172
|
+
{% if prop.organization == organization %}
|
|
173
|
+
<a class="table-action"
|
|
174
|
+
href="{% url 'bom:property-definition-edit' property_definition_id=prop.id %}"><i
|
|
175
|
+
class="material-icons left">edit</i>Edit</a>
|
|
176
|
+
<a class="table-action red-text"
|
|
177
|
+
href="{% url 'bom:property-definition-delete' property_definition_id=prop.id %}"
|
|
178
|
+
onclick="return confirm('Are you sure you want to delete this property definition?')"><i
|
|
179
|
+
class="material-icons left">delete</i></a>
|
|
180
|
+
{% else %}
|
|
181
|
+
<span class="table-action tooltipped grey-text d-flex-inline align-center"
|
|
182
|
+
data-position="top"
|
|
183
|
+
data-tooltip="System Default: Cannot be modified">
|
|
184
|
+
<i class="material-icons left" style="font-size: 1.2rem;">lock</i> System
|
|
185
|
+
</span>
|
|
186
|
+
{% endif %}
|
|
187
|
+
</td>
|
|
188
|
+
</tr>
|
|
189
|
+
{% endfor %}
|
|
190
|
+
</tbody>
|
|
191
|
+
</table>
|
|
192
|
+
{% else %}
|
|
193
|
+
<p>No global property definitions defined.</p>
|
|
194
|
+
{% endif %}
|
|
195
|
+
<div class="right-align" style="margin-top: 16px;">
|
|
196
|
+
<a href="{% url 'bom:property-definition-add' %}"
|
|
197
|
+
class="waves-effect waves-light btn btn-primary">
|
|
198
|
+
<i class="material-icons left">add</i>Add Property Definition
|
|
199
|
+
</a>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div class="divider"></div>
|
|
204
|
+
|
|
205
|
+
<div class="section">
|
|
206
|
+
<h4 class="section-title"><i class="material-icons teal-text text-darken-1">straighten</i>Units &
|
|
207
|
+
Quantities</h4>
|
|
208
|
+
<p>Define quantities of measure (e.g. Length, Resistance) and their associated units (e.g. m, mm,
|
|
209
|
+
Ohm, kOhm).</p>
|
|
210
|
+
{% if quantities_of_measure.count > 0 %}
|
|
211
|
+
<table class="striped tight">
|
|
212
|
+
<thead>
|
|
213
|
+
<tr>
|
|
214
|
+
<th class="text-normal">Quantity</th>
|
|
215
|
+
<th class="text-normal">Units</th>
|
|
216
|
+
<th class="text-normal">Options</th>
|
|
217
|
+
</tr>
|
|
218
|
+
</thead>
|
|
219
|
+
<tbody>
|
|
220
|
+
{% for qom in quantities_of_measure %}
|
|
221
|
+
<tr>
|
|
222
|
+
<td class="text-normal">{{ qom.name }}</td>
|
|
223
|
+
<td class="text-normal">
|
|
224
|
+
{% for unit in qom.units.all|slice:":3" %}
|
|
225
|
+
{{ unit.symbol }}{% if not forloop.last %}, {% endif %}
|
|
226
|
+
{% endfor %}
|
|
227
|
+
|
|
228
|
+
{% if qom.units.count > 3 %}
|
|
229
|
+
<span class="grey-text text-darken-1 tooltipped"
|
|
230
|
+
data-tooltip="
|
|
231
|
+
|
|
147
232
|
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
{% for unit in qom.units.all %}{{ unit.name }}{% if not forloop.last %}, {% endif %}{% endfor %}">
|
|
236
|
+
(+{{ qom.units.count|add:"-3" }} more)
|
|
237
|
+
</span>
|
|
238
|
+
{% endif %}
|
|
239
|
+
</td>
|
|
240
|
+
<td>
|
|
241
|
+
{% if qom.organization == organization %}
|
|
242
|
+
<a class="table-action"
|
|
243
|
+
href="{% url 'bom:quantity-of-measure-edit' quantity_of_measure_id=qom.id %}"><i
|
|
244
|
+
class="material-icons left">edit</i>Edit</a>
|
|
245
|
+
<a class="table-action red-text"
|
|
246
|
+
href="{% url 'bom:quantity-of-measure-delete' quantity_of_measure_id=qom.id %}"
|
|
247
|
+
onclick="return confirm('Are you sure you want to delete this quantity of measure and all its units?')"><i
|
|
248
|
+
class="material-icons left">delete</i></a>
|
|
249
|
+
{% else %}
|
|
250
|
+
<span class="table-action tooltipped grey-text d-flex-inline align-center"
|
|
251
|
+
data-position="top"
|
|
252
|
+
data-tooltip="System Default: Cannot be modified">
|
|
253
|
+
<i class="material-icons left" style="font-size: 1.2rem;">lock</i> System
|
|
254
|
+
</span>
|
|
255
|
+
{% endif %}
|
|
256
|
+
</td>
|
|
257
|
+
</tr>
|
|
258
|
+
{% endfor %}
|
|
259
|
+
</tbody>
|
|
260
|
+
</table>
|
|
261
|
+
{% else %}
|
|
262
|
+
<p>No quantities of measure defined.</p>
|
|
263
|
+
{% endif %}
|
|
264
|
+
<div class="right-align" style="margin-top: 16px;">
|
|
265
|
+
<a href="{% url 'bom:quantity-of-measure-add' %}"
|
|
266
|
+
class="waves-effect waves-light btn btn-primary">
|
|
267
|
+
<i class="material-icons left">add</i>Add Quantity of Measure
|
|
268
|
+
</a>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
{% if organization.number_scheme == 'S' %}
|
|
273
|
+
<div class="divider"></div>
|
|
148
274
|
<div class="section">
|
|
149
275
|
<h4 class="section-title" id="indabom-part-number"><i
|
|
150
276
|
class="material-icons teal-text text-darken-1">format_list_numbered</i>Part Number</h4>
|
|
@@ -242,6 +368,9 @@
|
|
|
242
368
|
</form>
|
|
243
369
|
</div>
|
|
244
370
|
|
|
371
|
+
{# Subscription & Billing placed after Users for a more natural flow #}
|
|
372
|
+
{% include 'bom/subscription_panel.html' %}
|
|
373
|
+
|
|
245
374
|
<div class="section">
|
|
246
375
|
<h4 class="section-title"><i class="material-icons teal-text text-darken-1">group</i>Users</h4>
|
|
247
376
|
<form name="seller" action="{% url 'bom:settings' tab_anchor=ORGANIZATION_TAB %}" method="post"
|
|
@@ -263,17 +392,18 @@
|
|
|
263
392
|
{% for org_user in users_in_organization %}
|
|
264
393
|
<tr>
|
|
265
394
|
<td>
|
|
266
|
-
|
|
267
|
-
|
|
395
|
+
{% if org_user != organization.owner %}
|
|
396
|
+
<label><input type="checkbox" class="filled-in"
|
|
397
|
+
name="remove_user_meta_id_{{ org_user.bom_profile.id }}"><span/></label>{% endif %}
|
|
268
398
|
</td>
|
|
269
|
-
<td class="text-normal">{
|
|
399
|
+
<td class="text-normal">{% if org_user == organization.owner %}Owner{% else %}
|
|
400
|
+
{{ org_user.bom_profile.get_role_display }}{% endif %}</td>
|
|
270
401
|
<td class="text-normal">{{ org_user.username }}</td>
|
|
271
402
|
<td class="text-normal">{{ org_user.first_name }} {{ org_user.last_name }}</td>
|
|
272
403
|
<td class="text-normal"><a href="mailto:{{ user.email }}">{{ org_user.email }}</a>
|
|
273
404
|
</td>
|
|
274
|
-
<td>
|
|
275
|
-
<a
|
|
276
|
-
href="{% url 'bom:user-meta-edit' user_meta_id=org_user.bom_profile.id %}"><i
|
|
405
|
+
<td class="table-action right-align">
|
|
406
|
+
<a href="{% url 'bom:user-meta-edit' user_meta_id=org_user.bom_profile.id %}"><i
|
|
277
407
|
class="material-icons left">edit</i>Edit</a>
|
|
278
408
|
</td>
|
|
279
409
|
</tr>
|
|
@@ -301,9 +431,6 @@
|
|
|
301
431
|
</form>
|
|
302
432
|
</div>
|
|
303
433
|
|
|
304
|
-
{# Subscription & Billing placed after Users for a more natural flow #}
|
|
305
|
-
{% include 'bom/subscription_panel.html' %}
|
|
306
|
-
|
|
307
434
|
<div class="section">
|
|
308
435
|
<h4 class="section-title"><i
|
|
309
436
|
class="material-icons teal-text text-darken-1">integration_instructions</i>Integrations</h4>
|
bom/tests.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import csv
|
|
2
|
-
from re import finditer
|
|
2
|
+
from re import finditer
|
|
3
3
|
from unittest import skip
|
|
4
4
|
|
|
5
5
|
from django.conf import settings
|
|
@@ -16,12 +16,11 @@ from .helpers import (
|
|
|
16
16
|
create_a_fake_subpart,
|
|
17
17
|
create_some_fake_manufacturers,
|
|
18
18
|
create_some_fake_part_classes,
|
|
19
|
+
create_some_fake_part_revision_property_definitions,
|
|
19
20
|
create_some_fake_parts,
|
|
20
|
-
create_some_fake_sellers,
|
|
21
21
|
create_user_and_organization,
|
|
22
22
|
)
|
|
23
|
-
from .models import
|
|
24
|
-
|
|
23
|
+
from .models import Part, PartClass, Seller, Subpart
|
|
25
24
|
|
|
26
25
|
TEST_FILES_DIR = "bom/test_files"
|
|
27
26
|
|
|
@@ -186,6 +185,7 @@ class TestBOM(TransactionTestCase):
|
|
|
186
185
|
self.assertEqual(response.status_code, 200)
|
|
187
186
|
|
|
188
187
|
def test_part_upload_bom(self):
|
|
188
|
+
sheen, voltage, _, _, _ = create_some_fake_part_revision_property_definitions(self.organization, False)
|
|
189
189
|
(p1, p2, p3, p4) = create_some_fake_parts(organization=self.organization)
|
|
190
190
|
|
|
191
191
|
test_file = 'test_bom.csv' if self.organization.number_variation_len > 0 else 'test_bom_6_no_variations.csv'
|
|
@@ -195,7 +195,7 @@ class TestBOM(TransactionTestCase):
|
|
|
195
195
|
|
|
196
196
|
messages = list(response.context.get('messages'))
|
|
197
197
|
for msg in messages:
|
|
198
|
-
self.assertNotEqual(msg.tags, "error")
|
|
198
|
+
self.assertNotEqual(msg.tags, "error", msg.message)
|
|
199
199
|
|
|
200
200
|
subparts = p2.latest().assembly.subparts.all()
|
|
201
201
|
|
|
@@ -227,7 +227,21 @@ class TestBOM(TransactionTestCase):
|
|
|
227
227
|
bom = p1.latest().indented()
|
|
228
228
|
self.assertEqual(len(bom.parts), 3)
|
|
229
229
|
|
|
230
|
+
def test_part_upload_bom_with_properties(self):
|
|
231
|
+
sheen, voltage, _, _, _ = create_some_fake_part_revision_property_definitions(self.organization)
|
|
232
|
+
(p1, p2, p3, p4) = create_some_fake_parts(organization=self.organization)
|
|
233
|
+
|
|
234
|
+
with open(f'{TEST_FILES_DIR}/test_bom_7_properties.csv') as test_csv:
|
|
235
|
+
response = self.client.post(reverse('bom:part-upload-bom', kwargs={'part_id': p2.id}), {'file': test_csv},
|
|
236
|
+
follow=True)
|
|
237
|
+
self.assertEqual(response.status_code, 200)
|
|
238
|
+
|
|
239
|
+
messages = list(response.context.get('messages'))
|
|
240
|
+
for msg in messages:
|
|
241
|
+
self.assertNotEqual(msg.tags, "error", msg.message)
|
|
242
|
+
|
|
230
243
|
def test_upload_bom(self):
|
|
244
|
+
create_some_fake_part_revision_property_definitions(self.organization, some_required=False)
|
|
231
245
|
(p1, p2, p3, p4) = create_some_fake_parts(organization=self.organization)
|
|
232
246
|
|
|
233
247
|
# Test OK page visit
|
|
@@ -253,7 +267,8 @@ class TestBOM(TransactionTestCase):
|
|
|
253
267
|
parent_part = Part.from_part_number(parent_part_number, organization=self.organization)
|
|
254
268
|
bom = parent_part.indented()
|
|
255
269
|
bom_list = list(bom.parts.values())
|
|
256
|
-
|
|
270
|
+
for idx, row in enumerate(test_list):
|
|
271
|
+
self.assertEqual(row['part_number'], bom_list[idx].part.full_part_number(), f'Row {idx + 1}')
|
|
257
272
|
|
|
258
273
|
# Check that we successfully updated an existing part (only tested for semi-intelligent scheme for now)
|
|
259
274
|
if self.organization.number_scheme == constants.NUMBER_SCHEME_SEMI_INTELLIGENT:
|
|
@@ -330,7 +345,17 @@ class TestBOM(TransactionTestCase):
|
|
|
330
345
|
self.assertTrue("it would cause infinite recursion. Uploading of this subpart skipped." in str(msg.message))
|
|
331
346
|
self.assertTrue("Row 15" in str(msg.message))
|
|
332
347
|
|
|
348
|
+
def test_upload_bom_with_properties(self):
|
|
349
|
+
(p1, p2, p3, p4) = create_some_fake_parts(organization=self.organization)
|
|
350
|
+
|
|
351
|
+
# Test OK upload
|
|
352
|
+
test_file = 'test_full_bom.csv'
|
|
353
|
+
with open(f'{TEST_FILES_DIR}/{test_file}') as test_csv:
|
|
354
|
+
response = self.client.post(reverse('bom:upload-bom'), {'file': test_csv}, follow=True)
|
|
355
|
+
self.assertEqual(response.status_code, 200)
|
|
356
|
+
|
|
333
357
|
def test_part_upload_bom_corner_cases(self):
|
|
358
|
+
create_some_fake_part_revision_property_definitions(self.organization, False)
|
|
334
359
|
(p1, p2, p3, p4) = create_some_fake_parts(organization=self.organization)
|
|
335
360
|
with open(f'{TEST_FILES_DIR}/test_bom_3_recursion.csv') as test_csv:
|
|
336
361
|
response = self.client.post(reverse('bom:part-upload-bom', kwargs={'part_id': p1.id}), {'file': test_csv}, follow=True)
|
|
@@ -347,7 +372,7 @@ class TestBOM(TransactionTestCase):
|
|
|
347
372
|
|
|
348
373
|
messages = list(response.context.get('messages'))
|
|
349
374
|
for msg in messages:
|
|
350
|
-
self.assertNotEqual(msg.tags, "error") # Should be OK since we will default revision to 1
|
|
375
|
+
self.assertNotEqual(msg.tags, "error", msg.message) # Should be OK since we will default revision to 1
|
|
351
376
|
|
|
352
377
|
def test_export_part_list(self):
|
|
353
378
|
create_some_fake_parts(organization=self.organization)
|
|
@@ -371,14 +396,37 @@ class TestBOM(TransactionTestCase):
|
|
|
371
396
|
self.assertEqual(part_classes.count(), 1)
|
|
372
397
|
part_class = part_classes[0]
|
|
373
398
|
|
|
374
|
-
# Test edit
|
|
399
|
+
# Test edit with property definitions
|
|
400
|
+
_, prop_def, _, _, _ = create_some_fake_part_revision_property_definitions(self.organization, False)
|
|
375
401
|
part_class_form_data['name'] = 'edited test part name'
|
|
402
|
+
part_class_form_data.update({
|
|
403
|
+
'prop-def-TOTAL_FORMS': '1',
|
|
404
|
+
'prop-def-INITIAL_FORMS': '0',
|
|
405
|
+
'prop-def-MIN_NUM_FORMS': '0',
|
|
406
|
+
'prop-def-MAX_NUM_FORMS': '1000',
|
|
407
|
+
'prop-def-0-property_definition': prop_def.id,
|
|
408
|
+
})
|
|
376
409
|
|
|
377
410
|
response = self.client.post(reverse('bom:part-class-edit', kwargs={'part_class_id': part_class.id}), part_class_form_data)
|
|
378
411
|
self.assertEqual(response.status_code, 302)
|
|
379
412
|
|
|
380
|
-
part_class
|
|
413
|
+
part_class.refresh_from_db()
|
|
381
414
|
self.assertEqual(part_class.name, part_class_form_data['name'])
|
|
415
|
+
self.assertEqual(part_class.property_definitions.count(), 1)
|
|
416
|
+
prop_def = part_class.property_definitions.first()
|
|
417
|
+
self.assertEqual(prop_def.name, 'Voltage')
|
|
418
|
+
|
|
419
|
+
# Test deleting property definition
|
|
420
|
+
part_class_form_data.update({
|
|
421
|
+
'prop-def-INITIAL_FORMS': '1',
|
|
422
|
+
'prop-def-0-property_definition': prop_def.id,
|
|
423
|
+
'prop-def-0-DELETE': 'on',
|
|
424
|
+
})
|
|
425
|
+
response = self.client.post(reverse('bom:part-class-edit', kwargs={'part_class_id': part_class.id}),
|
|
426
|
+
part_class_form_data)
|
|
427
|
+
self.assertEqual(response.status_code, 302)
|
|
428
|
+
part_class.refresh_from_db()
|
|
429
|
+
self.assertEqual(part_class.property_definitions.count(), 0)
|
|
382
430
|
|
|
383
431
|
def test_create_part(self):
|
|
384
432
|
(p1, p2, p3, p4) = create_some_fake_parts(organization=self.organization)
|
|
@@ -393,8 +441,8 @@ class TestBOM(TransactionTestCase):
|
|
|
393
441
|
'configuration': 'W',
|
|
394
442
|
'description': 'IC, MCU 32 Bit',
|
|
395
443
|
'revision': 'A',
|
|
396
|
-
'
|
|
397
|
-
'
|
|
444
|
+
'property_sheen': 'flat',
|
|
445
|
+
'property_voltage': '1.34',
|
|
398
446
|
}
|
|
399
447
|
|
|
400
448
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -417,6 +465,8 @@ class TestBOM(TransactionTestCase):
|
|
|
417
465
|
'number_item': '9999',
|
|
418
466
|
'description': 'IC, MCU 32 Bit',
|
|
419
467
|
'revision': 'A',
|
|
468
|
+
'property_sheen': 'flat',
|
|
469
|
+
'property_voltage': '1.34',
|
|
420
470
|
}
|
|
421
471
|
|
|
422
472
|
if self.organization.number_variation_len > 0:
|
|
@@ -434,6 +484,8 @@ class TestBOM(TransactionTestCase):
|
|
|
434
484
|
'number_variation': '',
|
|
435
485
|
'description': 'IC, MCU 32 Bit',
|
|
436
486
|
'revision': 'A',
|
|
487
|
+
'property_sheen': 'flat',
|
|
488
|
+
'property_voltage': '1.34',
|
|
437
489
|
}
|
|
438
490
|
|
|
439
491
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -447,6 +499,8 @@ class TestBOM(TransactionTestCase):
|
|
|
447
499
|
'number_item': '1234',
|
|
448
500
|
'description': 'IC, MCU 32 Bit',
|
|
449
501
|
'revision': 'A',
|
|
502
|
+
'property_sheen': 'flat',
|
|
503
|
+
'property_voltage': '1.34',
|
|
450
504
|
}
|
|
451
505
|
|
|
452
506
|
if self.organization.number_variation_len > 0:
|
|
@@ -464,6 +518,8 @@ class TestBOM(TransactionTestCase):
|
|
|
464
518
|
'number_variation': '',
|
|
465
519
|
'description': 'IC, MCU 32 Bit',
|
|
466
520
|
'revision': 'A',
|
|
521
|
+
'property_sheen': 'flat',
|
|
522
|
+
'property_voltage': '1.34',
|
|
467
523
|
}
|
|
468
524
|
|
|
469
525
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -479,6 +535,8 @@ class TestBOM(TransactionTestCase):
|
|
|
479
535
|
'number_variation': '',
|
|
480
536
|
'description': 'IC, MCU 32 Bit',
|
|
481
537
|
'revision': 'A',
|
|
538
|
+
'property_sheen': 'flat',
|
|
539
|
+
'property_voltage': '1.34',
|
|
482
540
|
}
|
|
483
541
|
|
|
484
542
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -506,8 +564,8 @@ class TestBOM(TransactionTestCase):
|
|
|
506
564
|
'configuration': 'W',
|
|
507
565
|
'description': 'IC, MCU 32 Bit',
|
|
508
566
|
'revision': 'A',
|
|
509
|
-
'
|
|
510
|
-
'
|
|
567
|
+
'property_sheen': 'flat',
|
|
568
|
+
'property_voltage': '1.34',
|
|
511
569
|
}
|
|
512
570
|
|
|
513
571
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -535,8 +593,8 @@ class TestBOM(TransactionTestCase):
|
|
|
535
593
|
'configuration': 'W',
|
|
536
594
|
'description': 'IC, MCU 32 Bit',
|
|
537
595
|
'revision': 'A',
|
|
538
|
-
'
|
|
539
|
-
'
|
|
596
|
+
'property_sheen': 'flat',
|
|
597
|
+
'property_voltage': '1.34',
|
|
540
598
|
}
|
|
541
599
|
|
|
542
600
|
number_variation = None
|
|
@@ -709,17 +767,20 @@ class TestBOM(TransactionTestCase):
|
|
|
709
767
|
found_error = True
|
|
710
768
|
self.assertTrue(found_error)
|
|
711
769
|
|
|
712
|
-
def
|
|
713
|
-
create_some_fake_part_classes(self.organization)
|
|
770
|
+
def test_upload_parts_break_too_many_characters(self):
|
|
771
|
+
pc1, _, _ = create_some_fake_part_classes(self.organization)
|
|
772
|
+
create_some_fake_part_revision_property_definitions(self.organization, part_class=pc1)
|
|
714
773
|
|
|
715
774
|
# Should break with data error
|
|
716
775
|
with open(f'{TEST_FILES_DIR}/test_new_parts_broken.csv') as test_csv:
|
|
717
776
|
response = self.client.post(reverse('bom:upload-parts'), {'file': test_csv}, follow=True)
|
|
718
777
|
messages = list(response.context.get('messages'))
|
|
719
778
|
|
|
720
|
-
self.assertTrue(len(messages)
|
|
721
|
-
|
|
722
|
-
|
|
779
|
+
self.assertTrue(len(messages) == 1)
|
|
780
|
+
msg = messages[0]
|
|
781
|
+
self.assertEqual(msg.tags, 'error', msg.message)
|
|
782
|
+
self.assertIn('Error on Row 2, property_sheen: Ensure this value has at most 255 characters (it has 483)',
|
|
783
|
+
msg.message)
|
|
723
784
|
|
|
724
785
|
def test_upload_part_with_sellers(self):
|
|
725
786
|
create_some_fake_part_classes(self.organization)
|
|
@@ -750,14 +811,14 @@ class TestBOM(TransactionTestCase):
|
|
|
750
811
|
with open(f'{TEST_FILES_DIR}/test_part_classes_no_comment.csv') as test_csv:
|
|
751
812
|
response = self.client.post(reverse('bom:settings'), {'file': test_csv, 'submit-part-class-upload': ''})
|
|
752
813
|
self.assertEqual(response.status_code, 200)
|
|
753
|
-
self.assertTrue('Part class 102 Resistor
|
|
814
|
+
self.assertTrue('Row 3: Part class 102 Resistor already defined.' in str(response.content))
|
|
754
815
|
|
|
755
816
|
# Submit with a weird csv file that sort of works
|
|
756
817
|
with open(f'{TEST_FILES_DIR}/test_part_classes_blank_rows.csv') as test_csv:
|
|
757
818
|
response = self.client.post(reverse('bom:settings'), {'file': test_csv, 'submit-part-class-upload': ''})
|
|
758
819
|
self.assertEqual(response.status_code, 200)
|
|
759
|
-
self.assertTrue('
|
|
760
|
-
self.assertTrue('
|
|
820
|
+
self.assertTrue('Row 3: Missing code.' in str(response.content))
|
|
821
|
+
self.assertTrue('Row 4: Missing code.' in str(response.content))
|
|
761
822
|
|
|
762
823
|
# Submit with a csv file exported with a byte order mask, typically from MS word I think
|
|
763
824
|
with open(f'{TEST_FILES_DIR}/test_part_classes_byte_order.csv') as test_csv:
|
|
@@ -1033,7 +1094,9 @@ class TestBOM(TransactionTestCase):
|
|
|
1033
1094
|
'value': '10k',
|
|
1034
1095
|
'part': p1.id,
|
|
1035
1096
|
'configuration': 'W',
|
|
1036
|
-
'copy_assembly': 'False'
|
|
1097
|
+
'copy_assembly': 'False',
|
|
1098
|
+
'property_sheen': 'flat',
|
|
1099
|
+
'property_voltage': '1.34',
|
|
1037
1100
|
}
|
|
1038
1101
|
|
|
1039
1102
|
response = self.client.post(
|
|
@@ -1048,7 +1111,9 @@ class TestBOM(TransactionTestCase):
|
|
|
1048
1111
|
'revision': '5',
|
|
1049
1112
|
'part': p3.id,
|
|
1050
1113
|
'configuration': 'W',
|
|
1051
|
-
'copy_assembly': 'true'
|
|
1114
|
+
'copy_assembly': 'true',
|
|
1115
|
+
'property_sheen': 'flat',
|
|
1116
|
+
'property_voltage': '1.34',
|
|
1052
1117
|
}
|
|
1053
1118
|
|
|
1054
1119
|
response = self.client.post(
|
|
@@ -1077,7 +1142,9 @@ class TestBOM(TransactionTestCase):
|
|
|
1077
1142
|
'revision': '4',
|
|
1078
1143
|
'attribute': 'resistance',
|
|
1079
1144
|
'value': '10k',
|
|
1080
|
-
'part': p1.id
|
|
1145
|
+
'part': p1.id,
|
|
1146
|
+
'property_sheen': 'Flat',
|
|
1147
|
+
'property_voltage': '0',
|
|
1081
1148
|
}
|
|
1082
1149
|
|
|
1083
1150
|
response = self.client.post(
|
|
@@ -1114,8 +1181,8 @@ class TestBOMIntelligent(TestBOM):
|
|
|
1114
1181
|
'configuration': 'W',
|
|
1115
1182
|
'description': 'IC, MCU 32 Bit',
|
|
1116
1183
|
'revision': 'A',
|
|
1117
|
-
'
|
|
1118
|
-
'
|
|
1184
|
+
'property_sheen': 'flat',
|
|
1185
|
+
'property_voltage': '1.34',
|
|
1119
1186
|
}
|
|
1120
1187
|
|
|
1121
1188
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -1137,6 +1204,8 @@ class TestBOMIntelligent(TestBOM):
|
|
|
1137
1204
|
'number_item': '9999',
|
|
1138
1205
|
'description': 'IC, MCU 32 Bit',
|
|
1139
1206
|
'revision': 'A',
|
|
1207
|
+
'property_sheen': 'flat',
|
|
1208
|
+
'property_voltage': '1.34',
|
|
1140
1209
|
}
|
|
1141
1210
|
|
|
1142
1211
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -1149,6 +1218,8 @@ class TestBOMIntelligent(TestBOM):
|
|
|
1149
1218
|
'number_item': '5432',
|
|
1150
1219
|
'description': 'IC, MCU 32 Bit',
|
|
1151
1220
|
'revision': 'A',
|
|
1221
|
+
'property_sheen': 'flat',
|
|
1222
|
+
'property_voltage': '1.34',
|
|
1152
1223
|
}
|
|
1153
1224
|
|
|
1154
1225
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -1161,6 +1232,8 @@ class TestBOMIntelligent(TestBOM):
|
|
|
1161
1232
|
'number_item': '1234A',
|
|
1162
1233
|
'description': 'IC, MCU 32 Bit',
|
|
1163
1234
|
'revision': 'A',
|
|
1235
|
+
'property_sheen': 'flat',
|
|
1236
|
+
'property_voltage': '1.34',
|
|
1164
1237
|
}
|
|
1165
1238
|
|
|
1166
1239
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -1173,6 +1246,8 @@ class TestBOMIntelligent(TestBOM):
|
|
|
1173
1246
|
'number_item': '1235',
|
|
1174
1247
|
'description': 'IC, MCU 32 Bit',
|
|
1175
1248
|
'revision': 'A',
|
|
1249
|
+
'property_sheen': 'flat',
|
|
1250
|
+
'property_voltage': '1.34',
|
|
1176
1251
|
}
|
|
1177
1252
|
|
|
1178
1253
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -1186,6 +1261,8 @@ class TestBOMIntelligent(TestBOM):
|
|
|
1186
1261
|
'number_item': p1.number_item,
|
|
1187
1262
|
'description': 'IC, MCU 32 Bit',
|
|
1188
1263
|
'revision': 'A',
|
|
1264
|
+
'property_sheen': 'flat',
|
|
1265
|
+
'property_voltage': '1.34',
|
|
1189
1266
|
}
|
|
1190
1267
|
|
|
1191
1268
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -1214,8 +1291,8 @@ class TestBOMIntelligent(TestBOM):
|
|
|
1214
1291
|
'configuration': 'W',
|
|
1215
1292
|
'description': 'IC, MCU 32 Bit',
|
|
1216
1293
|
'revision': 'A',
|
|
1217
|
-
'
|
|
1218
|
-
'
|
|
1294
|
+
'property_sheen': 'flat',
|
|
1295
|
+
'property_voltage': '1.34',
|
|
1219
1296
|
}
|
|
1220
1297
|
|
|
1221
1298
|
response = self.client.post(reverse('bom:create-part'), new_part_form_data)
|
|
@@ -1252,7 +1329,7 @@ class TestBOMIntelligent(TestBOM):
|
|
|
1252
1329
|
|
|
1253
1330
|
messages = list(response.context.get('messages'))
|
|
1254
1331
|
for msg in messages:
|
|
1255
|
-
self.assertNotEqual(msg.tags, "error")
|
|
1332
|
+
self.assertNotEqual(msg.tags, "error", msg.message)
|
|
1256
1333
|
|
|
1257
1334
|
subparts = p2.latest().assembly.subparts.all()
|
|
1258
1335
|
|
|
@@ -1382,6 +1459,15 @@ class TestBOMNoVariation(TestBOM):
|
|
|
1382
1459
|
def test_part_upload_bom_corner_cases(self):
|
|
1383
1460
|
pass
|
|
1384
1461
|
|
|
1462
|
+
@skip('too specific of a test case for now...')
|
|
1463
|
+
def test_part_upload_bom_with_properties(self):
|
|
1464
|
+
pass
|
|
1465
|
+
|
|
1466
|
+
@skip('too specific of a test case for now...')
|
|
1467
|
+
def test_upload_parts_break_too_many_characters(self):
|
|
1468
|
+
pass
|
|
1469
|
+
|
|
1470
|
+
|
|
1385
1471
|
@override_settings(BOM_CONFIG=settings.BOM_CONFIG_DEFAULT)
|
|
1386
1472
|
class TestForms(TestCase):
|
|
1387
1473
|
def setUp(self):
|
bom/urls.py
CHANGED
|
@@ -31,6 +31,12 @@ bom_patterns = [
|
|
|
31
31
|
path('export/', views.export_part_list, name='export-part-list'),
|
|
32
32
|
path('user-meta/<int:user_meta_id>/edit/', views.user_meta_edit, name='user-meta-edit'),
|
|
33
33
|
path('part-class/<int:part_class_id>/edit/', views.part_class_edit, name='part-class-edit'),
|
|
34
|
+
path('property-definition/add/', views.property_definition_edit, name='property-definition-add'),
|
|
35
|
+
path('property-definition/<int:property_definition_id>/edit/', views.property_definition_edit, name='property-definition-edit'),
|
|
36
|
+
path('property-definition/<int:property_definition_id>/delete/', views.property_definition_delete, name='property-definition-delete'),
|
|
37
|
+
path('quantity-of-measure/add/', views.quantity_of_measure_edit, name='quantity-of-measure-add'),
|
|
38
|
+
path('quantity-of-measure/<int:quantity_of_measure_id>/edit/', views.quantity_of_measure_edit, name='quantity-of-measure-edit'),
|
|
39
|
+
path('quantity-of-measure/<int:quantity_of_measure_id>/delete/', views.quantity_of_measure_delete, name='quantity-of-measure-delete'),
|
|
34
40
|
path('create-part/', views.create_part, name='create-part'),
|
|
35
41
|
path('upload-parts/', views.upload_parts, name='upload-parts'),
|
|
36
42
|
path('upload-parts-help/', views.upload_parts_help, name='upload-parts-help'),
|
bom/views/json_views.py
CHANGED
|
@@ -5,9 +5,9 @@ from django.shortcuts import get_object_or_404
|
|
|
5
5
|
from django.utils.decorators import method_decorator
|
|
6
6
|
from django.views import View
|
|
7
7
|
|
|
8
|
-
from bom.models import
|
|
9
|
-
from bom.third_party_apis.mouser import Mouser
|
|
8
|
+
from bom.models import PartRevision
|
|
10
9
|
from bom.third_party_apis.base_api import BaseApiError
|
|
10
|
+
from bom.third_party_apis.mouser import Mouser
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class BomJsonResponse(View):
|