django-bom 1.238__py3-none-any.whl → 1.240__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.
@@ -8,24 +8,38 @@ main {
8
8
  min-height: 100vh;
9
9
  }
10
10
 
11
+ p {
12
+ font-size: 1.2em;
13
+ font-weight: 300;
14
+ }
15
+
16
+ li {
17
+ font-size: 1.2em;
18
+ font-weight: 300;
19
+ }
20
+
11
21
  h1 {
12
- font-size: 3.3rem;
22
+ font-size: 3.5rem;
23
+ font-weight: 300;
13
24
  }
14
25
 
15
26
  h2 {
16
- font-size: 2.8rem;
27
+ font-size: 3.0rem;
28
+ font-weight: 300;
17
29
  }
18
30
 
19
31
  h3 {
20
- font-size: 2.2rem;
32
+ font-size: 1.6rem;
33
+ font-weight: 300;
21
34
  }
22
35
 
23
36
  h4 {
24
- font-size: 1.8rem;
37
+ font-size: 1.3rem;
38
+ font-weight: 300;
25
39
  }
26
40
 
27
41
  h5 {
28
- font-size: 1.3rem;
42
+ font-size: 1.1rem;
29
43
  }
30
44
 
31
45
  .tabs {
@@ -316,4 +330,70 @@ a.anchor {
316
330
  .pagination {
317
331
  display: none;
318
332
  }
333
+ }
334
+
335
+ /* --- Settings & Admin Layout Refinements (flat, Material-inspired) --- */
336
+ /* Scoped to .container-app so marketing pages and legacy views remain unchanged. */
337
+
338
+ /* Section spacing for denser admin/settings layouts */
339
+ .container-app .section {
340
+ padding-top: 16px;
341
+ padding-bottom: 16px;
342
+ }
343
+
344
+ /* Consistent section headers with optional leading/trailing icons */
345
+ .container-app .section-title {
346
+ display: flex;
347
+ align-items: center;
348
+ gap: 8px;
349
+ margin: 0 0 8px 0;
350
+ letter-spacing: 0.2px;
351
+ font-weight: 500;
352
+ }
353
+
354
+ .container-app .section-title .material-icons {
355
+ line-height: 1;
356
+ }
357
+
358
+ .container-app .section-title img {
359
+ height: 1.8rem;
360
+ margin-left: 6px;
361
+ vertical-align: middle;
362
+ }
363
+
364
+ /* When a service logo/image is used as a leading icon, adjust spacing */
365
+ .container-app .section-title img:first-child {
366
+ margin-left: 0;
367
+ margin-right: 6px;
368
+ }
369
+
370
+ /* Comfortable dividers between blocks */
371
+ .container-app .divider {
372
+ margin: 16px 0;
373
+ }
374
+
375
+ /* Slightly smaller paragraph text and tighter rhythm in settings */
376
+ .container-app .section p {
377
+ margin: 0 0 12px 0;
378
+ }
379
+
380
+ /* Tabs separation from content */
381
+ .container-app .tabs {
382
+ margin-bottom: 12px;
383
+ }
384
+
385
+ /* Flat collections (used as key-value lists in settings) */
386
+ .container-app .collection.z-depth-0 {
387
+ border: none;
388
+ }
389
+
390
+ .container-app .collection.z-depth-0 .collection-item {
391
+ border: none;
392
+ padding-left: 0;
393
+ }
394
+
395
+ /* Button group spacing in aligned action rows */
396
+ .container-app .right-align .btn,
397
+ .container-app .right-align .btn-flat {
398
+ margin-left: 4px;
319
399
  }
@@ -16,37 +16,43 @@
16
16
  </div>
17
17
 
18
18
  <div id="user" class="col s12">
19
- <h3>User Info</h3>
20
- <form name="seller" action="{% url 'bom:settings' tab_anchor=USER_TAB %}" method="post" class="col s12">
21
- {% csrf_token %}
22
- <div class="row">
23
- <div class="col s12">
24
- <p>Your role is: <b>{{ user.bom_profile.get_role_display }}</b></p>
25
- <p>Your username is: <b>{{ user.username }}</b></p>
26
- <p>To request a change in role please contact <a href="mailto:{{ organization.owner.email }}">{{ organization.owner.email }}</a> for assistance.</p>
19
+ <div class="section">
20
+ <h4 class="section-title"><i class="material-icons teal-text text-darken-1">person</i>User</h4>
21
+ <form name="seller" action="{% url 'bom:settings' tab_anchor=USER_TAB %}" method="post">
22
+ {% csrf_token %}
23
+ <div class="row">
24
+ <div class="col s12">
25
+ <p>Your role is: <b>{{ user.bom_profile.get_role_display }}</b></p>
26
+ <p>Your username is: <b>{{ user.username }}</b></p>
27
+ <p>To request a change in role please contact <a
28
+ href="mailto:{{ organization.owner.email }}">{{ organization.owner.email }}</a> for
29
+ assistance.</p>
30
+ </div>
27
31
  </div>
28
- </div>
29
- <div class="row">
30
- {{ user_form.first_name|materializecss:'s12 m4' }}
31
- {{ user_form.last_name|materializecss:'s12 m4' }}
32
- {{ user_form.email|materializecss:'s12 m4' }}
33
- </div>
34
- <div class="row">
35
- <div class="col s12 right-align">
36
- <button class="waves-effect waves-light btn btn-primary" type="submit" name="submit-edit-user">
37
- Save
38
- </button>
32
+ <div class="row">
33
+ {{ user_form.first_name|materializecss:'s12 m4' }}
34
+ {{ user_form.last_name|materializecss:'s12 m4' }}
35
+ {{ user_form.email|materializecss:'s12 m4' }}
39
36
  </div>
40
- </div>
41
- </form>
42
-
43
- <div class="row">
44
- <div class="col s12">
45
- <div class="card-panel">
46
- <h5 class="text-error">Danger Zone</h5>
47
- <p>Deleting your account is permanent. If you are the organization owner, your organization will also be deleted unless you transfer ownership before proceeding.</p>
48
- <a href="{% url 'account-delete' %}" class="btn btn-warning">Delete My Account</a>
37
+ <div class="row">
38
+ <div class="col s12 right-align">
39
+ <button class="waves-effect waves-light btn btn-primary" type="submit"
40
+ name="submit-edit-user">
41
+ Save
42
+ </button>
43
+ </div>
49
44
  </div>
45
+ </form>
46
+ </div>
47
+
48
+ <div class="section"
49
+ style="background: #ffebee; border: 1px solid #ffcdd2; padding: 16px; border-radius: 6px;">
50
+ <h5 class="section-title red-text text-darken-2" style="margin-top:0;"><i
51
+ class="material-icons red-text text-darken-2">warning</i>Danger Zone</h5>
52
+ <p>Deleting your account is permanent. If you are the organization owner, your organization will also be
53
+ deleted unless you transfer ownership before proceeding.</p>
54
+ <div class="right-align" style="margin-top: 8px;">
55
+ <a href="{% url 'account-delete' %}" class="btn red lighten-1">Delete My Account</a>
50
56
  </div>
51
57
  </div>
52
58
  </div>
@@ -54,9 +60,10 @@
54
60
  <div id="indabom" class="col s12">
55
61
  {% if profile.role == 'A' %}
56
62
  {% if organization.number_scheme == 'S' %}
57
- {% if part_classes.count > 0 %}
58
- <h3>Edit Part Classes</h3>
59
- <a href="{% url 'bom:help' %}#part-numbering" target="_blank">What is a part class?</a>
63
+ <div class="section">
64
+ <h4 class="section-title"><i class="material-icons teal-text text-darken-1">category</i>Part
65
+ Classes</h4>
66
+ <p><a href="{% url 'bom:help' %}#part-numbering" target="_blank">What is a part class?</a></p>
60
67
  <form name="seller" action="{% url 'bom:settings' tab_anchor=INDABOM_TAB %}" method="post" enctype="multipart/form-data">
61
68
  {% csrf_token %}
62
69
  {% if part_classes.count > 0 %}
@@ -78,8 +85,10 @@
78
85
  </div>
79
86
  <div class="col s2 m6 right-align">
80
87
  <div class="input-field">
81
- <button class="waves-effect btn-flat btn-icon-round tooltipped" type="submit" name="submit-part-class-export" data-tooltip="Export part classes."><i
82
- class="material-icons">file_download</i></button>
88
+ <button class="waves-effect btn-flat btn-icon-round tooltipped"
89
+ type="submit" name="submit-part-class-export"
90
+ data-tooltip="Export part classes."><i class="material-icons">file_download</i>
91
+ </button>
83
92
  </div>
84
93
  </div>
85
94
  </div>
@@ -90,7 +99,9 @@
90
99
  <th class="text-normal">Code</th>
91
100
  <th class="text-normal">Name</th>
92
101
  <th class="text-normal">Description</th>
93
- <th class="text-normal"><img height="18" style="padding: 4px 4px 0 4px;" alt="Mouser" title="Via Mouser.com" src="{% static 'bom/img/mouser.png' %}">Mouser
102
+ <th class="text-normal"><img height="18" style="padding: 4px 4px 0 4px;"
103
+ alt="Mouser" title="Via Mouser.com"
104
+ src="{% static 'bom/img/mouser.png' %}">Mouser
94
105
  Sourcing
95
106
  </th>
96
107
  <th class="text-normal">Options</th>
@@ -106,7 +117,9 @@
106
117
  <td class="text-normal">{{ part_class.name }}</td>
107
118
  <td class="text-normal">{{ part_class.comment }}</td>
108
119
  <td class="text-normal">{% if part_class.mouser_enabled %}
109
- <img height="24" style="padding: 4px 4px 0 4px;" alt="Mouser" title="Via Mouser.com" src="{% static 'bom/img/mouser.png' %}">{% endif %}</td>
120
+ <img height="24" style="padding: 4px 4px 0 4px;" alt="Mouser"
121
+ title="Via Mouser.com"
122
+ src="{% static 'bom/img/mouser.png' %}">{% endif %}</td>
110
123
  <td>
111
124
  <a class="waves-effect btn-flat"
112
125
  href="{% url 'bom:part-class-edit' part_class_id=part_class.id %}"><i
@@ -120,64 +133,91 @@
120
133
  <p>No part classes have been defined yet.</p>
121
134
  {% endif %}
122
135
  </form>
123
- {% endif %}
124
- <div class="row" style="padding-top: 16px;">
125
- <div class="col s12 right-align">
136
+ <div class="right-align" style="margin-top: 8px;">
126
137
  {% if part_classes.count == 0 %}
127
- <a href="{% url 'bom:help' %}#part-numbering" target="_blank">What is a part class?</a>
128
- <p>You may also use <a href="{% static 'bom/doc/sample_part_classes.csv' %}">this sample CSV file</a>.</p>
138
+ <div class="left-align" style="margin-bottom: 8px;">
139
+ <a href="{% url 'bom:help' %}#part-numbering" target="_blank">What is a part
140
+ class?</a>
141
+ <p>You may also use <a href="{% static 'bom/doc/sample_part_classes.csv' %}">this
142
+ sample CSV file</a>.</p>
143
+ </div>
129
144
  {% endif %}
130
145
  {% include 'bom/bom-form-modal.html' with modal_title='Upload Part Classes' form=part_class_csv_form action=part_class_form_action name='submit-part-class-upload' modal_description='To batch add part classes, upload a csv that contains columns with the headers<b>`name`</b> and <b>`code`</b>. You may optionally specify a description or comment by including a column with the header <b>`description`</b> or <b>`comment`</b>.' %}
131
146
  {% include 'bom/bom-form-modal.html' with modal_title='Add Part Class' form=part_class_form action=part_class_form_action name='submit-part-class-create' %}
132
147
  </div>
133
148
  </div>
134
- <h3 id="indabom-part-number">Part Number</h3>
135
- <form name="seller" action="{% url 'bom:settings' tab_anchor=INDABOM_TAB %}" method="post" enctype="multipart/form-data">
136
- {% csrf_token %}
137
- <p>You may only increase the number of digits for each component of the part number: the part class code (C), part item number (N), and the part variation (V).</p>
138
- <p>Your organization's current configuration is <b>{{ organization.number_cs }}-{{ organization.number_ns }}{% if organization.number_vs %}-
139
- {{ organization.number_vs }}{% endif %}</b></p>
140
- <div class="row">
141
- {{ organization_number_len_form|materializecss:'s4 m2' }}
142
- <div class="col s12 m6 input-field">
143
- <button class="waves-effect waves-light btn btn-primary" type="submit"
144
- name="submit-number-item-len"
145
- onclick="return confirm('Are you sure you want to change the number of digits?')">Save
146
- </button>
149
+
150
+ <div class="divider"></div>
151
+
152
+ <div class="section">
153
+ <h4 class="section-title" id="indabom-part-number"><i
154
+ class="material-icons teal-text text-darken-1">format_list_numbered</i>Part Number</h4>
155
+ <form name="seller" action="{% url 'bom:settings' tab_anchor=INDABOM_TAB %}" method="post"
156
+ enctype="multipart/form-data">
157
+ {% csrf_token %}
158
+ <p>You may only increase the number of digits for each component of the part number: the
159
+ part class code (C), part item number (N), and the part variation (V).</p>
160
+ <p>Your organization's current configuration is
161
+ <b>{{ organization.number_cs }}-{{ organization.number_ns }}
162
+ {% if organization.number_vs %}-{{ organization.number_vs }}{% endif %}</b></p>
163
+ <div class="row">
164
+ {{ organization_number_len_form|materializecss:'s4 m2' }}
165
+ <div class="col s12 m6 input-field">
166
+ <button class="waves-effect waves-light btn btn-primary" type="submit"
167
+ name="submit-number-item-len"
168
+ onclick="return confirm('Are you sure you want to change the number of digits?')">
169
+ Save
170
+ </button>
171
+ </div>
147
172
  </div>
148
- </div>
149
- </form>
173
+ </form>
174
+ </div>
150
175
  {% endif %}
151
- <h3>Change Organization Number Scheme</h3>
152
- <p>Your organization's number scheme is currently: <b>{{ organization.get_number_scheme_display }}</b></p>
153
- <ul class="browser-default">
154
- {% if organization.number_scheme == 'S' or organization_parts_count == 0 %}
155
- <li style="padding-bottom: 16px;"><b>Semi-intelligent</b> e.g. CCC-NNNN-YY<br>Consists of 3 components: a 3-digit part class, a N-digit part number, and a 2-digit variation.
156
- IndaBOM
157
- part numbers are designed to be simple to assign and simple to subsequently write, type, or speak. You define the part classes in your organization, and how long your
158
- N-digit
159
- part number is below.
160
- </li>
161
- {% endif %}
162
- {% if organization.number_scheme == 'I' or organization_parts_count == 0 %}
163
- <li><b>Intelligent</b> You control your numbers.<br>Intelligent part numbering on IndaBOM allows the user to assign any part number to a part. The part number contains
164
- descriptive details embedded within that provides noteworthy information about the part. For example, a capacitor may be named C0402X5R33PF to indicate that it is a
165
- capacitor of size "0402", using a X5R dialectric, and is 33pF.
166
- </li>
176
+
177
+ <div class="divider"></div>
178
+
179
+ <div class="section">
180
+ <h4 class="section-title"><i class="material-icons teal-text text-darken-1">swap_horiz</i>Change
181
+ Organization Number Scheme</h4>
182
+ <p>Your organization's number scheme is currently:
183
+ <b>{{ organization.get_number_scheme_display }}</b></p>
184
+ <ul class="browser-default">
185
+ {% if organization.number_scheme == 'S' or organization_parts_count == 0 %}
186
+ <li style="padding-bottom: 16px;"><b>Semi-intelligent</b> e.g. CCC-NNNN-YY<br>Consists of 3
187
+ components: a 3-digit part class, a N-digit part number, and a 2-digit variation.
188
+ IndaBOM part numbers are designed to be simple to assign and simple to subsequently
189
+ write, type, or speak. You define the part classes in your organization, and how long
190
+ your N-digit part number is below.
191
+ </li>
192
+ {% endif %}
193
+ {% if organization.number_scheme == 'I' or organization_parts_count == 0 %}
194
+ <li><b>Intelligent</b> You control your numbers.<br>Intelligent part numbering on IndaBOM
195
+ allows the user to assign any part number to a part. The part number contains
196
+ descriptive details embedded within that provides noteworthy information about the part.
197
+ For example, a capacitor may be named C0402X5R33PF to indicate that it is a capacitor of
198
+ size "0402", using a X5R dialectric, and is 33pF.
199
+ </li>
200
+ {% endif %}
201
+ </ul>
202
+ <p style="font-size: 15px;">You can read more about the options <a
203
+ href="{% url 'bom:help' %}#part-numbering" target="_blank">here</a>.</p>
204
+ {% if organization_parts_count > 0 %}
205
+ <p><b>You've already created {{ organization_parts_count }}
206
+ part{{ organization_parts_count|pluralize }}.</b> Since changing your organization number
207
+ scheme requires changing your parts numbers, please manually delete your parts then come
208
+ back here to change your organization number scheme. Alternatively we can help delete your
209
+ parts if you reach out to info@indabom.com.</p>
210
+ {% else %}
211
+ <form name="number-scheme" action="{% url 'bom:settings' tab_anchor=INDABOM_TAB %}"
212
+ method="post">
213
+ {% csrf_token %}
214
+ <button type="submit" name="change-number-scheme"
215
+ class="waves-effect waves-light btn red lighten-1">Change Scheme to
216
+ {% if organization.number_scheme == 'S' %}Intelligent{% else %}
217
+ Semi-Intelligent{% endif %}</button>
218
+ </form>
167
219
  {% endif %}
168
- </ul>
169
- <p style="font-size: 15px;">You can read more about the options <a href="{% url 'bom:help' %}#part-numbering" target="_blank">here</a>.</p>
170
- {% if organization_parts_count > 0 %}
171
- <p><b>You've already created {{ organization_parts_count }} part{{ organization_parts_count|pluralize }}.</b> Since changing your organization number scheme requires changing your
172
- parts numbers, please manually delete your parts then come back here to change your organization number scheme. Alternatively we can
173
- help delete your parts if you reach out to info@indabom.com.</p>
174
- {% else %}
175
- <form name="number-scheme" action="{% url 'bom:settings' tab_anchor=INDABOM_TAB %}" method="post" class="col s12">
176
- {% csrf_token %}
177
- <button type="submit" name="change-number-scheme" class="waves-effect waves-light btn red lighten-1">Change Scheme to {% if organization.number_scheme == 'S' %}
178
- Intelligent{% else %}Semi-Intelligent{% endif %}</button>
179
- </form>
180
- {% endif %}
220
+ </div>
181
221
  {% else %}
182
222
  {% include 'bom/nothing-to-see.html' with required_privilege='Admin' %}
183
223
  {% endif %}
@@ -185,143 +225,164 @@
185
225
 
186
226
  <div id="organization" class="col s12">
187
227
  {% if user.bom_profile.role == 'A' %}
188
- <h3>Organization</h3>
189
- <div class="row">
190
- <div class="col s12">
191
- <form name="seller" action="{% url 'bom:settings' tab_anchor=ORGANIZATION_TAB %}" method="post" class="col s12" enctype="multipart/form-data">
192
- {% csrf_token %}
193
- <div class="row">
194
- {{ organization_form|materializecss:'s12 m4' }}
228
+ <div class="section">
229
+ <h4 class="section-title"><i class="material-icons teal-text text-darken-1">business</i>Organization
230
+ </h4>
231
+ <form name="seller" action="{% url 'bom:settings' tab_anchor=ORGANIZATION_TAB %}" method="post"
232
+ enctype="multipart/form-data">
233
+ {% csrf_token %}
234
+ <div class="row">
235
+ {{ organization_form|materializecss:'s12 m4' }}
236
+ </div>
237
+ <div class="row">
238
+ <div class="col s12 right-align">
239
+ <button class="waves-effect waves-light btn btn-primary" type="submit"
240
+ name="submit-edit-organization"
241
+ onclick="return confirm('Are you sure you want to change the organization information?')">
242
+ Save
243
+ </button>
195
244
  </div>
196
- <div class="row">
197
- <div class="col s12 right-align">
198
- <button class="waves-effect waves-light btn btn-primary" type="submit"
199
- name="submit-edit-organization"
200
- onclick="return confirm('Are you sure you want to change the organization information?')">Save
201
- </button>
202
- </div>
245
+ </div>
246
+ </form>
247
+ </div>
248
+
249
+ <div class="section">
250
+ <h4 class="section-title"><i class="material-icons teal-text text-darken-1">group</i>Users</h4>
251
+ <form name="seller" action="{% url 'bom:settings' tab_anchor=ORGANIZATION_TAB %}" method="post"
252
+ enctype="multipart/form-data">
253
+ {% csrf_token %}
254
+ <table>
255
+ <thead>
256
+ <tr>
257
+ <th class="text-normal"><label><input type="checkbox" id="user-select-all"><span></span></label>
258
+ </th>
259
+ <th class="text-normal">Role</th>
260
+ <th class="text-normal">User Name</th>
261
+ <th class="text-normal">Full Name</th>
262
+ <th class="text-normal">Email</th>
263
+ <th class="text-normal"></th>
264
+ </tr>
265
+ </thead>
266
+ <tbody>
267
+ {% for org_user in users_in_organization %}
268
+ <tr>
269
+ <td>
270
+ <label><input type="checkbox" class="filled-in"
271
+ name="remove_user_meta_id_{{ org_user.bom_profile.id }}"><span/></label>
272
+ </td>
273
+ <td class="text-normal">{{ org_user.bom_profile.get_role_display }}</td>
274
+ <td class="text-normal">{{ org_user.username }}</td>
275
+ <td class="text-normal">{{ org_user.first_name }} {{ org_user.last_name }}</td>
276
+ <td class="text-normal"><a href="mailto:{{ user.email }}">{{ org_user.email }}</a>
277
+ </td>
278
+ <td>
279
+ <a class="waves-effect btn-flat"
280
+ href="{% url 'bom:user-meta-edit' user_meta_id=org_user.bom_profile.id %}"><i
281
+ class="material-icons left">edit</i>Edit</a>
282
+ </td>
283
+ </tr>
284
+ {% empty %}
285
+ <tr>
286
+ <td colspan="5" style="font-style: italic;">There are no additional users in this
287
+ organization.
288
+ </td>
289
+ </tr>
290
+ {% endfor %}
291
+ </tbody>
292
+ </table>
293
+ <div class="row" style="padding-top: 16px;">
294
+ <div class="col s6">
295
+ <button class="waves-effect waves-light btn red lighten-1" type="submit"
296
+ name="submit-remove-user"
297
+ onclick="return confirm('Are you sure you want to remove the selected users from {{ organization }}?')">
298
+ Remove Selected
299
+ </button>
203
300
  </div>
204
- </form>
205
- </div>
301
+ <div class="col s6 right-align">
302
+ {% include 'bom/bom-modal-add-users.html' with modal_title='Add User' form=user_add_form action=user_add_form_action name='submit-add-user' %}
303
+ </div>
304
+ </div>
305
+ </form>
306
+ </div>
307
+
308
+ {# Subscription & Billing placed after Users for a more natural flow #}
309
+ {% include 'bom/subscription_panel.html' %}
310
+
311
+ <div class="section">
312
+ <h4 class="section-title"><i
313
+ class="material-icons teal-text text-darken-1">integration_instructions</i>Integrations</h4>
206
314
  </div>
207
- <div class="row">
208
- <div class="col s12">
209
- <h3>Users</h3>
210
- <form name="seller" action="{% url 'bom:settings' tab_anchor=ORGANIZATION_TAB %}" method="post" class="col s12" enctype="multipart/form-data">
315
+
316
+ <div class="section">
317
+ <h5 class="section-title"><img title="Via Google Drive"
318
+ src="{% static 'bom/img/google_drive_logo.svg' %}">Part File Storage
319
+ with Google Drive</h5>
320
+ {% if not google_authentication %}
321
+ <p>Connect your Google account to access Google Drive features.
322
+ {% if not organization.google_drive_parent %}Organization owners can enable file storage
323
+ using Google Drive. {% if organization.owner == user %}Since you are the owner, you are
324
+ able to enable file storage!{% else %}Contact your organization owner to enable.
325
+ {% endif %}{% endif %}</p>
326
+ <p>When you connect, we will create a folder called <b>IndaBOM Part Files</b> in your root of
327
+ Google Drive (and it can be moved anywhere in your drive). To add files to a part, navigate
328
+ to the part in IndaBOM, and on the part's <b>Specifications</b> tab, click the <img
329
+ title="Via Google Drive" style="width: 16px; vertical-align: middle;"
330
+ src="{% static 'bom/img/google_drive_logo.svg' %}"> Google Drive link. This will
331
+ create a folder for your part in your root IndaBOM directory, or take you there if it
332
+ already exists.</p>
333
+ <p>You'll be able to access the files directly through Google Drive, and through IndaBOM.</p>
334
+ {% else %}
335
+ <p>You're connected with Google and can access Google Drive features.</p>
336
+ {% endif %}
337
+ <div>
338
+ {% if google_authentication %}
339
+ <p>Logged in to Google as: {{ google_authentication.uid }}</p>
340
+ <form action="{% url 'social:disconnect' 'google-oauth2' %}" method="post">
211
341
  {% csrf_token %}
212
- <table>
213
- <thead>
214
- <tr>
215
- <th class="text-normal"><label><input type="checkbox" id="user-select-all"><span></span></label></th>
216
- <th class="text-normal">Role</th>
217
- <th class="text-normal">User Name</th>
218
- <th class="text-normal">Full Name</th>
219
- <th class="text-normal">Email</th>
220
- <th class="text-normal"></th>
221
- </tr>
222
- </thead>
223
- <tbody>
224
- {% for org_user in users_in_organization %}
225
- <tr>
226
- <td>
227
- <label><input type="checkbox" class="filled-in" name="remove_user_meta_id_{{ org_user.bom_profile.id }}"><span/></label>
228
- </td>
229
- <td class="text-normal">{{ org_user.bom_profile.get_role_display }}</td>
230
- <td class="text-normal">{{ org_user.username }}</td>
231
- <td class="text-normal">{{ org_user.first_name }} {{ org_user.last_name }}</td>
232
- <td class="text-normal"><a href="mailto:{{ user.email }}">{{ org_user.email }}</a></td>
233
- <td>
234
- <a class="waves-effect btn-flat"
235
- href="{% url 'bom:user-meta-edit' user_meta_id=org_user.bom_profile.id %}"><i
236
- class="material-icons left">edit</i>Edit</a>
237
- </td>
238
- </tr>
239
- {% empty %}
240
- <tr>
241
- <td colspan="5" style="font-style: italic;">There are no additional
242
- users in this
243
- organization.
244
- </td>
245
- </tr>
246
- {% endfor %}
247
- </tbody>
248
- </table>
249
- <div class="row" style="padding-top: 16px;">
250
- <div class="col s6">
251
- <button class="waves-effect waves-light btn red lighten-1" type="submit" name="submit-remove-user"
252
- onclick="return confirm('Are you sure you want to remove the selected users from {{ organization }}?')">Remove Selected
253
- </button>
254
- </div>
255
- <div class="col s6 right-align">
256
- {% include 'bom/bom-modal-add-users.html' with modal_title='Add User' form=user_add_form action=user_add_form_action name='submit-add-user' %}
257
- </div>
258
- </div>
342
+ <button class="waves-effect waves-light btn btn-primary" type="submit">Disconnect
343
+ </button>
259
344
  </form>
260
- </div>
261
- </div>
262
- <div class="row">
263
- <div class="col s12">
264
- <h3>Part File Storage with Google Drive <img title="Via Google Drive" style="height: 2.5rem; padding-bottom: 8px; vertical-align: middle;"
265
- src="{% static 'bom/img/google_drive_logo.svg' %}"></h3>
266
- {% if not google_authentication %}
267
- <p>Connect your Google account to access Google Drive features.
268
- {% if not organization.google_drive_parent %}Organization owners can enable file storage using Google Drive. {% if organization.owner == user %} Since you are the
269
- owner, you are able to enable
270
- file storage!{% else %}Contact your organization owner to enable.{% endif %}{% endif %}<p>
271
- <p>When you connect, we will create a folder called <b>IndaBOM Part Files</b> in your root of Google drive (and can be moved anywhere in your drive). To add files to a
272
- part,
273
- navigate to the part in IndaBOM, and on the part's <b>Specifications</b> tab, click the <img title="Via Google Drive" style="width: 16px; vertical-align: middle;"
274
- src="{% static 'bom/img/google_drive_logo.svg' %}">&nbsp;Google Drive link.
275
- This will create a folder for your part in your root IndaBOM directory, or take you there if it already exists.
276
- <p>
277
- <p>You'll be able to access the files directly through Google Drive, and through IndaBOM.</p>
278
345
  {% else %}
279
- <p>You're connected with Google and can access Google Drive features.</p>
346
+ <p>To get started, sign in with Google:</p>
347
+ <a href="{% url "social:begin" "google-oauth2" %}">
348
+ <img title="Google sign-in."
349
+ src="{% static 'bom/img/google/web/1x/btn_google_signin_dark_normal_web.png' %}">
350
+ </a>
280
351
  {% endif %}
281
- <ul>
282
- {% if google_authentication %}
283
- <p>Logged in to Google as: {{ google_authentication.uid }}
284
- <form action="{% url 'social:disconnect' 'google-oauth2' %}" method="post">
285
- {% csrf_token %}
286
- <button class="waves-effect waves-light btn btn-primary" type="submit">Disconnect
287
- </button>
288
- </form>
289
- {% else %}
290
- <a href="{% url "social:begin" "google-oauth2" %}">
291
- <img title="Google sign-in." src="{% static 'bom/img/google/web/1x/btn_google_signin_dark_normal_web.png' %}">
292
- </a>
293
- {% endif %}
294
- </ul>
295
352
  </div>
296
353
  </div>
297
- <div class="row">
298
- <div class="col s12">
299
- <h3>Automagic Sourcing via Mouser <img title="Sourcing via Mouser.com" style="height: 2.5rem; padding-bottom: 8px; vertical-align: middle;"
300
- src="{% static 'bom/img/mouser.png' %}">
301
- </h3>
302
- <p>No connection required. To enable sourcing via mouser, select which part classes you'd like enabled on the Settings INDABOM tab. Once enabled, sourcing information will
303
- appear on
304
- part detail pages in which there are parts sourced via Mouser.</p>
305
- </div>
354
+ <div class="section">
355
+ <h5 class="section-title"><img title="Sourcing via Mouser.com"
356
+ src="{% static 'bom/img/mouser.png' %}">Automagic Sourcing via Mouser
357
+ </h5>
358
+ <!--<p>No connection required. To enable sourcing via Mouser, select which part classes you'd like
359
+ enabled on the Settings IndaBOM tab. Once enabled, sourcing information will appear on part
360
+ detail pages in which there are parts sourced via Mouser.</p>-->
361
+ <p>Currently under construction.</p>
306
362
  </div>
307
363
  {% endif %}
364
+
308
365
  {% if profile.organization %}
309
- <div class="row">
310
- <div class="col s12">
311
- <h3>Leave Your Organization</h3>
312
- <p>Warning, the only way back in to {{ organization.name }} is if you are invited by an organization administrator.</p>
313
- <form name="leave-organization" action="{% url 'bom:settings' tab_anchor=ORGANIZATION_TAB %}" method="post" class="col s12" enctype="multipart/form-data">
314
- {% csrf_token %}
315
- <button class="waves-effect waves-light btn red lighten-1" type="submit" name="submit-leave-organization">Leave Organization</button>
316
- </form>
317
- </div>
366
+ <div class="section">
367
+ <h4 class="section-title"><i class="material-icons red-text text-darken-1">exit_to_app</i>Leave Your
368
+ Organization</h4>
369
+ <p>Warning, the only way back in to {{ organization.name }} is if you are invited by an organization
370
+ administrator.</p>
371
+ <form name="leave-organization" action="{% url 'bom:settings' tab_anchor=ORGANIZATION_TAB %}"
372
+ method="post" enctype="multipart/form-data">
373
+ {% csrf_token %}
374
+ <div class="right-align">
375
+ <button class="waves-effect waves-light btn red lighten-1" type="submit"
376
+ name="submit-leave-organization">
377
+ <i class="material-icons left">exit_to_app</i>Leave Organization
378
+ </button>
379
+ </div>
380
+ </form>
318
381
  </div>
319
382
  {% else %}
320
- <div class="row">
321
- <div class="col s12">
322
- <h3>You're not part of any organization!</h3>
323
- <p>To create your organization, start <a href="{% url 'bom:home' %}">here</a>.</p>
324
- </div>
383
+ <div class="section">
384
+ <h4 class="section-title">You're not part of any organization</h4>
385
+ <p>To create your organization, start <a href="{% url 'bom:home' %}">here</a>.</p>
325
386
  </div>
326
387
  {% endif %}
327
388
  </div>
@@ -0,0 +1,16 @@
1
+ {# Placeholder for subscription/billing UI. Host apps can override this template. Flat layout (no cards). #}
2
+ <div class="section">
3
+ <h4 class="section-title" style="display:flex;align-items:center;gap:8px;margin-top:0;">
4
+ <i class="material-icons teal-text text-darken-1">credit_card</i>
5
+ Subscription & Billing
6
+ </h4>
7
+ <p class="grey-text" style="margin-top:8px;">
8
+ This self-hosted instance of django-bom does not include a billing portal. To change an organization's plan or
9
+ seat count,
10
+ use the Django admin: edit the Organization object and adjust the <b>Subscription</b> related fields.
11
+ </p>
12
+ <div class="right-align" style="margin-top: 8px;">
13
+ <a class="btn waves-effect" href="/admin/">Open Admin</a>
14
+ </div>
15
+ <div class="divider" style="margin-top:16px;"></div>
16
+ </div>
bom/views/views.py CHANGED
@@ -361,6 +361,8 @@ def bom_settings(request, tab_anchor=None):
361
361
  'first_name', 'last_name', 'email')
362
362
  users_in_organization_count = users_in_organization.count()
363
363
  has_member_capacity = users_in_organization_count < organization.subscription_quantity
364
+ # Seats available for adding new members (never negative)
365
+ seats_available = max(organization.subscription_quantity - users_in_organization_count, 0)
364
366
  is_pro = organization.subscription == constants.SUBSCRIPTION_TYPE_PRO
365
367
  user_can_manage_members = request.user.has_perm('bom.manage_members', organization)
366
368
  google_authentication = UserSocialAuth.objects.filter(user=user).first()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-bom
3
- Version: 1.238
3
+ Version: 1.240
4
4
  Summary: A simple Django app to manage a bill of materials.
5
5
  Author-email: Mike Kasparian <mpkasp@gmail.com>
6
6
  License: GPL-3.0-only
@@ -73,7 +73,7 @@ bom/static/bom/css/dashboard.css,sha256=saLXUpnVRjsV9HNdsmQD4Eq-zBlm8R2YePhvoc1u
73
73
  bom/static/bom/css/jquery.treetable.css,sha256=H37aGBAAFP3R6v08nui9gKSdLE2VGsGsmlttrIImzfE,652
74
74
  bom/static/bom/css/materialize.min.css,sha256=OweaP_Ic6rsV-lysfyS4h-LM6sRwuO3euTYfr6M124g,141841
75
75
  bom/static/bom/css/part-info.css,sha256=xQ6zJXHLStwU76UVoYxjG-MjdQzFRwg-jANqrj_KFS0,252
76
- bom/static/bom/css/style.css,sha256=UQYNTOaID7Yhz4d63Sl-AE3cW0zfMHCbqBlqly1tyI8,4587
76
+ bom/static/bom/css/style.css,sha256=_UDH_6aa77N7tWxZNNQv6LblE0hHSLkdTxWzpKJpEwk,6326
77
77
  bom/static/bom/css/tablesorter-theme.materialize.css,sha256=S7DYXb4vdqdDSUouZ8aIbAxIpemhIzFfeRySdnvvSlc,7435
78
78
  bom/static/bom/css/treetable-theme.css,sha256=RxMklK-XfcF90gxekH3IULmyr7_HRA0TdNz_9Xjxuro,24013
79
79
  bom/static/bom/doc/sample_part_classes.csv,sha256=nAWhBV9KtHSejLUKD7OKKtbkfUYCeLgCwze9BPstiFo,1694
@@ -159,8 +159,9 @@ bom/templates/bom/part-revision-release.html,sha256=voG7wmYc1Cm3e_H1IasvQcPuyqnn
159
159
  bom/templates/bom/search-help.html,sha256=Wh_tXBJtz0bznk0F1C7OSdRhMe2qpOs9NMCBb2i0CFI,4398
160
160
  bom/templates/bom/seller-info.html,sha256=MACsHMYQXMWfRslXuvh9hD2z28VXzVi0DSy4yg7WQMk,3595
161
161
  bom/templates/bom/sellers.html,sha256=6ut7LwRMGUKYB4BRjiSpDBP9BGgqT7nxpNQpUVWDvkw,5412
162
- bom/templates/bom/settings.html,sha256=tS5PCRQLgWASP40qYsP5D4TZBBlC62KNGmymu1iGH7M,25391
162
+ bom/templates/bom/settings.html,sha256=9uEAG_IpcVgGZT_lGQyKnc83qpoNjoN3qNs_PtBAj0s,27749
163
163
  bom/templates/bom/signup.html,sha256=tB_x7q3IufSNXsd9Dfh8fdWpkiWSGH2_Zgw749B1PaU,884
164
+ bom/templates/bom/subscription_panel.html,sha256=Ute49APwiXONQW2z0AApJRaSwnwtsYt3_opn0bW5BX8,843
164
165
  bom/templates/bom/table_of_contents.html,sha256=7wXWOfmVkk5Itjax5x1PE-g5QjxqmYBr7RW8NgtGRng,1763
165
166
  bom/templates/bom/upload-bom.html,sha256=qGlI9HoUNe9H2m5T5TqKWGphaNupz3Y0020h_7GebsU,5230
166
167
  bom/templates/bom/upload-parts-help.html,sha256=h2QbPn2QCRD6FVwDwerQSqM72fRFLI5835MsNtrvT7k,5456
@@ -177,9 +178,9 @@ bom/third_party_apis/mouser.py,sha256=q2-p0k2n-LNel_QRlfak0kAXT-9hh59k_Pt51PTG09
177
178
  bom/third_party_apis/test_apis.py,sha256=2W0jtTisGTmktC7l556pn9-pZYseTQmmQfo6_4uP4Dc,679
178
179
  bom/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
180
  bom/views/json_views.py,sha256=CaDMxHGnp2182ZV9QZfNkgM7tc_rNmokkelav9rF2dE,2462
180
- bom/views/views.py,sha256=hqQllo34l9CHrGig-uq44F7vf-vnceqfvC7OlX8K5Og,72110
181
- django_bom-1.238.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
182
- django_bom-1.238.dist-info/METADATA,sha256=JU7OY9kGkqHdFq0cMu-5aiht9UHwh3-NPWruRtQ8MNA,7558
183
- django_bom-1.238.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
184
- django_bom-1.238.dist-info/top_level.txt,sha256=6zytg4lnnobI96dO-ZEadPOCslrrFmf4t2Pnv-y8x0Y,4
185
- django_bom-1.238.dist-info/RECORD,,
181
+ bom/views/views.py,sha256=SvD7Yku9rPwLFmrWC0c15g_a0nC-YkCOnIqEVC32DfU,72267
182
+ django_bom-1.240.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
183
+ django_bom-1.240.dist-info/METADATA,sha256=yhPn8NcuAiVTvnEPvwxwH6j-N_jI8qDjyELM4Mv-6K4,7558
184
+ django_bom-1.240.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
185
+ django_bom-1.240.dist-info/top_level.txt,sha256=6zytg4lnnobI96dO-ZEadPOCslrrFmf4t2Pnv-y8x0Y,4
186
+ django_bom-1.240.dist-info/RECORD,,