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.

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