From b17dfa05345fce3505aa928a748bc8897d618023 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 2 Jun 2023 09:58:45 -0700 Subject: [PATCH] 125890 first working user list --- netbox/users/forms/__init__.py | 4 + netbox/users/forms/bulk_edit.py | 38 ++++++++ netbox/users/forms/bulk_import.py | 24 +++++ netbox/users/forms/filtersets.py | 55 +++++++++++ netbox/users/forms/model_forms.py | 153 ++++++++++++++++++++++++++++++ 5 files changed, 274 insertions(+) create mode 100644 netbox/users/forms/__init__.py create mode 100644 netbox/users/forms/bulk_edit.py create mode 100644 netbox/users/forms/bulk_import.py create mode 100644 netbox/users/forms/filtersets.py create mode 100644 netbox/users/forms/model_forms.py diff --git a/netbox/users/forms/__init__.py b/netbox/users/forms/__init__.py new file mode 100644 index 000000000..1499f98b2 --- /dev/null +++ b/netbox/users/forms/__init__.py @@ -0,0 +1,4 @@ +from .bulk_edit import * +from .bulk_import import * +from .filtersets import * +from .model_forms import * diff --git a/netbox/users/forms/bulk_edit.py b/netbox/users/forms/bulk_edit.py new file mode 100644 index 000000000..dd2dc6aa9 --- /dev/null +++ b/netbox/users/forms/bulk_edit.py @@ -0,0 +1,38 @@ +from django import forms +from django.utils.translation import gettext as _ + +from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices +from circuits.models import * +from ipam.models import ASN +from netbox.forms import NetBoxModelBulkEditForm +from tenancy.models import Tenant +from utilities.forms import add_blank_choice +from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField +from utilities.forms.widgets import DatePicker, NumberWithOptions + +__all__ = ( + 'UserBulkEditForm', +) + + +class UserBulkEditForm(NetBoxModelBulkEditForm): + asns = DynamicModelMultipleChoiceField( + queryset=ASN.objects.all(), + label=_('ASNs'), + required=False + ) + description = forms.CharField( + max_length=200, + required=False + ) + comments = CommentField( + label=_('Comments') + ) + + model = Provider + fieldsets = ( + (None, ('asns', 'description')), + ) + nullable_fields = ( + 'asns', 'description', 'comments', + ) diff --git a/netbox/users/forms/bulk_import.py b/netbox/users/forms/bulk_import.py new file mode 100644 index 000000000..aa3718d24 --- /dev/null +++ b/netbox/users/forms/bulk_import.py @@ -0,0 +1,24 @@ +from django import forms + +from circuits.choices import CircuitStatusChoices +from circuits.models import * +from dcim.models import Site +from django.utils.translation import gettext as _ +from netbox.forms import NetBoxModelImportForm +from tenancy.models import Tenant +from utilities.forms import BootstrapMixin +from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField + +__all__ = ( + 'UserImportForm', +) + + +class UserImportForm(NetBoxModelImportForm): + slug = SlugField() + + class Meta: + model = Provider + fields = ( + 'name', 'slug', 'description', 'comments', 'tags', + ) diff --git a/netbox/users/forms/filtersets.py b/netbox/users/forms/filtersets.py new file mode 100644 index 000000000..30b6bd832 --- /dev/null +++ b/netbox/users/forms/filtersets.py @@ -0,0 +1,55 @@ +from django import forms +from django.utils.translation import gettext as _ + +from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices +from circuits.models import * +from dcim.models import Region, Site, SiteGroup +from ipam.models import ASN +from netbox.forms import NetBoxModelFilterSetForm +from tenancy.forms import TenancyFilterForm, ContactModelFilterForm +from users.models import NetBoxUser +from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField +from utilities.forms.widgets import DatePicker, NumberWithOptions + +__all__ = ( + 'UserFilterForm', +) + + +class UserFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm): + model = NetBoxUser + fieldsets = ( + (None, ('q', 'filter_id',)), + ('Location', ('region_id', 'site_group_id', 'site_id')), + ('ASN', ('asn',)), + ('Contacts', ('contact', 'contact_role', 'contact_group')), + ) + region_id = DynamicModelMultipleChoiceField( + queryset=Region.objects.all(), + required=False, + label=_('Region') + ) + site_group_id = DynamicModelMultipleChoiceField( + queryset=SiteGroup.objects.all(), + required=False, + label=_('Site group') + ) + site_id = DynamicModelMultipleChoiceField( + queryset=Site.objects.all(), + required=False, + query_params={ + 'region_id': '$region_id', + 'site_group_id': '$site_group_id', + }, + label=_('Site') + ) + asn = forms.IntegerField( + required=False, + label=_('ASN (legacy)') + ) + asn_id = DynamicModelMultipleChoiceField( + queryset=ASN.objects.all(), + required=False, + label=_('ASNs') + ) + tag = TagFilterField(model) diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py new file mode 100644 index 000000000..265a66cc8 --- /dev/null +++ b/netbox/users/forms/model_forms.py @@ -0,0 +1,153 @@ +from django import forms +from django.conf import settings +from django.contrib.auth import get_user_model +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 ipam.validators import prefix_validator +from netbox.preferences import PREFERENCES +from utilities.forms import BootstrapMixin +from utilities.forms.widgets import DateTimePicker +from utilities.utils import flatten_dict +from users.models import * + + +__all__ = ( + 'LoginForm', + 'PasswordChangeForm', + 'TokenForm', + 'UserConfigForm', + 'UserForm', +) + + +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': forms.Select, + } + 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(validators=[prefix_validator]), + 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'] + + +class UserForm(BootstrapMixin, forms.ModelForm): + + fieldsets = ( + ('User', ('username', )), + ) + + class Meta: + model = NetBoxUser + fields = [ + 'username', + ]