mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Split core views into separate modules
This commit is contained in:
parent
96818cacf0
commit
de1b346da0
@ -11,7 +11,7 @@ from django.http import Http404, HttpResponseRedirect
|
|||||||
|
|
||||||
from extras.context_managers import change_logging
|
from extras.context_managers import change_logging
|
||||||
from netbox.config import clear_config
|
from netbox.config import clear_config
|
||||||
from netbox.views import server_error
|
from netbox.views import handler_500
|
||||||
from utilities.api import is_api_request, rest_api_server_error
|
from utilities.api import is_api_request, rest_api_server_error
|
||||||
|
|
||||||
|
|
||||||
@ -201,4 +201,4 @@ class ExceptionHandlingMiddleware:
|
|||||||
|
|
||||||
# Return a custom error message, or fall back to Django's default 500 error handling
|
# Return a custom error message, or fall back to Django's default 500 error handling
|
||||||
if custom_template:
|
if custom_template:
|
||||||
return server_error(request, template_name=custom_template)
|
return handler_500(request, template_name=custom_template)
|
||||||
|
@ -14,7 +14,6 @@ from netbox.views import HomeView, StaticMediaFailureView, SearchView
|
|||||||
from users.views import LoginView, LogoutView
|
from users.views import LoginView, LogoutView
|
||||||
from .admin import admin_site
|
from .admin import admin_site
|
||||||
|
|
||||||
|
|
||||||
openapi_info = openapi.Info(
|
openapi_info = openapi.Info(
|
||||||
title="NetBox API",
|
title="NetBox API",
|
||||||
default_version='v3',
|
default_version='v3',
|
||||||
@ -100,5 +99,5 @@ urlpatterns = [
|
|||||||
path('{}'.format(settings.BASE_PATH), include(_patterns))
|
path('{}'.format(settings.BASE_PATH), include(_patterns))
|
||||||
]
|
]
|
||||||
|
|
||||||
handler404 = 'netbox.views.handler_404'
|
handler404 = 'netbox.views.errors.handler_404'
|
||||||
handler500 = 'netbox.views.server_error'
|
handler500 = 'netbox.views.errors.handler_500'
|
||||||
|
@ -1,227 +1,2 @@
|
|||||||
import platform
|
from .misc import *
|
||||||
import sys
|
from .errors import *
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.http import HttpResponseServerError
|
|
||||||
from django.shortcuts import redirect, render
|
|
||||||
from django.template import loader
|
|
||||||
from django.template.exceptions import TemplateDoesNotExist
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from django.views.decorators.csrf import requires_csrf_token
|
|
||||||
from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found
|
|
||||||
from django.views.generic import View
|
|
||||||
from django_tables2 import RequestConfig
|
|
||||||
from packaging import version
|
|
||||||
from sentry_sdk import capture_message
|
|
||||||
|
|
||||||
from circuits.models import Circuit, Provider
|
|
||||||
from dcim.models import (
|
|
||||||
Cable, ConsolePort, Device, DeviceType, Interface, PowerPanel, PowerFeed, PowerPort, Rack, Site,
|
|
||||||
)
|
|
||||||
from extras.models import ObjectChange
|
|
||||||
from extras.tables import ObjectChangeTable
|
|
||||||
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF
|
|
||||||
from netbox.forms import SearchForm
|
|
||||||
from netbox.search import LookupTypes
|
|
||||||
from netbox.search.backends import search_backend
|
|
||||||
from netbox.tables import SearchTable
|
|
||||||
from tenancy.models import Contact, Tenant
|
|
||||||
from utilities.htmx import is_htmx
|
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
|
||||||
from virtualization.models import Cluster, VirtualMachine
|
|
||||||
from wireless.models import WirelessLAN, WirelessLink
|
|
||||||
|
|
||||||
Link = namedtuple('Link', ('label', 'viewname', 'permission', 'count'))
|
|
||||||
|
|
||||||
|
|
||||||
class HomeView(View):
|
|
||||||
template_name = 'home.html'
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
|
|
||||||
return redirect('login')
|
|
||||||
|
|
||||||
console_connections = ConsolePort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
|
||||||
_path__is_complete=True
|
|
||||||
).count
|
|
||||||
power_connections = PowerPort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
|
||||||
_path__is_complete=True
|
|
||||||
).count
|
|
||||||
interface_connections = Interface.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
|
|
||||||
_path__is_complete=True
|
|
||||||
).count
|
|
||||||
|
|
||||||
def get_count_queryset(model):
|
|
||||||
return model.objects.restrict(request.user, 'view').count
|
|
||||||
|
|
||||||
def build_stats():
|
|
||||||
org = (
|
|
||||||
Link(_('Sites'), 'dcim:site_list', 'dcim.view_site', get_count_queryset(Site)),
|
|
||||||
Link(_('Tenants'), 'tenancy:tenant_list', 'tenancy.view_tenant', get_count_queryset(Tenant)),
|
|
||||||
Link(_('Contacts'), 'tenancy:contact_list', 'tenancy.view_contact', get_count_queryset(Contact)),
|
|
||||||
)
|
|
||||||
dcim = (
|
|
||||||
Link(_('Racks'), 'dcim:rack_list', 'dcim.view_rack', get_count_queryset(Rack)),
|
|
||||||
Link(_('Device Types'), 'dcim:devicetype_list', 'dcim.view_devicetype', get_count_queryset(DeviceType)),
|
|
||||||
Link(_('Devices'), 'dcim:device_list', 'dcim.view_device', get_count_queryset(Device)),
|
|
||||||
)
|
|
||||||
ipam = (
|
|
||||||
Link(_('VRFs'), 'ipam:vrf_list', 'ipam.view_vrf', get_count_queryset(VRF)),
|
|
||||||
Link(_('Aggregates'), 'ipam:aggregate_list', 'ipam.view_aggregate', get_count_queryset(Aggregate)),
|
|
||||||
Link(_('Prefixes'), 'ipam:prefix_list', 'ipam.view_prefix', get_count_queryset(Prefix)),
|
|
||||||
Link(_('IP Ranges'), 'ipam:iprange_list', 'ipam.view_iprange', get_count_queryset(IPRange)),
|
|
||||||
Link(_('IP Addresses'), 'ipam:ipaddress_list', 'ipam.view_ipaddress', get_count_queryset(IPAddress)),
|
|
||||||
Link(_('VLANs'), 'ipam:vlan_list', 'ipam.view_vlan', get_count_queryset(VLAN)),
|
|
||||||
)
|
|
||||||
circuits = (
|
|
||||||
Link(_('Providers'), 'circuits:provider_list', 'circuits.view_provider', get_count_queryset(Provider)),
|
|
||||||
Link(_('Circuits'), 'circuits:circuit_list', 'circuits.view_circuit', get_count_queryset(Circuit))
|
|
||||||
)
|
|
||||||
virtualization = (
|
|
||||||
Link(_('Clusters'), 'virtualization:cluster_list', 'virtualization.view_cluster',
|
|
||||||
get_count_queryset(Cluster)),
|
|
||||||
Link(_('Virtual Machines'), 'virtualization:virtualmachine_list', 'virtualization.view_virtualmachine',
|
|
||||||
get_count_queryset(VirtualMachine)),
|
|
||||||
)
|
|
||||||
connections = (
|
|
||||||
Link(_('Cables'), 'dcim:cable_list', 'dcim.view_cable', get_count_queryset(Cable)),
|
|
||||||
Link(_('Interfaces'), 'dcim:interface_connections_list', 'dcim.view_interface', interface_connections),
|
|
||||||
Link(_('Console'), 'dcim:console_connections_list', 'dcim.view_consoleport', console_connections),
|
|
||||||
Link(_('Power'), 'dcim:power_connections_list', 'dcim.view_powerport', power_connections),
|
|
||||||
)
|
|
||||||
power = (
|
|
||||||
Link(_('Power Panels'), 'dcim:powerpanel_list', 'dcim.view_powerpanel', get_count_queryset(PowerPanel)),
|
|
||||||
Link(_('Power Feeds'), 'dcim:powerfeed_list', 'dcim.view_powerfeed', get_count_queryset(PowerFeed)),
|
|
||||||
)
|
|
||||||
wireless = (
|
|
||||||
Link(_('Wireless LANs'), 'wireless:wirelesslan_list', 'wireless.view_wirelesslan',
|
|
||||||
get_count_queryset(WirelessLAN)),
|
|
||||||
Link(_('Wireless Links'), 'wireless:wirelesslink_list', 'wireless.view_wirelesslink',
|
|
||||||
get_count_queryset(WirelessLink)),
|
|
||||||
)
|
|
||||||
stats = (
|
|
||||||
(_('Organization'), org, 'domain'),
|
|
||||||
(_('IPAM'), ipam, 'counter'),
|
|
||||||
(_('Virtualization'), virtualization, 'monitor'),
|
|
||||||
(_('Inventory'), dcim, 'server'),
|
|
||||||
(_('Circuits'), circuits, 'transit-connection-variant'),
|
|
||||||
(_('Connections'), connections, 'cable-data'),
|
|
||||||
(_('Power'), power, 'flash'),
|
|
||||||
(_('Wireless'), wireless, 'wifi'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return stats
|
|
||||||
|
|
||||||
# Compile changelog table
|
|
||||||
changelog = ObjectChange.objects.restrict(request.user, 'view').prefetch_related(
|
|
||||||
'user', 'changed_object_type'
|
|
||||||
)[:10]
|
|
||||||
changelog_table = ObjectChangeTable(changelog, user=request.user)
|
|
||||||
|
|
||||||
# Check whether a new release is available. (Only for staff/superusers.)
|
|
||||||
new_release = None
|
|
||||||
if request.user.is_staff or request.user.is_superuser:
|
|
||||||
latest_release = cache.get('latest_release')
|
|
||||||
if latest_release:
|
|
||||||
release_version, release_url = latest_release
|
|
||||||
if release_version > version.parse(settings.VERSION):
|
|
||||||
new_release = {
|
|
||||||
'version': str(release_version),
|
|
||||||
'url': release_url,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'search_form': SearchForm(),
|
|
||||||
'stats': build_stats(),
|
|
||||||
'changelog_table': changelog_table,
|
|
||||||
'new_release': new_release,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class SearchView(View):
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
results = []
|
|
||||||
highlight = None
|
|
||||||
|
|
||||||
# Initialize search form
|
|
||||||
form = SearchForm(request.GET) if 'q' in request.GET else SearchForm()
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
# Restrict results by object type
|
|
||||||
object_types = []
|
|
||||||
for obj_type in form.cleaned_data['obj_types']:
|
|
||||||
app_label, model_name = obj_type.split('.')
|
|
||||||
object_types.append(ContentType.objects.get_by_natural_key(app_label, model_name))
|
|
||||||
|
|
||||||
lookup = form.cleaned_data['lookup'] or LookupTypes.PARTIAL
|
|
||||||
results = search_backend.search(
|
|
||||||
form.cleaned_data['q'],
|
|
||||||
user=request.user,
|
|
||||||
object_types=object_types,
|
|
||||||
lookup=lookup
|
|
||||||
)
|
|
||||||
|
|
||||||
if form.cleaned_data['lookup'] != LookupTypes.EXACT:
|
|
||||||
highlight = form.cleaned_data['q']
|
|
||||||
|
|
||||||
table = SearchTable(results, highlight=highlight)
|
|
||||||
|
|
||||||
# Paginate the table results
|
|
||||||
RequestConfig(request, {
|
|
||||||
'paginator_class': EnhancedPaginator,
|
|
||||||
'per_page': get_paginate_count(request)
|
|
||||||
}).configure(table)
|
|
||||||
|
|
||||||
# If this is an HTMX request, return only the rendered table HTML
|
|
||||||
if is_htmx(request):
|
|
||||||
return render(request, 'htmx/table.html', {
|
|
||||||
'table': table,
|
|
||||||
})
|
|
||||||
|
|
||||||
return render(request, 'search.html', {
|
|
||||||
'form': form,
|
|
||||||
'table': table,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class StaticMediaFailureView(View):
|
|
||||||
"""
|
|
||||||
Display a user-friendly error message with troubleshooting tips when a static media file fails to load.
|
|
||||||
"""
|
|
||||||
def get(self, request):
|
|
||||||
return render(request, 'media_failure.html', {
|
|
||||||
'filename': request.GET.get('filename')
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def handler_404(request, exception):
|
|
||||||
"""
|
|
||||||
Wrap Django's default 404 handler to enable Sentry reporting.
|
|
||||||
"""
|
|
||||||
capture_message("Page not found", level="error")
|
|
||||||
|
|
||||||
return page_not_found(request, exception)
|
|
||||||
|
|
||||||
|
|
||||||
@requires_csrf_token
|
|
||||||
def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
|
|
||||||
"""
|
|
||||||
Custom 500 handler to provide additional context when rendering 500.html.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
template = loader.get_template(template_name)
|
|
||||||
except TemplateDoesNotExist:
|
|
||||||
return HttpResponseServerError('<h1>Server Error (500)</h1>', content_type='text/html')
|
|
||||||
type_, error, traceback = sys.exc_info()
|
|
||||||
|
|
||||||
return HttpResponseServerError(template.render({
|
|
||||||
'error': error,
|
|
||||||
'exception': str(type_),
|
|
||||||
'netbox_version': settings.VERSION,
|
|
||||||
'python_version': platform.python_version(),
|
|
||||||
}))
|
|
||||||
|
56
netbox/netbox/views/errors.py
Normal file
56
netbox/netbox/views/errors.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponseServerError
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.template import loader
|
||||||
|
from django.template.exceptions import TemplateDoesNotExist
|
||||||
|
from django.views.decorators.csrf import requires_csrf_token
|
||||||
|
from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found
|
||||||
|
from django.views.generic import View
|
||||||
|
from sentry_sdk import capture_message
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'handler_404',
|
||||||
|
'handler_500',
|
||||||
|
'StaticMediaFailureView',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StaticMediaFailureView(View):
|
||||||
|
"""
|
||||||
|
Display a user-friendly error message with troubleshooting tips when a static media file fails to load.
|
||||||
|
"""
|
||||||
|
def get(self, request):
|
||||||
|
return render(request, 'media_failure.html', {
|
||||||
|
'filename': request.GET.get('filename')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def handler_404(request, exception):
|
||||||
|
"""
|
||||||
|
Wrap Django's default 404 handler to enable Sentry reporting.
|
||||||
|
"""
|
||||||
|
capture_message("Page not found", level="error")
|
||||||
|
|
||||||
|
return page_not_found(request, exception)
|
||||||
|
|
||||||
|
|
||||||
|
@requires_csrf_token
|
||||||
|
def handler_500(request, template_name=ERROR_500_TEMPLATE_NAME):
|
||||||
|
"""
|
||||||
|
Custom 500 handler to provide additional context when rendering 500.html.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
template = loader.get_template(template_name)
|
||||||
|
except TemplateDoesNotExist:
|
||||||
|
return HttpResponseServerError('<h1>Server Error (500)</h1>', content_type='text/html')
|
||||||
|
type_, error, traceback = sys.exc_info()
|
||||||
|
|
||||||
|
return HttpResponseServerError(template.render({
|
||||||
|
'error': error,
|
||||||
|
'exception': str(type_),
|
||||||
|
'netbox_version': settings.VERSION,
|
||||||
|
'python_version': platform.python_version(),
|
||||||
|
}))
|
183
netbox/netbox/views/misc.py
Normal file
183
netbox/netbox/views/misc.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from django.views.generic import View
|
||||||
|
from django_tables2 import RequestConfig
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
from circuits.models import Circuit, Provider
|
||||||
|
from dcim.models import (
|
||||||
|
Cable, ConsolePort, Device, DeviceType, Interface, PowerPanel, PowerFeed, PowerPort, Rack, Site,
|
||||||
|
)
|
||||||
|
from extras.models import ObjectChange
|
||||||
|
from extras.tables import ObjectChangeTable
|
||||||
|
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF
|
||||||
|
from netbox.forms import SearchForm
|
||||||
|
from netbox.search import LookupTypes
|
||||||
|
from netbox.search.backends import search_backend
|
||||||
|
from netbox.tables import SearchTable
|
||||||
|
from tenancy.models import Contact, Tenant
|
||||||
|
from utilities.htmx import is_htmx
|
||||||
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
|
from virtualization.models import Cluster, VirtualMachine
|
||||||
|
from wireless.models import WirelessLAN, WirelessLink
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'HomeView',
|
||||||
|
'SearchView',
|
||||||
|
)
|
||||||
|
|
||||||
|
Link = namedtuple('Link', ('label', 'viewname', 'permission', 'count'))
|
||||||
|
|
||||||
|
|
||||||
|
class HomeView(View):
|
||||||
|
template_name = 'home.html'
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
|
||||||
|
return redirect('login')
|
||||||
|
|
||||||
|
console_connections = ConsolePort.objects.restrict(request.user, 'view')\
|
||||||
|
.prefetch_related('_path').filter(_path__is_complete=True).count
|
||||||
|
power_connections = PowerPort.objects.restrict(request.user, 'view')\
|
||||||
|
.prefetch_related('_path').filter(_path__is_complete=True).count
|
||||||
|
interface_connections = Interface.objects.restrict(request.user, 'view')\
|
||||||
|
.prefetch_related('_path').filter(_path__is_complete=True).count
|
||||||
|
|
||||||
|
def get_count_queryset(model):
|
||||||
|
return model.objects.restrict(request.user, 'view').count
|
||||||
|
|
||||||
|
def build_stats():
|
||||||
|
org = (
|
||||||
|
Link(_('Sites'), 'dcim:site_list', 'dcim.view_site', get_count_queryset(Site)),
|
||||||
|
Link(_('Tenants'), 'tenancy:tenant_list', 'tenancy.view_tenant', get_count_queryset(Tenant)),
|
||||||
|
Link(_('Contacts'), 'tenancy:contact_list', 'tenancy.view_contact', get_count_queryset(Contact)),
|
||||||
|
)
|
||||||
|
dcim = (
|
||||||
|
Link(_('Racks'), 'dcim:rack_list', 'dcim.view_rack', get_count_queryset(Rack)),
|
||||||
|
Link(_('Device Types'), 'dcim:devicetype_list', 'dcim.view_devicetype', get_count_queryset(DeviceType)),
|
||||||
|
Link(_('Devices'), 'dcim:device_list', 'dcim.view_device', get_count_queryset(Device)),
|
||||||
|
)
|
||||||
|
ipam = (
|
||||||
|
Link(_('VRFs'), 'ipam:vrf_list', 'ipam.view_vrf', get_count_queryset(VRF)),
|
||||||
|
Link(_('Aggregates'), 'ipam:aggregate_list', 'ipam.view_aggregate', get_count_queryset(Aggregate)),
|
||||||
|
Link(_('Prefixes'), 'ipam:prefix_list', 'ipam.view_prefix', get_count_queryset(Prefix)),
|
||||||
|
Link(_('IP Ranges'), 'ipam:iprange_list', 'ipam.view_iprange', get_count_queryset(IPRange)),
|
||||||
|
Link(_('IP Addresses'), 'ipam:ipaddress_list', 'ipam.view_ipaddress', get_count_queryset(IPAddress)),
|
||||||
|
Link(_('VLANs'), 'ipam:vlan_list', 'ipam.view_vlan', get_count_queryset(VLAN)),
|
||||||
|
)
|
||||||
|
circuits = (
|
||||||
|
Link(_('Providers'), 'circuits:provider_list', 'circuits.view_provider', get_count_queryset(Provider)),
|
||||||
|
Link(_('Circuits'), 'circuits:circuit_list', 'circuits.view_circuit', get_count_queryset(Circuit))
|
||||||
|
)
|
||||||
|
virtualization = (
|
||||||
|
Link(_('Clusters'), 'virtualization:cluster_list', 'virtualization.view_cluster',
|
||||||
|
get_count_queryset(Cluster)),
|
||||||
|
Link(_('Virtual Machines'), 'virtualization:virtualmachine_list', 'virtualization.view_virtualmachine',
|
||||||
|
get_count_queryset(VirtualMachine)),
|
||||||
|
)
|
||||||
|
connections = (
|
||||||
|
Link(_('Cables'), 'dcim:cable_list', 'dcim.view_cable', get_count_queryset(Cable)),
|
||||||
|
Link(_('Interfaces'), 'dcim:interface_connections_list', 'dcim.view_interface', interface_connections),
|
||||||
|
Link(_('Console'), 'dcim:console_connections_list', 'dcim.view_consoleport', console_connections),
|
||||||
|
Link(_('Power'), 'dcim:power_connections_list', 'dcim.view_powerport', power_connections),
|
||||||
|
)
|
||||||
|
power = (
|
||||||
|
Link(_('Power Panels'), 'dcim:powerpanel_list', 'dcim.view_powerpanel', get_count_queryset(PowerPanel)),
|
||||||
|
Link(_('Power Feeds'), 'dcim:powerfeed_list', 'dcim.view_powerfeed', get_count_queryset(PowerFeed)),
|
||||||
|
)
|
||||||
|
wireless = (
|
||||||
|
Link(_('Wireless LANs'), 'wireless:wirelesslan_list', 'wireless.view_wirelesslan',
|
||||||
|
get_count_queryset(WirelessLAN)),
|
||||||
|
Link(_('Wireless Links'), 'wireless:wirelesslink_list', 'wireless.view_wirelesslink',
|
||||||
|
get_count_queryset(WirelessLink)),
|
||||||
|
)
|
||||||
|
stats = (
|
||||||
|
(_('Organization'), org, 'domain'),
|
||||||
|
(_('IPAM'), ipam, 'counter'),
|
||||||
|
(_('Virtualization'), virtualization, 'monitor'),
|
||||||
|
(_('Inventory'), dcim, 'server'),
|
||||||
|
(_('Circuits'), circuits, 'transit-connection-variant'),
|
||||||
|
(_('Connections'), connections, 'cable-data'),
|
||||||
|
(_('Power'), power, 'flash'),
|
||||||
|
(_('Wireless'), wireless, 'wifi'),
|
||||||
|
)
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
# Compile changelog table
|
||||||
|
changelog = ObjectChange.objects.restrict(request.user, 'view').prefetch_related(
|
||||||
|
'user', 'changed_object_type'
|
||||||
|
)[:10]
|
||||||
|
changelog_table = ObjectChangeTable(changelog, user=request.user)
|
||||||
|
|
||||||
|
# Check whether a new release is available. (Only for staff/superusers.)
|
||||||
|
new_release = None
|
||||||
|
if request.user.is_staff or request.user.is_superuser:
|
||||||
|
latest_release = cache.get('latest_release')
|
||||||
|
if latest_release:
|
||||||
|
release_version, release_url = latest_release
|
||||||
|
if release_version > version.parse(settings.VERSION):
|
||||||
|
new_release = {
|
||||||
|
'version': str(release_version),
|
||||||
|
'url': release_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, self.template_name, {
|
||||||
|
'search_form': SearchForm(),
|
||||||
|
'stats': build_stats(),
|
||||||
|
'changelog_table': changelog_table,
|
||||||
|
'new_release': new_release,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class SearchView(View):
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
results = []
|
||||||
|
highlight = None
|
||||||
|
|
||||||
|
# Initialize search form
|
||||||
|
form = SearchForm(request.GET) if 'q' in request.GET else SearchForm()
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
|
||||||
|
# Restrict results by object type
|
||||||
|
object_types = []
|
||||||
|
for obj_type in form.cleaned_data['obj_types']:
|
||||||
|
app_label, model_name = obj_type.split('.')
|
||||||
|
object_types.append(ContentType.objects.get_by_natural_key(app_label, model_name))
|
||||||
|
|
||||||
|
lookup = form.cleaned_data['lookup'] or LookupTypes.PARTIAL
|
||||||
|
results = search_backend.search(
|
||||||
|
form.cleaned_data['q'],
|
||||||
|
user=request.user,
|
||||||
|
object_types=object_types,
|
||||||
|
lookup=lookup
|
||||||
|
)
|
||||||
|
|
||||||
|
if form.cleaned_data['lookup'] != LookupTypes.EXACT:
|
||||||
|
highlight = form.cleaned_data['q']
|
||||||
|
|
||||||
|
table = SearchTable(results, highlight=highlight)
|
||||||
|
|
||||||
|
# Paginate the table results
|
||||||
|
RequestConfig(request, {
|
||||||
|
'paginator_class': EnhancedPaginator,
|
||||||
|
'per_page': get_paginate_count(request)
|
||||||
|
}).configure(table)
|
||||||
|
|
||||||
|
# If this is an HTMX request, return only the rendered table HTML
|
||||||
|
if is_htmx(request):
|
||||||
|
return render(request, 'htmx/table.html', {
|
||||||
|
'table': table,
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(request, 'search.html', {
|
||||||
|
'form': form,
|
||||||
|
'table': table,
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user