Repository: netbox-community/netbox

Issue:
User Dashboard can become permanently broken when misconfiguring DEFAULT_DASHBOARD
### Deployment Type

Self-hosted

### NetBox Version

v3.7.0

### Python Version

3.9

### Steps to Reproduce

1. User johndoe modifies their dashboard over the GUI
2. Administrator changes DEFAULT_DASHBOARD in configuration.py with a broken configuration (Assigned a wrong value in a bookmarks widget)
3. Bookmark widget looks fine without logging in since it won't load any data
4. After johndoe logs in, they reset their dashboard
5. Site crashes with an Field Error Exception since the dashboard is misconfigured
6. johndoe can't reset their dashboard anymore, even after the error in DEFAULT_DASHBOARD is corrected

### Expected Behavior

When resetting the dashboard, I would expect it to now always follow the changes of DEFAULT_DASHBOARD.

### Observed Behavior

Resetting the dashboard seems to copy the current state of DEFAULT_DASHBOARD. If that state is broken, a user cannot reset it anymore.
Only the main page with the dashboard crashes.


Discussion:
Workaround: I was able to reset the dashboard by logging in with my test user johndoe and appending /extras/dashboard/reset/ to the URL. From there I could reset the dashboard as i normally would.

Resolving patch:
diff --git a/netbox/extras/dashboard/utils.py b/netbox/extras/dashboard/utils.py
--- a/netbox/extras/dashboard/utils.py
+++ b/netbox/extras/dashboard/utils.py
@@ -53,13 +53,13 @@ def get_dashboard(user):
     return dashboard
 
 
-def get_default_dashboard():
+def get_default_dashboard(config=None):
     from extras.models import Dashboard
 
     dashboard = Dashboard()
-    default_config = settings.DEFAULT_DASHBOARD or DEFAULT_DASHBOARD
+    config = config or settings.DEFAULT_DASHBOARD or DEFAULT_DASHBOARD
 
-    for widget in default_config:
+    for widget in config:
         id = str(uuid.uuid4())
         dashboard.layout.append({
             'id': id,
diff --git a/netbox/netbox/views/misc.py b/netbox/netbox/views/misc.py
--- a/netbox/netbox/views/misc.py
+++ b/netbox/netbox/views/misc.py
@@ -2,14 +2,17 @@
 from collections import namedtuple
 
 from django.conf import settings
+from django.contrib import messages
 from django.contrib.contenttypes.models import ContentType
 from django.core.cache import cache
 from django.shortcuts import redirect, render
+from django.utils.translation import gettext_lazy as _
 from django.views.generic import View
 from django_tables2 import RequestConfig
 from packaging import version
 
-from extras.dashboard.utils import get_dashboard
+from extras.constants import DEFAULT_DASHBOARD
+from extras.dashboard.utils import get_dashboard, get_default_dashboard
 from netbox.forms import SearchForm
 from netbox.search import LookupTypes
 from netbox.search.backends import search_backend
@@ -33,7 +36,13 @@ def get(self, request):
             return redirect('login')
 
         # Construct the user's custom dashboard layout
-        dashboard = get_dashboard(request.user).get_layout()
+        try:
+            dashboard = get_dashboard(request.user).get_layout()
+        except Exception:
+            messages.error(request, _(
+                "There was an error loading the dashboard configuration. A default dashboard is in use."
+            ))
+            dashboard = get_default_dashboard(config=DEFAULT_DASHBOARD).get_layout()
 
         # Check whether a new release is available. (Only for staff/superusers.)
         new_release = None


Test patch: