django-bom 1.259__py3-none-any.whl → 1.265__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/auth_backends.py CHANGED
@@ -12,36 +12,46 @@ class OrganizationPermissionBackend:
12
12
 
13
13
  - Uses Django's has_perm(user, perm, obj) to evaluate permissions tied to an Organization.
14
14
  - Superusers are granted all permissions.
15
- - For `bom.manage_members`: User must be owner or admin within the organization.
15
+ - Owners and Admins are granted all permissions within their organization.
16
+ - Viewers are granted view-only permissions within their organization.
16
17
  """
17
18
 
18
19
  def authenticate(self, request, **credentials): # pragma: no cover - not used for auth
19
20
  return None
20
21
 
21
22
  def has_perm(self, user_obj, perm: str, obj: Optional[object] = None):
22
- # Only handle our specific object-level permission. Let other backends process others.
23
23
  if not user_obj or not user_obj.is_authenticated:
24
24
  return False
25
25
 
26
26
  if user_obj.is_superuser:
27
27
  return True
28
28
 
29
- if perm != 'bom.manage_members':
29
+ # Only handle 'bom' app permissions.
30
+ if not perm.startswith('bom.'):
30
31
  return None
31
32
 
32
- if obj is None or not isinstance(obj, Organization):
33
- return False
34
-
35
33
  profile = user_obj.bom_profile()
36
- if not profile or profile.organization_id != obj.id:
34
+ if not profile or not profile.organization:
37
35
  return False
38
36
 
39
- if obj.subscription != constants.SUBSCRIPTION_TYPE_PRO:
40
- return False
37
+ # If an object is provided, ensure it belongs to the user's organization.
38
+ if obj is not None:
39
+ obj_org = None
40
+ if isinstance(obj, Organization):
41
+ obj_org = obj
42
+ elif hasattr(obj, 'organization'):
43
+ obj_org = obj.organization
41
44
 
42
- is_owner = obj.owner_id == user_obj.id
43
- is_admin = getattr(profile, 'role', None) == constants.ROLE_TYPE_ADMIN
44
- if not (is_owner or is_admin):
45
- return False
45
+ if obj_org and obj_org.id != profile.organization_id:
46
+ return False
47
+
48
+ # Owners and Admins can do everything within their organization
49
+ if profile.can_manage_organization():
50
+ return True
51
+
52
+ # Viewers can only view within their organization
53
+ if profile.role == constants.ROLE_TYPE_VIEWER:
54
+ if perm.startswith('bom.view_'):
55
+ return True
46
56
 
47
- return True
57
+ return False
bom/decorators.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from django.contrib import messages
2
- from django.core.exceptions import PermissionDenied
3
2
  from django.http import HttpResponseRedirect
4
3
  from django.urls import reverse
5
4
 
@@ -22,9 +21,10 @@ def google_authenticated(function):
22
21
 
23
22
  def organization_admin(function):
24
23
  def wrap(request, *args, **kwargs):
25
- if request.user.bom_profile().role != 'A':
24
+ organization = request.user.bom_profile().organization
25
+ if not request.user.has_perm('bom.manage_members', organization):
26
26
  messages.error(request, "You don't have permission to perform this action.")
27
- return HttpResponseRedirect(request.META.get('HTTP_REFERER'), reverse('bom:home'))
27
+ return HttpResponseRedirect(request.META.get('HTTP_REFERER') or reverse('bom:home'))
28
28
  return function(request, *args, **kwargs)
29
29
 
30
30
  wrap.__doc__ = function.__doc__
bom/models.py CHANGED
@@ -187,6 +187,9 @@ class AbstractUserMeta(models.Model):
187
187
  def is_organization_owner(self) -> bool:
188
188
  return self.organization.owner == self.user if self.organization else False
189
189
 
190
+ def can_manage_organization(self) -> bool:
191
+ return self.role == ROLE_TYPE_ADMIN or self.is_organization_owner()
192
+
190
193
  class Meta:
191
194
  abstract = True
192
195
 
bom/settings.py CHANGED
@@ -103,6 +103,7 @@ TEMPLATES = [
103
103
 
104
104
  AUTHENTICATION_BACKENDS = (
105
105
  'social_core.backends.google.GoogleOAuth2',
106
+ 'bom.auth_backends.OrganizationPermissionBackend',
106
107
  'django.contrib.auth.backends.ModelBackend',
107
108
  )
108
109
 
@@ -8,38 +8,36 @@ 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;
11
+ p, li {
12
+ font-size: 1.1rem; /* Slightly larger to compensate for thin weight */
18
13
  font-weight: 300;
19
14
  }
20
15
 
16
+ /* We use significant size differences to replace the lack of bold weights */
21
17
  h1 {
22
- font-size: 3.5rem;
18
+ font-size: 2.5rem;
23
19
  font-weight: 300;
20
+ line-height: 1.1;
24
21
  }
25
22
 
26
23
  h2 {
27
- font-size: 3.0rem;
24
+ font-size: 2.0rem;
28
25
  font-weight: 300;
29
26
  }
30
27
 
31
28
  h3 {
32
- font-size: 1.6rem;
29
+ font-size: 1.7rem;
33
30
  font-weight: 300;
34
31
  }
35
32
 
36
33
  h4 {
37
- font-size: 1.3rem;
34
+ font-size: 1.4rem;
38
35
  font-weight: 300;
39
36
  }
40
37
 
41
38
  h5 {
42
39
  font-size: 1.1rem;
40
+ font-weight: 400;
43
41
  }
44
42
 
45
43
  .tabs {
@@ -415,4 +413,70 @@ a.anchor {
415
413
  .container-app .right-align .btn,
416
414
  .container-app .right-align .btn-flat {
417
415
  margin-left: 4px;
416
+ }
417
+
418
+
419
+ /* Master-Detail Settings Layout */
420
+ .settings-grid {
421
+ margin-top: 20px;
422
+ }
423
+
424
+ @media only screen and (min-width: 993px) {
425
+ .settings-grid {
426
+ display: flex;
427
+ flex-wrap: nowrap;
428
+ align-items: stretch;
429
+ }
430
+ }
431
+
432
+ .settings-nav {
433
+ padding: 0 !important;
434
+ border-right: 1px solid #e0e0e0;
435
+ min-height: 70vh;
436
+ }
437
+
438
+ .settings-nav .collection {
439
+ border: none;
440
+ margin: 0;
441
+ }
442
+
443
+ .settings-nav .collection .collection-item {
444
+ border: none;
445
+ padding: 12px 20px;
446
+ display: flex;
447
+ align-items: center;
448
+ color: #5f6368;
449
+ cursor: pointer;
450
+ transition: background-color 0.2s;
451
+ }
452
+
453
+ .settings-nav .collection .collection-item:hover {
454
+ background-color: #f1f3f4;
455
+ }
456
+
457
+ .settings-nav .collection .collection-item.active {
458
+ background-color: rgba(165, 214, 167, 0.5);
459
+ color: #000000;
460
+ font-weight: 500;
461
+ }
462
+
463
+ .settings-nav .collection .collection-item .material-icons {
464
+ margin-right: 16px;
465
+ font-size: 20px;
466
+ }
467
+
468
+ .settings-content {
469
+ padding: 0 40px !important;
470
+ }
471
+
472
+ @media only screen and (max-width: 992px) {
473
+ .settings-nav {
474
+ border-right: none;
475
+ border-bottom: 1px solid #e0e0e0;
476
+ min-height: auto;
477
+ }
478
+
479
+ .settings-content {
480
+ padding: 0 20px !important;
481
+ }
418
482
  }
@@ -17,11 +17,11 @@
17
17
  </div>
18
18
  </div>
19
19
  <div class="modal-footer">
20
- <a href="#!" class="modal-close waves-effect waves-green btn-flat">Cancel</a>
20
+ <button type="button" class="modal-close waves-effect waves-green btn-flat">Cancel</button>
21
21
  {% if form %}
22
22
  <button class="waves-effect waves-light btn btn-primary" type="submit" name="{{ name }}">Submit</button>
23
23
  {% else %}
24
- <a href="#!" class="modal-close waves-effect green lighten-1 waves-green btn-flat">Ok</a>
24
+ <button type="button" class="modal-close waves-effect green lighten-1 waves-green btn-flat">Ok</button>
25
25
  {% endif %}
26
26
  </div>
27
27
  </form>
@@ -28,13 +28,12 @@
28
28
  </div>
29
29
  </div>
30
30
  <div class="modal-footer">
31
- <a href="#!" class="modal-close waves-effect waves-green btn-flat">Cancel</a>
31
+ <button type="button" class="modal-close waves-effect waves-green btn-flat">Cancel</button>
32
+
32
33
  {% if is_pro and user_can_manage_members %}
33
34
  <button class="waves-effect waves-light btn btn-primary" type="submit" name="{{ name }}">Submit</button>
34
- {% elif is_pro and not user_can_manage_members %}
35
- <a href="#!" class="modal-close waves-effect green lighten-1 waves-green btn-flat">Ok</a>
36
35
  {% else %}
37
- <a href="#!" class="modal-close waves-effect green lighten-1 waves-green btn-flat">Ok</a>
36
+ <button type="button" class="modal-close waves-effect green lighten-1 waves-green btn-flat">Ok</button>
38
37
  {% endif %}
39
38
  </div>
40
39
  </form>