diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index f34abba28..345e3379d 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -117,6 +117,7 @@ class CircuitTypeEditView(CircuitTypeCreateView): class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'circuits.delete_circuittype' cls = CircuitType + queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')) table = tables.CircuitTypeTable default_return_url = 'circuits:circuittype_list' @@ -184,6 +185,7 @@ class CircuitBulkImportView(PermissionRequiredMixin, BulkImportView): class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'circuits.change_circuit' cls = Circuit + queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site') filter = filters.CircuitFilter table = tables.CircuitTable form = forms.CircuitBulkEditForm @@ -193,6 +195,7 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView): class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'circuits.delete_circuit' cls = Circuit + queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site') filter = filters.CircuitFilter table = tables.CircuitTable default_return_url = 'circuits:circuit_list' diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 7c13be4f0..427f0bb42 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -285,19 +285,10 @@ class DeviceTypeTable(BaseTable): is_pdu = tables.BooleanColumn(verbose_name='PDU') is_network_device = tables.BooleanColumn(verbose_name='Net') subdevice_role = tables.TemplateColumn(SUBDEVICE_ROLE_TEMPLATE, verbose_name='Subdevice Role') + instance_count = tables.Column(verbose_name='Instances') class Meta(BaseTable.Meta): model = DeviceType - fields = ( - 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu', - 'is_network_device', 'subdevice_role', - ) - - -class DeviceTypeDetailTable(DeviceTypeTable): - instance_count = tables.Column(verbose_name='Instances') - - class Meta(DeviceTypeTable.Meta): fields = ( 'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'instance_count', @@ -315,7 +306,6 @@ class ConsolePortTemplateTable(BaseTable): model = ConsolePortTemplate fields = ('pk', 'name') empty_text = "None" - show_header = False class ConsoleServerPortTemplateTable(BaseTable): @@ -325,7 +315,6 @@ class ConsoleServerPortTemplateTable(BaseTable): model = ConsoleServerPortTemplate fields = ('pk', 'name') empty_text = "None" - show_header = False class PowerPortTemplateTable(BaseTable): @@ -335,7 +324,6 @@ class PowerPortTemplateTable(BaseTable): model = PowerPortTemplate fields = ('pk', 'name') empty_text = "None" - show_header = False class PowerOutletTemplateTable(BaseTable): @@ -345,7 +333,6 @@ class PowerOutletTemplateTable(BaseTable): model = PowerOutletTemplate fields = ('pk', 'name') empty_text = "None" - show_header = False class InterfaceTemplateTable(BaseTable): @@ -356,7 +343,6 @@ class InterfaceTemplateTable(BaseTable): model = InterfaceTemplate fields = ('pk', 'name', 'mgmt_only', 'form_factor') empty_text = "None" - show_header = False class DeviceBayTemplateTable(BaseTable): @@ -366,7 +352,6 @@ class DeviceBayTemplateTable(BaseTable): model = DeviceBayTemplate fields = ('pk', 'name') empty_text = "None" - show_header = False # diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index d07bb1b9d..9af1a320c 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -205,6 +205,7 @@ class RegionEditView(RegionCreateView): class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_region' cls = Region + queryset = Region.objects.annotate(site_count=Count('sites')) table = tables.RegionTable default_return_url = 'dcim:region_list' @@ -274,6 +275,7 @@ class SiteBulkImportView(PermissionRequiredMixin, BulkImportView): class SiteBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_site' cls = Site + queryset = Site.objects.select_related('region', 'tenant') filter = filters.SiteFilter table = tables.SiteTable form = forms.SiteBulkEditForm @@ -308,6 +310,7 @@ class RackGroupEditView(RackGroupCreateView): class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rackgroup' cls = RackGroup + queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks')) filter = filters.RackGroupFilter table = tables.RackGroupTable default_return_url = 'dcim:rackgroup_list' @@ -339,6 +342,7 @@ class RackRoleEditView(RackRoleCreateView): class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rackrole' cls = RackRole + queryset = RackRole.objects.annotate(rack_count=Count('racks')) table = tables.RackRoleTable default_return_url = 'dcim:rackrole_list' @@ -458,6 +462,7 @@ class RackBulkImportView(PermissionRequiredMixin, BulkImportView): class RackBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_rack' cls = Rack + queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role') filter = filters.RackFilter table = tables.RackTable form = forms.RackBulkEditForm @@ -467,6 +472,7 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView): class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rack' cls = Rack + queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role') filter = filters.RackFilter table = tables.RackTable default_return_url = 'dcim:rack_list' @@ -544,6 +550,7 @@ class ManufacturerEditView(ManufacturerCreateView): class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_manufacturer' cls = Manufacturer + queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types')) table = tables.ManufacturerTable default_return_url = 'dcim:manufacturer_list' @@ -556,7 +563,7 @@ class DeviceTypeListView(ObjectListView): queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter filter_form = forms.DeviceTypeFilterForm - table = tables.DeviceTypeDetailTable + table = tables.DeviceTypeTable template_name = 'dcim/devicetype_list.html' @@ -568,24 +575,30 @@ class DeviceTypeView(View): # Component tables consoleport_table = tables.ConsolePortTemplateTable( - natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')) + natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + show_header=False ) consoleserverport_table = tables.ConsoleServerPortTemplateTable( - natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')) + natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + show_header=False ) powerport_table = tables.PowerPortTemplateTable( - natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')) + natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + show_header=False ) poweroutlet_table = tables.PowerOutletTemplateTable( - natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')) + natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + show_header=False ) interface_table = tables.InterfaceTemplateTable( list(InterfaceTemplate.objects.order_naturally( devicetype.interface_ordering - ).filter(device_type=devicetype)) + ).filter(device_type=devicetype)), + show_header=False ) devicebay_table = tables.DeviceBayTemplateTable( - natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')) + natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')), + show_header=False ) if request.user.has_perm('dcim.change_devicetype'): consoleport_table.base_columns['pk'].visible = True @@ -627,6 +640,7 @@ class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView): class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_devicetype' cls = DeviceType + queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter table = tables.DeviceTypeTable form = forms.DeviceTypeBulkEditForm @@ -636,6 +650,7 @@ class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView): class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicetype' cls = DeviceType + queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter table = tables.DeviceTypeTable default_return_url = 'dcim:devicetype_list' @@ -777,6 +792,7 @@ class DeviceRoleEditView(DeviceRoleCreateView): class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicerole' cls = DeviceRole + queryset = DeviceRole.objects.annotate(device_count=Count('devices')) table = tables.DeviceRoleTable default_return_url = 'dcim:devicerole_list' @@ -807,6 +823,7 @@ class PlatformEditView(PlatformCreateView): class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_platform' cls = Platform + queryset = Platform.objects.annotate(device_count=Count('devices')) table = tables.PlatformTable default_return_url = 'dcim:platform_list' @@ -971,6 +988,7 @@ class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView): class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_device' cls = Device + queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') filter = filters.DeviceFilter table = tables.DeviceTable form = forms.DeviceBulkEditForm @@ -980,6 +998,7 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView): class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_device' cls = Device + queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') filter = filters.DeviceFilter table = tables.DeviceTable default_return_url = 'dcim:device_list' diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 8753d5f94..65ab5b2e4 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -161,6 +161,14 @@ class RIRTable(BaseTable): name = tables.LinkColumn(verbose_name='Name') is_private = tables.BooleanColumn(verbose_name='Private') aggregate_count = tables.Column(verbose_name='Aggregates') + actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='') + + class Meta(BaseTable.Meta): + model = RIR + fields = ('pk', 'name', 'is_private', 'aggregate_count', 'actions') + + +class RIRDetailTable(RIRTable): stats_total = tables.Column(accessor='stats.total', verbose_name='Total', footer=lambda table: sum(r.stats['total'] for r in table.data)) stats_active = tables.Column(accessor='stats.active', verbose_name='Active', @@ -172,12 +180,12 @@ class RIRTable(BaseTable): stats_available = tables.Column(accessor='stats.available', verbose_name='Available', footer=lambda table: sum(r.stats['available'] for r in table.data)) utilization = tables.TemplateColumn(template_code=RIR_UTILIZATION, verbose_name='Utilization') - actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='') - class Meta(BaseTable.Meta): - model = RIR - fields = ('pk', 'name', 'is_private', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved', - 'stats_deprecated', 'stats_available', 'utilization', 'actions') + class Meta(RIRTable.Meta): + fields = ( + 'pk', 'name', 'is_private', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved', + 'stats_deprecated', 'stats_available', 'utilization', 'actions', + ) # diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index a669cb428..05f16aa35 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -142,6 +142,7 @@ class VRFBulkImportView(PermissionRequiredMixin, BulkImportView): class VRFBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_vrf' cls = VRF + queryset = VRF.objects.select_related('tenant') filter = filters.VRFFilter table = tables.VRFTable form = forms.VRFBulkEditForm @@ -151,7 +152,9 @@ class VRFBulkEditView(PermissionRequiredMixin, BulkEditView): class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_vrf' cls = VRF + queryset = VRF.objects.select_related('tenant') filter = filters.VRFFilter + table = tables.VRFTable default_return_url = 'ipam:vrf_list' @@ -163,7 +166,7 @@ class RIRListView(ObjectListView): queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')) filter = filters.RIRFilter filter_form = forms.RIRFilterForm - table = tables.RIRTable + table = tables.RIRDetailTable template_name = 'ipam/rir_list.html' def alter_queryset(self, request): @@ -259,7 +262,9 @@ class RIREditView(RIRCreateView): class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_rir' cls = RIR + queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')) filter = filters.RIRFilter + table = tables.RIRTable default_return_url = 'ipam:rir_list' @@ -360,6 +365,7 @@ class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView): class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_aggregate' cls = Aggregate + queryset = Aggregate.objects.select_related('rir') filter = filters.AggregateFilter table = tables.AggregateTable form = forms.AggregateBulkEditForm @@ -369,7 +375,9 @@ class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView): class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_aggregate' cls = Aggregate + queryset = Aggregate.objects.select_related('rir') filter = filters.AggregateFilter + table = tables.AggregateTable default_return_url = 'ipam:aggregate_list' @@ -399,6 +407,7 @@ class RoleEditView(RoleCreateView): class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_role' cls = Role + table = tables.RoleTable default_return_url = 'ipam:role_list' @@ -564,6 +573,7 @@ class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView): class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_prefix' cls = Prefix + queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') filter = filters.PrefixFilter table = tables.PrefixTable form = forms.PrefixBulkEditForm @@ -573,7 +583,9 @@ class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView): class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_prefix' cls = Prefix + queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') filter = filters.PrefixFilter + table = tables.PrefixTable default_return_url = 'ipam:prefix_list' @@ -669,6 +681,7 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView): class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_ipaddress' cls = IPAddress + queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device') filter = filters.IPAddressFilter table = tables.IPAddressTable form = forms.IPAddressBulkEditForm @@ -678,7 +691,9 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView): class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_ipaddress' cls = IPAddress + queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device') filter = filters.IPAddressFilter + table = tables.IPAddressTable default_return_url = 'ipam:ipaddress_list' @@ -710,7 +725,9 @@ class VLANGroupEditView(VLANGroupCreateView): class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_vlangroup' cls = VLANGroup + queryset = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans')) filter = filters.VLANGroupFilter + table = tables.VLANGroupTable default_return_url = 'ipam:vlangroup_list' @@ -771,6 +788,7 @@ class VLANBulkImportView(PermissionRequiredMixin, BulkImportView): class VLANBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_vlan' cls = VLAN + queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role') filter = filters.VLANFilter table = tables.VLANTable form = forms.VLANBulkEditForm @@ -780,7 +798,9 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView): class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_vlan' cls = VLAN + queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role') filter = filters.VLANFilter + table = tables.VLANTable default_return_url = 'ipam:vlan_list' diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index 85d2bd9ee..71cf42c13 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -55,6 +55,8 @@ class SecretRoleEditView(SecretRoleCreateView): class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'secrets.delete_secretrole' cls = SecretRole + queryset = SecretRole.objects.annotate(secret_count=Count('secrets')) + table = tables.SecretRoleTable default_return_url = 'secrets:secretrole_list' @@ -239,6 +241,7 @@ class SecretBulkImportView(BulkImportView): class SecretBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'secrets.change_secret' cls = Secret + queryset = Secret.objects.select_related('role', 'device') filter = filters.SecretFilter table = tables.SecretTable form = forms.SecretBulkEditForm @@ -248,5 +251,7 @@ class SecretBulkEditView(PermissionRequiredMixin, BulkEditView): class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'secrets.delete_secret' cls = Secret + queryset = Secret.objects.select_related('role', 'device') filter = filters.SecretFilter + table = tables.SecretTable default_return_url = 'secrets:secret_list' diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 6906be267..e176075cb 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -42,6 +42,8 @@ class TenantGroupEditView(TenantGroupCreateView): class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'tenancy.delete_tenantgroup' cls = TenantGroup + queryset = TenantGroup.objects.annotate(tenant_count=Count('tenants')) + table = tables.TenantGroupTable default_return_url = 'tenancy:tenantgroup_list' @@ -113,6 +115,7 @@ class TenantBulkImportView(PermissionRequiredMixin, BulkImportView): class TenantBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'tenancy.change_tenant' cls = Tenant + queryset = Tenant.objects.select_related('group') filter = filters.TenantFilter table = tables.TenantTable form = forms.TenantBulkEditForm @@ -122,5 +125,7 @@ class TenantBulkEditView(PermissionRequiredMixin, BulkEditView): class TenantBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'tenancy.delete_tenant' cls = Tenant + queryset = Tenant.objects.select_related('group') filter = filters.TenantFilter + table = tables.TenantTable default_return_url = 'tenancy:tenant_list' diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index e5e10731f..a1d001380 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -461,6 +461,7 @@ class BulkEditView(View): cls: The model of the objects being edited parent_cls: The model of the parent object (if any) + queryset: Custom queryset to use when retrieving objects (e.g. to select related objects) filter: FilterSet to apply when deleting by QuerySet table: The table used to display devices being edited form: The form class used to edit objects in bulk @@ -470,9 +471,10 @@ class BulkEditView(View): """ cls = None parent_cls = None + queryset = None filter = None - form = None table = None + form = None template_name = 'utilities/obj_bulk_edit.html' default_return_url = 'home' @@ -539,7 +541,9 @@ class BulkEditView(View): initial_data['pk'] = pk_list form = self.form(self.cls, initial=initial_data) - table = self.table(self.cls.objects.filter(pk__in=pk_list), orderable=False) + # Retrieve objects being edited + queryset = self.queryset or self.cls.objects.all() + table = self.table(queryset.filter(pk__in=pk_list), orderable=False) if not table.rows: messages.warning(request, "No {} were selected.".format(self.cls._meta.verbose_name_plural)) return redirect(return_url) @@ -605,6 +609,7 @@ class BulkDeleteView(View): cls: The model of the objects being deleted parent_cls: The model of the parent object (if any) + queryset: Custom queryset to use when retrieving objects (e.g. to select related objects) filter: FilterSet to apply when deleting by QuerySet table: The table used to display devices being deleted form: The form class used to delete objects in bulk @@ -614,6 +619,7 @@ class BulkDeleteView(View): """ cls = None parent_cls = None + queryset = None filter = None table = None form = None @@ -665,7 +671,9 @@ class BulkDeleteView(View): else: form = form_cls(initial={'pk': pk_list, 'return_url': return_url}) - table = self.table(self.cls.objects.filter(pk__in=pk_list), orderable=False) + # Retrieve objects being deleted + queryset = self.queryset or self.cls.objects.all() + table = self.table(queryset.filter(pk__in=pk_list), orderable=False) if not table.rows: messages.warning(request, "No {} were selected for deletion.".format(self.cls._meta.verbose_name_plural)) return redirect(return_url)