from django import forms from django.conf import settings from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm from django.contrib.postgres.forms import SimpleArrayField from django.utils.html import mark_safe from django.utils.translation import gettext as _ from ipam.formfields import IPNetworkFormField from netbox.preferences import PREFERENCES from utilities.forms import BootstrapMixin, DateTimePicker, StaticSelect from utilities.utils import flatten_dict from .models import Token, UserConfig class LoginForm(BootstrapMixin, AuthenticationForm): pass class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm): pass class UserConfigFormMetaclass(forms.models.ModelFormMetaclass): def __new__(mcs, name, bases, attrs): # Emulate a declared field for each supported user preference preference_fields = {} for field_name, preference in PREFERENCES.items(): description = f'{preference.description}
' if preference.description else '' help_text = f'{description}{field_name}' field_kwargs = { 'label': preference.label, 'choices': preference.choices, 'help_text': mark_safe(help_text), 'coerce': preference.coerce, 'required': False, 'widget': StaticSelect, } preference_fields[field_name] = forms.TypedChoiceField(**field_kwargs) attrs.update(preference_fields) return super().__new__(mcs, name, bases, attrs) class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMetaclass): fieldsets = ( ('User Interface', ( 'pagination.per_page', 'pagination.placement', 'ui.colormode', )), ('Miscellaneous', ( 'data_format', )), ) # List of clearable preferences pk = forms.MultipleChoiceField( choices=[], required=False ) class Meta: model = UserConfig fields = () def __init__(self, *args, instance=None, **kwargs): # Get initial data from UserConfig instance initial_data = flatten_dict(instance.data) kwargs['initial'] = initial_data super().__init__(*args, instance=instance, **kwargs) # Compile clearable preference choices self.fields['pk'].choices = ( (f'tables.{table_name}', '') for table_name in instance.data.get('tables', []) ) def save(self, *args, **kwargs): # Set UserConfig data for pref_name, value in self.cleaned_data.items(): if pref_name == 'pk': continue self.instance.set(pref_name, value, commit=False) # Clear selected preferences for preference in self.cleaned_data['pk']: self.instance.clear(preference) return super().save(*args, **kwargs) @property def plugin_fields(self): return [ name for name in self.fields.keys() if name.startswith('plugins.') ] class TokenForm(BootstrapMixin, forms.ModelForm): key = forms.CharField( required=False, help_text=_("If no key is provided, one will be generated automatically.") ) allowed_ips = SimpleArrayField( base_field=IPNetworkFormField(), required=False, label=_('Allowed IPs'), help_text=_('Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for no restrictions. ' 'Example: 10.1.1.0/24,192.168.10.16/32,2001:db8:1::/64'), ) class Meta: model = Token fields = [ 'key', 'write_enabled', 'expires', 'description', 'allowed_ips', ] widgets = { 'expires': DateTimePicker(), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Omit the key field if token retrieval is not permitted if self.instance.pk and not settings.ALLOW_TOKEN_RETRIEVAL: del self.fields['key']