We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Plugins must define app_name in api/urls.py to be included in API root view
### Environment
* Python version: 3.6.9
* NetBox version: 2.9.7

### Steps to Reproduce
I'm actually not certain whether this is better classified as a bug or a lapse in the documentation, but I'm opting to propose it as a bug because I think it can be addressed by a code change.

1. Create/install a plugin which provides at least one REST API endpoint. (Assume the plugin's name is `myplugin`.)
2. Within `api/urls.py`, set `app_name = myplugin-api`
3. Visit `/api/plugins/` in the browser. The plugin should be included.
4. Remove the `app_name` definition from `api/urls.py`.
5. Visit `/api/plugins/` in the browser again. The plugin no longer appears.

### Expected Behavior
Plugin API endpoints should be detected automatically. AFAICT there's no reason a plugin should need to declare `app_name`. (The core apps do this, but only because they're being included dynamically.)

### Observed Behavior
Plugin API endpoints disappear from the list when `app_name` is not declared.

It seems like the need for the `app_name` variable could be obviated within `PluginsAPIRootView._get_plugin_entry()` by changing

```
api_app_name = import_object(f"{plugin}.api.urls.app_name")
```

to

```
api_app_name = f'{app_config.name}-api'
```

This actually raises another point: the full URL name for a plugin API endpoint is currently in the format `plugins-api:myplugin-api:url-name`, which seems a bit unwieldy: `plugins-api:myplugin:url-name` should suffice. However, this would be a breaking change for any plugins which rely on reverse URL resolution to their REST API endpoints.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `netbox/extras/plugins/views.py`
Content:
```
1 from collections import OrderedDict
2 
3 from django.apps import apps
4 from django.conf import settings
5 from django.shortcuts import render
6 from django.urls.exceptions import NoReverseMatch
7 from django.views.generic import View
8 from rest_framework import permissions
9 from rest_framework.response import Response
10 from rest_framework.reverse import reverse
11 from rest_framework.views import APIView
12 
13 from extras.plugins.utils import import_object
14 
15 
16 class InstalledPluginsAdminView(View):
17     """
18     Admin view for listing all installed plugins
19     """
20     def get(self, request):
21         plugins = [apps.get_app_config(plugin) for plugin in settings.PLUGINS]
22         return render(request, 'extras/admin/plugins_list.html', {
23             'plugins': plugins,
24         })
25 
26 
27 class InstalledPluginsAPIView(APIView):
28     """
29     API view for listing all installed plugins
30     """
31     permission_classes = [permissions.IsAdminUser]
32     _ignore_model_permissions = True
33     exclude_from_schema = True
34     swagger_schema = None
35 
36     def get_view_name(self):
37         return "Installed Plugins"
38 
39     @staticmethod
40     def _get_plugin_data(plugin_app_config):
41         return {
42             'name': plugin_app_config.verbose_name,
43             'package': plugin_app_config.name,
44             'author': plugin_app_config.author,
45             'author_email': plugin_app_config.author_email,
46             'description': plugin_app_config.description,
47             'verison': plugin_app_config.version
48         }
49 
50     def get(self, request, format=None):
51         return Response([self._get_plugin_data(apps.get_app_config(plugin)) for plugin in settings.PLUGINS])
52 
53 
54 class PluginsAPIRootView(APIView):
55     _ignore_model_permissions = True
56     exclude_from_schema = True
57     swagger_schema = None
58 
59     def get_view_name(self):
60         return "Plugins"
61 
62     @staticmethod
63     def _get_plugin_entry(plugin, app_config, request, format):
64         # Check if the plugin specifies any API URLs
65         api_app_name = import_object(f"{plugin}.api.urls.app_name")
66         if api_app_name is None:
67             # Plugin does not expose an API
68             return None
69 
70         try:
71             entry = (getattr(app_config, 'base_url', app_config.label), reverse(
72                 f"plugins-api:{api_app_name}:api-root",
73                 request=request,
74                 format=format
75             ))
76         except NoReverseMatch:
77             # The plugin does not include an api-root url
78             entry = None
79 
80         return entry
81 
82     def get(self, request, format=None):
83 
84         entries = []
85         for plugin in settings.PLUGINS:
86             app_config = apps.get_app_config(plugin)
87             entry = self._get_plugin_entry(plugin, app_config, request, format)
88             if entry is not None:
89                 entries.append(entry)
90 
91         return Response(OrderedDict((
92             ('installed-plugins', reverse('plugins-api:plugins-list', request=request, format=format)),
93             *entries
94         )))
95
```
--- 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()

