Repository: saleor/saleor

Issue:
Use enum as permission code
### What I'm trying to achieve
Code transparency and type safety.

### Describe a proposed solution
Return `enum` instead of `string` as permission code.


Discussion:
Blocks #2964
I removed the `blocker` label - let's implement changes in #2964 using strings for permission codes and later we'll refactor them to enums.
Please, how can I locate where the permission codes are?
@the-bionic permissions are obtained via `get_permissions()` helper function (it returns only the ones we are using) 
What needs to be done in this PR:
- [ ] Create PermissionEnum from permissions in `get_permissions()`
- [ ] Use it for `PermissionDisplay` type, and all mutations/types applicable
- [ ] Adjust tests accordingly 
I'm sorry, I missed this. I picked another ticket. When I finish with that and this is still open, I'll pick it up.
Doing this: 
``` python
PermissionEnum = graphene.Enum(
    'PermissionEnum', [(str_to_enum(permission.codename), permission.codename)
                       for permission in get_permissions()])
``` 
during import time crashes the app when testing. 

```django-pytest``` complains because the code is trying to access the database during import time here: ```get_permissions()```

``` python
def get_permissions(permissions=None):
    if permissions is None:
        permissions = MODELS_PERMISSIONS
    codenames = [permission.split('.')[1] for permission in permissions]
    return Permission.objects.filter(codename__in=codenames).prefetch_related(
        'content_type').order_by('codename')
```

``` Failed: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it. ```

What if this was used instead?
``` python
codenames = [permission.split('.')[1] for permission in MODELS_PERMISSIONS]
PermissionEnum = graphene.Enum(
    'PermissionEnum', [(str_to_enum(codename), codename)
                       for codename in codenames)
``` 
to remove db access during import time?

https://github.com/pytest-dev/pytest-django/issues/499
I'm fine with the second option to be honest, permissions are constant and cannot be dynamically changed, so using `MODELS_PERMISSIONS` will do the job 

Resolving patch:
diff --git a/saleor/core/permissions.py b/saleor/core/permissions.py
--- a/saleor/core/permissions.py
+++ b/saleor/core/permissions.py
@@ -13,9 +13,13 @@
     'site.manage_settings']
 
 
+def split_permission_codename(permissions):
+    return [permission.split('.')[1] for permission in permissions]
+
+
 def get_permissions(permissions=None):
     if permissions is None:
         permissions = MODELS_PERMISSIONS
-    codenames = [permission.split('.')[1] for permission in permissions]
+    codenames = split_permission_codename(permissions)
     return Permission.objects.filter(codename__in=codenames).prefetch_related(
         'content_type').order_by('codename')
diff --git a/saleor/graphql/core/types/common.py b/saleor/graphql/core/types/common.py
--- a/saleor/graphql/core/types/common.py
+++ b/saleor/graphql/core/types/common.py
@@ -2,6 +2,8 @@
 
 import graphene
 from graphene_django import DjangoObjectType
+from saleor.core.permissions import MODELS_PERMISSIONS
+from saleor.graphql.core.utils import str_to_enum
 
 from ....core import weight
 from ..connection import CountableConnection
@@ -59,8 +61,14 @@ class LanguageDisplay(graphene.ObjectType):
     language = graphene.String(description='Language.', required=True)
 
 
+PermissionEnum = graphene.Enum(
+    'PermissionEnum', [
+        (str_to_enum(codename.split('.')[1]), codename)
+        for codename in MODELS_PERMISSIONS])
+
+
 class PermissionDisplay(graphene.ObjectType):
