Просмотр исходного кода

Add `label` to forms, views and templates

Jonathan Senecal 5 лет назад
Родитель
Сommit
286a3e6ca2
4 измененных файлов с 207 добавлено и 17 удалено
  1. 188 12
      netbox/dcim/forms.py
  2. 4 0
      netbox/templates/dcim/interface.html
  3. 1 0
      netbox/templates/dcim/interface_edit.html
  4. 14 5
      netbox/utilities/views.py

+ 188 - 12
netbox/dcim/forms.py

@@ -1032,7 +1032,7 @@ class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
     class Meta:
     class Meta:
         model = ConsolePortTemplate
         model = ConsolePortTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type',
+            'device_type', 'name', 'label', 'type',
         ]
         ]
         widgets = {
         widgets = {
             'device_type': forms.HiddenInput(),
             'device_type': forms.HiddenInput(),
@@ -1046,11 +1046,27 @@ class ConsolePortTemplateCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=add_blank_choice(ConsolePortTypeChoices),
         choices=add_blank_choice(ConsolePortTypeChoices),
         widget=StaticSelect2()
         widget=StaticSelect2()
     )
     )
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} ports, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
 
 
 class ConsolePortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
 class ConsolePortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
@@ -1072,7 +1088,7 @@ class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
     class Meta:
     class Meta:
         model = ConsoleServerPortTemplate
         model = ConsoleServerPortTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type',
+            'device_type', 'name', 'label', 'type',
         ]
         ]
         widgets = {
         widgets = {
             'device_type': forms.HiddenInput(),
             'device_type': forms.HiddenInput(),
@@ -1086,11 +1102,27 @@ class ConsoleServerPortTemplateCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=add_blank_choice(ConsolePortTypeChoices),
         choices=add_blank_choice(ConsolePortTypeChoices),
         widget=StaticSelect2()
         widget=StaticSelect2()
     )
     )
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} ports, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
 
 
 class ConsoleServerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
 class ConsoleServerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
@@ -1112,7 +1144,7 @@ class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
     class Meta:
     class Meta:
         model = PowerPortTemplate
         model = PowerPortTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type', 'maximum_draw', 'allocated_draw',
+            'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw',
         ]
         ]
         widgets = {
         widgets = {
             'device_type': forms.HiddenInput(),
             'device_type': forms.HiddenInput(),
@@ -1126,6 +1158,10 @@ class PowerPortTemplateCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=add_blank_choice(PowerPortTypeChoices),
         choices=add_blank_choice(PowerPortTypeChoices),
         required=False
         required=False
@@ -1141,6 +1177,18 @@ class PowerPortTemplateCreateForm(BootstrapMixin, forms.Form):
         help_text="Allocated power draw (watts)"
         help_text="Allocated power draw (watts)"
     )
     )
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} ports, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
 
 
 class PowerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
 class PowerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
@@ -1172,7 +1220,7 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
     class Meta:
     class Meta:
         model = PowerOutletTemplate
         model = PowerOutletTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type', 'power_port', 'feed_leg',
+            'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg',
         ]
         ]
         widgets = {
         widgets = {
             'device_type': forms.HiddenInput(),
             'device_type': forms.HiddenInput(),
@@ -1196,6 +1244,10 @@ class PowerOutletTemplateCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=add_blank_choice(PowerOutletTypeChoices),
         choices=add_blank_choice(PowerOutletTypeChoices),
         required=False
         required=False
@@ -1221,6 +1273,18 @@ class PowerOutletTemplateCreateForm(BootstrapMixin, forms.Form):
             device_type=device_type
             device_type=device_type
         )
         )
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} ports, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
 
 
 class PowerOutletTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
 class PowerOutletTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
@@ -1247,7 +1311,7 @@ class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
     class Meta:
     class Meta:
         model = InterfaceTemplate
         model = InterfaceTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type', 'mgmt_only',
+            'device_type', 'name', 'label', 'type', 'mgmt_only',
         ]
         ]
         widgets = {
         widgets = {
             'device_type': forms.HiddenInput(),
             'device_type': forms.HiddenInput(),
@@ -1262,6 +1326,10 @@ class InterfaceTemplateCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=InterfaceTypeChoices,
         choices=InterfaceTypeChoices,
         widget=StaticSelect2()
         widget=StaticSelect2()
@@ -1271,6 +1339,18 @@ class InterfaceTemplateCreateForm(BootstrapMixin, forms.Form):
         label='Management only'
         label='Management only'
     )
     )
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} interfaces, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
 
 
 class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
 class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
@@ -1504,7 +1584,7 @@ class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
     class Meta:
     class Meta:
         model = ConsolePortTemplate
         model = ConsolePortTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type',
+            'device_type', 'name', 'label', 'type',
         ]
         ]
 
 
 
 
@@ -1513,7 +1593,7 @@ class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
     class Meta:
     class Meta:
         model = ConsoleServerPortTemplate
         model = ConsoleServerPortTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type',
+            'device_type', 'name', 'label', 'type',
         ]
         ]
 
 
 
 