```

diff --git a/netbox/extras/plugins/views.py b/netbox/extras/plugins/views.py
--- a/netbox/extras/plugins/views.py
+++ b/netbox/extras/plugins/views.py
@@ -10,8 +10,6 @@
 from rest_framework.reverse import reverse
 from rest_framework.views import APIView
 
-from extras.plugins.utils import import_object
-
 
 class InstalledPluginsAdminView(View):
     """
@@ -62,11 +60,7 @@
     @staticmethod
     def _get_plugin_entry(plugin, app_config, request, format):
         # Check if the plugin specifies any API URLs
-        api_app_name = import_object(f"{plugin}.api.urls.app_name")
-        if api_app_name is None:
-            # Plugin does not expose an API
-            return None
-
+        api_app_name = f'{app_config.name}-api'
         try:
             entry = (getattr(app_config, 'base_url', app_config.label), reverse(
                 f"plugins-api:{api_app_name}:api-root",

{"golden_diff": "diff --git a/netbox/extras/plugins/views.py b/netbox/extras/plugins/views.py\n--- a/netbox/extras/plugins/views.py\n+++ b/netbox/extras/plugins/views.py\n@@ -10,8 +10,6 @@\n from rest_framework.reverse import reverse\n from rest_framework.views import APIView\n \n-from extras.plugins.utils import import_object\n-\n \n class InstalledPluginsAdminView(View):\n     \"\"\"\n@@ -62,11 +60,7 @@\n     @staticmethod\n     def _get_plugin_entry(plugin, app_config, request, format):\n         # Check if the plugin specifies any API URLs\n-        api_app_name = import_object(f\"{plugin}.api.urls.app_name\")\n-        if api_app_name is None:\n-            # Plugin does not expose an API\n-            return None\n-\n+        api_app_name = f'{app_config.name}-api'\n         try:\n             entry = (getattr(app_config, 'base_url', app_config.label), reverse(\n                 f\"plugins-api:{api_app_name}:api-root\",\n", "issue": "Plugins must define app_name in api/urls.py to be included in API root view\n### Environment\r\n* Python version: 3.6.9\r\n* NetBox version: 2.9.7\r\n\r\n### Steps to Reproduce\r\nI'm actually not certain whether this is better classified as a bug or a lapse in the documentation, but I'm opting to propose it as a bug because I think it can be addressed by a code change.\r\n\r\n1. Create/install a plugin which provides at least one REST API endpoint. (Assume the plugin's name is `myplugin`.)\r\n2. Within `api/urls.py`, set `app_name = myplugin-api`\r\n3. Visit `/api/plugins/` in the browser. The plugin should be included.\r\n4. Remove the `app_name` definition from `api/urls.py`.\r\n5. Visit `/api/plugins/` in the browser again. The plugin no longer appears.\r\n\r\n### Expected Behavior\r\nPlugin API endpoints should be detected automatically. AFAICT there's no reason a plugin should need to declare `app_name`. (The core apps do this, but only because they're being included dynamically.)\r\n\r\n### Observed Behavior\r\nPlugin API endpoints disappear from the list when `app_name` is not declared.\r\n\r\nIt seems like the need for the `app_name` variable could be obviated within `PluginsAPIRootView._get_plugin_entry()` by changing\r\n\r\n```\r\napi_app_name = import_object(f\"{plugin}.api.urls.app_name\")\r\n```\r\n\r\nto\r\n\r\n```\r\napi_app_name = f'{app_config.name}-api'\r\n```\r\n\r\nThis actually raises another point: the full URL name for a plugin API endpoint is currently in the format `plugins-api:myplugin-api:url-name`, which seems a bit unwieldy: `plugins-api:myplugin:url-name` should suffice. However, this would be a breaking change for any plugins which rely on reverse URL resolution to their REST API endpoints.\n", "before_files": [{"content": "from collections import OrderedDict\n\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.shortcuts import render\nfrom django.urls.exceptions import NoReverseMatch\nfrom django.views.generic import View\nfrom rest_framework import permissions\nfrom rest_framework.response import Response\nfrom rest_framework.reverse import reverse\nfrom rest_framework.views import APIView\n\nfrom extras.plugins.utils import import_object\n\n\nclass InstalledPluginsAdminView(View):\n    \"\"\"\n    Admin view for listing all installed plugins\n    \"\"\"\n    def get(self, request):\n        plugins = [apps.get_app_config(plugin) for plugin in settings.PLUGINS]\n        return render(request, 'extras/admin/plugins_list.html', {\n            'plugins': plugins,\n        })\n\n\nclass InstalledPluginsAPIView(APIView):\n    \"\"\"\n    API view for listing all installed plugins\n    \"\"\"\n    permission_classes = [permissions.IsAdminUser]\n    _ignore_model_permissions = True\n    exclude_from_schema = True\n    swagger_schema = None\n\n    def get_view_name(self):\n        return \"Installed Plugins\"\n\n    @staticmethod\n    def _get_plugin_data(plugin_app_config):\n        return {\n            'name': plugin_app_config.verbose_name,\n            'package': plugin_app_config.name,\n            'author': plugin_app_config.author,\n            'author_email': plugin_app_config.author_email,\n            'description': plugin_app_config.description,\n            'verison': plugin_app_config.version\n        }\n\n    def get(self, request, format=None):\n        return Response([self._get_plugin_data(apps.get_app_config(plugin)) for plugin in settings.PLUGINS])\n\n\nclass PluginsAPIRootView(APIView):\n    _ignore_model_permissions = True\n    exclude_from_schema = True\n    swagger_schema = None\n\n    def get_view_name(self):\n        return \"Plugins\"\n\n    @staticmethod\n    def _get_plugin_entry(plugin, app_config, request, format):\n        # Check if the plugin specifies any API URLs\n        api_app_name = import_object(f\"{plugin}.api.urls.app_name\")\n        if api_app_name is None:\n            # Plugin does not expose an API\n            return None\n\n        try:\n            entry = (getattr(app_config, 'base_url', app_config.label), reverse(\n                f\"plugins-api:{api_app_name}:api-root\",\n                request=request,\n                format=format\n            ))\n        except NoReverseMatch:\n            # The plugin does not include an api-root url\n            entry = None\n\n        return entry\n\n    def get(self, request, format=None):\n\n        entries = []\n        for plugin in settings.PLUGINS:\n            app_config = apps.get_app_config(plugin)\n            entry = self._get_plugin_entry(plugin, app_config, request, format)\n            if entry is not None:\n                entries.append(entry)\n\n        return Response(OrderedDict((\n            ('installed-plugins', reverse('plugins-api:plugins-list', request=request, format=format)),\n            *entries\n        )))\n", "path": "netbox/extras/plugins/views.py"}], "after_files": [{"content": "from collections import OrderedDict\n\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.shortcuts import render\nfrom django.urls.exceptions import NoReverseMatch\nfrom django.views.generic import View\nfrom rest_framework import permissions\nfrom rest_framework.response import Response\nfrom rest_framework.reverse import reverse\nfrom rest_framework.views import APIView\n\n\nclass InstalledPluginsAdminView(View):\n    \"\"\"\n    Admin view for listing all installed plugins\n    \"\"\"\n    def get(self, request):\n        plugins = [apps.get_app_config(plugin) for plugin in settings.PLUGINS]\n        return render(request, 'extras/admin/plugins_list.html', {\n            'plugins': plugins,\n        })\n\n\nclass InstalledPluginsAPIView(APIView):\n    \"\"\"\n    API view for listing all installed plugins\n    \"\"\"\n    permission_classes = [permissions.IsAdminUser]\n    _ignore_model_permissions = True\n    exclude_from_schema = True\n    swagger_schema = None\n\n    def get_view_name(self):\n        return \"Installed Plugins\"\n\n    @staticmethod\n    def _get_plugin_data(plugin_app_config):\n        return {\n            'name': plugin_app_config.verbose_name,\n            'package': plugin_app_config.name,\n            'author': plugin_app_config.author,\n            'author_email': plugin_app_config.author_email,\n            'description': plugin_app_config.description,\n            'verison': plugin_app_config.version\n        }\n\n    def get(self, request, format=None):\n        return Response([self._get_plugin_data(apps.get_app_config(plugin)) for plugin in settings.PLUGINS])\n\n\nclass PluginsAPIRootView(APIView):\n    _ignore_model_permissions = True\n    exclude_from_schema = True\n    swagger_schema = None\n\n    def get_view_name(self):\n        return \"Plugins\"\n\n    @staticmethod\n    def _get_plugin_entry(plugin, app_config, request, format):\n        # Check if the plugin specifies any API URLs\n        api_app_name = f'{app_config.name}-api'\n        try:\n            entry = (getattr(app_config, 'base_url', app_config.label), reverse(\n                f\"plugins-api:{api_app_name}:api-root\",\n                request=request,\n                format=format\n            ))\n        except NoReverseMatch:\n            # The plugin does not include an api-root url\n            entry = None\n\n        return entry\n\n    def get(self, request, format=None):\n\n        entries = []\n        for plugin in settings.PLUGINS:\n            app_config = apps.get_app_config(plugin)\n            entry = self._get_plugin_entry(plugin, app_config, request, format)\n            if entry is not None:\n                entries.append(entry)\n\n        return Response(OrderedDict((\n            ('installed-plugins', reverse('plugins-api:plugins-list', request=request, format=format)),\n            *entries\n        )))\n", "path": "netbox/extras/plugins/views.py"}]}