django-bom 1.240__py3-none-any.whl → 1.252__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 +19 -13
- bom/auth_backends.py +3 -1
- bom/forms.py +6 -25
- bom/migrations/0050_alter_organization_options.py +17 -0
- bom/migrations/0051_alter_manufacturer_organization_and_more.py +41 -0
- bom/models.py +62 -19
- bom/settings.py +4 -6
- bom/templates/bom/organization-create.html +6 -3
- bom/templates/bom/settings.html +78 -72
- bom/views/json_views.py +2 -2
- bom/views/views.py +17 -7
- {django_bom-1.240.dist-info → django_bom-1.252.dist-info}/METADATA +1 -1
- {django_bom-1.240.dist-info → django_bom-1.252.dist-info}/RECORD +16 -14
- {django_bom-1.240.dist-info → django_bom-1.252.dist-info}/WHEEL +0 -0
- {django_bom-1.240.dist-info → django_bom-1.252.dist-info}/licenses/LICENSE +0 -0
- {django_bom-1.240.dist-info → django_bom-1.252.dist-info}/top_level.txt +0 -0
bom/admin.py
CHANGED
|
@@ -6,29 +6,27 @@ from .models import (
|
|
|
6
6
|
Assembly,
|
|
7
7
|
Manufacturer,
|
|
8
8
|
ManufacturerPart,
|
|
9
|
-
Organization,
|
|
10
9
|
Part,
|
|
11
10
|
PartClass,
|
|
12
11
|
PartRevision,
|
|
13
12
|
Seller,
|
|
14
13
|
SellerPart,
|
|
15
14
|
Subpart,
|
|
16
|
-
|
|
15
|
+
get_organization_model,
|
|
16
|
+
get_user_meta_model
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
User = get_user_model()
|
|
20
|
+
UserMeta = get_user_meta_model()
|
|
21
|
+
Organization = get_organization_model()
|
|
21
22
|
|
|
22
23
|
class UserMetaInline(admin.TabularInline):
|
|
23
24
|
model = UserMeta
|
|
25
|
+
verbose_name = 'BOM User Meta'
|
|
24
26
|
raw_id_fields = ('organization',)
|
|
25
27
|
can_delete = False
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
class UserAdmin(UserAdmin):
|
|
29
|
-
inlines = (UserMetaInline,)
|
|
30
|
-
|
|
31
|
-
|
|
32
30
|
class OrganizationAdmin(admin.ModelAdmin):
|
|
33
31
|
list_display = ('name',)
|
|
34
32
|
|
|
@@ -142,13 +140,21 @@ class AssemblyAdmin(admin.ModelAdmin):
|
|
|
142
140
|
]
|
|
143
141
|
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
143
|
+
current_admin = admin.site._registry.get(User)
|
|
144
|
+
|
|
145
|
+
if current_admin:
|
|
146
|
+
admin_class = current_admin.__class__
|
|
147
|
+
inlines = list(admin_class.inlines or [])
|
|
148
|
+
if UserMetaInline not in inlines:
|
|
149
|
+
inlines.append(UserMetaInline)
|
|
150
|
+
admin_class.inlines = inlines
|
|
151
|
+
else:
|
|
152
|
+
class BomUserAdmin(UserAdmin):
|
|
153
|
+
inlines = [UserMetaInline]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
admin.site.register(User, BomUserAdmin)
|
|
150
157
|
|
|
151
|
-
admin.site.register(User, UserAdmin)
|
|
152
158
|
admin.site.register(Organization, OrganizationAdmin)
|
|
153
159
|
admin.site.register(Seller, SellerAdmin)
|
|
154
160
|
admin.site.register(SellerPart, SellerPartAdmin)
|
bom/auth_backends.py
CHANGED
bom/forms.py
CHANGED
|
@@ -1,41 +1,27 @@
|
|
|
1
1
|
import codecs
|
|
2
2
|
import csv
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Type, TypeVar
|
|
5
4
|
|
|
6
5
|
from django import forms
|
|
7
6
|
from django.contrib.auth.forms import UserCreationForm
|
|
8
7
|
from django.core.exceptions import ValidationError
|
|
9
|
-
from django.core.validators import MaxLengthValidator,
|
|
8
|
+
from django.core.validators import MaxLengthValidator, MinLengthValidator
|
|
10
9
|
from django.db import IntegrityError
|
|
11
10
|
from django.forms.models import model_to_dict
|
|
12
11
|
from django.utils.translation import gettext_lazy as _
|
|
13
|
-
|
|
14
12
|
from djmoney.money import Money
|
|
15
13
|
|
|
16
14
|
from .constants import (
|
|
17
|
-
CONFIGURATION_TYPES,
|
|
18
15
|
CURRENT_UNITS,
|
|
19
16
|
DISTANCE_UNITS,
|
|
20
17
|
FREQUENCY_UNITS,
|
|
21
18
|
INTERFACE_TYPES,
|
|
22
19
|
MEMORY_UNITS,
|
|
23
|
-
NUMBER_CLASS_CODE_LEN_DEFAULT,
|
|
24
|
-
NUMBER_CLASS_CODE_LEN_MAX,
|
|
25
|
-
NUMBER_CLASS_CODE_LEN_MIN,
|
|
26
|
-
NUMBER_ITEM_LEN_DEFAULT,
|
|
27
|
-
NUMBER_ITEM_LEN_MAX,
|
|
28
|
-
NUMBER_ITEM_LEN_MIN,
|
|
29
20
|
NUMBER_SCHEME_INTELLIGENT,
|
|
30
21
|
NUMBER_SCHEME_SEMI_INTELLIGENT,
|
|
31
|
-
NUMBER_VARIATION_LEN_DEFAULT,
|
|
32
|
-
NUMBER_VARIATION_LEN_MAX,
|
|
33
|
-
NUMBER_VARIATION_LEN_MIN,
|
|
34
22
|
PACKAGE_TYPES,
|
|
35
23
|
POWER_UNITS,
|
|
36
24
|
ROLE_TYPE_VIEWER,
|
|
37
|
-
ROLE_TYPES,
|
|
38
|
-
SUBSCRIPTION_TYPES,
|
|
39
25
|
TEMPERATURE_UNITS,
|
|
40
26
|
VALUE_UNITS,
|
|
41
27
|
VOLTAGE_UNITS,
|
|
@@ -43,11 +29,9 @@ from .constants import (
|
|
|
43
29
|
WEIGHT_UNITS,
|
|
44
30
|
)
|
|
45
31
|
from .csv_headers import (
|
|
46
|
-
BOMFlatCSVHeaders,
|
|
47
32
|
BOMIndentedCSVHeaders,
|
|
48
33
|
CSVHeaderError,
|
|
49
34
|
PartClassesCSVHeaders,
|
|
50
|
-
PartsListCSVHeaders,
|
|
51
35
|
)
|
|
52
36
|
from .form_fields import AutocompleteTextInput
|
|
53
37
|
from .models import (
|
|
@@ -55,7 +39,6 @@ from .models import (
|
|
|
55
39
|
AssemblySubparts,
|
|
56
40
|
Manufacturer,
|
|
57
41
|
ManufacturerPart,
|
|
58
|
-
Organization,
|
|
59
42
|
Part,
|
|
60
43
|
PartClass,
|
|
61
44
|
PartRevision,
|
|
@@ -63,20 +46,18 @@ from .models import (
|
|
|
63
46
|
SellerPart,
|
|
64
47
|
Subpart,
|
|
65
48
|
User,
|
|
66
|
-
|
|
49
|
+
get_user_meta_model,
|
|
50
|
+
get_organization_model,
|
|
67
51
|
)
|
|
68
52
|
from .utils import (
|
|
69
|
-
check_references_for_duplicates,
|
|
70
|
-
get_from_dict,
|
|
71
53
|
listify_string,
|
|
72
|
-
prep_for_sorting_nicely,
|
|
73
54
|
stringify_list,
|
|
74
55
|
)
|
|
75
|
-
from .validators import alphanumeric
|
|
76
|
-
|
|
56
|
+
from .validators import alphanumeric
|
|
77
57
|
|
|
78
58
|
logger = logging.getLogger(__name__)
|
|
79
|
-
|
|
59
|
+
Organization = get_organization_model()
|
|
60
|
+
UserMeta = get_user_meta_model()
|
|
80
61
|
|
|
81
62
|
class UserModelChoiceField(forms.ModelChoiceField):
|
|
82
63
|
def label_from_instance(self, user):
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated by Django 5.2.8 on 2025-12-16 18:43
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('bom', '0049_alter_assembly_id_alter_assemblysubparts_id_and_more'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterModelOptions(
|
|
14
|
+
name='organization',
|
|
15
|
+
options={'permissions': (('manage_members', 'Can manage organization members'),)},
|
|
16
|
+
),
|
|
17
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Generated by Django 5.2.8 on 2026-01-04 00:59
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
('bom', '0050_alter_organization_options'),
|
|
12
|
+
migrations.swappable_dependency(settings.BOM_ORGANIZATION_MODEL),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AlterField(
|
|
17
|
+
model_name='manufacturer',
|
|
18
|
+
name='organization',
|
|
19
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL),
|
|
20
|
+
),
|
|
21
|
+
migrations.AlterField(
|
|
22
|
+
model_name='part',
|
|
23
|
+
name='organization',
|
|
24
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL),
|
|
25
|
+
),
|
|
26
|
+
migrations.AlterField(
|
|
27
|
+
model_name='partclass',
|
|
28
|
+
name='organization',
|
|
29
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL),
|
|
30
|
+
),
|
|
31
|
+
migrations.AlterField(
|
|
32
|
+
model_name='seller',
|
|
33
|
+
name='organization',
|
|
34
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL),
|
|
35
|
+
),
|
|
36
|
+
migrations.AlterField(
|
|
37
|
+
model_name='usermeta',
|
|
38
|
+
name='organization',
|
|
39
|
+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL),
|
|
40
|
+
),
|
|
41
|
+
]
|
bom/models.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
|
+
from django.apps import apps
|
|
5
6
|
from django.conf import settings
|
|
6
7
|
from django.contrib.auth import get_user_model
|
|
7
8
|
from django.core.cache import cache
|
|
@@ -23,32 +24,54 @@ logger = logging.getLogger(__name__)
|
|
|
23
24
|
User = get_user_model()
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
def get_user_meta_model():
|
|
28
|
+
from django.apps import apps
|
|
29
|
+
from django.conf import settings
|
|
30
|
+
return apps.get_model(settings.BOM_USER_META_MODEL)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_organization_model():
|
|
34
|
+
from django.apps import apps
|
|
35
|
+
from django.conf import settings
|
|
36
|
+
return apps.get_model(settings.BOM_ORGANIZATION_MODEL)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _user_meta(self, organization=None):
|
|
40
|
+
from django.apps import apps
|
|
41
|
+
from django.conf import settings
|
|
42
|
+
UserMetaModel = apps.get_model(settings.BOM_USER_META_MODEL)
|
|
43
|
+
meta, created = UserMetaModel.objects.get_or_create(
|
|
44
|
+
user=self,
|
|
45
|
+
defaults={'organization': organization}
|
|
46
|
+
)
|
|
47
|
+
return meta
|
|
48
|
+
|
|
49
|
+
|
|
26
50
|
class OrganizationScopedModel(models.Model):
|
|
27
|
-
organization = models.ForeignKey(
|
|
51
|
+
organization = models.ForeignKey(settings.BOM_ORGANIZATION_MODEL, on_delete=models.CASCADE, db_index=True)
|
|
28
52
|
|
|
29
53
|
class Meta:
|
|
30
54
|
abstract = True
|
|
31
55
|
|
|
32
56
|
|
|
33
|
-
class
|
|
57
|
+
class AbstractOrganization(models.Model):
|
|
34
58
|
name = models.CharField(max_length=255, default=None)
|
|
35
|
-
subscription = models.CharField(max_length=1, choices=SUBSCRIPTION_TYPES)
|
|
36
|
-
subscription_quantity = models.IntegerField(default=0)
|
|
37
59
|
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
38
60
|
number_scheme = models.CharField(max_length=1, choices=NUMBER_SCHEMES, default=NUMBER_SCHEME_SEMI_INTELLIGENT)
|
|
39
61
|
number_class_code_len = models.PositiveIntegerField(default=NUMBER_CLASS_CODE_LEN_DEFAULT,
|
|
40
|
-
validators=[MinValueValidator(NUMBER_CLASS_CODE_LEN_MIN),
|
|
62
|
+
validators=[MinValueValidator(NUMBER_CLASS_CODE_LEN_MIN),
|
|
63
|
+
MaxValueValidator(NUMBER_CLASS_CODE_LEN_MAX)])
|
|
41
64
|
number_item_len = models.PositiveIntegerField(default=NUMBER_ITEM_LEN_DEFAULT,
|
|
42
|
-
validators=[MinValueValidator(NUMBER_ITEM_LEN_MIN),
|
|
65
|
+
validators=[MinValueValidator(NUMBER_ITEM_LEN_MIN),
|
|
66
|
+
MaxValueValidator(NUMBER_ITEM_LEN_MAX)])
|
|
43
67
|
number_variation_len = models.PositiveIntegerField(default=NUMBER_VARIATION_LEN_DEFAULT,
|
|
44
|
-
validators=[MinValueValidator(NUMBER_VARIATION_LEN_MIN),
|
|
68
|
+
validators=[MinValueValidator(NUMBER_VARIATION_LEN_MIN),
|
|
69
|
+
MaxValueValidator(NUMBER_VARIATION_LEN_MAX)])
|
|
45
70
|
google_drive_parent = models.CharField(max_length=128, blank=True, default=None, null=True)
|
|
46
71
|
currency = CurrencyField(max_length=3, choices=CURRENCY_CHOICES, default='USD')
|
|
47
72
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
("manage_members", "Can manage organization members"),
|
|
51
|
-
)
|
|
73
|
+
subscription = models.CharField(max_length=1, choices=SUBSCRIPTION_TYPES)
|
|
74
|
+
subscription_quantity = models.IntegerField(default=0)
|
|
52
75
|
|
|
53
76
|
def number_cs(self):
|
|
54
77
|
return "C" * self.number_class_code_len
|
|
@@ -76,13 +99,25 @@ class Organization(models.Model):
|
|
|
76
99
|
return self.owner.email
|
|
77
100
|
|
|
78
101
|
def save(self, *args, **kwargs):
|
|
79
|
-
super(
|
|
80
|
-
SellerPart.objects.filter(seller__organization=self).update(unit_cost_currency=self.currency,
|
|
102
|
+
super(AbstractOrganization, self).save()
|
|
103
|
+
SellerPart.objects.filter(seller__organization=self).update(unit_cost_currency=self.currency,
|
|
104
|
+
nre_cost_currency=self.currency)
|
|
81
105
|
|
|
106
|
+
class Meta:
|
|
107
|
+
abstract = True
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Organization(AbstractOrganization):
|
|
111
|
+
class Meta:
|
|
112
|
+
swappable = 'BOM_ORGANIZATION_MODEL'
|
|
113
|
+
permissions = (
|
|
114
|
+
("manage_members", "Can manage organization members"),
|
|
115
|
+
)
|
|
82
116
|
|
|
83
|
-
|
|
117
|
+
|
|
118
|
+
class AbstractUserMeta(models.Model):
|
|
84
119
|
user = models.OneToOneField(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE)
|
|
85
|
-
organization = models.ForeignKey(
|
|
120
|
+
organization = models.ForeignKey(settings.BOM_ORGANIZATION_MODEL, blank=True, null=True, on_delete=models.CASCADE)
|
|
86
121
|
role = models.CharField(max_length=1, choices=ROLE_TYPES)
|
|
87
122
|
|
|
88
123
|
def get_or_create_organization(self):
|
|
@@ -92,7 +127,9 @@ class UserMeta(models.Model):
|
|
|
92
127
|
else:
|
|
93
128
|
org_name = self.user.first_name + ' ' + self.user.last_name
|
|
94
129
|
|
|
95
|
-
|
|
130
|
+
OrganizationModel = apps.get_model(settings.BOM_ORGANIZATION_MODEL)
|
|
131
|
+
organization, created = OrganizationModel.objects.get_or_create(owner=self.user, defaults={'name': org_name,
|
|
132
|
+
'subscription': 'F'})
|
|
96
133
|
|
|
97
134
|
self.organization = organization
|
|
98
135
|
self.role = 'A'
|
|
@@ -109,10 +146,13 @@ class UserMeta(models.Model):
|
|
|
109
146
|
def is_organization_owner(self) -> bool:
|
|
110
147
|
return self.organization.owner == self.user if self.organization else False
|
|
111
148
|
|
|
112
|
-
|
|
113
|
-
|
|
149
|
+
class Meta:
|
|
150
|
+
abstract = True
|
|
114
151
|
|
|
115
|
-
|
|
152
|
+
|
|
153
|
+
class UserMeta(AbstractUserMeta):
|
|
154
|
+
class Meta:
|
|
155
|
+
swappable = 'BOM_USER_META_MODEL'
|
|
116
156
|
|
|
117
157
|
|
|
118
158
|
class PartClass(OrganizationScopedModel):
|
|
@@ -730,3 +770,6 @@ class SellerPart(models.Model, AsDictModel):
|
|
|
730
770
|
|
|
731
771
|
def __str__(self):
|
|
732
772
|
return u'%s' % (self.manufacturer_part.part.full_part_number() + ' ' + self.seller.name)
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
User.add_to_class('bom_profile', _user_meta)
|
bom/settings.py
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import logging
|
|
2
|
+
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from django.utils.log import DEFAULT_LOGGING
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
# BASE CONFIGURATION & LOCAL SETTINGS
|
|
8
|
-
# --------------------------------------------------------------------------
|
|
5
|
+
from django.utils.log import DEFAULT_LOGGING
|
|
9
6
|
|
|
10
7
|
logger = logging.getLogger(__name__)
|
|
11
8
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
12
9
|
|
|
13
|
-
# Attempt to load local settings (similar pattern to your original, but clean)
|
|
14
10
|
try:
|
|
15
11
|
from .local_settings import *
|
|
16
12
|
except ImportError:
|
|
@@ -27,6 +23,8 @@ BOM_CONFIG_DEFAULT = {
|
|
|
27
23
|
'page_size': 50,
|
|
28
24
|
}
|
|
29
25
|
}
|
|
26
|
+
BOM_ORGANIZATION_MODEL = 'bom.Organization'
|
|
27
|
+
BOM_USER_META_MODEL = 'bom.UserMeta'
|
|
30
28
|
|
|
31
29
|
# Apply custom settings over defaults
|
|
32
30
|
bom_config_new = BOM_CONFIG_DEFAULT.copy()
|
|
@@ -40,9 +40,12 @@
|
|
|
40
40
|
|
|
41
41
|
<div id="join-organization" style="display: none;">
|
|
42
42
|
<h3 class="center">Join an existing Organization</h3>
|
|
43
|
-
<h5 class="center" style="padding-top: 36px;">To join an existing organization, you must provide
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
<h5 class="center" style="padding-top: 36px;">To join an existing organization, you must provide your
|
|
44
|
+
username (not e-mail) to your IndaBOM organization owner. <br><br>Your username is
|
|
45
|
+
<b>{{ user.username }}</b>
|
|
46
|
+
click
|
|
47
|
+
<a href="mailto:?subject=Add me to your IndaBOM Organization&body=Hi,%0D%0A%0D%0APlease add me to your IndaBOM organization. To do so, log in, go to Settings > Organization and add me via my username {{ user.username }}">here</a>
|
|
48
|
+
to send an e-mail to your organization owner with instructions.</h5>
|
|
46
49
|
<br><br>
|
|
47
50
|
<p class="center">Oops! I meant to <a class="modal-trigger" onclick="resetView()">create a new organization</a>.</p>
|
|
48
51
|
</div>
|
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 s12">
|
|
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>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
</ul>
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
<div id="user" class="col s12">
|
|
18
|
+
<div id="user" class="col l8 offset-l2 s12">
|
|
19
19
|
<div class="section">
|
|
20
20
|
<h4 class="section-title"><i class="material-icons teal-text text-darken-1">person</i>User</h4>
|
|
21
21
|
<form name="seller" action="{% url 'bom:settings' tab_anchor=USER_TAB %}" method="post">
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
</div>
|
|
31
31
|
</div>
|
|
32
32
|
<div class="row">
|
|
33
|
-
{{ user_form.first_name|materializecss:'s12
|
|
34
|
-
{{ user_form.last_name|materializecss:'s12
|
|
35
|
-
{{ user_form.email|materializecss:'s12
|
|
33
|
+
{{ user_form.first_name|materializecss:'s12 l6' }}
|
|
34
|
+
{{ user_form.last_name|materializecss:'s12 l6' }}
|
|
35
|
+
{{ user_form.email|materializecss:'s12' }}
|
|
36
36
|
</div>
|
|
37
37
|
<div class="row">
|
|
38
38
|
<div class="col s12 right-align">
|
|
@@ -57,18 +57,17 @@
|
|
|
57
57
|
</div>
|
|
58
58
|
</div>
|
|
59
59
|
|
|
60
|
-
<div id="indabom" class="col s12">
|
|
60
|
+
<div id="indabom" class="col s12 l8 offset-l2">
|
|
61
61
|
{% if profile.role == 'A' %}
|
|
62
62
|
{% if organization.number_scheme == 'S' %}
|
|
63
63
|
<div class="section">
|
|
64
64
|
<h4 class="section-title"><i class="material-icons teal-text text-darken-1">category</i>Part
|
|
65
65
|
Classes</h4>
|
|
66
|
-
<p><a href="{% url 'bom:help' %}#part-numbering" target="_blank">What is a part class?</a></p>
|
|
67
66
|
<form name="seller" action="{% url 'bom:settings' tab_anchor=INDABOM_TAB %}" method="post" enctype="multipart/form-data">
|
|
68
67
|
{% csrf_token %}
|
|
69
68
|
{% if part_classes.count > 0 %}
|
|
70
69
|
<div class="row" style="margin-bottom: 0;">
|
|
71
|
-
<div class="input-field col s8
|
|
70
|
+
<div class="input-field col s8 l4">
|
|
72
71
|
<select name="part-class-action">
|
|
73
72
|
<option value="" disabled selected>Choose your action</option>
|
|
74
73
|
<option value="submit-part-class-enable-mouser">Enable Mouser</option>
|
|
@@ -77,13 +76,13 @@
|
|
|
77
76
|
</select>
|
|
78
77
|
<label>Action</label>
|
|
79
78
|
</div>
|
|
80
|
-
<div class="col s2
|
|
79
|
+
<div class="col s2 l2">
|
|
81
80
|
<div class="input-field">
|
|
82
81
|
<button class="waves-effect waves-light btn btn-primary" type="submit">Go
|
|
83
82
|
</button>
|
|
84
83
|
</div>
|
|
85
84
|
</div>
|
|
86
|
-
<div class="col s2
|
|
85
|
+
<div class="col s2 l6 right-align">
|
|
87
86
|
<div class="input-field">
|
|
88
87
|
<button class="waves-effect btn-flat btn-icon-round tooltipped"
|
|
89
88
|
type="submit" name="submit-part-class-export"
|
|
@@ -130,18 +129,15 @@
|
|
|
130
129
|
</tbody>
|
|
131
130
|
</table>
|
|
132
131
|
{% else %}
|
|
133
|
-
<p>No part classes have been defined yet
|
|
132
|
+
<p>No part classes have been defined yet. <a href="{% url 'bom:help' %}#part-numbering"
|
|
133
|
+
target="_blank">What is a part class?</a>
|
|
134
|
+
</p>
|
|
135
|
+
<p>To get started, add your first part class, or upload some here. To help, here is <a
|
|
136
|
+
href="{% static 'bom/doc/sample_part_classes.csv' %}">a sample
|
|
137
|
+
CSV file</a>.</p>
|
|
134
138
|
{% endif %}
|
|
135
139
|
</form>
|
|
136
|
-
<div class="right-align" style="margin-top:
|
|
137
|
-
{% if part_classes.count == 0 %}
|
|
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>
|
|
144
|
-
{% endif %}
|
|
140
|
+
<div class="right-align" style="margin-top: 16px;">
|
|
145
141
|
{% 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>.' %}
|
|
146
142
|
{% 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' %}
|
|
147
143
|
</div>
|
|
@@ -161,8 +157,8 @@
|
|
|
161
157
|
<b>{{ organization.number_cs }}-{{ organization.number_ns }}
|
|
162
158
|
{% if organization.number_vs %}-{{ organization.number_vs }}{% endif %}</b></p>
|
|
163
159
|
<div class="row">
|
|
164
|
-
{{ organization_number_len_form|materializecss:'s4
|
|
165
|
-
<div class="col s12
|
|
160
|
+
{{ organization_number_len_form|materializecss:'s4 l2' }}
|
|
161
|
+
<div class="col s12 l6 input-field right-align">
|
|
166
162
|
<button class="waves-effect waves-light btn btn-primary" type="submit"
|
|
167
163
|
name="submit-number-item-len"
|
|
168
164
|
onclick="return confirm('Are you sure you want to change the number of digits?')">
|
|
@@ -223,7 +219,7 @@
|
|
|
223
219
|
{% endif %}
|
|
224
220
|
</div>
|
|
225
221
|
|
|
226
|
-
<div id="organization" class="col s12">
|
|
222
|
+
<div id="organization" class="col s12 l8 offset-l2">
|
|
227
223
|
{% if user.bom_profile.role == 'A' %}
|
|
228
224
|
<div class="section">
|
|
229
225
|
<h4 class="section-title"><i class="material-icons teal-text text-darken-1">business</i>Organization
|
|
@@ -232,7 +228,7 @@
|
|
|
232
228
|
enctype="multipart/form-data">
|
|
233
229
|
{% csrf_token %}
|
|
234
230
|
<div class="row">
|
|
235
|
-
{{ organization_form|materializecss:'s12
|
|
231
|
+
{{ organization_form|materializecss:'s12' }}
|
|
236
232
|
</div>
|
|
237
233
|
<div class="row">
|
|
238
234
|
<div class="col s12 right-align">
|
|
@@ -246,6 +242,9 @@
|
|
|
246
242
|
</form>
|
|
247
243
|
</div>
|
|
248
244
|
|
|
245
|
+
{# Subscription & Billing placed after Users for a more natural flow #}
|
|
246
|
+
{% include 'bom/subscription_panel.html' %}
|
|
247
|
+
|
|
249
248
|
<div class="section">
|
|
250
249
|
<h4 class="section-title"><i class="material-icons teal-text text-darken-1">group</i>Users</h4>
|
|
251
250
|
<form name="seller" action="{% url 'bom:settings' tab_anchor=ORGANIZATION_TAB %}" method="post"
|
|
@@ -305,60 +304,67 @@
|
|
|
305
304
|
</form>
|
|
306
305
|
</div>
|
|
307
306
|
|
|
308
|
-
{# Subscription & Billing placed after Users for a more natural flow #}
|
|
309
|
-
{% include 'bom/subscription_panel.html' %}
|
|
310
|
-
|
|
311
307
|
<div class="section">
|
|
312
308
|
<h4 class="section-title"><i
|
|
313
309
|
class="material-icons teal-text text-darken-1">integration_instructions</i>Integrations</h4>
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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">
|
|
341
|
-
{% csrf_token %}
|
|
342
|
-
<button class="waves-effect waves-light btn btn-primary" type="submit">Disconnect
|
|
343
|
-
</button>
|
|
344
|
-
</form>
|
|
310
|
+
<div class="section">
|
|
311
|
+
<h5 class="section-title"><img title="Via Google Drive"
|
|
312
|
+
src="{% static 'bom/img/google_drive_logo.svg' %}">Part File
|
|
313
|
+
Storage
|
|
314
|
+
with Google Drive</h5>
|
|
315
|
+
{% if not google_authentication %}
|
|
316
|
+
<p>Connect your Google account to access Google Drive features.
|
|
317
|
+
{% if not organization.google_drive_parent %}Organization owners can enable file storage
|
|
318
|
+
using Google Drive. {% if organization.owner == user %}Since you are the owner, you
|
|
319
|
+
are
|
|
320
|
+
able to enable file storage!{% else %}Contact your organization owner to enable.
|
|
321
|
+
{% endif %}{% endif %}</p>
|
|
322
|
+
<p>When you connect, we will create a folder called <b>IndaBOM Part Files</b> in your root
|
|
323
|
+
of
|
|
324
|
+
Google Drive (and it can be moved anywhere in your drive). To add files to a part,
|
|
325
|
+
navigate
|
|
326
|
+
to the part in IndaBOM, and on the part's <b>Specifications</b> tab, click the <img
|
|
327
|
+
title="Via Google Drive" style="width: 16px; vertical-align: middle;"
|
|
328
|
+
src="{% static 'bom/img/google_drive_logo.svg' %}"> Google Drive link. This will
|
|
329
|
+
create a folder for your part in your root IndaBOM directory, or take you there if it
|
|
330
|
+
already exists.</p>
|
|
331
|
+
<p>You'll be able to access the files directly through Google Drive, and through
|
|
332
|
+
IndaBOM.</p>
|
|
345
333
|
{% else %}
|
|
346
|
-
<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>
|
|
334
|
+
<p>You're connected with Google and can access Google Drive features.</p>
|
|
351
335
|
{% endif %}
|
|
336
|
+
<div>
|
|
337
|
+
{% if google_authentication %}
|
|
338
|
+
<p>Logged in to Google as: {{ google_authentication.uid }}</p>
|
|
339
|
+
<div class="right-align">
|
|
340
|
+
<form action="{% url 'social:disconnect' 'google-oauth2' %}" method="post">
|
|
341
|
+
{% csrf_token %}
|
|
342
|
+
<button class="waves-effect waves-light btn btn-primary" type="submit">
|
|
343
|
+
Disconnect
|
|
344
|
+
</button>
|
|
345
|
+
</form>
|
|
346
|
+
</div>
|
|
347
|
+
{% else %}
|
|
348
|
+
<p>To get started, sign in with Google:</p>
|
|
349
|
+
<div class="right-align">
|
|
350
|
+
<a href="{% url "social:begin" "google-oauth2" %}">
|
|
351
|
+
<img title="Google sign-in."
|
|
352
|
+
src="{% static 'bom/img/google/web/1x/btn_google_signin_dark_normal_web.png' %}">
|
|
353
|
+
</a>
|
|
354
|
+
</div>
|
|
355
|
+
{% endif %}
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
<div class="section">
|
|
359
|
+
<h5 class="section-title"><img title="Sourcing via Mouser.com"
|
|
360
|
+
src="{% static 'bom/img/mouser.png' %}">Automagic Sourcing via
|
|
361
|
+
Mouser
|
|
362
|
+
</h5>
|
|
363
|
+
<!--<p>No connection required. To enable sourcing via Mouser, select which part classes you'd like
|
|
364
|
+
enabled on the Settings IndaBOM tab. Once enabled, sourcing information will appear on part
|
|
365
|
+
detail pages in which there are parts sourced via Mouser.</p>-->
|
|
366
|
+
<p>Currently under construction.</p>
|
|
352
367
|
</div>
|
|
353
|
-
</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>
|
|
362
368
|
</div>
|
|
363
369
|
{% endif %}
|
|
364
370
|
|
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):
|
bom/views/views.py
CHANGED
|
@@ -69,11 +69,12 @@ from bom.models import (
|
|
|
69
69
|
SellerPart,
|
|
70
70
|
Subpart,
|
|
71
71
|
User,
|
|
72
|
-
|
|
72
|
+
get_user_meta_model
|
|
73
73
|
)
|
|
74
74
|
from bom.utils import check_references_for_duplicates, listify_string, prep_for_sorting_nicely
|
|
75
75
|
|
|
76
76
|
logger = logging.getLogger(__name__)
|
|
77
|
+
UserMeta = get_user_meta_model()
|
|
77
78
|
BOM_LOGIN_URL = getattr(settings, "BOM_LOGIN_URL", None) or settings.LOGIN_URL
|
|
78
79
|
|
|
79
80
|
def form_error_messages(form_errors) -> [str]:
|
|
@@ -357,7 +358,7 @@ def bom_settings(request, tab_anchor=None):
|
|
|
357
358
|
part_classes = PartClass.objects.all().filter(organization=organization)
|
|
358
359
|
|
|
359
360
|
users_in_organization = User.objects.filter(
|
|
360
|
-
id__in=UserMeta.objects.filter(organization=organization).values_list('user', flat=True)).
|
|
361
|
+
id__in=UserMeta.objects.filter(organization=organization).values_list('user', flat=True)).order_by(
|
|
361
362
|
'first_name', 'last_name', 'email')
|
|
362
363
|
users_in_organization_count = users_in_organization.count()
|
|
363
364
|
has_member_capacity = users_in_organization_count < organization.subscription_quantity
|
|
@@ -400,8 +401,13 @@ def bom_settings(request, tab_anchor=None):
|
|
|
400
401
|
added_user_profile = user_add_form.save()
|
|
401
402
|
messages.info(request, f"Added {added_user_profile.user.first_name} {added_user_profile.user.last_name} to your organization.")
|
|
402
403
|
else:
|
|
403
|
-
|
|
404
|
-
|
|
404
|
+
for field, errors in user_add_form.errors.items():
|
|
405
|
+
for error in errors:
|
|
406
|
+
messages.error(request, f"{field.capitalize()}: {error}")
|
|
407
|
+
users_in_organization.all()
|
|
408
|
+
users_in_organization_count = users_in_organization.count()
|
|
409
|
+
has_member_capacity = users_in_organization_count < organization.subscription_quantity
|
|
410
|
+
seats_available = max(organization.subscription_quantity - users_in_organization_count, 0)
|
|
405
411
|
elif 'clear-add-user' in request.POST:
|
|
406
412
|
tab_anchor = ORGANIZATION_TAB
|
|
407
413
|
user_add_form = UserAddForm()
|
|
@@ -421,7 +427,10 @@ def bom_settings(request, tab_anchor=None):
|
|
|
421
427
|
user_meta.save()
|
|
422
428
|
except UserMeta.DoesNotExist:
|
|
423
429
|
messages.error(request, "No user found with given id {}.".format(user_meta_id))
|
|
424
|
-
|
|
430
|
+
users_in_organization.all()
|
|
431
|
+
users_in_organization_count = users_in_organization.count()
|
|
432
|
+
has_member_capacity = users_in_organization_count < organization.subscription_quantity
|
|
433
|
+
seats_available = max(organization.subscription_quantity - users_in_organization_count, 0)
|
|
425
434
|
elif 'submit-edit-organization' in request.POST:
|
|
426
435
|
tab_anchor = ORGANIZATION_TAB
|
|
427
436
|
organization_form = OrganizationFormEditSettings(request.POST, instance=organization, user=user)
|
|
@@ -462,8 +471,7 @@ def bom_settings(request, tab_anchor=None):
|
|
|
462
471
|
tab_anchor = INDABOM_TAB
|
|
463
472
|
part_class_csv_form = PartClassCSVForm(request.POST, request.FILES, organization=organization)
|
|
464
473
|
if part_class_csv_form.is_valid():
|
|
465
|
-
|
|
466
|
-
messages.info(request, success)
|
|
474
|
+
messages.info(request, f'Successfully uploaded {len(part_class_csv_form.successes)} part classes.')
|
|
467
475
|
for warning in part_class_csv_form.warnings:
|
|
468
476
|
messages.warning(request, warning)
|
|
469
477
|
else:
|
|
@@ -522,6 +530,8 @@ def bom_settings(request, tab_anchor=None):
|
|
|
522
530
|
profile.save()
|
|
523
531
|
if users_in_organization == 0:
|
|
524
532
|
organization.delete()
|
|
533
|
+
else:
|
|
534
|
+
messages.warning(request, "No action was taken because no form field was submitted.")
|
|
525
535
|
|
|
526
536
|
user_form = UserForm(instance=user)
|
|
527
537
|
user_add_form = UserAddForm()
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
bom/__init__.py,sha256=HuvSMR9cYQcppTZGD0XjUVUBtHWwWMh1yMQzk_2wTS4,41
|
|
2
|
-
bom/admin.py,sha256=
|
|
2
|
+
bom/admin.py,sha256=3fgD0_e5U76P1UT8CTeWbmqo3KK3PKz8ECz-5a_pXEM,4401
|
|
3
3
|
bom/apps.py,sha256=TJMUTSX1h2genPwCq6SN6g1fhrrSmjEXGg2zQFa_ryM,147
|
|
4
|
-
bom/auth_backends.py,sha256=
|
|
4
|
+
bom/auth_backends.py,sha256=Xj3MCUJg3r9M4v1c8wrxC3lwpE5F8_2_HD5s9M_5Rms,1480
|
|
5
5
|
bom/base_classes.py,sha256=CrWD7wlIkwYb90VoGcVOcw2WoQszRrCje-Re5d_UW1Q,1183
|
|
6
6
|
bom/constants.py,sha256=5CFE0uvKTL91w24rqdAB6EMgpIyw0HhfmmktxF4gCgs,4296
|
|
7
7
|
bom/context_processors.py,sha256=OxMVCqxGtRoHR7aJfTs6xANpxJQzBDUIuNTG1f_Xjoo,312
|
|
8
8
|
bom/csv_headers.py,sha256=1VDJ7aNqYjDhf5_rfWBZqO6wsIS99oD01yXx2nNmIHQ,12620
|
|
9
9
|
bom/decorators.py,sha256=fCK-lxJTBp3LEdZtKMyQg5hRqxQm5RJny9eb7oyjbtE,1233
|
|
10
10
|
bom/form_fields.py,sha256=tLqchl0j8izBCZnm82NMyHpQV6EuGgCjCl5lrnGR2V0,2816
|
|
11
|
-
bom/forms.py,sha256=
|
|
11
|
+
bom/forms.py,sha256=wmfurA8M0cxc72nLPsMo0-m6JOXzXOhFF2MSRXzZnVI,68836
|
|
12
12
|
bom/helpers.py,sha256=ONsDM0agG9sKJWMjN4IRNlWx2HNF7T0CXM-ts0GRiAY,15031
|
|
13
13
|
bom/local_settings.py,sha256=yE4aupIquCWsFms44qoCrRrlIyM3sqpOkiwyj1WLxI8,820
|
|
14
|
-
bom/models.py,sha256=
|
|
14
|
+
bom/models.py,sha256=GWnSNEDP5po_fo9CphwtimZTFkSrbjKCs6zg0g-LKD8,37834
|
|
15
15
|
bom/part_bom.py,sha256=30HYAKAEhtadiM9tk6vgCQnn7gNJeuXbzF5gXvMvKG4,8720
|
|
16
|
-
bom/settings.py,sha256=
|
|
16
|
+
bom/settings.py,sha256=aBg0PHaK9NvNZNmfnPrU4-kp2GQeeAGMB_EVvMoAmJs,8197
|
|
17
17
|
bom/tests.py,sha256=ZqcTUYVXeWjAqzKAV6hp6SKTU0_IOTwIEboTujl7N_M,69905
|
|
18
18
|
bom/urls.py,sha256=sGNKO8BsTO_TDPsqB-c_fqRozaNHOf9WYRaOy-7OLAE,6841
|
|
19
19
|
bom/utils.py,sha256=z_2jACSkRc0hsc0mdR8tOK10KiSDeM0a6rXIpztPDuA,7302
|
|
@@ -68,6 +68,8 @@ bom/migrations/0046_alter_sellerpart_unique_together.py,sha256=KcOwhf5Vh02wDq_Z3
|
|
|
68
68
|
bom/migrations/0047_sellerpart_seller_part_number.py,sha256=QdjdWMNlSyLHB7uSTq_8xhPxnYAXR8yht-d_AsMvJBw,446
|
|
69
69
|
bom/migrations/0048_rename_part_organization_number_class_bom_part_organiz_b333d6_idx_and_more.py,sha256=rZ_mfd_xFu4BglhYxkNuQVqwThZ721kjrlCAn9LkNRo,50969
|
|
70
70
|
bom/migrations/0049_alter_assembly_id_alter_assemblysubparts_id_and_more.py,sha256=l1q5BCNVYWD6Ngf9pGzYq1hQMvvshmdFlm33On63YNc,3247
|
|
71
|
+
bom/migrations/0050_alter_organization_options.py,sha256=n-YGAoUdUxYdh5NY0Zpz2T4CWEOR7tDjSFFk-KZD_tw,432
|
|
72
|
+
bom/migrations/0051_alter_manufacturer_organization_and_more.py,sha256=xjnkZhEgsJDsfK9leBPxxQ2oG_Qf2FdrttTx-lldmjw,1539
|
|
71
73
|
bom/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
74
|
bom/static/bom/css/dashboard.css,sha256=saLXUpnVRjsV9HNdsmQD4Eq-zBlm8R2YePhvoc1uifk,245
|
|
73
75
|
bom/static/bom/css/jquery.treetable.css,sha256=H37aGBAAFP3R6v08nui9gKSdLE2VGsGsmlttrIImzfE,652
|
|
@@ -149,7 +151,7 @@ bom/templates/bom/help.html,sha256=hi16fwtVqeWP3Vb3vOpLSKx0ioB1MODJ8lkh6bczfiM,8
|
|
|
149
151
|
bom/templates/bom/manufacturer-info.html,sha256=jR98qXONqquEeda92djQgmFfmJiC8jbsGgyXuzJY100,3742
|
|
150
152
|
bom/templates/bom/manufacturers.html,sha256=3ZF8Up-IiAvR6CCmrj_92qPPh7vKrVQaEN05KKX2yrA,5550
|
|
151
153
|
bom/templates/bom/nothing-to-see.html,sha256=cRspNAHlSfv7KjQwp34gANhVqQbXzfFpqtRSm6NoV9s,657
|
|
152
|
-
bom/templates/bom/organization-create.html,sha256=
|
|
154
|
+
bom/templates/bom/organization-create.html,sha256=yLrEJH8BlD-W1USk8pYAzTvr-Qw9ZM5rBGYO3n6lCu0,7982
|
|
153
155
|
bom/templates/bom/part-info.html,sha256=lilYygmR8cQhQ5lJeYK6dj-OdIm72s1SerELHqyrPSs,25406
|
|
154
156
|
bom/templates/bom/part-revision-display.html,sha256=t_wwzf910fhBc2vFjqoISnhX4OEr7pkfh8R-RGq_6ac,5509
|
|
155
157
|
bom/templates/bom/part-revision-edit.html,sha256=gOWiRd8Vq0z912_fI9UtBC0yYkcF_lruMQfAWN4kqw0,1624
|
|
@@ -159,7 +161,7 @@ bom/templates/bom/part-revision-release.html,sha256=voG7wmYc1Cm3e_H1IasvQcPuyqnn
|
|
|
159
161
|
bom/templates/bom/search-help.html,sha256=Wh_tXBJtz0bznk0F1C7OSdRhMe2qpOs9NMCBb2i0CFI,4398
|
|
160
162
|
bom/templates/bom/seller-info.html,sha256=MACsHMYQXMWfRslXuvh9hD2z28VXzVi0DSy4yg7WQMk,3595
|
|
161
163
|
bom/templates/bom/sellers.html,sha256=6ut7LwRMGUKYB4BRjiSpDBP9BGgqT7nxpNQpUVWDvkw,5412
|
|
162
|
-
bom/templates/bom/settings.html,sha256=
|
|
164
|
+
bom/templates/bom/settings.html,sha256=az0QXxrCPEDa8XRG7q3ASDe8dIP3G941qKmijrzwczw,28241
|
|
163
165
|
bom/templates/bom/signup.html,sha256=tB_x7q3IufSNXsd9Dfh8fdWpkiWSGH2_Zgw749B1PaU,884
|
|
164
166
|
bom/templates/bom/subscription_panel.html,sha256=Ute49APwiXONQW2z0AApJRaSwnwtsYt3_opn0bW5BX8,843
|
|
165
167
|
bom/templates/bom/table_of_contents.html,sha256=7wXWOfmVkk5Itjax5x1PE-g5QjxqmYBr7RW8NgtGRng,1763
|
|
@@ -177,10 +179,10 @@ bom/third_party_apis/google_drive.py,sha256=8ECE9_8f1KAyNTtkalws-Gl51wxAaTLP40bD
|
|
|
177
179
|
bom/third_party_apis/mouser.py,sha256=q2-p0k2n-LNel_QRlfak0kAXT-9hh59k_Pt51PTG09s,5576
|
|
178
180
|
bom/third_party_apis/test_apis.py,sha256=2W0jtTisGTmktC7l556pn9-pZYseTQmmQfo6_4uP4Dc,679
|
|
179
181
|
bom/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
180
|
-
bom/views/json_views.py,sha256=
|
|
181
|
-
bom/views/views.py,sha256=
|
|
182
|
-
django_bom-1.
|
|
183
|
-
django_bom-1.
|
|
184
|
-
django_bom-1.
|
|
185
|
-
django_bom-1.
|
|
186
|
-
django_bom-1.
|
|
182
|
+
bom/views/json_views.py,sha256=LK3-njLZrILLqZxCuE-_sUEC2z2GBxQFRysX67-h14c,2334
|
|
183
|
+
bom/views/views.py,sha256=IB0pgdQojvuvykopTdpM42m8EMgy8oYJ4Ui01TR30Ys,73146
|
|
184
|
+
django_bom-1.252.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
185
|
+
django_bom-1.252.dist-info/METADATA,sha256=JCrxqDW9rFFK-tSg056gcNzbkcVU89iuahZ1tiKCK7o,7558
|
|
186
|
+
django_bom-1.252.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
187
|
+
django_bom-1.252.dist-info/top_level.txt,sha256=6zytg4lnnobI96dO-ZEadPOCslrrFmf4t2Pnv-y8x0Y,4
|
|
188
|
+
django_bom-1.252.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|