@@ -1522,7 +1602,7 @@ class PowerPortTemplateImportForm(ComponentTemplateImportForm):
     class Meta:
     class Meta:
         model = PowerPortTemplate
         model = PowerPortTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type', 'maximum_draw', 'allocated_draw',
+            'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw',
         ]
         ]
 
 
 
 
@@ -1536,7 +1616,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
     class Meta:
     class Meta:
         model = PowerOutletTemplate
         model = PowerOutletTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type', 'power_port', 'feed_leg',
+            'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg',
         ]
         ]
 
 
 
 
@@ -1548,7 +1628,7 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
     class Meta:
     class Meta:
         model = InterfaceTemplate
         model = InterfaceTemplate
         fields = [
         fields = [
-            'device_type', 'name', 'type', 'mgmt_only',
+            'device_type', 'name', 'label', 'type', 'mgmt_only',
         ]
         ]
 
 
 
 
@@ -2199,12 +2279,28 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
 
 
     def clean_tags(self):
     def clean_tags(self):
         # Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we
         # Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we
         # must first convert the list of tags to a string.
         # must first convert the list of tags to a string.
         return ','.join(self.cleaned_data.get('tags'))
         return ','.join(self.cleaned_data.get('tags'))
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} {}}, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, self.type, label_pattern_count)
+            })
+
 
 
 #
 #
 # Console ports
 # Console ports
