Browse Source

Fixes #1229: Fix validation error on forms where API search is used

Jeremy Stretch 8 years ago
parent
commit
1dd5e2c926

+ 14 - 14
netbox/circuits/forms.py

@@ -167,7 +167,9 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
     )
     rack = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'site'},
+        chains=(
+            ('site', 'site'),
+        ),
         required=False,
         label='Rack',
         widget=APISelect(
@@ -177,7 +179,10 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
     )
     device = ChainedModelChoiceField(
         queryset=Device.objects.all(),
-        chains={'site': 'site', 'rack': 'rack'},
+        chains=(
+            ('site', 'site'),
+            ('rack', 'rack'),
+        ),
         required=False,
         label='Device',
         widget=APISelect(
@@ -186,20 +191,13 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
             attrs={'filter-for': 'interface'}
         )
     )
-    livesearch = forms.CharField(
-        required=False,
-        label='Device',
-        widget=Livesearch(
-            query_key='q',
-            query_url='dcim-api:device-list',
-            field_to_update='device'
-        )
-    )
     interface = ChainedModelChoiceField(
         queryset=Interface.objects.exclude(form_factor__in=VIRTUAL_IFACE_TYPES).select_related(
             'circuit_termination', 'connected_as_a', 'connected_as_b'
         ),
-        chains={'device': 'device'},
+        chains=(
+            ('device', 'device'),
+        ),
         required=False,
         label='Interface',
         widget=APISelect(
@@ -210,8 +208,10 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
 
     class Meta:
         model = CircuitTermination
-        fields = ['term_side', 'site', 'rack', 'device', 'livesearch', 'interface', 'port_speed', 'upstream_speed',
-                  'xconnect_id', 'pp_info']
+        fields = [
+            'term_side', 'site', 'rack', 'device', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id',
+            'pp_info',
+        ]
         help_texts = {
             'port_speed': "Physical circuit speed",
             'xconnect_id': "ID of the local cross-connect",

+ 86 - 26
netbox/dcim/forms.py

@@ -190,7 +190,9 @@ class RackRoleForm(BootstrapMixin, forms.ModelForm):
 class RackForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     group = ChainedModelChoiceField(
         queryset=RackGroup.objects.all(),
-        chains={'site': 'site'},
+        chains=(
+            ('site', 'site'),
+        ),
         required=False,
         widget=APISelect(
             api_url='/api/dcim/rack-groups/?site_id={{site}}',
@@ -545,7 +547,9 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     )
     rack = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'site'},
+        chains=(
+            ('site', 'site'),
+        ),
         required=False,
         widget=APISelect(
             api_url='/api/dcim/racks/?site_id={{site}}',
@@ -570,7 +574,9 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     )
     device_type = ChainedModelChoiceField(
         queryset=DeviceType.objects.all(),
-        chains={'manufacturer': 'manufacturer'},
+        chains=(
+            ('manufacturer', 'manufacturer'),
+        ),
         label='Device type',
         widget=APISelect(
             api_url='/api/dcim/device-types/?manufacturer_id={{manufacturer}}',
@@ -957,20 +963,29 @@ class ConsoleConnectionImportForm(BootstrapMixin, BulkImportForm):
 class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
     site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
-        widget=forms.HiddenInput(),
+        required=False,
+        widget=forms.Select(
+            attrs={'filter-for': 'rack'}
+        )
     )
     rack = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'site'},
+        chains=(
+            ('site', 'site'),
+        ),
         label='Rack',
         required=False,
-        widget=forms.Select(
+        widget=APISelect(
+            api_url='/api/dcim/racks/?site_id={{site}}',
             attrs={'filter-for': 'console_server', 'nullable': 'true'}
         )
     )
     console_server = ChainedModelChoiceField(
         queryset=Device.objects.filter(device_type__is_console_server=True),
-        chains={'site': 'site', 'rack': 'rack'},
+        chains=(
+            ('site', 'site'),
+            ('rack', 'rack'),
+        ),
         label='Console Server',
         required=False,
         widget=APISelect(
@@ -990,7 +1005,9 @@ class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelF
     )
     cs_port = ChainedModelChoiceField(
         queryset=ConsoleServerPort.objects.all(),
-        chains={'device': 'console_server'},
+        chains=(
+            ('device', 'console_server'),
+        ),
         label='Port',
         widget=APISelect(
             api_url='/api/dcim/console-server-ports/?device_id={{console_server}}',
@@ -1035,20 +1052,29 @@ class ConsoleServerPortCreateForm(DeviceComponentForm):
 class ConsoleServerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
     site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
-        widget=forms.HiddenInput(),
+        required=False,
+        widget=forms.Select(
+            attrs={'filter-for': 'rack'}
+        )
     )
     rack = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'site'},
+        chains=(
+            ('site', 'site'),
+        ),
         label='Rack',
         required=False,
-        widget=forms.Select(
+        widget=APISelect(
+            api_url='/api/dcim/racks/?site_id={{site}}',
             attrs={'filter-for': 'device', 'nullable': 'true'}
         )
     )
     device = ChainedModelChoiceField(
         queryset=Device.objects.all(),
-        chains={'site': 'site', 'rack': 'rack'},
+        chains=(
+            ('site', 'site'),
+            ('rack', 'rack'),
+        ),
         label='Device',
         required=False,
         widget=APISelect(
@@ -1068,7 +1094,9 @@ class ConsoleServerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.
     )
     port = ChainedModelChoiceField(
         queryset=ConsolePort.objects.all(),
-        chains={'device': 'device'},
+        chains=(
+            ('device', 'device'),
+        ),
         label='Port',
         widget=APISelect(
             api_url='/api/dcim/console-ports/?device_id={{device}}',
@@ -1182,19 +1210,31 @@ class PowerConnectionImportForm(BootstrapMixin, BulkImportForm):
 
 
 class PowerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
-    site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.HiddenInput())
+    site = forms.ModelChoiceField(
+        queryset=Site.objects.all(),
+        required=False,
+        widget=forms.Select(
+            attrs={'filter-for': 'rack'}
+        )
+    )
     rack = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'site'},
+        chains=(
+            ('site', 'site'),
+        ),
         label='Rack',
         required=False,
-        widget=forms.Select(
+        widget=APISelect(
+            api_url='/api/dcim/racks/?site_id={{site}}',
             attrs={'filter-for': 'pdu', 'nullable': 'true'}
         )
     )
     pdu = ChainedModelChoiceField(
         queryset=Device.objects.all(),
-        chains={'site': 'site', 'rack': 'rack'},
+        chains=(
+            ('site', 'site'),
+            ('rack', 'rack'),
+        ),
         label='PDU',
         required=False,
         widget=APISelect(
@@ -1214,7 +1254,9 @@ class PowerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
     )
     power_outlet = ChainedModelChoiceField(
         queryset=PowerOutlet.objects.all(),
-        chains={'device': 'pdu'},
+        chains=(
+            ('device', 'pdu'),
+        ),
         label='Outlet',
         widget=APISelect(
             api_url='/api/dcim/power-outlets/?device_id={{pdu}}',
@@ -1259,20 +1301,29 @@ class PowerOutletCreateForm(DeviceComponentForm):
 class PowerOutletConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
     site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
-        widget=forms.HiddenInput()
+        required=False,
+        widget=forms.Select(
+            attrs={'filter-for': 'rack'}
+        )
     )
     rack = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'site'},
+        chains=(
+            ('site', 'site'),
+        ),
         label='Rack',
         required=False,
-        widget=forms.Select(
+        widget=APISelect(
+            api_url='/api/dcim/racks/?site_id={{site}}',
             attrs={'filter-for': 'device', 'nullable': 'true'}
         )
     )
     device = ChainedModelChoiceField(
         queryset=Device.objects.all(),
-        chains={'site': 'site', 'rack': 'rack'},
+        chains=(
+            ('site', 'site'),
+            ('rack', 'rack'),
+        ),
         label='Device',
         required=False,
         widget=APISelect(
@@ -1292,7 +1343,9 @@ class PowerOutletConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
     )
     port = ChainedModelChoiceField(
         queryset=PowerPort.objects.all(),
-        chains={'device': 'device'},
+        chains=(
+            ('device', 'device'),
+        ),
         label='Port',
         widget=APISelect(
             api_url='/api/dcim/power-ports/?device_id={{device}}',
@@ -1412,7 +1465,9 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
     )
     rack_b = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'site_b'},
+        chains=(
+            ('site', 'site_b'),
+        ),
         label='Rack',
         required=False,
         widget=APISelect(
@@ -1422,7 +1477,10 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
     )
     device_b = ChainedModelChoiceField(
         queryset=Device.objects.all(),
-        chains={'site': 'site_b', 'rack': 'rack_b'},
+        chains=(
+            ('site', 'site_b'),
+            ('rack', 'rack_b'),
+        ),
         label='Device',
         required=False,
         widget=APISelect(
@@ -1444,7 +1502,9 @@ class InterfaceConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFor
         queryset=Interface.objects.exclude(form_factor__in=VIRTUAL_IFACE_TYPES).select_related(
             'circuit_termination', 'connected_as_a', 'connected_as_b'
         ),
-        chains={'device': 'device_b'},
+        chains=(
+            ('device', 'device_b'),
+        ),
         label='Interface',
         widget=APISelect(
             api_url='/api/dcim/interfaces/?device_id={{device_b}}&type=physical',

+ 17 - 17
netbox/dcim/views.py

@@ -944,9 +944,9 @@ def consoleport_connect(request, pk):
 
     else:
         form = forms.ConsolePortConnectionForm(instance=consoleport, initial={
-            'site': request.GET.get('site', consoleport.device.site),
-            'rack': request.GET.get('rack', None),
-            'console_server': request.GET.get('console_server', None),
+            'site': request.GET.get('site'),
+            'rack': request.GET.get('rack'),
+            'console_server': request.GET.get('console_server'),
             'connection_status': CONNECTION_STATUS_CONNECTED,
         })
 
@@ -1061,9 +1061,9 @@ def consoleserverport_connect(request, pk):
 
     else:
         form = forms.ConsoleServerPortConnectionForm(initial={
-            'site': request.GET.get('site', consoleserverport.device.site),
-            'rack': request.GET.get('rack', None),
-            'device': request.GET.get('device', None),
+            'site': request.GET.get('site'),
+            'rack': request.GET.get('rack'),
+            'device': request.GET.get('device'),
             'connection_status': CONNECTION_STATUS_CONNECTED,
         })
 
@@ -1167,9 +1167,9 @@ def powerport_connect(request, pk):
 
     else:
         form = forms.PowerPortConnectionForm(instance=powerport, initial={
-            'site': request.GET.get('site', powerport.device.site),
-            'rack': request.GET.get('rack', None),
-            'pdu': request.GET.get('pdu', None),
+            'site': request.GET.get('site'),
+            'rack': request.GET.get('rack'),
+            'pdu': request.GET.get('pdu'),
             'connection_status': CONNECTION_STATUS_CONNECTED,
         })
 
@@ -1284,9 +1284,9 @@ def poweroutlet_connect(request, pk):
 
     else:
         form = forms.PowerOutletConnectionForm(initial={
-            'site': request.GET.get('site', poweroutlet.device.site),
-            'rack': request.GET.get('rack', None),
-            'device': request.GET.get('device', None),
+            'site': request.GET.get('site'),
+            'rack': request.GET.get('rack'),
+            'device': request.GET.get('device'),
             'connection_status': CONNECTION_STATUS_CONNECTED,
         })
 
@@ -1616,11 +1616,11 @@ def interfaceconnection_add(request, pk):
 
     else:
         form = forms.InterfaceConnectionForm(device, initial={
-            'interface_a': request.GET.get('interface_a', None),
-            'site_b': request.GET.get('site_b', device.site),
-            'rack_b': request.GET.get('rack_b', None),
-            'device_b': request.GET.get('device_b', None),
-            'interface_b': request.GET.get('interface_b', None),
+            'interface_a': request.GET.get('interface_a'),
+            'site_b': request.GET.get('site_b'),
+            'rack_b': request.GET.get('rack_b'),
+            'device_b': request.GET.get('device_b'),
+            'interface_b': request.GET.get('interface_b'),
         })
 
     return render(request, 'dcim/interfaceconnection_edit.html', {

+ 40 - 15
netbox/ipam/forms.py

@@ -168,12 +168,21 @@ class RoleForm(BootstrapMixin, forms.ModelForm):
 
 class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     site = forms.ModelChoiceField(
-        queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
+        queryset=Site.objects.all(),
+        required=False,
+        label='Site',
+        widget=forms.Select(
             attrs={'filter-for': 'vlan', 'nullable': 'true'}
         )
     )
     vlan = ChainedModelChoiceField(
-        queryset=VLAN.objects.all(), chains={'site': 'site'}, required=False, label='VLAN', widget=APISelect(
+        queryset=VLAN.objects.all(),
+        chains=(
+            ('site', 'site'),
+        ),
+        required=False,
+        label='VLAN',
+        widget=APISelect(
             api_url='/api/ipam/vlans/?site_id={{site}}', display_field='display_name'
         )
     )
@@ -322,7 +331,9 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
     )
     interface_rack = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'interface_site'},
+        chains=(
+            ('site', 'interface_site'),
+        ),
         required=False,
         label='Rack',
         widget=APISelect(
@@ -333,7 +344,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
     )
     interface_device = ChainedModelChoiceField(
         queryset=Device.objects.all(),
-        chains={'site': 'interface_site', 'rack': 'interface_rack'},
+        chains=(
+            ('site', 'interface_site'),
+            ('rack', 'interface_rack'),
+        ),
         required=False,
         label='Device',
         widget=APISelect(
@@ -344,7 +358,9 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
     )
     interface = ChainedModelChoiceField(
         queryset=Interface.objects.all(),
-        chains={'device': 'interface_device'},
+        chains=(
+            ('device', 'interface_device'),
+        ),
         required=False,
         widget=APISelect(
             api_url='/api/dcim/interfaces/?device_id={{interface_device}}'
@@ -355,34 +371,41 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         required=False,
         label='Site',
         widget=forms.Select(
-            attrs={'filter-for': 'nat_device'}
+            attrs={'filter-for': 'nat_rack'}
         )
     )
     nat_rack = ChainedModelChoiceField(
         queryset=Rack.objects.all(),
-        chains={'site': 'nat_site'},
+        chains=(
+            ('site', 'nat_site'),
+        ),
         required=False,
         label='Rack',
         widget=APISelect(
-            api_url='/api/dcim/racks/?site_id={{interface_site}}',
+            api_url='/api/dcim/racks/?site_id={{nat_site}}',
             display_field='display_name',
             attrs={'filter-for': 'nat_device', 'nullable': 'true'}
         )
     )
     nat_device = ChainedModelChoiceField(
         queryset=Device.objects.all(),
-        chains={'site': 'nat_site'},
+        chains=(
+            ('site', 'nat_site'),
+            ('rack', 'nat_rack'),
+        ),
         required=False,
         label='Device',
         widget=APISelect(
-            api_url='/api/dcim/devices/?site_id={{nat_site}}',
+            api_url='/api/dcim/devices/?site_id={{nat_site}}&rack_id={{nat_rack}}',
             display_field='display_name',
             attrs={'filter-for': 'nat_inside'}
         )
     )
     nat_inside = ChainedModelChoiceField(
         queryset=IPAddress.objects.all(),
-        chains={'interface__device': 'nat_device'},
+        chains=(
+            ('interface__device', 'nat_device'),
+        ),
         required=False,
         label='IP Address',
         widget=APISelect(
@@ -392,7 +415,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
     )
     livesearch = forms.CharField(
         required=False,
-        label='IP Address',
+        label='Search',
         widget=Livesearch(
             query_key='q',
             query_url='ipam-api:ipaddress-list',
@@ -405,8 +428,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
     class Meta:
         model = IPAddress
         fields = [
-            'address', 'vrf', 'status', 'description', 'interface', 'primary_for_device', 'nat_inside', 'tenant_group',
-            'tenant',
+            'address', 'vrf', 'status', 'description', 'interface', 'primary_for_device', 'nat_site', 'nat_rack',
+            'nat_inside', 'tenant_group', 'tenant',
         ]
 
     def __init__(self, *args, **kwargs):
@@ -627,7 +650,9 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     )
     group = ChainedModelChoiceField(
         queryset=VLANGroup.objects.all(),
-        chains={'site': 'site'},
+        chains=(
+            ('site', 'site'),
+        ),
         required=False,
         label='Group',
         widget=APISelect(

+ 2 - 17
netbox/templates/circuits/circuittermination_edit.html

@@ -45,23 +45,8 @@
                             </div>
                         </div>
                         {% render_field form.site %}
-                        <div class="row">
-                            <div class="col-md-9 col-md-offset-3">
-                                <ul class="nav nav-tabs" role="tablist">
-                                    <li role="presentation" class="active"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
-                                    <li role="presentation"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="tab-content">
-                            <div class="tab-pane active" id="select">
-                                {% render_field form.rack %}
-                                {% render_field form.device %}
-                            </div>
-                            <div class="tab-pane" id="search">
-                                {% render_field form.livesearch %}
-                            </div>
-                        </div>
+                        {% render_field form.rack %}
+                        {% render_field form.device %}
                         {% render_field form.interface %}
                     </div>
                 </div>

+ 1 - 6
netbox/templates/dcim/consoleport_connect.html

@@ -32,12 +32,7 @@
                             {% render_field form.livesearch %}
                         </div>
                         <div class="tab-pane" id="select">
-                            <div class="form-group">
-                                <label class="col-md-3 control-label">Site</label>
-                                <div class="col-md-9">
-                                    <p class="form-control-static">{{ consoleport.device.site }}</p>
-                                </div>
-                            </div>
+                            {% render_field form.site %}
                             {% render_field form.rack %}
                             {% render_field form.console_server %}
                         </div>

+ 1 - 6
netbox/templates/dcim/consoleserverport_connect.html

@@ -32,12 +32,7 @@
                             {% render_field form.livesearch %}
                         </div>
                         <div class="tab-pane" id="select">
-                            <div class="form-group">
-                                <label class="col-md-3 control-label">Site</label>
-                                <div class="col-md-9">
-                                    <p class="form-control-static">{{ consoleserverport.device.site }}</p>
-                                </div>
-                            </div>
+                            {% render_field form.site %}
                             {% render_field form.rack %}
                             {% render_field form.device %}
                         </div>

+ 1 - 6
netbox/templates/dcim/poweroutlet_connect.html

@@ -32,12 +32,7 @@
                             {% render_field form.livesearch %}
                         </div>
                         <div class="tab-pane" id="select">
-                            <div class="form-group">
-                                <label class="col-md-3 control-label">Site</label>
-                                <div class="col-md-9">
-                                    <p class="form-control-static">{{ poweroutlet.device.site }}</p>
-                                </div>
-                            </div>
+                            {% render_field form.site %}
                             {% render_field form.rack %}
                             {% render_field form.device %}
                         </div>

+ 1 - 6
netbox/templates/dcim/powerport_connect.html

@@ -32,12 +32,7 @@
                             {% render_field form.livesearch %}
                         </div>
                         <div class="tab-pane" id="select">
-                            <div class="form-group">
-                                <label class="col-md-3 control-label">Site</label>
-                                <div class="col-md-9">
-                                    <p class="form-control-static">{{ powerport.device.site }}</p>
-                                </div>
-                            </div>
+                            {% render_field form.site %}
                             {% render_field form.rack %}
                             {% render_field form.pdu %}
                         </div>

+ 1 - 0
netbox/templates/ipam/ipaddress_edit.html

@@ -47,6 +47,7 @@
             <div class="tab-content">
                 <div class="tab-pane active" id="select">
                     {% render_field form.nat_site %}
+                    {% render_field form.nat_rack %}
                     {% render_field form.nat_device %}
                 </div>
                 <div class="tab-pane" id="search">

+ 3 - 1
netbox/tenancy/forms.py

@@ -81,7 +81,9 @@ class TenancyForm(ChainedFieldsMixin, forms.Form):
     )
     tenant = ChainedModelChoiceField(
         queryset=Tenant.objects.all(),
-        chains={'group': 'tenant_group'},
+        chains=(
+            ('group', 'tenant_group'),
+        ),
         required=False,
         widget=APISelect(
             api_url='/api/tenancy/tenants/?group_id={{tenant_group}}'

+ 4 - 2
netbox/utilities/forms.py

@@ -443,17 +443,19 @@ class ChainedFieldsMixin(forms.BaseForm):
             if isinstance(field, ChainedModelChoiceField):
 
                 filters_dict = {}
-                for db_field, parent_field in field.chains.items():
+                for (db_field, parent_field) in field.chains:
                     if self.is_bound and self.data.get(parent_field):
                         filters_dict[db_field] = self.data[parent_field]
                     elif self.initial.get(parent_field):
                         filters_dict[db_field] = self.initial[parent_field]
                     elif self.fields[parent_field].widget.attrs.get('nullable'):
                         filters_dict[db_field] = None
+                    else:
+                        break
 
                 if filters_dict:
                     field.queryset = field.queryset.filter(**filters_dict)
-                else:
+                elif not self.is_bound:
                     field.queryset = field.queryset.none()