mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Introduce UserConfigForm for managing user preferences
This commit is contained in:
parent
5e32c69e0e
commit
70f257b1ea
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
@ -1,7 +1,6 @@
|
|||||||
import { initConnectionToggle } from './connectionToggle';
|
import { initConnectionToggle } from './connectionToggle';
|
||||||
import { initDepthToggle } from './depthToggle';
|
import { initDepthToggle } from './depthToggle';
|
||||||
import { initMoveButtons } from './moveOptions';
|
import { initMoveButtons } from './moveOptions';
|
||||||
import { initPreferenceUpdate } from './preferences';
|
|
||||||
import { initReslug } from './reslug';
|
import { initReslug } from './reslug';
|
||||||
import { initSelectAll } from './selectAll';
|
import { initSelectAll } from './selectAll';
|
||||||
|
|
||||||
@ -11,7 +10,6 @@ export function initButtons(): void {
|
|||||||
initConnectionToggle,
|
initConnectionToggle,
|
||||||
initReslug,
|
initReslug,
|
||||||
initSelectAll,
|
initSelectAll,
|
||||||
initPreferenceUpdate,
|
|
||||||
initMoveButtons,
|
initMoveButtons,
|
||||||
]) {
|
]) {
|
||||||
func();
|
func();
|
||||||
|
@ -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<HTMLFormElement>('preferences-update');
|
|
||||||
if (form !== null) {
|
|
||||||
form.addEventListener('submit', handlePreferenceSave);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +1,27 @@
|
|||||||
{% extends 'users/base.html' %}
|
{% extends 'users/base.html' %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block title %}User Preferences{% endblock %}
|
{% block title %}User Preferences{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post" action="" id="preferences-update">
|
<form method="post" action="" id="preferences-update">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="field-group mb-3">
|
|
||||||
<h5>Color Mode</h5>
|
{% for group, fields in form.Meta.fieldsets %}
|
||||||
<p class="text-muted">Set preferred UI color mode</p>
|
<div class="field-group my-5">
|
||||||
{% with color_mode=preferences|get_key:'ui.colormode'%}
|
<div class="row mb-2">
|
||||||
<div class="form-check form-check-inline">
|
<h5 class="offset-sm-3">{{ group }}</h5>
|
||||||
<input class="form-check-input" type="radio" name="ui.colormode" id="color-mode-preference-dark" value="dark"{% if color_mode == 'dark'%} checked{% endif %}>
|
</div>
|
||||||
<label class="form-check-label" for="color-mode-preference-dark">Dark</label>
|
{% for name in fields %}
|
||||||
|
{% render_field form|getfield:name %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
{% endfor %}
|
||||||
<input class="form-check-input" type="radio" name="ui.colormode" id="color-mode-preference-light" value="light"{% if color_mode == 'light'%} checked{% endif %}>
|
|
||||||
<label class="form-check-label" for="color-mode-preference-light">Light</label>
|
<div class="text-end my-3">
|
||||||
</div>
|
<a class="btn btn-outline-secondary" href="{% url 'user:preferences' %}">Cancel</a>
|
||||||
{% endwith %}
|
<button type="submit" name="_update" class="btn btn-primary">Save </button>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col">
|
|
||||||
<button type="submit" class="btn btn-primary" name="_update">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if preferences %}
|
|
||||||
<div class="field-group mb-3">
|
|
||||||
<h5>Other Preferences</h5>
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><input type="checkbox" class="toggle form-check-input" title="Toggle All"></th>
|
|
||||||
<th>Preference</th>
|
|
||||||
<th>Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for key, value in preferences.items %}
|
|
||||||
<tr>
|
|
||||||
<td class="min-width"><input class="form-check-input" type="checkbox" name="pk" value="{{ key }}"></td>
|
|
||||||
<td><samp>{{ key }}</samp></td>
|
|
||||||
<td><samp>{{ value }}</samp></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<button type="submit" class="btn btn-danger" name="_delete">
|
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Clear Selected
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<h3 class="text-muted text-center">No preferences found</h3>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,7 +2,9 @@ from django import forms
|
|||||||
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
|
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
|
||||||
|
|
||||||
from utilities.forms import BootstrapMixin, DateTimePicker
|
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):
|
class LoginForm(BootstrapMixin, AuthenticationForm):
|
||||||
@ -13,6 +15,68 @@ class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm):
|
|||||||
pass
|
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):
|
class TokenForm(BootstrapMixin, forms.ModelForm):
|
||||||
key = forms.CharField(
|
key = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -19,7 +19,7 @@ from extras.models import ObjectChange
|
|||||||
from extras.tables import ObjectChangeTable
|
from extras.tables import ObjectChangeTable
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from .forms import LoginForm, PasswordChangeForm, TokenForm
|
from .forms import LoginForm, PasswordChangeForm, TokenForm, UserConfigForm
|
||||||
from .models import Token
|
from .models import Token
|
||||||
|
|
||||||
|
|
||||||
@ -137,32 +137,28 @@ class UserConfigView(LoginRequiredMixin, View):
|
|||||||
template_name = 'users/preferences.html'
|
template_name = 'users/preferences.html'
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
userconfig = request.user.config
|
||||||
|
form = UserConfigForm(instance=userconfig)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'preferences': request.user.config.all(),
|
'form': form,
|
||||||
'active_tab': 'preferences',
|
'active_tab': 'preferences',
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
userconfig = request.user.config
|
userconfig = request.user.config
|
||||||
data = userconfig.all()
|
form = UserConfigForm(request.POST, instance=userconfig)
|
||||||
|
|
||||||
# Delete selected preferences
|
if form.is_valid():
|
||||||
if "_delete" in request.POST:
|
form.save()
|
||||||
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)
|
|
||||||
|
|
||||||
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):
|
class ChangePasswordView(LoginRequiredMixin, View):
|
||||||
|
@ -282,7 +282,7 @@ def flatten_dict(d, prefix='', separator='.'):
|
|||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
key = separator.join([prefix, k]) if prefix else k
|
key = separator.join([prefix, k]) if prefix else k
|
||||||
if type(v) is dict:
|
if type(v) is dict:
|
||||||
ret.update(flatten_dict(v, prefix=key))
|
ret.update(flatten_dict(v, prefix=key, separator=separator))
|
||||||
else:
|
else:
|
||||||
ret[key] = v
|
ret[key] = v
|
||||||
return ret
|
return ret
|
||||||
|
Loading…
Reference in New Issue
Block a user