@@ -2229,7 +2325,7 @@ class ConsolePortForm(BootstrapMixin, forms.ModelForm):
     class Meta:
     class Meta:
         model = ConsolePort
         model = ConsolePort
         fields = [
         fields = [
-            'device', 'name', 'type', 'description', 'tags',
+            'device', 'name', 'label', 'type', 'description', 'tags',
         ]
         ]
         widgets = {
         widgets = {
             'device': forms.HiddenInput(),
             'device': forms.HiddenInput(),
@@ -2243,6 +2339,10 @@ class ConsolePortCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=add_blank_choice(ConsolePortTypeChoices),
         choices=add_blank_choice(ConsolePortTypeChoices),
         required=False,
         required=False,
@@ -2256,6 +2356,18 @@ class ConsolePortCreateForm(BootstrapMixin, forms.Form):
         required=False
         required=False
     )
     )
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} ports, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
 
 
 class ConsolePortBulkCreateForm(
 class ConsolePortBulkCreateForm(
     form_from_model(ConsolePort, ['type', 'description', 'tags']),
     form_from_model(ConsolePort, ['type', 'description', 'tags']),
@@ -2329,6 +2441,10 @@ class ConsoleServerPortCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=add_blank_choice(ConsolePortTypeChoices),
         choices=add_blank_choice(ConsolePortTypeChoices),
         required=False,
         required=False,
@@ -2342,6 +2458,18 @@ class ConsoleServerPortCreateForm(BootstrapMixin, forms.Form):
         required=False
         required=False
     )
     )
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} ports, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
 
 
 class ConsoleServerPortBulkCreateForm(
 class ConsoleServerPortBulkCreateForm(
     form_from_model(ConsoleServerPort, ['type', 'description', 'tags']),
     form_from_model(ConsoleServerPort, ['type', 'description', 'tags']),
@@ -2429,6 +2557,10 @@ class PowerPortCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=add_blank_choice(PowerPortTypeChoices),
         choices=add_blank_choice(PowerPortTypeChoices),
         required=False,
         required=False,
@@ -2451,6 +2583,17 @@ class PowerPortCreateForm(BootstrapMixin, forms.Form):
     tags = TagField(
     tags = TagField(
         required=False
         required=False
     )
     )
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} ports, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
 
 
 
 
 class PowerPortBulkCreateForm(
 class PowerPortBulkCreateForm(
@@ -2538,6 +2681,10 @@ class PowerOutletCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=add_blank_choice(PowerOutletTypeChoices),
         choices=add_blank_choice(PowerOutletTypeChoices),
         required=False,
         required=False,
@@ -2568,6 +2715,18 @@ class PowerOutletCreateForm(BootstrapMixin, forms.Form):
         )
         )
         self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
         self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} ports, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
 
 
 class PowerOutletBulkCreateForm(
 class PowerOutletBulkCreateForm(
     form_from_model(PowerOutlet, ['type', 'feed_leg', 'description', 'tags']),
     form_from_model(PowerOutlet, ['type', 'feed_leg', 'description', 'tags']),
@@ -2721,7 +2880,7 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
     class Meta:
     class Meta:
         model = Interface
         model = Interface
         fields = [
         fields = [
-            'device', 'name', 'type', 'enabled', 'lag', 'mac_address', 'mtu', 'mgmt_only', 'description',
+            'device', 'name', 'label', 'type', 'enabled', 'lag', 'mac_address', 'mtu', 'mgmt_only', 'description',
             'mode', 'untagged_vlan', 'tagged_vlans', 'tags',
             'mode', 'untagged_vlan', 'tagged_vlans', 'tags',
         ]
         ]
         widgets = {
         widgets = {
@@ -2763,6 +2922,10 @@ class InterfaceCreateForm(BootstrapMixin, InterfaceCommonForm, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         label='Name'
     )
     )
+    label_pattern = ExpandableNameField(
+        label='Label',
+        required=False
+    )
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         choices=InterfaceTypeChoices,
         choices=InterfaceTypeChoices,
         widget=StaticSelect2(),
         widget=StaticSelect2(),
@@ -2843,6 +3006,19 @@ class InterfaceCreateForm(BootstrapMixin, InterfaceCommonForm, forms.Form):
         self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', device.site.pk)
         self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', device.site.pk)
         self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk)
         self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk)
 
 
+    def clean(self):
+
+        # Validate that the number of ports being created from both the name_pattern and label_pattern are equal
+        name_pattern_count = len(self.cleaned_data['name_pattern'])
+        label_pattern_count = len(self.cleaned_data['label_pattern'])
+        if label_pattern_count and name_pattern_count != label_pattern_count:
+            raise forms.ValidationError({
+                'label_pattern': 'The provided name pattern will create {} interfaces, however {} labels will '
+                'be generated. These counts must match.'.format(
+                    name_pattern_count, label_pattern_count)
+            })
+
+
 
 
 class InterfaceBulkCreateForm(
 class InterfaceBulkCreateForm(
     form_from_model(Interface, ['type', 'enabled', 'mtu', 'mgmt_only', 'description', 'tags']),
     form_from_model(Interface, ['type', 'enabled', 'mtu', 'mgmt_only', 'description', 'tags']),

+ 4 - 0
netbox/templates/dcim/interface.html

@@ -58,6 +58,10 @@
                     <td>Name</td>
                     <td>Name</td>
                     <td>{{ interface.name }}</td>
                     <td>{{ interface.name }}</td>
                 </tr>
                 </tr>
+                <tr>
+                    <td>Label</td>
+                    <td>{{ interface.label }}</td>
+                </tr>
                 <tr>
                 <tr>
                     <td>Type</td>
                     <td>Type</td>
                     <td>{{ interface.get_type_display }}</td>
                     <td>{{ interface.get_type_display }}</td>

+ 1 - 0
netbox/templates/dcim/interface_edit.html

@@ -6,6 +6,7 @@
         <div class="panel-heading"><strong>Interface</strong></div>
         <div class="panel-heading"><strong>Interface</strong></div>
         <div class="panel-body">
         <div class="panel-body">
             {% render_field form.name %}
             {% render_field form.name %}
+            {% render_field form.label %}
             {% render_field form.type %}
             {% render_field form.type %}
             {% render_field form.enabled %}
             {% render_field form.enabled %}
             {% render_field form.lag %}
             {% render_field form.lag %}

+ 14 - 5
netbox/utilities/views.py

@@ -919,21 +919,26 @@ class ComponentCreateView(GetReturnURLMixin, View):
             new_components = []
             new_components = []
             data = deepcopy(request.POST)
             data = deepcopy(request.POST)
 
 
-            for i, name in enumerate(form.cleaned_data['name_pattern']):
-
+            names = form.cleaned_data['name_pattern']
+            labels = form.cleaned_data.get('label_pattern')
+            for pos, name in enumerate(names):
+                label = labels[pos] if labels else None
                 # Initialize the individual component form
                 # Initialize the individual component form
                 data['name'] = name
                 data['name'] = name
+                data['label'] = label
                 if hasattr(form, 'get_iterative_data'):
                 if hasattr(form, 'get_iterative_data'):
-                    data.update(form.get_iterative_data(i))
+                    data.update(form.get_iterative_data(pos))
                 component_form = self.model_form(data)
                 component_form = self.model_form(data)
 
 
                 if component_form.is_valid():
                 if component_form.is_valid():
                     new_components.append(component_form)
                     new_components.append(component_form)
                 else:
                 else:
                     for field, errors in component_form.errors.as_data().items():
                     for field, errors in component_form.errors.as_data().items():
-                        # Assign errors on the child form's name field to name_pattern on the parent form
+                        # Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form
                         if field == 'name':
                         if field == 'name':
                             field = 'name_pattern'
                             field = 'name_pattern'
+                        if field == 'label':
+                            field = 'label_pattern'
                         for e in errors:
                         for e in errors:
                             form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
                             form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
 
 
@@ -1003,10 +1008,14 @@ class BulkComponentCreateView(GetReturnURLMixin, View):
                         for obj in data['pk']:
                         for obj in data['pk']:
 
 
                             names = data['name_pattern']
                             names = data['name_pattern']
-                            for name in names:
+                            labels = data['label_pattern']
+                            for pos, name in enumerate(names):
+                                label = labels[pos] if labels else None
+
                                 component_data = {
                                 component_data = {
                                     self.parent_field: obj.pk,
                                     self.parent_field: obj.pk,
                                     'name': name,
                                     'name': name,
+                                    'label': label
                                 }
                                 }
                                 component_data.update(data)
                                 component_data.update(data)
                                 component_form = self.model_form(component_data)
                                 component_form = self.model_form(component_data)