diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 95fd99270..740fbe7e7 100644 Binary files a/netbox/project-static/dist/netbox.js and b/netbox/project-static/dist/netbox.js differ diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 6fbe0874b..116aad5e6 100644 Binary files a/netbox/project-static/dist/netbox.js.map and b/netbox/project-static/dist/netbox.js.map differ diff --git a/netbox/project-static/src/buttons/index.ts b/netbox/project-static/src/buttons/index.ts index 251e0feaf..6a9001cd1 100644 --- a/netbox/project-static/src/buttons/index.ts +++ b/netbox/project-static/src/buttons/index.ts @@ -1,7 +1,6 @@ import { initConnectionToggle } from './connectionToggle'; import { initDepthToggle } from './depthToggle'; import { initMoveButtons } from './moveOptions'; -import { initPreferenceUpdate } from './preferences'; import { initReslug } from './reslug'; import { initSelectAll } from './selectAll'; @@ -11,7 +10,6 @@ export function initButtons(): void { initConnectionToggle, initReslug, initSelectAll, - initPreferenceUpdate, initMoveButtons, ]) { func(); diff --git a/netbox/project-static/src/buttons/preferences.ts b/netbox/project-static/src/buttons/preferences.ts deleted file mode 100644 index 6e8b21c02..000000000 --- a/netbox/project-static/src/buttons/preferences.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { setColorMode } from '../colorMode'; -import { getElement } from '../util'; - -/** - * Perform actions in the UI based on the value of user profile updates. - * - * @param event Form Submit - */ -function handlePreferenceSave(event: Event): void { - // Create a FormData instance to access the form values. - const form = event.currentTarget as HTMLFormElement; - const formData = new FormData(form); - - // Update the UI color mode immediately when the user preference changes. - if (formData.get('ui.colormode') === 'dark') { - setColorMode('dark'); - } else if (formData.get('ui.colormode') === 'light') { - setColorMode('light'); - } -} - -/** - * Initialize handlers for user profile updates. - */ -export function initPreferenceUpdate(): void { - const form = getElement('preferences-update'); - if (form !== null) { - form.addEventListener('submit', handlePreferenceSave); - } -} diff --git a/netbox/templates/users/preferences.html b/netbox/templates/users/preferences.html index bbb92bde0..254b5b8ff 100644 --- a/netbox/templates/users/preferences.html +++ b/netbox/templates/users/preferences.html @@ -1,57 +1,27 @@ {% extends 'users/base.html' %} {% load helpers %} +{% load form_helpers %} {% block title %}User Preferences{% endblock %} {% block content %}
{% csrf_token %} -
-
Color Mode
-

Set preferred UI color mode

- {% with color_mode=preferences|get_key:'ui.colormode'%} -
- - + + {% for group, fields in form.Meta.fieldsets %} +
+
+
{{ group }}
+
+ {% for name in fields %} + {% render_field form|getfield:name %} + {% endfor %}
-
- - -
- {% endwith %} + {% endfor %} + +
+ Cancel +
-
-
- -
-
- {% if preferences %} -
-
Other Preferences
- - - - - - - - - - {% for key, value in preferences.items %} - - - - - - {% endfor %} - -
PreferenceValue
{{ key }}{{ value }}
- -
- {% else %} -

No preferences found

- {% endif %} {% endblock %} diff --git a/netbox/users/forms.py b/netbox/users/forms.py index 8bd54cb66..7007ef958 100644 --- a/netbox/users/forms.py +++ b/netbox/users/forms.py @@ -2,7 +2,9 @@ from django import forms from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm from utilities.forms import BootstrapMixin, DateTimePicker -from .models import Token +from utilities.paginator import EnhancedPaginator +from utilities.utils import flatten_dict +from .models import Token, UserConfig class LoginForm(BootstrapMixin, AuthenticationForm): @@ -13,6 +15,68 @@ class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm): pass +def get_page_lengths(): + return [ + (v, str(v)) for v in EnhancedPaginator.default_page_lengths + ] + + +class UserConfigForm(BootstrapMixin, forms.ModelForm): + pagination__per_page = forms.TypedChoiceField( + label='Page length', + coerce=lambda val: int(val), + choices=get_page_lengths, + required=False + ) + ui__colormode = forms.ChoiceField( + label='Color mode', + choices=( + ('light', 'Light'), + ('dark', 'Dark'), + ), + required=False + ) + extras__configcontext__format = forms.ChoiceField( + label='ConfigContext format', + choices=( + ('json', 'JSON'), + ('yaml', 'YAML'), + ), + required=False + ) + + class Meta: + model = UserConfig + fields = () + fieldsets = ( + ('User Interface', ( + 'pagination__per_page', + 'ui__colormode', + )), + ('Miscellaneous', ( + 'extras__configcontext__format', + )), + ) + + def __init__(self, *args, instance=None, **kwargs): + + # Get initial data from UserConfig instance + initial_data = flatten_dict(instance.data, separator='__') + kwargs['initial'] = initial_data + + super().__init__(*args, instance=instance, **kwargs) + + def save(self, *args, **kwargs): + + # Set UserConfig data + for field_name, value in self.cleaned_data.items(): + pref_name = field_name.replace('__', '.') + print(f'{pref_name}: {value}') + self.instance.set(pref_name, value, commit=False) + + return super().save(*args, **kwargs) + + class TokenForm(BootstrapMixin, forms.ModelForm): key = forms.CharField( required=False, diff --git a/netbox/users/views.py b/netbox/users/views.py index ecf3295b5..cd3c34aa9 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -19,7 +19,7 @@ from extras.models import ObjectChange from extras.tables import ObjectChangeTable from netbox.config import get_config from utilities.forms import ConfirmationForm -from .forms import LoginForm, PasswordChangeForm, TokenForm +from .forms import LoginForm, PasswordChangeForm, TokenForm, UserConfigForm from .models import Token @@ -137,32 +137,28 @@ class UserConfigView(LoginRequiredMixin, View): template_name = 'users/preferences.html' def get(self, request): + userconfig = request.user.config + form = UserConfigForm(instance=userconfig) return render(request, self.template_name, { - 'preferences': request.user.config.all(), + 'form': form, 'active_tab': 'preferences', }) def post(self, request): userconfig = request.user.config - data = userconfig.all() + form = UserConfigForm(request.POST, instance=userconfig) - # Delete selected preferences - if "_delete" in request.POST: - for key in request.POST.getlist('pk'): - if key in data: - userconfig.clear(key) - # Update specific values - elif "_update" in request.POST: - for key in request.POST: - if not key.startswith('_') and not key.startswith('csrf'): - for value in request.POST.getlist(key): - userconfig.set(key, value) + if form.is_valid(): + form.save() - userconfig.save() - messages.success(request, "Your preferences have been updated.") + messages.success(request, "Your preferences have been updated.") + return redirect('user:preferences') - return redirect('user:preferences') + return render(request, self.template_name, { + 'form': form, + 'active_tab': 'preferences', + }) class ChangePasswordView(LoginRequiredMixin, View): diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 3234135fb..3fc50ddc4 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -282,7 +282,7 @@ def flatten_dict(d, prefix='', separator='.'): for k, v in d.items(): key = separator.join([prefix, k]) if prefix else k if type(v) is dict: - ret.update(flatten_dict(v, prefix=key)) + ret.update(flatten_dict(v, prefix=key, separator=separator)) else: ret[key] = v return ret