-    code = graphene.String(
+    code = PermissionEnum(
         description='Internal code for permission.', required=True)
     name = graphene.String(
         description='Describe action(s) allowed to do by permission.',
diff --git a/saleor/graphql/utils.py b/saleor/graphql/utils.py
--- a/saleor/graphql/utils.py
+++ b/saleor/graphql/utils.py
@@ -4,7 +4,7 @@
 from graphql.error import GraphQLError
 from graphql_relay import from_global_id
 
-from .core.types.common import PermissionDisplay
+from .core.types.common import PermissionDisplay, PermissionEnum
 
 registry = get_global_registry()
 
@@ -93,8 +93,12 @@ def format_permissions_for_display(permissions):
     Keyword arguments:
     permissions - queryset with permissions
     """
-    return [
-        PermissionDisplay(
-            code='.'.join(
-                [permission.content_type.app_label, permission.codename]),
-            name=permission.name) for permission in permissions]
+    formatted_permissions = []
+    for permission in permissions:
+        codename = '.'.join(
+            [permission.content_type.app_label, permission.codename])
+        formatted_permissions.append(
+            PermissionDisplay(
+                code=PermissionEnum.get(codename),
+                name=permission.name))
+    return formatted_permissions


Test patch:
diff --git a/saleor/static/dashboard-next/storybook/__snapshots__/Stories.test.ts.snap b/saleor/static/dashboard-next/storybook/__snapshots__/Stories.test.ts.snap
--- a/saleor/static/dashboard-next/storybook/__snapshots__/Stories.test.ts.snap
+++ b/saleor/static/dashboard-next/storybook/__snapshots__/Stories.test.ts.snap
@@ -41169,7 +41169,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="account.impersonate_users"
+                        name="IMPERSONATE_USERS"
                         type="checkbox"
                       />
                     </span>
@@ -41203,7 +41203,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="discount.manage_discounts"
+                        name="MANAGE_DISCOUNTS"
                         type="checkbox"
                       />
                     </span>
@@ -41237,7 +41237,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="menu.manage_menus"
+                        name="MANAGE_MENUS"
                         type="checkbox"
                       />
                     </span>
@@ -41271,7 +41271,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="order.manage_orders"
+                        name="MANAGE_ORDERS"
                         type="checkbox"
                       />
                     </span>
@@ -41305,7 +41305,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="page.manage_pages"
+                        name="MANAGE_PAGES"
                         type="checkbox"
                       />
                     </span>
@@ -41339,7 +41339,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="product.manage_products"
+                        name="MANAGE_PRODUCTS"
                         type="checkbox"
                       />
                     </span>
@@ -41373,7 +41373,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="site.manage_settings"
+                        name="MANAGE_SETTINGS"
                         type="checkbox"
                       />
                     </span>
@@ -41407,7 +41407,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="shipping.manage_shipping"
+                        name="MANAGE_SHIPPING"
                         type="checkbox"
                       />
                     </span>
@@ -41441,7 +41441,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="account.manage_staff"
+                        name="MANAGE_STAFF"
                         type="checkbox"
                       />
                     </span>
@@ -41475,7 +41475,7 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
                       <input
                         class="MuiSwitchBase-input-id"
                         disabled=""
-                        name="account.manage_users"
+                        name="MANAGE_USERS"
                         type="checkbox"
                       />
                     </span>
@@ -41822,7 +41822,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       </svg>
                       <input
                         class="MuiSwitchBase-input-id"
-                        name="account.impersonate_users"
+                        name="IMPERSONATE_USERS"
                         type="checkbox"
                       />
                     </span>
@@ -41858,7 +41858,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="discount.manage_discounts"
+                        name="MANAGE_DISCOUNTS"
                         type="checkbox"
                       />
                     </span>
@@ -41894,7 +41894,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="menu.manage_menus"
+                        name="MANAGE_MENUS"
                         type="checkbox"
                       />
                     </span>
@@ -41930,7 +41930,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="order.manage_orders"
+                        name="MANAGE_ORDERS"
                         type="checkbox"
                       />
                     </span>
@@ -41966,7 +41966,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="page.manage_pages"
+                        name="MANAGE_PAGES"
                         type="checkbox"
                       />
                     </span>
@@ -42002,7 +42002,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="product.manage_products"
+                        name="MANAGE_PRODUCTS"
                         type="checkbox"
                       />
                     </span>
@@ -42038,7 +42038,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="site.manage_settings"
+                        name="MANAGE_SETTINGS"
                         type="checkbox"
                       />
                     </span>
@@ -42074,7 +42074,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="shipping.manage_shipping"
+                        name="MANAGE_SHIPPING"
                         type="checkbox"
                       />
                     </span>
@@ -42110,7 +42110,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="account.manage_staff"
+                        name="MANAGE_STAFF"
                         type="checkbox"
                       />
                     </span>
@@ -42146,7 +42146,7 @@ exports[`Storyshots Views / Staff / Staff member details not admin 1`] = `
                       <input
                         checked=""
                         class="MuiSwitchBase-input-id"
-                        name="account.manage_users"
+                        name="MANAGE_USERS"
                         type="checkbox"
                       />
                     </span>
diff --git a/tests/api/test_account.py b/tests/api/test_account.py
--- a/tests/api/test_account.py
+++ b/tests/api/test_account.py
@@ -70,7 +70,6 @@ def test_token_create_user_data(
 
     permission = permission_manage_orders
     staff_user.user_permissions.add(permission)
-    code = '.'.join([permission.content_type.name, permission.codename])
     name = permission.name
     user_id = graphene.Node.to_global_id('User', staff_user.id)
 
@@ -81,7 +80,7 @@ def test_token_create_user_data(
     assert token_data['user']['id'] == user_id
     assert token_data['user']['email'] == staff_user.email
     assert token_data['user']['permissions'][0]['name'] == name
-    assert token_data['user']['permissions'][0]['code'] == code
+    assert token_data['user']['permissions'][0]['code'] == 'MANAGE_ORDERS'
 
 
 def test_query_user(staff_api_client, customer_user, permission_manage_users):
@@ -450,7 +449,7 @@ def test_staff_create(
     assert data['user']['isStaff'] == True
     assert data['user']['isActive'] == True
     permissions = data['user']['permissions']
-    assert permissions[0]['code'] == permission_manage_products_codename
+    assert permissions[0]['code'] == 'MANAGE_PRODUCTS'
 
     assert send_password_reset_mock.call_count == 1
     args, kwargs = send_password_reset_mock.call_args
@@ -806,3 +805,4 @@ def test_customer_reset_password(
     content = get_graphql_content(response)
     assert send_password_reset_mock.called
     assert send_password_reset_mock.mock_calls[0][1][1] == customer_user.email
+
diff --git a/tests/api/test_shop.py b/tests/api/test_shop.py
--- a/tests/api/test_shop.py
+++ b/tests/api/test_shop.py
@@ -2,6 +2,7 @@
 from django.conf import settings
 from django_countries import countries
 from saleor.core.permissions import MODELS_PERMISSIONS
+from saleor.graphql.core.utils import str_to_enum
 from saleor.site import AuthenticationBackends
 from saleor.site.models import Site
 from tests.api.utils import get_graphql_content
@@ -132,7 +133,8 @@ def test_query_permissions(staff_api_client, permission_manage_users):
     permissions_codes = {permission.get('code') for permission in permissions}
     assert len(permissions_codes) == len(MODELS_PERMISSIONS)
     for code in permissions_codes:
-        assert code in MODELS_PERMISSIONS
+        assert code in [
+            str_to_enum(code.split('.')[1]) for code in MODELS_PERMISSIONS]
 
 
 def test_query_navigation(user_api_client, site_settings):