prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Quick view of what roles are assigned to each group
  ![image](https://user-images.githubusercontent.com/10080325/96441885-fc56d000-1255-11eb-93fd-e9a1d7a16471.png)
  
  As above:
  
  - Next to each group, show a column for each possible role
  - For each cell, show which permissions are used (read / add / modify / delete)
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `InvenTree/users/admin.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 from __future__ import unicode_literals
  3 
  4 from django.utils.translation import ugettext_lazy as _
  5 
  6 from django.contrib import admin, messages
  7 from django import forms
  8 from django.contrib.auth import get_user_model
  9 from django.contrib.admin.widgets import FilteredSelectMultiple
  10 from django.contrib.auth.models import Group
  11 from django.contrib.auth.admin import UserAdmin
  12 from django.utils.safestring import mark_safe
  13 
  14 from users.models import RuleSet
  15 
  16 User = get_user_model()
  17 
  18 
  19 class RuleSetInline(admin.TabularInline):
  20     """
  21     Class for displaying inline RuleSet data in the Group admin page.
  22     """
  23 
  24     model = RuleSet
  25     can_delete = False
  26     verbose_name = 'Ruleset'
  27     verbose_plural_name = 'Rulesets'
  28     fields = ['name'] + [option for option in RuleSet.RULE_OPTIONS]
  29     readonly_fields = ['name']
  30     max_num = len(RuleSet.RULESET_CHOICES)
  31     min_num = 1
  32     extra = 0
  33 
  34 
  35 class InvenTreeGroupAdminForm(forms.ModelForm):
  36     """
  37     Custom admin form for the Group model.
  38 
  39     Adds the ability for editing user membership directly in the group admin page.
  40     """
  41 
  42     class Meta:
  43         model = Group
  44         exclude = []
  45         fields = [
  46             'name',
  47             'users',
  48         ]
  49 
  50     def __init__(self, *args, **kwargs):
  51         super().__init__(*args, **kwargs)
  52 
  53         if self.instance.pk:
  54             # Populate the users field with the current Group users.
  55             self.fields['users'].initial = self.instance.user_set.all()
  56 
  57     # Add the users field.
  58     users = forms.ModelMultipleChoiceField(
  59         queryset=User.objects.all(),
  60         required=False,
  61         widget=FilteredSelectMultiple('users', False),
  62         label=_('Users'),
  63         help_text=_('Select which users are assigned to this group')
  64     )
  65 
  66     def save_m2m(self):
  67         # Add the users to the Group.
  68 
  69         self.instance.user_set.set(self.cleaned_data['users'])
  70 
  71     def save(self, *args, **kwargs):
  72         # Default save
  73         instance = super().save()
  74         # Save many-to-many data
  75         self.save_m2m()
  76         return instance
  77 
  78 
  79 class RoleGroupAdmin(admin.ModelAdmin):
  80     """
  81     Custom admin interface for the Group model
  82     """
  83 
  84     form = InvenTreeGroupAdminForm
  85 
  86     inlines = [
  87         RuleSetInline,
  88     ]
  89 
  90     def get_formsets_with_inlines(self, request, obj=None):
  91         for inline in self.get_inline_instances(request, obj):
  92             # Hide RuleSetInline in the 'Add role' view
  93             if not isinstance(inline, RuleSetInline) or obj is not None:
  94                 yield inline.get_formset(request, obj), inline
  95 
  96     filter_horizontal = ['permissions']
  97 
  98     def save_model(self, request, obj, form, change):
  99         """
  100             This method serves two purposes:
  101             - show warning message whenever the group users belong to multiple groups
  102             - skip saving of the group instance model as inlines needs to be saved before.
  103         """
  104 
  105         # Get form cleaned data
  106         users = form.cleaned_data['users']
  107 
  108         # Check for users who are members of multiple groups
  109         warning_message = ''
  110         for user in users:
  111             if user.groups.all().count() > 1:
  112                 warning_message += f'<br>- <b>{user.username}</b> is member of: '
  113                 for idx, group in enumerate(user.groups.all()):
  114                     warning_message += f'<b>{group.name}</b>'
  115                     if idx < len(user.groups.all()) - 1:
  116                         warning_message += ', '
  117 
  118         # If any, display warning message when group is saved
  119         if warning_message:
  120             warning_message = mark_safe(_(f'The following users are members of multiple groups:'
  121                                           f'{warning_message}'))
  122             messages.add_message(request, messages.WARNING, warning_message)
  123 
  124     def save_formset(self, request, form, formset, change):
  125         # Save inline Rulesets
  126         formset.save()
  127         # Save Group instance and update permissions
  128         form.instance.save(update_fields=['name'])
  129 
  130 
  131 class InvenTreeUserAdmin(UserAdmin):
  132     """
  133     Custom admin page for the User model.
  134 
  135     Hides the "permissions" view as this is now handled
  136     entirely by groups and RuleSets.
  137 
  138     (And it's confusing!)
  139     """
  140 
  141     fieldsets = (
  142         (None, {'fields': ('username', 'password')}),
  143         (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
  144         (_('Permissions'), {
  145             'fields': ('is_active', 'is_staff', 'is_superuser', 'groups'),
  146         }),
  147         (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
  148     )
  149 
  150 
  151 admin.site.unregister(Group)
  152 admin.site.register(Group, RoleGroupAdmin)
  153 
  154 admin.site.unregister(User)
  155 admin.site.register(User, InvenTreeUserAdmin)
  156
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py
  --- a/InvenTree/users/admin.py
  +++ b/InvenTree/users/admin.py
  @@ -87,6 +87,64 @@
           RuleSetInline,
       ]
   
  +    list_display = ('name', 'admin', 'part', 'stock', 'build', 'purchase_order', 'sales_order')
  +
  +    def get_rule_set(self, obj, rule_set_type):
  +        ''' Return list of permissions for the given ruleset '''
  +
  +        # Get all rulesets associated to object
  +        rule_sets = RuleSet.objects.filter(group=obj.pk)
  +
  +        # Select ruleset based on type
  +        for rule_set in rule_sets:
  +            if rule_set.name == rule_set_type:
  +                break
  +
  +        def append_permission_level(permission_level, next_level):
  +            if not permission_level:
  +                return next_level
  +
  +            if permission_level[:-1].endswith('|'):
  +                permission_level += next_level
  +            else:
  +                permission_level += ' | ' + next_level
  +
  +            return permission_level
  +
  +        permission_level = ''
  +
  +        if rule_set.can_view:
  +            permission_level = append_permission_level(permission_level, 'V')
  +
  +        if rule_set.can_add:
  +            permission_level = append_permission_level(permission_level, 'A')
  +
  +        if rule_set.can_change:
  +            permission_level = append_permission_level(permission_level, 'C')
  +
  +        if rule_set.can_delete:
  +            permission_level = append_permission_level(permission_level, 'D')
  +        
  +        return permission_level
  +
  +    def admin(self, obj):
  +        return self.get_rule_set(obj, 'admin')
  +
  +    def part(self, obj):
  +        return self.get_rule_set(obj, 'part')
  +
  +    def stock(self, obj):
  +        return self.get_rule_set(obj, 'stock')
  +
  +    def build(self, obj):
  +        return self.get_rule_set(obj, 'build')
  +
  +    def purchase_order(self, obj):
  +        return self.get_rule_set(obj, 'purchase_order')
  +
  +    def sales_order(self, obj):
  +        return self.get_rule_set(obj, 'sales_order')
  +
       def get_formsets_with_inlines(self, request, obj=None):
           for inline in self.get_inline_instances(request, obj):
               # Hide RuleSetInline in the 'Add role' view
  
verification_info: {"golden_diff": "diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py\n--- a/InvenTree/users/admin.py\n+++ b/InvenTree/users/admin.py\n@@ -87,6 +87,64 @@\n         RuleSetInline,\n     ]\n \n+    list_display = ('name', 'admin', 'part', 'stock', 'build', 'purchase_order', 'sales_order')\n+\n+    def get_rule_set(self, obj, rule_set_type):\n+        ''' Return list of permissions for the given ruleset '''\n+\n+        # Get all rulesets associated to object\n+        rule_sets = RuleSet.objects.filter(group=obj.pk)\n+\n+        # Select ruleset based on type\n+        for rule_set in rule_sets:\n+            if rule_set.name == rule_set_type:\n+                break\n+\n+        def append_permission_level(permission_level, next_level):\n+            if not permission_level:\n+                return next_level\n+\n+            if permission_level[:-1].endswith('|'):\n+                permission_level += next_level\n+            else:\n+                permission_level += ' | ' + next_level\n+\n+            return permission_level\n+\n+        permission_level = ''\n+\n+        if rule_set.can_view:\n+            permission_level = append_permission_level(permission_level, 'V')\n+\n+        if rule_set.can_add:\n+            permission_level = append_permission_level(permission_level, 'A')\n+\n+        if rule_set.can_change:\n+            permission_level = append_permission_level(permission_level, 'C')\n+\n+        if rule_set.can_delete:\n+            permission_level = append_permission_level(permission_level, 'D')\n+        \n+        return permission_level\n+\n+    def admin(self, obj):\n+        return self.get_rule_set(obj, 'admin')\n+\n+    def part(self, obj):\n+        return self.get_rule_set(obj, 'part')\n+\n+    def stock(self, obj):\n+        return self.get_rule_set(obj, 'stock')\n+\n+    def build(self, obj):\n+        return self.get_rule_set(obj, 'build')\n+\n+    def purchase_order(self, obj):\n+        return self.get_rule_set(obj, 'purchase_order')\n+\n+    def sales_order(self, obj):\n+        return self.get_rule_set(obj, 'sales_order')\n+\n     def get_formsets_with_inlines(self, request, obj=None):\n         for inline in self.get_inline_instances(request, obj):\n             # Hide RuleSetInline in the 'Add role' view\n", "issue": "Quick view of what roles are assigned to each group\n![image](https://user-images.githubusercontent.com/10080325/96441885-fc56d000-1255-11eb-93fd-e9a1d7a16471.png)\r\n\r\nAs above:\r\n\r\n- Next to each group, show a column for each possible role\r\n- For each cell, show which permissions are used (read / add / modify / delete)\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom django.contrib import admin, messages\nfrom django import forms\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.admin.widgets import FilteredSelectMultiple\nfrom django.contrib.auth.models import Group\nfrom django.contrib.auth.admin import UserAdmin\nfrom django.utils.safestring import mark_safe\n\nfrom users.models import RuleSet\n\nUser = get_user_model()\n\n\nclass RuleSetInline(admin.TabularInline):\n    \"\"\"\n    Class for displaying inline RuleSet data in the Group admin page.\n    \"\"\"\n\n    model = RuleSet\n    can_delete = False\n    verbose_name = 'Ruleset'\n    verbose_plural_name = 'Rulesets'\n    fields = ['name'] + [option for option in RuleSet.RULE_OPTIONS]\n    readonly_fields = ['name']\n    max_num = len(RuleSet.RULESET_CHOICES)\n    min_num = 1\n    extra = 0\n\n\nclass InvenTreeGroupAdminForm(forms.ModelForm):\n    \"\"\"\n    Custom admin form for the Group model.\n\n    Adds the ability for editing user membership directly in the group admin page.\n    \"\"\"\n\n    class Meta:\n        model = Group\n        exclude = []\n        fields = [\n            'name',\n            'users',\n        ]\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        if self.instance.pk:\n            # Populate the users field with the current Group users.\n            self.fields['users'].initial = self.instance.user_set.all()\n\n    # Add the users field.\n    users = forms.ModelMultipleChoiceField(\n        queryset=User.objects.all(),\n        required=False,\n        widget=FilteredSelectMultiple('users', False),\n        label=_('Users'),\n        help_text=_('Select which users are assigned to this group')\n    )\n\n    def save_m2m(self):\n        # Add the users to the Group.\n\n        self.instance.user_set.set(self.cleaned_data['users'])\n\n    def save(self, *args, **kwargs):\n        # Default save\n        instance = super().save()\n        # Save many-to-many data\n        self.save_m2m()\n        return instance\n\n\nclass RoleGroupAdmin(admin.ModelAdmin):\n    \"\"\"\n    Custom admin interface for the Group model\n    \"\"\"\n\n    form = InvenTreeGroupAdminForm\n\n    inlines = [\n        RuleSetInline,\n    ]\n\n    def get_formsets_with_inlines(self, request, obj=None):\n        for inline in self.get_inline_instances(request, obj):\n            # Hide RuleSetInline in the 'Add role' view\n            if not isinstance(inline, RuleSetInline) or obj is not None:\n                yield inline.get_formset(request, obj), inline\n\n    filter_horizontal = ['permissions']\n\n    def save_model(self, request, obj, form, change):\n        \"\"\"\n            This method serves two purposes:\n            - show warning message whenever the group users belong to multiple groups\n            - skip saving of the group instance model as inlines needs to be saved before.\n        \"\"\"\n\n        # Get form cleaned data\n        users = form.cleaned_data['users']\n\n        # Check for users who are members of multiple groups\n        warning_message = ''\n        for user in users:\n            if user.groups.all().count() > 1:\n                warning_message += f'<br>- <b>{user.username}</b> is member of: '\n                for idx, group in enumerate(user.groups.all()):\n                    warning_message += f'<b>{group.name}</b>'\n                    if idx < len(user.groups.all()) - 1:\n                        warning_message += ', '\n\n        # If any, display warning message when group is saved\n        if warning_message:\n            warning_message = mark_safe(_(f'The following users are members of multiple groups:'\n                                          f'{warning_message}'))\n            messages.add_message(request, messages.WARNING, warning_message)\n\n    def save_formset(self, request, form, formset, change):\n        # Save inline Rulesets\n        formset.save()\n        # Save Group instance and update permissions\n        form.instance.save(update_fields=['name'])\n\n\nclass InvenTreeUserAdmin(UserAdmin):\n    \"\"\"\n    Custom admin page for the User model.\n\n    Hides the \"permissions\" view as this is now handled\n    entirely by groups and RuleSets.\n\n    (And it's confusing!)\n    \"\"\"\n\n    fieldsets = (\n        (None, {'fields': ('username', 'password')}),\n        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),\n        (_('Permissions'), {\n            'fields': ('is_active', 'is_staff', 'is_superuser', 'groups'),\n        }),\n        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),\n    )\n\n\nadmin.site.unregister(Group)\nadmin.site.register(Group, RoleGroupAdmin)\n\nadmin.site.unregister(User)\nadmin.site.register(User, InvenTreeUserAdmin)\n", "path": "InvenTree/users/admin.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals\n\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom django.contrib import admin, messages\nfrom django import forms\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.admin.widgets import FilteredSelectMultiple\nfrom django.contrib.auth.models import Group\nfrom django.contrib.auth.admin import UserAdmin\nfrom django.utils.safestring import mark_safe\n\nfrom users.models import RuleSet\n\nUser = get_user_model()\n\n\nclass RuleSetInline(admin.TabularInline):\n    \"\"\"\n    Class for displaying inline RuleSet data in the Group admin page.\n    \"\"\"\n\n    model = RuleSet\n    can_delete = False\n    verbose_name = 'Ruleset'\n    verbose_plural_name = 'Rulesets'\n    fields = ['name'] + [option for option in RuleSet.RULE_OPTIONS]\n    readonly_fields = ['name']\n    max_num = len(RuleSet.RULESET_CHOICES)\n    min_num = 1\n    extra = 0\n\n\nclass InvenTreeGroupAdminForm(forms.ModelForm):\n    \"\"\"\n    Custom admin form for the Group model.\n\n    Adds the ability for editing user membership directly in the group admin page.\n    \"\"\"\n\n    class Meta:\n        model = Group\n        exclude = []\n        fields = [\n            'name',\n            'users',\n        ]\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        if self.instance.pk:\n            # Populate the users field with the current Group users.\n            self.fields['users'].initial = self.instance.user_set.all()\n\n    # Add the users field.\n    users = forms.ModelMultipleChoiceField(\n        queryset=User.objects.all(),\n        required=False,\n        widget=FilteredSelectMultiple('users', False),\n        label=_('Users'),\n        help_text=_('Select which users are assigned to this group')\n    )\n\n    def save_m2m(self):\n        # Add the users to the Group.\n\n        self.instance.user_set.set(self.cleaned_data['users'])\n\n    def save(self, *args, **kwargs):\n        # Default save\n        instance = super().save()\n        # Save many-to-many data\n        self.save_m2m()\n        return instance\n\n\nclass RoleGroupAdmin(admin.ModelAdmin):\n    \"\"\"\n    Custom admin interface for the Group model\n    \"\"\"\n\n    form = InvenTreeGroupAdminForm\n\n    inlines = [\n        RuleSetInline,\n    ]\n\n    list_display = ('name', 'admin', 'part', 'stock', 'build', 'purchase_order', 'sales_order')\n\n    def get_rule_set(self, obj, rule_set_type):\n        ''' Return list of permissions for the given ruleset '''\n\n        # Get all rulesets associated to object\n        rule_sets = RuleSet.objects.filter(group=obj.pk)\n\n        # Select ruleset based on type\n        for rule_set in rule_sets:\n            if rule_set.name == rule_set_type:\n                break\n\n        def append_permission_level(permission_level, next_level):\n            if not permission_level:\n                return next_level\n\n            if permission_level[:-1].endswith('|'):\n                permission_level += next_level\n            else:\n                permission_level += ' | ' + next_level\n\n            return permission_level\n\n        permission_level = ''\n\n        if rule_set.can_view:\n            permission_level = append_permission_level(permission_level, 'V')\n\n        if rule_set.can_add:\n            permission_level = append_permission_level(permission_level, 'A')\n\n        if rule_set.can_change:\n            permission_level = append_permission_level(permission_level, 'C')\n\n        if rule_set.can_delete:\n            permission_level = append_permission_level(permission_level, 'D')\n        \n        return permission_level\n\n    def admin(self, obj):\n        return self.get_rule_set(obj, 'admin')\n\n    def part(self, obj):\n        return self.get_rule_set(obj, 'part')\n\n    def stock(self, obj):\n        return self.get_rule_set(obj, 'stock')\n\n    def build(self, obj):\n        return self.get_rule_set(obj, 'build')\n\n    def purchase_order(self, obj):\n        return self.get_rule_set(obj, 'purchase_order')\n\n    def sales_order(self, obj):\n        return self.get_rule_set(obj, 'sales_order')\n\n    def get_formsets_with_inlines(self, request, obj=None):\n        for inline in self.get_inline_instances(request, obj):\n            # Hide RuleSetInline in the 'Add role' view\n            if not isinstance(inline, RuleSetInline) or obj is not None:\n                yield inline.get_formset(request, obj), inline\n\n    filter_horizontal = ['permissions']\n\n    def save_model(self, request, obj, form, change):\n        \"\"\"\n            This method serves two purposes:\n            - show warning message whenever the group users belong to multiple groups\n            - skip saving of the group instance model as inlines needs to be saved before.\n        \"\"\"\n\n        # Get form cleaned data\n        users = form.cleaned_data['users']\n\n        # Check for users who are members of multiple groups\n        warning_message = ''\n        for user in users:\n            if user.groups.all().count() > 1:\n                warning_message += f'<br>- <b>{user.username}</b> is member of: '\n                for idx, group in enumerate(user.groups.all()):\n                    warning_message += f'<b>{group.name}</b>'\n                    if idx < len(user.groups.all()) - 1:\n                        warning_message += ', '\n\n        # If any, display warning message when group is saved\n        if warning_message:\n            warning_message = mark_safe(_(f'The following users are members of multiple groups:'\n                                          f'{warning_message}'))\n            messages.add_message(request, messages.WARNING, warning_message)\n\n    def save_formset(self, request, form, formset, change):\n        # Save inline Rulesets\n        formset.save()\n        # Save Group instance and update permissions\n        form.instance.save(update_fields=['name'])\n\n\nclass InvenTreeUserAdmin(UserAdmin):\n    \"\"\"\n    Custom admin page for the User model.\n\n    Hides the \"permissions\" view as this is now handled\n    entirely by groups and RuleSets.\n\n    (And it's confusing!)\n    \"\"\"\n\n    fieldsets = (\n        (None, {'fields': ('username', 'password')}),\n        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),\n        (_('Permissions'), {\n            'fields': ('is_active', 'is_staff', 'is_superuser', 'groups'),\n        }),\n        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),\n    )\n\n\nadmin.site.unregister(Group)\nadmin.site.register(Group, RoleGroupAdmin)\n\nadmin.site.unregister(User)\nadmin.site.register(User, InvenTreeUserAdmin)\n", "path": "InvenTree/users/admin.py"}]}