content: from django import forms
  from django.utils.translation import gettext_lazy as _
  
  from dcim.models import *
  from netbox.forms import NetBoxModelForm
  from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
  from utilities.forms.rendering import FieldSet, TabbedGroups
  from utilities.forms.widgets import APISelect
  from . import model_forms
  
  __all__ = (
      'ComponentCreateForm',
      'ConsolePortCreateForm',
      'ConsolePortTemplateCreateForm',
      'ConsoleServerPortCreateForm',
      'ConsoleServerPortTemplateCreateForm',
      'DeviceBayCreateForm',
      'DeviceBayTemplateCreateForm',
      'FrontPortCreateForm',
      'FrontPortTemplateCreateForm',
      'InterfaceCreateForm',
      'InterfaceTemplateCreateForm',
      'InventoryItemCreateForm',
      'InventoryItemTemplateCreateForm',
      'ModuleBayCreateForm',
      'ModuleBayTemplateCreateForm',
      'PowerOutletCreateForm',
      'PowerOutletTemplateCreateForm',
      'PowerPortCreateForm',
      'PowerPortTemplateCreateForm',
      'RearPortCreateForm',
      'RearPortTemplateCreateForm',
      'VirtualChassisCreateForm',
  )
  
  
  class ComponentCreateForm(forms.Form):
      """
      Subclass this form when facilitating the creation of one or more component or component template objects based on
      a name pattern.
      """
      name = ExpandableNameField(
          label=_('Name'),
      )
      label = ExpandableNameField(
          label=_('Label'),
          required=False,
          help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
      )
  
      # Identify the fields which support replication (i.e. ExpandableNameFields). This is referenced by
      # ComponentCreateView when creating objects.
      replication_fields = ('name', 'label')
  
      def clean(self):
          super().clean()
  
          # Validate that all replication fields generate an equal number of values (or a single value)
          if not (patterns := self.cleaned_data.get(self.replication_fields[0])):
              return
          pattern_count = len(patterns)
          for field_name in self.replication_fields:
              value_count = len(self.cleaned_data[field_name])
              if self.cleaned_data[field_name]:
                  if value_count == 1:
                      # If the field resolves to a single value (because no pattern was used), multiply it by the number
                      # of expected values. This allows us to reuse the same label when creating multiple components.
                      self.cleaned_data[field_name] = self.cleaned_data[field_name] * pattern_count
                  elif value_count != pattern_count:
                      raise forms.ValidationError({
                          field_name: _(
                              "The provided pattern specifies {value_count} values, but {pattern_count} are expected."
                          ).format(value_count=value_count, pattern_count=pattern_count)
                      }, code='label_pattern_mismatch')
  
  
  #
  # Device component templates
  #
  
  class ConsolePortTemplateCreateForm(ComponentCreateForm, model_forms.ConsolePortTemplateForm):
  
      class Meta(model_forms.ConsolePortTemplateForm.Meta):
          exclude = ('name', 'label')
  
  
  class ConsoleServerPortTemplateCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortTemplateForm):
  
      class Meta(model_forms.ConsoleServerPortTemplateForm.Meta):
          exclude = ('name', 'label')
  
  
  class PowerPortTemplateCreateForm(ComponentCreateForm, model_forms.PowerPortTemplateForm):
  
      class Meta(model_forms.PowerPortTemplateForm.Meta):
          exclude = ('name', 'label')
  
  
  class PowerOutletTemplateCreateForm(ComponentCreateForm, model_forms.PowerOutletTemplateForm):
  
      class Meta(model_forms.PowerOutletTemplateForm.Meta):
          exclude = ('name', 'label')
  
  
  class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemplateForm):
  
      class Meta(model_forms.InterfaceTemplateForm.Meta):
          exclude = ('name', 'label')
  
  
  class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
      rear_port = forms.MultipleChoiceField(
          choices=[],
          label=_('Rear ports'),
          help_text=_('Select one rear port assignment for each front port being created.'),
          widget=forms.SelectMultiple(attrs={'size': 6})
      )
  
      # Override fieldsets from FrontPortTemplateForm to omit rear_port_position
      fieldsets = (
          FieldSet(
              TabbedGroups(
                  FieldSet('device_type', name=_('Device Type')),
                  FieldSet('module_type', name=_('Module Type')),
              ),
              'name', 'label', 'type', 'color', 'rear_port', 'description',
          ),
      )
  
      class Meta(model_forms.FrontPortTemplateForm.Meta):
          exclude = ('name', 'label', 'rear_port', 'rear_port_position')
  
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
  
          # TODO: This needs better validation
          if 'device_type' in self.initial or self.data.get('device_type'):
              parent = DeviceType.objects.get(
                  pk=self.initial.get('device_type') or self.data.get('device_type')
              )
          elif 'module_type' in self.initial or self.data.get('module_type'):
              parent = ModuleType.objects.get(
                  pk=self.initial.get('module_type') or self.data.get('module_type')
              )
          else:
              return
  
          # Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
          occupied_port_positions = [
              (front_port.rear_port_id, front_port.rear_port_position)
              for front_port in parent.frontporttemplates.all()
          ]
  
          # Populate rear port choices
          choices = []
          rear_ports = parent.rearporttemplates.all()
          for rear_port in rear_ports:
              for i in range(1, rear_port.positions + 1):
                  if (rear_port.pk, i) not in occupied_port_positions:
                      choices.append(
                          ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
                      )
          self.fields['rear_port'].choices = choices
  
      def clean(self):
          super().clean()
  
          # Check that the number of FrontPortTemplates to be created matches the selected number of RearPortTemplate
          # positions
          frontport_count = len(self.cleaned_data['name'])
          rearport_count = len(self.cleaned_data['rear_port'])
          if frontport_count != rearport_count:
              raise forms.ValidationError({
                  'rear_port': _(
                      "The number of front port templates to be created ({frontport_count}) must match the selected "
                      "number of rear port positions ({rearport_count})."
                  ).format(
                      frontport_count=frontport_count,
                      rearport_count=rearport_count
                  )
              })
  
      def get_iterative_data(self, iteration):
  
          # Assign rear port and position from selected set
          rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
  
          return {
              'rear_port': int(rear_port),
              'rear_port_position': int(position),
          }
  
  
  class RearPortTemplateCreateForm(ComponentCreateForm, model_forms.RearPortTemplateForm):
  
      class Meta(model_forms.RearPortTemplateForm.Meta):
          exclude = ('name', 'label')
  
  
  class DeviceBayTemplateCreateForm(ComponentCreateForm, model_forms.DeviceBayTemplateForm):
  
      class Meta(model_forms.DeviceBayTemplateForm.Meta):
          exclude = ('name', 'label')
  
  
  class ModuleBayTemplateCreateForm(ComponentCreateForm, model_forms.ModuleBayTemplateForm):
      position = ExpandableNameField(
          label=_('Position'),
          required=False,
          help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
      )
      replication_fields = ('name', 'label', 'position')
  
      class Meta(model_forms.ModuleBayTemplateForm.Meta):
          exclude = ('name', 'label', 'position')
  
  
  class InventoryItemTemplateCreateForm(ComponentCreateForm, model_forms.InventoryItemTemplateForm):
  
      class Meta(model_forms.InventoryItemTemplateForm.Meta):
          exclude = ('name', 'label')
  
  
  #
  # Device components
  #
  
  class ConsolePortCreateForm(ComponentCreateForm, model_forms.ConsolePortForm):
  
      class Meta(model_forms.ConsolePortForm.Meta):
          exclude = ('name', 'label')
  
  
  class ConsoleServerPortCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortForm):
  
      class Meta(model_forms.ConsoleServerPortForm.Meta):
          exclude = ('name', 'label')
  
  
  class PowerPortCreateForm(ComponentCreateForm, model_forms.PowerPortForm):
  
      class Meta(model_forms.PowerPortForm.Meta):
          exclude = ('name', 'label')
  
  
  class PowerOutletCreateForm(ComponentCreateForm, model_forms.PowerOutletForm):
  
      class Meta(model_forms.PowerOutletForm.Meta):
          exclude = ('name', 'label')
  
  
  class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
  
      class Meta(model_forms.InterfaceForm.Meta):
          exclude = ('name', 'label')
  
  
  class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
      device = DynamicModelChoiceField(
          label=_('Device'),
          queryset=Device.objects.all(),
          selector=True,
          widget=APISelect(
              # TODO: Clean up the application of HTMXSelect attributes
              attrs={
                  'hx-get': '.',
                  'hx-include': '#form_fields',
                  'hx-target': '#form_fields',
              }
          )
      )
      rear_port = forms.MultipleChoiceField(
          choices=[],
          label=_('Rear ports'),
          help_text=_('Select one rear port assignment for each front port being created.'),
          widget=forms.SelectMultiple(attrs={'size': 6})
      )
  
      # Override fieldsets from FrontPortForm to omit rear_port_position
      fieldsets = (
          FieldSet(
              'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
          ),
      )
  
      class Meta(model_forms.FrontPortForm.Meta):
          exclude = ('name', 'label', 'rear_port', 'rear_port_position')
  
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
  
          if device_id := self.data.get('device') or self.initial.get('device'):
              device = Device.objects.get(pk=device_id)
          else:
              return
  
          # Determine which rear port positions are occupied. These will be excluded from the list of available
          # mappings.
          occupied_port_positions = [
              (front_port.rear_port_id, front_port.rear_port_position)
              for front_port in device.frontports.all()
          ]
  
          # Populate rear port choices
          choices = []
          rear_ports = RearPort.objects.filter(device=device)
          for rear_port in rear_ports:
              for i in range(1, rear_port.positions + 1):
                  if (rear_port.pk, i) not in occupied_port_positions:
                      choices.append(
                          ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
                      )
          self.fields['rear_port'].choices = choices
  
      def clean(self):
          super().clean()
  
          # Check that the number of FrontPorts to be created matches the selected number of RearPort positions
          frontport_count = len(self.cleaned_data['name'])
          rearport_count = len(self.cleaned_data['rear_port'])
          if frontport_count != rearport_count:
              raise forms.ValidationError({
                  'rear_port': _(
                      "The number of front ports to be created ({frontport_count}) must match the selected number of "
                      "rear port positions ({rearport_count})."
                  ).format(
                      frontport_count=frontport_count,
                      rearport_count=rearport_count
                  )
              })
  
      def get_iterative_data(self, iteration):
  
          # Assign rear port and position from selected set
          rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
  
          return {
              'rear_port': int(rear_port),
              'rear_port_position': int(position),
          }
  
  
  class RearPortCreateForm(ComponentCreateForm, model_forms.RearPortForm):
  
      class Meta(model_forms.RearPortForm.Meta):
          exclude = ('name', 'label')
  
  
  class DeviceBayCreateForm(ComponentCreateForm, model_forms.DeviceBayForm):
  
      class Meta(model_forms.DeviceBayForm.Meta):
          exclude = ('name', 'label')
  
  
  class ModuleBayCreateForm(ComponentCreateForm, model_forms.ModuleBayForm):
      position = ExpandableNameField(
          label=_('Position'),
          required=False,
          help_text=_('Alphanumeric ranges are supported. (Must match the number of objects being created.)')
      )
      replication_fields = ('name', 'label', 'position')
  
      class Meta(model_forms.ModuleBayForm.Meta):
          exclude = ('name', 'label', 'position')
  
  
  class InventoryItemCreateForm(ComponentCreateForm, model_forms.InventoryItemForm):
  
      class Meta(model_forms.InventoryItemForm.Meta):
          exclude = ('name', 'label')
  
  
  #
  # Virtual chassis
  #
  
  class VirtualChassisCreateForm(NetBoxModelForm):
      region = DynamicModelChoiceField(
          label=_('Region'),
          queryset=Region.objects.all(),
          required=False,
          initial_params={
              'sites': '$site'
          }
      )
      site_group = DynamicModelChoiceField(
          label=_('Site group'),
          queryset=SiteGroup.objects.all(),
          required=False,
          initial_params={
              'sites': '$site'
          }
      )
      site = DynamicModelChoiceField(
          label=_('Site'),
          queryset=Site.objects.all(),
          required=False,
          query_params={
              'region_id': '$region',
              'group_id': '$site_group',
          }
      )
      rack = DynamicModelChoiceField(
          label=_('Rack'),
          queryset=Rack.objects.all(),
          required=False,
          null_option='None',
          query_params={
              'site_id': '$site'
          }
      )
      members = DynamicModelMultipleChoiceField(
          label=_('Members'),
          queryset=Device.objects.all(),
          required=False,
          query_params={
              'virtual_chassis_id': 'null',
              'site_id': '$site',
              'rack_id': '$rack',
          }
      )
      initial_position = forms.IntegerField(
          label=_('Initial position'),
          initial=1,
          required=False,
          help_text=_('Position of the first member device. Increases by one for each additional member.')
      )
  
      fieldsets = (
          FieldSet('name', 'domain', 'description', 'tags', name=_('Virtual Chassis')),
          FieldSet('region', 'site_group', 'site', 'rack', 'members', 'initial_position', name=_('Member Devices')),
      )
  
      class Meta:
          model = VirtualChassis
          fields = [
              'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position',
              'tags',
          ]
  
      def clean(self):
          super().clean()
  
          if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
              raise forms.ValidationError({
                  'initial_position': _("A position must be specified for the first VC member.")
              })
  
      def save(self, *args, **kwargs):
          instance = super().save(*args, **kwargs)
  
          # Assign VC members
          if instance.pk and self.cleaned_data['members']:
              initial_position = self.cleaned_data.get('initial_position', 1)
              for i, member in enumerate(self.cleaned_data['members'], start=initial_position):
                  member.snapshot()
                  member.virtual_chassis = instance
                  member.vc_position = i
                  member.save()
  
          return instance
  