We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
No field to edit Cluster comments
### Issue type
[ ] Feature request <!-- Requesting the implementation of a new feature -->
[X] Bug report      <!-- Reporting unexpected or erroneous behavior -->
[ ] Documentation   <!-- Proposing a modification to the documentation -->

### Environment

* Python version:  3.5.2
* NetBox version:  origin/dev-2.2 e93129f1

### Description

When you view Clusters you can see there is a 'comments' field.  However, when you create a Cluster there is no field to enter the comments. Ditto when you click Edit on an existing cluster (e.g. `/virtualization/clusters/1/edit/`).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `netbox/virtualization/forms.py`
Content:
```
1 from __future__ import unicode_literals
2 
3 from mptt.forms import TreeNodeChoiceField
4 
5 from django import forms
6 from django.db.models import Count
7 
8 from dcim.constants import IFACE_FF_VIRTUAL, VIFACE_FF_CHOICES
9 from dcim.formfields import MACAddressFormField
10 from dcim.models import Device, Interface, Platform, Rack, Region, Site
11 from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
12 from tenancy.forms import TenancyForm
13 from tenancy.models import Tenant
14 from utilities.forms import (
15     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
16     ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
17     ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea,
18 )
19 from .constants import STATUS_CHOICES
20 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
21 
22 
23 #
24 # Cluster types
25 #
26 
27 class ClusterTypeForm(BootstrapMixin, forms.ModelForm):
28     slug = SlugField()
29 
30     class Meta:
31         model = ClusterType
32         fields = ['name', 'slug']
33 
34 
35 #
36 # Cluster groups
37 #
38 
39 class ClusterGroupForm(BootstrapMixin, forms.ModelForm):
40     slug = SlugField()
41 
42     class Meta:
43         model = ClusterGroup
44         fields = ['name', 'slug']
45 
46 
47 #
48 # Clusters
49 #
50 
51 class ClusterForm(BootstrapMixin, CustomFieldForm):
52 
53     class Meta:
54         model = Cluster
55         fields = ['name', 'type', 'group']
56 
57 
58 class ClusterCSVForm(forms.ModelForm):
59     type = forms.ModelChoiceField(
60         queryset=ClusterType.objects.all(),
61         to_field_name='name',
62         help_text='Name of cluster type',
63         error_messages={
64             'invalid_choice': 'Invalid cluster type name.',
65         }
66     )
67     group = forms.ModelChoiceField(
68         queryset=ClusterGroup.objects.all(),
69         to_field_name='name',
70         required=False,
71         help_text='Name of cluster group',
72         error_messages={
73             'invalid_choice': 'Invalid cluster group name.',
74         }
75     )
76 
77     class Meta:
78         model = Cluster
79         fields = ['name', 'type', 'group', 'comments']
80 
81 
82 class ClusterBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
83     pk = forms.ModelMultipleChoiceField(queryset=Cluster.objects.all(), widget=forms.MultipleHiddenInput)
84     type = forms.ModelChoiceField(queryset=ClusterType.objects.all(), required=False)
85     group = forms.ModelChoiceField(queryset=ClusterGroup.objects.all(), required=False)
86     comments = CommentField(widget=SmallTextarea)
87 
88     class Meta:
89         nullable_fields = ['group', 'comments']
90 
91 
92 class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
93     model = Cluster
94     q = forms.CharField(required=False, label='Search')
95     group = FilterChoiceField(
96         queryset=ClusterGroup.objects.annotate(filter_count=Count('clusters')),
97         to_field_name='slug',
98         null_option=(0, 'None'),
99         required=False,
100     )
101     type = FilterChoiceField(
102         queryset=ClusterType.objects.annotate(filter_count=Count('clusters')),
103         to_field_name='slug',
104         required=False,
105     )
106 
107 
108 class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
109     region = TreeNodeChoiceField(
110         queryset=Region.objects.all(),
111         required=False,
112         widget=forms.Select(
113             attrs={'filter-for': 'site', 'nullable': 'true'}
114         )
115     )
116     site = ChainedModelChoiceField(
117         queryset=Site.objects.all(),
118         chains=(
119             ('region', 'region'),
120         ),
121         required=False,
122         widget=APISelect(
123             api_url='/api/dcim/sites/?region_id={{region}}',
124             attrs={'filter-for': 'rack'}
125         )
126     )
127     rack = ChainedModelChoiceField(
128         queryset=Rack.objects.all(),
129         chains=(
130             ('site', 'site'),
131         ),
132         required=False,
133         widget=APISelect(
134             api_url='/api/dcim/racks/?site_id={{site}}',
135             attrs={'filter-for': 'devices', 'nullable': 'true'}
136         )
137     )
138     devices = ChainedModelMultipleChoiceField(
139         queryset=Device.objects.filter(cluster__isnull=True),
140         chains=(
141             ('site', 'site'),
142             ('rack', 'rack'),
143         ),
144         label='Device',
145         widget=APISelectMultiple(
146             api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
147             display_field='display_name',
148             disabled_indicator='cluster'
149         )
150     )
151 
152     class Meta:
153         fields = ['region', 'site', 'rack', 'devices']
154 
155     def __init__(self, *args, **kwargs):
156 
157         super(ClusterAddDevicesForm, self).__init__(*args, **kwargs)
158 
159         self.fields['devices'].choices = []
160 
161 
162 class ClusterRemoveDevicesForm(ConfirmationForm):
163     pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
164 
165 
166 #
167 # Virtual Machines
168 #
169 
170 class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
171     cluster_group = forms.ModelChoiceField(
172         queryset=ClusterGroup.objects.all(),
173         required=False,
174         widget=forms.Select(
175             attrs={'filter-for': 'cluster', 'nullable': 'true'}
176         )
177     )
178     cluster = ChainedModelChoiceField(
179         queryset=Cluster.objects.all(),
180         chains=(
181             ('group', 'cluster_group'),
182         ),
183         widget=APISelect(
184             api_url='/api/virtualization/clusters/?group_id={{cluster_group}}'
185         )
186     )
187 
188     class Meta:
189         model = VirtualMachine
190         fields = [
191             'name', 'status', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
192         ]
193 
194     def __init__(self, *args, **kwargs):
195 
196         # Initialize helper selector
197         instance = kwargs.get('instance')
198         if instance.pk and instance.cluster is not None:
199             initial = kwargs.get('initial', {}).copy()
200             initial['cluster_group'] = instance.cluster.group
201             kwargs['initial'] = initial
202 
203         super(VirtualMachineForm, self).__init__(*args, **kwargs)
204 
205 
206 class VirtualMachineCSVForm(forms.ModelForm):
207     status = CSVChoiceField(
208         choices=STATUS_CHOICES,
209         required=False,
210         help_text='Operational status of device'
211     )
212     cluster = forms.ModelChoiceField(
213         queryset=Cluster.objects.all(),
214         to_field_name='name',
215         help_text='Name of parent cluster',
216         error_messages={
217             'invalid_choice': 'Invalid cluster name.',
218         }
219     )
220     tenant = forms.ModelChoiceField(
221         queryset=Tenant.objects.all(),
222         required=False,
223         to_field_name='name',
224         help_text='Name of assigned tenant',
225         error_messages={
226             'invalid_choice': 'Tenant not found.'
227         }
228     )
229     platform = forms.ModelChoiceField(
230         queryset=Platform.objects.all(),
231         required=False,
232         to_field_name='name',
233         help_text='Name of assigned platform',
234         error_messages={
235             'invalid_choice': 'Invalid platform.',
236         }
237     )
238 
239     class Meta:
240         model = VirtualMachine
241         fields = ['name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
242 
243 
244 class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
245     pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)
246     status = forms.ChoiceField(choices=add_blank_choice(STATUS_CHOICES), required=False, initial='')
247     cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False)
248     tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
249     platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)
250     vcpus = forms.IntegerField(required=False, label='vCPUs')
251     memory = forms.IntegerField(required=False, label='Memory (MB)')
252     disk = forms.IntegerField(required=False, label='Disk (GB)')
253     comments = CommentField(widget=SmallTextarea)
254 
255     class Meta:
256         nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
257 
258 
259 def vm_status_choices():
260     status_counts = {}
261     for status in VirtualMachine.objects.values('status').annotate(count=Count('status')).order_by('status'):
262         status_counts[status['status']] = status['count']
263     return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in STATUS_CHOICES]
264 
265 
266 class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
267     model = VirtualMachine
268     q = forms.CharField(required=False, label='Search')
269     cluster_group = FilterChoiceField(
270         queryset=ClusterGroup.objects.all(),
271         to_field_name='slug',
272         null_option=(0, 'None'),
273     )
274     cluster_id = FilterChoiceField(
275         queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
276         label='Cluster'
277     )
278     status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)
279 
280 
281 #
282 # VM interfaces
283 #
284 
285 class InterfaceForm(BootstrapMixin, forms.ModelForm):
286 
287     class Meta:
288         model = Interface
289         fields = ['virtual_machine', 'name', 'form_factor', 'enabled', 'mac_address', 'mtu', 'description']
290         widgets = {
291             'virtual_machine': forms.HiddenInput(),
292             'form_factor': forms.HiddenInput(),
293         }
294 
295 
296 class InterfaceCreateForm(ComponentForm):
297     name_pattern = ExpandableNameField(label='Name')
298     form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES, initial=IFACE_FF_VIRTUAL, widget=forms.HiddenInput())
299     enabled = forms.BooleanField(required=False)
300     mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
301     mac_address = MACAddressFormField(required=False, label='MAC Address')
302     description = forms.CharField(max_length=100, required=False)
303 
304     def __init__(self, *args, **kwargs):
305 
306         # Set interfaces enabled by default
307         kwargs['initial'] = kwargs.get('initial', {}).copy()
308         kwargs['initial'].update({'enabled': True})
309 
310         super(InterfaceCreateForm, self).__init__(*args, **kwargs)
311 
312 
313 class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
314     pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)
315     virtual_machine = forms.ModelChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.HiddenInput)
316     enabled = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
317     mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
318     description = forms.CharField(max_length=100, required=False)
319 
320     class Meta:
321         nullable_fields = ['mtu', 'description']
322 
323 
324 #
325 # Bulk VirtualMachine component creation
326 #
327 
328 class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
329     pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)
330     name_pattern = ExpandableNameField(label='Name')
331 
332 
333 class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):
334     form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES, initial=IFACE_FF_VIRTUAL, widget=forms.HiddenInput())
335     enabled = forms.BooleanField(required=False, initial=True)
336     mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
337     description = forms.CharField(max_length=100, required=False)
338
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py
--- a/netbox/virtualization/forms.py
+++ b/netbox/virtualization/forms.py
@@ -49,10 +49,11 @@
 #
 
 class ClusterForm(BootstrapMixin, CustomFieldForm):
+    comments = CommentField(widget=SmallTextarea)
 
     class Meta:
         model = Cluster
-        fields = ['name', 'type', 'group']
+        fields = ['name', 'type', 'group', 'comments']
 
 
 class ClusterCSVForm(forms.ModelForm):

{"golden_diff": "diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py\n--- a/netbox/virtualization/forms.py\n+++ b/netbox/virtualization/forms.py\n@@ -49,10 +49,11 @@\n #\n \n class ClusterForm(BootstrapMixin, CustomFieldForm):\n+    comments = CommentField(widget=SmallTextarea)\n \n     class Meta:\n         model = Cluster\n-        fields = ['name', 'type', 'group']\n+        fields = ['name', 'type', 'group', 'comments']\n \n \n class ClusterCSVForm(forms.ModelForm):\n", "issue": "No field to edit Cluster comments\n### Issue type\r\n[ ] Feature request <!-- Requesting the implementation of a new feature -->\r\n[X] Bug report      <!-- Reporting unexpected or erroneous behavior -->\r\n[ ] Documentation   <!-- Proposing a modification to the documentation -->\r\n\r\n### Environment\r\n\r\n* Python version:  3.5.2\r\n* NetBox version:  origin/dev-2.2 e93129f1\r\n\r\n### Description\r\n\r\nWhen you view Clusters you can see there is a 'comments' field.  However, when you create a Cluster there is no field to enter the comments. Ditto when you click Edit on an existing cluster (e.g. `/virtualization/clusters/1/edit/`).\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nfrom mptt.forms import TreeNodeChoiceField\n\nfrom django import forms\nfrom django.db.models import Count\n\nfrom dcim.constants import IFACE_FF_VIRTUAL, VIFACE_FF_CHOICES\nfrom dcim.formfields import MACAddressFormField\nfrom dcim.models import Device, Interface, Platform, Rack, Region, Site\nfrom extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm\nfrom tenancy.forms import TenancyForm\nfrom tenancy.models import Tenant\nfrom utilities.forms import (\n    add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,\n    ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,\n    ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea,\n)\nfrom .constants import STATUS_CHOICES\nfrom .models import Cluster, ClusterGroup, ClusterType, VirtualMachine\n\n\n#\n# Cluster types\n#\n\nclass ClusterTypeForm(BootstrapMixin, forms.ModelForm):\n    slug = SlugField()\n\n    class Meta:\n        model = ClusterType\n        fields = ['name', 'slug']\n\n\n#\n# Cluster groups\n#\n\nclass ClusterGroupForm(BootstrapMixin, forms.ModelForm):\n    slug = SlugField()\n\n    class Meta:\n        model = ClusterGroup\n        fields = ['name', 'slug']\n\n\n#\n# Clusters\n#\n\nclass ClusterForm(BootstrapMixin, CustomFieldForm):\n\n    class Meta:\n        model = Cluster\n        fields = ['name', 'type', 'group']\n\n\nclass ClusterCSVForm(forms.ModelForm):\n    type = forms.ModelChoiceField(\n        queryset=ClusterType.objects.all(),\n        to_field_name='name',\n        help_text='Name of cluster type',\n        error_messages={\n            'invalid_choice': 'Invalid cluster type name.',\n        }\n    )\n    group = forms.ModelChoiceField(\n        queryset=ClusterGroup.objects.all(),\n        to_field_name='name',\n        required=False,\n        help_text='Name of cluster group',\n        error_messages={\n            'invalid_choice': 'Invalid cluster group name.',\n        }\n    )\n\n    class Meta:\n        model = Cluster\n        fields = ['name', 'type', 'group', 'comments']\n\n\nclass ClusterBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):\n    pk = forms.ModelMultipleChoiceField(queryset=Cluster.objects.all(), widget=forms.MultipleHiddenInput)\n    type = forms.ModelChoiceField(queryset=ClusterType.objects.all(), required=False)\n    group = forms.ModelChoiceField(queryset=ClusterGroup.objects.all(), required=False)\n    comments = CommentField(widget=SmallTextarea)\n\n    class Meta:\n        nullable_fields = ['group', 'comments']\n\n\nclass ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):\n    model = Cluster\n    q = forms.CharField(required=False, label='Search')\n    group = FilterChoiceField(\n        queryset=ClusterGroup.objects.annotate(filter_count=Count('clusters')),\n        to_field_name='slug',\n        null_option=(0, 'None'),\n        required=False,\n    )\n    type = FilterChoiceField(\n        queryset=ClusterType.objects.annotate(filter_count=Count('clusters')),\n        to_field_name='slug',\n        required=False,\n    )\n\n\nclass ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):\n    region = TreeNodeChoiceField(\n        queryset=Region.objects.all(),\n        required=False,\n        widget=forms.Select(\n            attrs={'filter-for': 'site', 'nullable': 'true'}\n        )\n    )\n    site = ChainedModelChoiceField(\n        queryset=Site.objects.all(),\n        chains=(\n            ('region', 'region'),\n        ),\n        required=False,\n        widget=APISelect(\n            api_url='/api/dcim/sites/?region_id={{region}}',\n            attrs={'filter-for': 'rack'}\n        )\n    )\n    rack = ChainedModelChoiceField(\n        queryset=Rack.objects.all(),\n        chains=(\n            ('site', 'site'),\n        ),\n        required=False,\n        widget=APISelect(\n            api_url='/api/dcim/racks/?site_id={{site}}',\n            attrs={'filter-for': 'devices', 'nullable': 'true'}\n        )\n    )\n    devices = ChainedModelMultipleChoiceField(\n        queryset=Device.objects.filter(cluster__isnull=True),\n        chains=(\n            ('site', 'site'),\n            ('rack', 'rack'),\n        ),\n        label='Device',\n        widget=APISelectMultiple(\n            api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',\n            display_field='display_name',\n            disabled_indicator='cluster'\n        )\n    )\n\n    class Meta:\n        fields = ['region', 'site', 'rack', 'devices']\n\n    def __init__(self, *args, **kwargs):\n\n        super(ClusterAddDevicesForm, self).__init__(*args, **kwargs)\n\n        self.fields['devices'].choices = []\n\n\nclass ClusterRemoveDevicesForm(ConfirmationForm):\n    pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)\n\n\n#\n# Virtual Machines\n#\n\nclass VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):\n    cluster_group = forms.ModelChoiceField(\n        queryset=ClusterGroup.objects.all(),\n        required=False,\n        widget=forms.Select(\n            attrs={'filter-for': 'cluster', 'nullable': 'true'}\n        )\n    )\n    cluster = ChainedModelChoiceField(\n        queryset=Cluster.objects.all(),\n        chains=(\n            ('group', 'cluster_group'),\n        ),\n        widget=APISelect(\n            api_url='/api/virtualization/clusters/?group_id={{cluster_group}}'\n        )\n    )\n\n    class Meta:\n        model = VirtualMachine\n        fields = [\n            'name', 'status', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',\n        ]\n\n    def __init__(self, *args, **kwargs):\n\n        # Initialize helper selector\n        instance = kwargs.get('instance')\n        if instance.pk and instance.cluster is not None:\n            initial = kwargs.get('initial', {}).copy()\n            initial['cluster_group'] = instance.cluster.group\n            kwargs['initial'] = initial\n\n        super(VirtualMachineForm, self).__init__(*args, **kwargs)\n\n\nclass VirtualMachineCSVForm(forms.ModelForm):\n    status = CSVChoiceField(\n        choices=STATUS_CHOICES,\n        required=False,\n        help_text='Operational status of device'\n    )\n    cluster = forms.ModelChoiceField(\n        queryset=Cluster.objects.all(),\n        to_field_name='name',\n        help_text='Name of parent cluster',\n        error_messages={\n            'invalid_choice': 'Invalid cluster name.',\n        }\n    )\n    tenant = forms.ModelChoiceField(\n        queryset=Tenant.objects.all(),\n        required=False,\n        to_field_name='name',\n        help_text='Name of assigned tenant',\n        error_messages={\n            'invalid_choice': 'Tenant not found.'\n        }\n    )\n    platform = forms.ModelChoiceField(\n        queryset=Platform.objects.all(),\n        required=False,\n        to_field_name='name',\n        help_text='Name of assigned platform',\n        error_messages={\n            'invalid_choice': 'Invalid platform.',\n        }\n    )\n\n    class Meta:\n        model = VirtualMachine\n        fields = ['name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']\n\n\nclass VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):\n    pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)\n    status = forms.ChoiceField(choices=add_blank_choice(STATUS_CHOICES), required=False, initial='')\n    cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False)\n    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)\n    platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)\n    vcpus = forms.IntegerField(required=False, label='vCPUs')\n    memory = forms.IntegerField(required=False, label='Memory (MB)')\n    disk = forms.IntegerField(required=False, label='Disk (GB)')\n    comments = CommentField(widget=SmallTextarea)\n\n    class Meta:\n        nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']\n\n\ndef vm_status_choices():\n    status_counts = {}\n    for status in VirtualMachine.objects.values('status').annotate(count=Count('status')).order_by('status'):\n        status_counts[status['status']] = status['count']\n    return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in STATUS_CHOICES]\n\n\nclass VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):\n    model = VirtualMachine\n    q = forms.CharField(required=False, label='Search')\n    cluster_group = FilterChoiceField(\n        queryset=ClusterGroup.objects.all(),\n        to_field_name='slug',\n        null_option=(0, 'None'),\n    )\n    cluster_id = FilterChoiceField(\n        queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),\n        label='Cluster'\n    )\n    status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)\n\n\n#\n# VM interfaces\n#\n\nclass InterfaceForm(BootstrapMixin, forms.ModelForm):\n\n    class Meta:\n        model = Interface\n        fields = ['virtual_machine', 'name', 'form_factor', 'enabled', 'mac_address', 'mtu', 'description']\n        widgets = {\n            'virtual_machine': forms.HiddenInput(),\n            'form_factor': forms.HiddenInput(),\n        }\n\n\nclass InterfaceCreateForm(ComponentForm):\n    name_pattern = ExpandableNameField(label='Name')\n    form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES, initial=IFACE_FF_VIRTUAL, widget=forms.HiddenInput())\n    enabled = forms.BooleanField(required=False)\n    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')\n    mac_address = MACAddressFormField(required=False, label='MAC Address')\n    description = forms.CharField(max_length=100, required=False)\n\n    def __init__(self, *args, **kwargs):\n\n        # Set interfaces enabled by default\n        kwargs['initial'] = kwargs.get('initial', {}).copy()\n        kwargs['initial'].update({'enabled': True})\n\n        super(InterfaceCreateForm, self).__init__(*args, **kwargs)\n\n\nclass InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):\n    pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)\n    virtual_machine = forms.ModelChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.HiddenInput)\n    enabled = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)\n    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')\n    description = forms.CharField(max_length=100, required=False)\n\n    class Meta:\n        nullable_fields = ['mtu', 'description']\n\n\n#\n# Bulk VirtualMachine component creation\n#\n\nclass VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):\n    pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)\n    name_pattern = ExpandableNameField(label='Name')\n\n\nclass VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):\n    form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES, initial=IFACE_FF_VIRTUAL, widget=forms.HiddenInput())\n    enabled = forms.BooleanField(required=False, initial=True)\n    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')\n    description = forms.CharField(max_length=100, required=False)\n", "path": "netbox/virtualization/forms.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nfrom mptt.forms import TreeNodeChoiceField\n\nfrom django import forms\nfrom django.db.models import Count\n\nfrom dcim.constants import VIFACE_FF_CHOICES\nfrom dcim.formfields import MACAddressFormField\nfrom dcim.models import Device, Interface, Platform, Rack, Region, Site\nfrom extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm\nfrom tenancy.forms import TenancyForm\nfrom tenancy.models import Tenant\nfrom utilities.forms import (\n    add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,\n    ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,\n    ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, SlugField, SmallTextarea,\n)\nfrom .constants import STATUS_CHOICES\nfrom .models import Cluster, ClusterGroup, ClusterType, VirtualMachine\n\n\n#\n# Cluster types\n#\n\nclass ClusterTypeForm(BootstrapMixin, forms.ModelForm):\n    slug = SlugField()\n\n    class Meta:\n        model = ClusterType\n        fields = ['name', 'slug']\n\n\n#\n# Cluster groups\n#\n\nclass ClusterGroupForm(BootstrapMixin, forms.ModelForm):\n    slug = SlugField()\n\n    class Meta:\n        model = ClusterGroup\n        fields = ['name', 'slug']\n\n\n#\n# Clusters\n#\n\nclass ClusterForm(BootstrapMixin, CustomFieldForm):\n    comments = CommentField(widget=SmallTextarea)\n\n    class Meta:\n        model = Cluster\n        fields = ['name', 'type', 'group', 'comments']\n\n\nclass ClusterCSVForm(forms.ModelForm):\n    type = forms.ModelChoiceField(\n        queryset=ClusterType.objects.all(),\n        to_field_name='name',\n        help_text='Name of cluster type',\n        error_messages={\n            'invalid_choice': 'Invalid cluster type name.',\n        }\n    )\n    group = forms.ModelChoiceField(\n        queryset=ClusterGroup.objects.all(),\n        to_field_name='name',\n        required=False,\n        help_text='Name of cluster group',\n        error_messages={\n            'invalid_choice': 'Invalid cluster group name.',\n        }\n    )\n\n    class Meta:\n        model = Cluster\n        fields = ['name', 'type', 'group', 'comments']\n\n\nclass ClusterBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):\n    pk = forms.ModelMultipleChoiceField(queryset=Cluster.objects.all(), widget=forms.MultipleHiddenInput)\n    type = forms.ModelChoiceField(queryset=ClusterType.objects.all(), required=False)\n    group = forms.ModelChoiceField(queryset=ClusterGroup.objects.all(), required=False)\n    comments = CommentField(widget=SmallTextarea)\n\n    class Meta:\n        nullable_fields = ['group', 'comments']\n\n\nclass ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):\n    model = Cluster\n    q = forms.CharField(required=False, label='Search')\n    group = FilterChoiceField(\n        queryset=ClusterGroup.objects.annotate(filter_count=Count('clusters')),\n        to_field_name='slug',\n        null_option=(0, 'None'),\n        required=False,\n    )\n    type = FilterChoiceField(\n        queryset=ClusterType.objects.annotate(filter_count=Count('clusters')),\n        to_field_name='slug',\n        required=False,\n    )\n\n\nclass ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):\n    region = TreeNodeChoiceField(\n        queryset=Region.objects.all(),\n        required=False,\n        widget=forms.Select(\n            attrs={'filter-for': 'site', 'nullable': 'true'}\n        )\n    )\n    site = ChainedModelChoiceField(\n        queryset=Site.objects.all(),\n        chains=(\n            ('region', 'region'),\n        ),\n        required=False,\n        widget=APISelect(\n            api_url='/api/dcim/sites/?region_id={{region}}',\n            attrs={'filter-for': 'rack'}\n        )\n    )\n    rack = ChainedModelChoiceField(\n        queryset=Rack.objects.all(),\n        chains=(\n            ('site', 'site'),\n        ),\n        required=False,\n        widget=APISelect(\n            api_url='/api/dcim/racks/?site_id={{site}}',\n            attrs={'filter-for': 'devices', 'nullable': 'true'}\n        )\n    )\n    devices = ChainedModelMultipleChoiceField(\n        queryset=Device.objects.filter(cluster__isnull=True),\n        chains=(\n            ('site', 'site'),\n            ('rack', 'rack'),\n        ),\n        label='Device',\n        widget=APISelectMultiple(\n            api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',\n            display_field='display_name',\n            disabled_indicator='cluster'\n        )\n    )\n\n    class Meta:\n        fields = ['region', 'site', 'rack', 'devices']\n\n    def __init__(self, *args, **kwargs):\n\n        super(ClusterAddDevicesForm, self).__init__(*args, **kwargs)\n\n        self.fields['devices'].choices = []\n\n\nclass ClusterRemoveDevicesForm(ConfirmationForm):\n    pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)\n\n\n#\n# Virtual Machines\n#\n\nclass VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):\n    cluster_group = forms.ModelChoiceField(\n        queryset=ClusterGroup.objects.all(),\n        required=False,\n        widget=forms.Select(\n            attrs={'filter-for': 'cluster', 'nullable': 'true'}\n        )\n    )\n    cluster = ChainedModelChoiceField(\n        queryset=Cluster.objects.all(),\n        chains=(\n            ('group', 'cluster_group'),\n        ),\n        widget=APISelect(\n            api_url='/api/virtualization/clusters/?group_id={{cluster_group}}'\n        )\n    )\n\n    class Meta:\n        model = VirtualMachine\n        fields = [\n            'name', 'status', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',\n        ]\n\n    def __init__(self, *args, **kwargs):\n\n        # Initialize helper selector\n        instance = kwargs.get('instance')\n        if instance.pk and instance.cluster is not None:\n            initial = kwargs.get('initial', {}).copy()\n            initial['cluster_group'] = instance.cluster.group\n            kwargs['initial'] = initial\n\n        super(VirtualMachineForm, self).__init__(*args, **kwargs)\n\n\nclass VirtualMachineCSVForm(forms.ModelForm):\n    status = CSVChoiceField(\n        choices=STATUS_CHOICES,\n        required=False,\n        help_text='Operational status of device'\n    )\n    cluster = forms.ModelChoiceField(\n        queryset=Cluster.objects.all(),\n        to_field_name='name',\n        help_text='Name of parent cluster',\n        error_messages={\n            'invalid_choice': 'Invalid cluster name.',\n        }\n    )\n    tenant = forms.ModelChoiceField(\n        queryset=Tenant.objects.all(),\n        required=False,\n        to_field_name='name',\n        help_text='Name of assigned tenant',\n        error_messages={\n            'invalid_choice': 'Tenant not found.'\n        }\n    )\n    platform = forms.ModelChoiceField(\n        queryset=Platform.objects.all(),\n        required=False,\n        to_field_name='name',\n        help_text='Name of assigned platform',\n        error_messages={\n            'invalid_choice': 'Invalid platform.',\n        }\n    )\n\n    class Meta:\n        model = VirtualMachine\n        fields = ['name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']\n\n\nclass VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):\n    pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)\n    status = forms.ChoiceField(choices=add_blank_choice(STATUS_CHOICES), required=False, initial='')\n    cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False)\n    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)\n    platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)\n    vcpus = forms.IntegerField(required=False, label='vCPUs')\n    memory = forms.IntegerField(required=False, label='Memory (MB)')\n    disk = forms.IntegerField(required=False, label='Disk (GB)')\n    comments = CommentField(widget=SmallTextarea)\n\n    class Meta:\n        nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']\n\n\ndef vm_status_choices():\n    status_counts = {}\n    for status in VirtualMachine.objects.values('status').annotate(count=Count('status')).order_by('status'):\n        status_counts[status['status']] = status['count']\n    return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in STATUS_CHOICES]\n\n\nclass VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):\n    model = VirtualMachine\n    q = forms.CharField(required=False, label='Search')\n    cluster_group = FilterChoiceField(\n        queryset=ClusterGroup.objects.all(),\n        to_field_name='slug',\n        null_option=(0, 'None'),\n    )\n    cluster_id = FilterChoiceField(\n        queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),\n        label='Cluster'\n    )\n    status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)\n\n\n#\n# VM interfaces\n#\n\nclass InterfaceForm(BootstrapMixin, forms.ModelForm):\n\n    class Meta:\n        model = Interface\n        fields = ['virtual_machine', 'name', 'form_factor', 'enabled', 'mac_address', 'mtu', 'description']\n        widgets = {\n            'virtual_machine': forms.HiddenInput(),\n        }\n\n\nclass InterfaceCreateForm(ComponentForm):\n    name_pattern = ExpandableNameField(label='Name')\n    form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES)\n    enabled = forms.BooleanField(required=False)\n    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')\n    mac_address = MACAddressFormField(required=False, label='MAC Address')\n    description = forms.CharField(max_length=100, required=False)\n\n    def __init__(self, *args, **kwargs):\n\n        # Set interfaces enabled by default\n        kwargs['initial'] = kwargs.get('initial', {}).copy()\n        kwargs['initial'].update({'enabled': True})\n\n        super(InterfaceCreateForm, self).__init__(*args, **kwargs)\n\n\nclass InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):\n    pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)\n    virtual_machine = forms.ModelChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.HiddenInput)\n    enabled = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)\n    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')\n    description = forms.CharField(max_length=100, required=False)\n\n    class Meta:\n        nullable_fields = ['mtu', 'description']\n\n\n#\n# Bulk VirtualMachine component creation\n#\n\nclass VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):\n    pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)\n    name_pattern = ExpandableNameField(label='Name')\n\n\nclass VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):\n    form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES)\n    enabled = forms.BooleanField(required=False, initial=True)\n    mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')\n    description = forms.CharField(max_length=100, required=False)\n", "path": "netbox/virtualization/forms.py"}]}