content: import django_filters
  from django import forms
  from django.conf import settings
  from django.forms import BoundField
  
  from utilities.forms import widgets
  from utilities.views import get_action_url
  
  __all__ = (
      'DynamicChoiceField',
      'DynamicModelChoiceField',
      'DynamicModelMultipleChoiceField',
      'DynamicMultipleChoiceField',
  )
  
  
  #
  # Choice fields
  #
  
  class DynamicChoiceField(forms.ChoiceField):
  
      def get_bound_field(self, form, field_name):
          bound_field = BoundField(form, self, field_name)
          data = bound_field.value()
  
          if data is not None:
              self.choices = [
                  choice for choice in self.choices if choice[0] == data
              ]
          else:
              self.choices = []
  
          return bound_field
  
  
  class DynamicMultipleChoiceField(forms.MultipleChoiceField):
  
      def get_bound_field(self, form, field_name):
          bound_field = BoundField(form, self, field_name)
          data = bound_field.value()
  
          if data is not None:
              self.choices = [
                  choice for choice in self.choices if choice[0] and choice[0] in data
              ]
  
          return bound_field
  
  
  #
  # Model choice fields
  #
  
  class DynamicModelChoiceMixin:
      """
      Override `get_bound_field()` to avoid pre-populating field choices with a SQL query. The field will be
      rendered only with choices set via bound data. Choices are populated on-demand via the APISelect widget.
  
      Attributes:
          query_params: A dictionary of additional key/value pairs to attach to the API request
          initial_params: A dictionary of child field references to use for selecting a parent field's initial value
          null_option: The string used to represent a null selection (if any)
          disabled_indicator: The name of the field which, if populated, will disable selection of the
              choice (DEPRECATED: pass `context={'disabled': '$fieldname'}` instead)
          context: A mapping of <option> template variables to their API data keys (optional; see below)
          selector: Include an advanced object selection widget to assist the user in identifying the desired object
          quick_add: Include a widget to quickly create a new related object for assignment. NOTE: Nested usage of
              quick-add fields is not currently supported.
          quick_add_params: A dictionary of initial data to include when launching the quick-add form (optional). The
              token string "$pk" will be replaced with the primary key of the form's instance, if any.
  
      Context keys:
          value: The name of the attribute which contains the option's value (default: 'id')
          label: The name of the attribute used as the option's human-friendly label (default: 'display')
          description: The name of the attribute to use as a description (default: 'description')
          depth: The name of the attribute which indicates an object's depth within a recursive hierarchy; must be a
              positive integer (default: '_depth')
          disabled: The name of the attribute which, if true, signifies that the option should be disabled
          parent: The name of the attribute which represents the object's parent object (e.g. device for an interface)
          count: The name of the attribute which contains a numeric count of related objects
      """
      filter = django_filters.ModelChoiceFilter
      widget = widgets.APISelect
  
      def __init__(
              self,
              queryset,
              *,
              query_params=None,
              initial_params=None,
              null_option=None,
              disabled_indicator=None,
              context=None,
              selector=False,
              quick_add=False,
              quick_add_params=None,
              **kwargs
      ):
          self.model = queryset.model
          self.query_params = query_params or {}
          self.initial_params = initial_params or {}
          self.null_option = null_option
          self.disabled_indicator = disabled_indicator
          self.context = context or {}
          self.selector = selector
          self.quick_add = quick_add
          self.quick_add_params = quick_add_params or {}
  
          super().__init__(queryset, **kwargs)
  
      def widget_attrs(self, widget):
          attrs = {}
  
          # Set the string used to represent a null option
          if self.null_option is not None:
              attrs['data-null-option'] = self.null_option
  
          # Set any custom template attributes for TomSelect
          for var, accessor in self.context.items():
              attrs[f'ts-{var}-field'] = accessor
  
          # Attach any static query parameters
          if len(self.query_params) > 0:
              widget.add_query_params(self.query_params)
  
          # Include object selector?
          if self.selector:
              attrs['selector'] = self.model._meta.label_lower
  
          return attrs
  
      def get_bound_field(self, form, field_name):
          bound_field = BoundField(form, self, field_name)
          widget = bound_field.field.widget
  
          # Set initial value based on prescribed child fields (if not already set)
          if not self.initial and self.initial_params:
              filter_kwargs = {}
              for kwarg, child_field in self.initial_params.items():
                  value = form.initial.get(child_field.lstrip('$'))
                  if value:
                      filter_kwargs[kwarg] = value
              if filter_kwargs:
                  self.initial = self.queryset.filter(**filter_kwargs).first()
  
          # Modify the QuerySet of the field before we return it. Limit choices to any data already bound: Options
          # will be populated on-demand via the APISelect widget.
          data = bound_field.value()
  
          if data:
              # When the field is multiple choice pass the data as a list if it's not already
              if isinstance(bound_field.field, DynamicModelMultipleChoiceField) and type(data) is not list:
                  data = [data]
  
              field_name = getattr(self, 'to_field_name') or 'pk'
              filter = self.filter(field_name=field_name)
              try:
                  self.queryset = filter.filter(self.queryset, data)
              except (TypeError, ValueError):
                  # Catch any error caused by invalid initial data passed from the user
                  self.queryset = self.queryset.none()
          else:
              self.queryset = self.queryset.none()
  
          # Normalize the widget choices to a list to accommodate the "null" option, if set
          if self.null_option:
              widget.choices = [
                  (settings.FILTERS_NULL_CHOICE_VALUE, self.null_option),
                  *[c for c in widget.choices]
              ]
  
          # Set the data URL on the APISelect widget (if not already set)
          if not widget.attrs.get('data-url'):
              widget.attrs['data-url'] = get_action_url(self.queryset.model, action='list', rest_api=True)
  
          # Include quick add?
          if self.quick_add:
              widget.quick_add_context = {
                  'url': get_action_url(self.model, action='add'),
                  'params': {},
              }
              for k, v in self.quick_add_params.items():
                  if v == '$pk':
                      # Replace "$pk" token with the primary key of the form's instance (if any)
                      if getattr(form.instance, 'pk', None):
                          widget.quick_add_context['params'][k] = form.instance.pk
                  else:
                      widget.quick_add_context['params'][k] = v
  
          return bound_field
  
  
  class DynamicModelChoiceField(DynamicModelChoiceMixin, forms.ModelChoiceField):
      """
      Dynamic selection field for a single object, backed by NetBox's REST API.
      """
      def clean(self, value):
          """
          When null option is enabled and "None" is sent as part of a form to be submitted, it is sent as the
          string 'null'.  This will check for that condition and gracefully handle the conversion to a NoneType.
          """
          if self.null_option is not None and value == settings.FILTERS_NULL_CHOICE_VALUE:
              return None
          return super().clean(value)
  
  
  class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultipleChoiceField):
      """
      A multiple-choice version of `DynamicModelChoiceField`.
      """
      filter = django_filters.ModelMultipleChoiceFilter
      widget = widgets.APISelectMultiple
  
      def clean(self, value):
          value = value or []
  
          # When null option is enabled and "None" is sent as part of a form to be submitted, it is sent as the
          # string 'null'.  This will check for that condition and gracefully handle the conversion to a NoneType.
          if self.null_option is not None and settings.FILTERS_NULL_CHOICE_VALUE in value:
              value = [v for v in value if v != settings.FILTERS_NULL_CHOICE_VALUE]
              return [None, *super().clean(value)]
  
          return super().clean(value)
  