django-bom 1.235__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 +161 -0
- bom/apps.py +8 -0
- bom/base_classes.py +31 -0
- bom/constants.py +210 -0
- bom/context_processors.py +9 -0
- bom/csv_headers.py +274 -0
- bom/decorators.py +32 -0
- bom/form_fields.py +59 -0
- bom/forms.py +1400 -0
- bom/helpers.py +292 -0
- bom/local_settings.py +36 -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/__init__.py +0 -0
- bom/models.py +756 -0
- bom/part_bom.py +192 -0
- bom/settings.py +225 -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 +308 -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/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 +69 -0
- bom/templates/bom/add-sellerpart.html +96 -0
- bom/templates/bom/base-menu.html +7 -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 +35 -0
- bom/templates/bom/bom-form.html +34 -0
- bom/templates/bom/bom-signup.html +16 -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 +271 -0
- bom/templates/bom/components/seller-part-list.html +63 -0
- bom/templates/bom/create-part.html +68 -0
- bom/templates/bom/dashboard-menu.html +15 -0
- bom/templates/bom/dashboard.html +300 -0
- bom/templates/bom/edit-manufacturer-part.html +75 -0
- bom/templates/bom/edit-part-class.html +36 -0
- bom/templates/bom/edit-part.html +71 -0
- bom/templates/bom/edit-user-meta.html +41 -0
- bom/templates/bom/help.html +1363 -0
- bom/templates/bom/manufacturer-info.html +83 -0
- bom/templates/bom/manufacturers.html +96 -0
- bom/templates/bom/nothing-to-see.html +15 -0
- bom/templates/bom/organization-create.html +135 -0
- bom/templates/bom/part-info.html +440 -0
- bom/templates/bom/part-revision-display.html +184 -0
- bom/templates/bom/part-revision-edit.html +42 -0
- bom/templates/bom/part-revision-manage-bom.html +116 -0
- bom/templates/bom/part-revision-new.html +60 -0
- bom/templates/bom/part-revision-release.html +48 -0
- bom/templates/bom/search-help.html +105 -0
- bom/templates/bom/seller-info.html +83 -0
- bom/templates/bom/sellers.html +96 -0
- bom/templates/bom/settings.html +405 -0
- bom/templates/bom/signup.html +28 -0
- bom/templates/bom/table_of_contents.html +47 -0
- bom/templates/bom/upload-bom.html +112 -0
- bom/templates/bom/upload-parts-help.html +107 -0
- bom/templates/bom/upload-parts.html +54 -0
- bom/templates/registration/login.html +39 -0
- bom/tests.py +1506 -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 +89 -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 +1620 -0
- bom/wsgi.py +16 -0
- django_bom-1.235.dist-info/METADATA +207 -0
- django_bom-1.235.dist-info/RECORD +182 -0
- django_bom-1.235.dist-info/WHEEL +5 -0
- django_bom-1.235.dist-info/licenses/LICENSE +674 -0
- django_bom-1.235.dist-info/top_level.txt +1 -0
bom/part_bom.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
|
|
4
|
+
from djmoney.money import Money
|
|
5
|
+
|
|
6
|
+
from .base_classes import AsDictModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PartBom(AsDictModel):
|
|
13
|
+
def __init__(self, part_revision, quantity, unit_cost=None, missing_item_costs=0, nre_cost=None, out_of_pocket_cost=None):
|
|
14
|
+
self.part_revision = part_revision
|
|
15
|
+
self.parts = OrderedDict()
|
|
16
|
+
self.quantity = quantity
|
|
17
|
+
self._currency = self.part_revision.part.organization.currency
|
|
18
|
+
if unit_cost is None:
|
|
19
|
+
unit_cost = Money(0, self._currency)
|
|
20
|
+
if nre_cost is None:
|
|
21
|
+
nre_cost = Money(0, self._currency)
|
|
22
|
+
if out_of_pocket_cost is None:
|
|
23
|
+
out_of_pocket_cost = Money(0, self._currency)
|
|
24
|
+
|
|
25
|
+
self.unit_cost = unit_cost
|
|
26
|
+
self.missing_item_costs = missing_item_costs # count of items that have no cost
|
|
27
|
+
self.nre_cost = nre_cost
|
|
28
|
+
self.out_of_pocket_cost = out_of_pocket_cost # cost of buying self.quantity with MOQs
|
|
29
|
+
|
|
30
|
+
def cost(self):
|
|
31
|
+
return self.unit_cost * self.quantity
|
|
32
|
+
|
|
33
|
+
def total_out_of_pocket_cost(self):
|
|
34
|
+
return self.out_of_pocket_cost + self.nre_cost
|
|
35
|
+
|
|
36
|
+
def append_item_and_update(self, item):
|
|
37
|
+
if item.bom_id in self.parts:
|
|
38
|
+
self.parts[item.bom_id].extended_quantity += item.extended_quantity
|
|
39
|
+
ref = ', ' + item.references
|
|
40
|
+
self.parts[item.bom_id].references += ref
|
|
41
|
+
else:
|
|
42
|
+
self.parts[item.bom_id] = item
|
|
43
|
+
|
|
44
|
+
item.total_extended_quantity = int(self.quantity) * item.extended_quantity
|
|
45
|
+
self.update_bom_for_part(item)
|
|
46
|
+
|
|
47
|
+
def update_bom_for_part(self, bom_part):
|
|
48
|
+
if bom_part.do_not_load:
|
|
49
|
+
bom_part.order_quantity = 0
|
|
50
|
+
bom_part.order_cost = 0
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
if bom_part.seller_part:
|
|
54
|
+
try:
|
|
55
|
+
bom_part.order_quantity = bom_part.seller_part.order_quantity(bom_part.total_extended_quantity)
|
|
56
|
+
bom_part.order_cost = bom_part.total_extended_quantity * bom_part.seller_part.unit_cost
|
|
57
|
+
except AttributeError:
|
|
58
|
+
pass
|
|
59
|
+
self.unit_cost = (self.unit_cost + bom_part.seller_part.unit_cost * bom_part.extended_quantity) if bom_part.seller_part.unit_cost is not None else self.unit_cost
|
|
60
|
+
self.out_of_pocket_cost = self.out_of_pocket_cost + bom_part.out_of_pocket_cost()
|
|
61
|
+
self.nre_cost = (self.nre_cost + bom_part.seller_part.nre_cost) if bom_part.seller_part.nre_cost is not None else self.nre_cost
|
|
62
|
+
else:
|
|
63
|
+
self.missing_item_costs += 1
|
|
64
|
+
|
|
65
|
+
def update(self):
|
|
66
|
+
self.missing_item_costs = 0
|
|
67
|
+
self.unit_cost = Money(0, self._currency)
|
|
68
|
+
self.out_of_pocket_cost = Money(0, self._currency)
|
|
69
|
+
self.nre_cost = Money(0, self._currency)
|
|
70
|
+
for _, bom_part in self.parts.items():
|
|
71
|
+
self.update_bom_for_part(bom_part)
|
|
72
|
+
|
|
73
|
+
def mouser_parts(self):
|
|
74
|
+
mouser_items = {}
|
|
75
|
+
for bom_id, item in self.parts.items():
|
|
76
|
+
if item.part.id not in mouser_items and item.part.number_class.mouser_enabled:
|
|
77
|
+
for manufacturer_part in item.part.manufacturer_parts():
|
|
78
|
+
mouser_items.update({bom_id: manufacturer_part})
|
|
79
|
+
if not manufacturer_part.mouser_disable:
|
|
80
|
+
mouser_items.update({bom_id: manufacturer_part})
|
|
81
|
+
return mouser_items
|
|
82
|
+
|
|
83
|
+
def manufacturer_parts(self, source_mouser=False):
|
|
84
|
+
# TODO: optimize this query to not hit the DB in a for loop
|
|
85
|
+
if source_mouser:
|
|
86
|
+
mps = []
|
|
87
|
+
for item in self.parts:
|
|
88
|
+
if item.part.manufacturer_part.source_mouser:
|
|
89
|
+
mps.append(item.part.manufacturer_part)
|
|
90
|
+
return mps
|
|
91
|
+
return [item.part.manufacturer_part for item in self.parts]
|
|
92
|
+
|
|
93
|
+
def as_dict(self, include_id=False):
|
|
94
|
+
d = super().as_dict()
|
|
95
|
+
d['unit_cost'] = self.unit_cost.amount
|
|
96
|
+
d['nre'] = self.nre_cost.amount
|
|
97
|
+
d['out_of_pocket_cost'] = self.out_of_pocket_cost.amount
|
|
98
|
+
return d
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class PartBomItem(AsDictModel):
|
|
102
|
+
def __init__(self, bom_id, part, part_revision, do_not_load, references, quantity, extended_quantity, seller_part=None):
|
|
103
|
+
# top_level_quantity is the highest quantity, typically a order quantity for the highest assembly level in a BOM
|
|
104
|
+
# A bom item should not care about its parent quantity
|
|
105
|
+
self.bom_id = bom_id
|
|
106
|
+
self.part = part
|
|
107
|
+
self.part_revision = part_revision
|
|
108
|
+
self.do_not_load = do_not_load
|
|
109
|
+
self.references = references
|
|
110
|
+
|
|
111
|
+
self.quantity = quantity # quantity is the quantity per each direct parent assembly
|
|
112
|
+
self.extended_quantity = extended_quantity # extended_quantity, is the item quantity used in the top level assembly (e.g. assuming PartBom.quantity = 1)
|
|
113
|
+
self.total_extended_quantity = None # extended_quantity * top_level_quantity (PartBom.quantity) - Set when appending to PartBom
|
|
114
|
+
self.order_quantity = None # order quantity taking into MOQ/MPQ constraints - Set when appending to PartBom
|
|
115
|
+
|
|
116
|
+
self._currency = self.part.organization.currency
|
|
117
|
+
|
|
118
|
+
self.order_cost = Money(0, self._currency) # order_cost is updated similar to above order_quantity - Set when appending to PartBom
|
|
119
|
+
self.seller_part = seller_part
|
|
120
|
+
|
|
121
|
+
self.api_info = None
|
|
122
|
+
|
|
123
|
+
def extended_cost(self):
|
|
124
|
+
try:
|
|
125
|
+
return self.extended_quantity * self.seller_part.unit_cost
|
|
126
|
+
except (AttributeError, TypeError) as err:
|
|
127
|
+
logger.log(logging.INFO, '[part_bom.py] ' + str(err))
|
|
128
|
+
return Money(0, self._currency)
|
|
129
|
+
|
|
130
|
+
def out_of_pocket_cost(self):
|
|
131
|
+
try:
|
|
132
|
+
return self.order_quantity * self.seller_part.unit_cost
|
|
133
|
+
except (AttributeError, TypeError) as err:
|
|
134
|
+
logger.log(logging.INFO, '[part_bom.py] ' + str(err))
|
|
135
|
+
return Money(0, self._currency)
|
|
136
|
+
|
|
137
|
+
def as_dict(self, include_id=False):
|
|
138
|
+
dict = super().as_dict()
|
|
139
|
+
del dict['bom_id']
|
|
140
|
+
return dict
|
|
141
|
+
|
|
142
|
+
def as_dict_for_export(self):
|
|
143
|
+
return {
|
|
144
|
+
'part_number': self.part.full_part_number(),
|
|
145
|
+
'quantity': self.quantity,
|
|
146
|
+
'do_not_load': self.do_not_load,
|
|
147
|
+
'part_class': self.part.number_class.name if self.part.number_class else '',
|
|
148
|
+
'references': self.references,
|
|
149
|
+
'part_synopsis': self.part_revision.synopsis(),
|
|
150
|
+
'part_description': self.part_revision.description,
|
|
151
|
+
'part_revision': self.part_revision.revision,
|
|
152
|
+
'part_manufacturer': self.part.primary_manufacturer_part.manufacturer.name if self.part.primary_manufacturer_part is not None and self.part.primary_manufacturer_part.manufacturer is not None else '',
|
|
153
|
+
'part_manufacturer_part_number': self.part.primary_manufacturer_part.manufacturer_part_number if self.part.primary_manufacturer_part is not None else '',
|
|
154
|
+
'part_ext_qty': self.extended_quantity,
|
|
155
|
+
'part_order_qty': self.order_quantity,
|
|
156
|
+
'part_seller': self.seller_part.seller.name if self.seller_part is not None else '',
|
|
157
|
+
'part_seller_part_number': self.seller_part.seller_part_number if self.seller_part is not None else '',
|
|
158
|
+
'part_cost': self.seller_part.unit_cost if self.seller_part is not None else '',
|
|
159
|
+
'part_moq': self.seller_part.minimum_order_quantity if self.seller_part is not None else 0,
|
|
160
|
+
'part_nre': self.seller_part.nre_cost if self.seller_part is not None else 0,
|
|
161
|
+
'part_ext_cost': self.extended_cost(),
|
|
162
|
+
'part_out_of_pocket_cost': self.out_of_pocket_cost(),
|
|
163
|
+
'part_lead_time_days': self.seller_part.lead_time_days if self.seller_part is not None else 0,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
def manufacturer_parts_for_export(self):
|
|
167
|
+
return [mp.as_dict_for_export() for mp in self.part.manufacturer_parts(exclude_primary=True)]
|
|
168
|
+
|
|
169
|
+
def seller_parts_for_export(self):
|
|
170
|
+
return [sp.as_dict_for_export() for sp in self.part.seller_parts(exclude_primary=True)]
|
|
171
|
+
|
|
172
|
+
def __str__(self):
|
|
173
|
+
return f'{self.part.full_part_number()}, qty: {self.quantity}'
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class PartIndentedBomItem(PartBomItem, AsDictModel):
|
|
177
|
+
def __init__(self, indent_level, parent_id, subpart, parent_quantity, *args, **kwargs):
|
|
178
|
+
super().__init__(*args, **kwargs)
|
|
179
|
+
self.indent_level = indent_level
|
|
180
|
+
self.parent_id = parent_id
|
|
181
|
+
self.subpart = subpart
|
|
182
|
+
self.parent_quantity = parent_quantity
|
|
183
|
+
|
|
184
|
+
def as_dict_for_export(self):
|
|
185
|
+
dict = super().as_dict_for_export()
|
|
186
|
+
dict.update({
|
|
187
|
+
'level': self.indent_level,
|
|
188
|
+
})
|
|
189
|
+
return dict
|
|
190
|
+
|
|
191
|
+
def __str__(self):
|
|
192
|
+
return f'level: {self.indent_level}, {super().__str__()}'
|
bom/settings.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django settings for indabom project.
|
|
3
|
+
|
|
4
|
+
Generated by 'django-admin startproject' using Django 1.10.5.
|
|
5
|
+
|
|
6
|
+
For more information on this file, see
|
|
7
|
+
https://docs.djangoproject.com/en/1.10/topics/settings/
|
|
8
|
+
|
|
9
|
+
For the full list of settings and their values, see
|
|
10
|
+
https://docs.djangoproject.com/en/1.10/ref/settings/
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
BASE_DIR = None
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from bom.local_settings import *
|
|
20
|
+
except ImportError as e:
|
|
21
|
+
print(e)
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
25
|
+
if not BASE_DIR:
|
|
26
|
+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
27
|
+
|
|
28
|
+
# Quick-start development settings - unsuitable for production
|
|
29
|
+
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
|
30
|
+
|
|
31
|
+
# Application definition
|
|
32
|
+
|
|
33
|
+
INSTALLED_APPS = [
|
|
34
|
+
'bom.apps.BomConfig',
|
|
35
|
+
'django.contrib.admin',
|
|
36
|
+
'django.contrib.auth',
|
|
37
|
+
'django.contrib.contenttypes',
|
|
38
|
+
'django.contrib.sessions',
|
|
39
|
+
'django.contrib.messages',
|
|
40
|
+
'django.contrib.staticfiles',
|
|
41
|
+
'materializecssform',
|
|
42
|
+
'social_django',
|
|
43
|
+
'djmoney',
|
|
44
|
+
'djmoney.contrib.exchange',
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
MIDDLEWARE = [
|
|
48
|
+
'django.middleware.security.SecurityMiddleware',
|
|
49
|
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
50
|
+
'django.middleware.common.CommonMiddleware',
|
|
51
|
+
'django.middleware.csrf.CsrfViewMiddleware',
|
|
52
|
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
53
|
+
'django.contrib.messages.middleware.MessageMiddleware',
|
|
54
|
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
55
|
+
'social_django.middleware.SocialAuthExceptionMiddleware',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
ROOT_URLCONF = 'bom.urls'
|
|
59
|
+
|
|
60
|
+
AUTHENTICATION_BACKENDS = (
|
|
61
|
+
'social_core.backends.google.GoogleOAuth2',
|
|
62
|
+
'django.contrib.auth.backends.ModelBackend',
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
TEMPLATES = [
|
|
66
|
+
{
|
|
67
|
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
68
|
+
'DIRS': [os.path.join(BASE_DIR, 'bom/templates/bom')],
|
|
69
|
+
'APP_DIRS': True,
|
|
70
|
+
'OPTIONS': {
|
|
71
|
+
'context_processors': [
|
|
72
|
+
'django.template.context_processors.debug',
|
|
73
|
+
'django.template.context_processors.request',
|
|
74
|
+
'django.contrib.auth.context_processors.auth',
|
|
75
|
+
'django.contrib.messages.context_processors.messages',
|
|
76
|
+
'django.template.context_processors.media',
|
|
77
|
+
'social_django.context_processors.backends',
|
|
78
|
+
'social_django.context_processors.login_redirect',
|
|
79
|
+
'bom.context_processors.bom_config',
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
WSGI_APPLICATION = 'bom.wsgi.application'
|
|
86
|
+
|
|
87
|
+
# Password validation
|
|
88
|
+
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
|
|
89
|
+
|
|
90
|
+
AUTH_PASSWORD_VALIDATORS = [
|
|
91
|
+
{
|
|
92
|
+
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
102
|
+
},
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
LOGGING = {
|
|
106
|
+
'version': 1,
|
|
107
|
+
'disable_existing_loggers': False,
|
|
108
|
+
'handlers': {
|
|
109
|
+
# Include the default Django email handler for errors
|
|
110
|
+
# This is what you'd get without configuring logging at all.
|
|
111
|
+
'mail_admins': {
|
|
112
|
+
'class': 'django.utils.log.AdminEmailHandler',
|
|
113
|
+
'level': 'ERROR',
|
|
114
|
+
# But the emails are plain text by default - HTML is nicer
|
|
115
|
+
'include_html': True,
|
|
116
|
+
},
|
|
117
|
+
# Log to a text file that can be rotated by logrotate
|
|
118
|
+
'logfile': {
|
|
119
|
+
'class': 'logging.handlers.WatchedFileHandler',
|
|
120
|
+
'filename': '/var/log/indabom/django.log' if not DEBUG else './bom.log'
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
'loggers': {
|
|
124
|
+
# Again, default Django configuration to email unhandled exceptions
|
|
125
|
+
'django.request': {
|
|
126
|
+
'handlers': ['mail_admins'],
|
|
127
|
+
'level': 'ERROR',
|
|
128
|
+
'propagate': True,
|
|
129
|
+
},
|
|
130
|
+
# Might as well log any errors anywhere else in Django
|
|
131
|
+
'django': {
|
|
132
|
+
'handlers': ['logfile'],
|
|
133
|
+
'level': 'ERROR',
|
|
134
|
+
'propagate': False,
|
|
135
|
+
},
|
|
136
|
+
# django-bom app
|
|
137
|
+
'bom': {
|
|
138
|
+
'handlers': ['logfile'],
|
|
139
|
+
'level': 'INFO', # Or maybe INFO or DEBUG
|
|
140
|
+
'propagate': False
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
DEFAULT_AUTO_FIELD='django.db.models.AutoField'
|
|
146
|
+
|
|
147
|
+
# Internationalization
|
|
148
|
+
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
|
149
|
+
|
|
150
|
+
LANGUAGE_CODE = 'en-us'
|
|
151
|
+
|
|
152
|
+
TIME_ZONE = 'UTC'
|
|
153
|
+
|
|
154
|
+
USE_I18N = True
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
USE_TZ = True
|
|
158
|
+
|
|
159
|
+
# Static files (CSS, JavaScript, Images)
|
|
160
|
+
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
|
161
|
+
|
|
162
|
+
STATIC_URL = '/static/'
|
|
163
|
+
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
|
|
164
|
+
|
|
165
|
+
MEDIA_URL = '/media/'
|
|
166
|
+
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
|
|
167
|
+
|
|
168
|
+
LOGIN_URL = '/login/'
|
|
169
|
+
LOGOUT_URL = '/logout/'
|
|
170
|
+
|
|
171
|
+
LOGIN_REDIRECT_URL = '/'
|
|
172
|
+
LOGOUT_REDIRECT_URL = '/'
|
|
173
|
+
|
|
174
|
+
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/settings?tab_anchor=file'
|
|
175
|
+
SOCIAL_AUTH_DISCONNECT_REDIRECT_URL = '/settings?tab_anchor=file'
|
|
176
|
+
SOCIAL_AUTH_LOGIN_ERROR_URL = '/'
|
|
177
|
+
|
|
178
|
+
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['email', 'profile', 'https://www.googleapis.com/auth/drive',
|
|
179
|
+
'https://www.googleapis.com/auth/plus.login']
|
|
180
|
+
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {
|
|
181
|
+
'access_type': 'offline',
|
|
182
|
+
'approval_prompt': 'force' # forces storage of refresh token
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
SOCIAL_AUTH_PIPELINE = (
|
|
186
|
+
'social_core.pipeline.social_auth.social_details',
|
|
187
|
+
'social_core.pipeline.social_auth.social_uid',
|
|
188
|
+
'social_core.pipeline.social_auth.social_user',
|
|
189
|
+
'social_core.pipeline.user.get_username',
|
|
190
|
+
'social_core.pipeline.social_auth.associate_by_email',
|
|
191
|
+
'social_core.pipeline.user.create_user',
|
|
192
|
+
'social_core.pipeline.social_auth.associate_user',
|
|
193
|
+
'social_core.pipeline.social_auth.load_extra_data',
|
|
194
|
+
'social_core.pipeline.user.user_details',
|
|
195
|
+
'bom.third_party_apis.google_drive.initialize_parent',
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
SOCIAL_AUTH_DISCONNECT_PIPELINE = (
|
|
199
|
+
'social_core.pipeline.disconnect.allowed_to_disconnect',
|
|
200
|
+
'bom.third_party_apis.google_drive.uninitialize_parent',
|
|
201
|
+
'social_core.pipeline.disconnect.get_entries',
|
|
202
|
+
'social_core.pipeline.disconnect.revoke_tokens',
|
|
203
|
+
'social_core.pipeline.disconnect.disconnect',
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# django-money settings
|
|
207
|
+
CURRENCY_DECIMAL_PLACES = 4
|
|
208
|
+
EXCHANGE_BACKEND = 'djmoney.contrib.exchange.backends.FixerBackend'
|
|
209
|
+
|
|
210
|
+
# django-bom configuration
|
|
211
|
+
BOM_CONFIG_DEFAULT = {
|
|
212
|
+
'base_template': 'base.html',
|
|
213
|
+
'mouser_api_key': None,
|
|
214
|
+
'admin_dashboard': {
|
|
215
|
+
'enable_autocomplete': True,
|
|
216
|
+
'page_size': 50,
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
bom_config_new = BOM_CONFIG_DEFAULT.copy()
|
|
221
|
+
bom_config_new.update(BOM_CONFIG)
|
|
222
|
+
BOM_CONFIG = bom_config_new
|
|
223
|
+
|
|
224
|
+
# Custom login url for BOM_LOGIN
|
|
225
|
+
BOM_LOGIN_URL = None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
table.treetable span.indenter {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
text-align: right;
|
|
6
|
+
|
|
7
|
+
/* Disable text selection of nodes (for better D&D UX) */
|
|
8
|
+
user-select: none;
|
|
9
|
+
-khtml-user-select: none;
|
|
10
|
+
-moz-user-select: none;
|
|
11
|
+
-o-user-select: none;
|
|
12
|
+
-webkit-user-select: none;
|
|
13
|
+
|
|
14
|
+
/* Force content-box box model for indenter (Bootstrap compatibility) */
|
|
15
|
+
-webkit-box-sizing: content-box;
|
|
16
|
+
-moz-box-sizing: content-box;
|
|
17
|
+
box-sizing: content-box;
|
|
18
|
+
|
|
19
|
+
width: 19px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
table.treetable span.indenter a {
|
|
23
|
+
background-position: left center;
|
|
24
|
+
background-repeat: no-repeat;
|
|
25
|
+
display: inline-block;
|
|
26
|
+
text-decoration: none;
|
|
27
|
+
width: 19px;
|
|
28
|
+
}
|