from uuid import UUID from django.utils.translation import gettext_lazy as _ from django_filters import rest_framework as filters from django_filters.rest_framework import DjangoFilterBackend from rest_framework.exceptions import ValidationError from swapper import load_model from openwisp_users.api.filters import OrganizationManagedFilter Template = load_model("config", "Template") Vpn = load_model("config", "Vpn") Device = load_model("config", "Device") DeviceGroup = load_model("config", "DeviceGroup") class BaseConfigAPIFilter(OrganizationManagedFilter): def _set_valid_filterform_lables(self): # When not filtering on a model field, an error message # with the label "[invalid_name]" will be displayed in filter form. # To avoid this error, we need to provide the label explicitly. raise NotImplementedError class TemplateListFilter(BaseConfigAPIFilter): created__gte = filters.DateTimeFilter( field_name="created", lookup_expr="gte", ) created__lt = filters.DateTimeFilter( field_name="created", lookup_expr="lt", ) def _set_valid_filterform_lables(self): self.filters["backend"].label = _("Template backend") self.filters["type"].label = _("Template type") def __init__(self, *args, **kwargs): super(TemplateListFilter, self).__init__(*args, **kwargs) self._set_valid_filterform_lables() class Meta(BaseConfigAPIFilter.Meta): model = Template fields = BaseConfigAPIFilter.Meta.fields + [ "backend", "type", "default", "required", "created", ] class VPNListFilter(BaseConfigAPIFilter): def _set_valid_filterform_lables(self): self.filters["backend"].label = _("VPN Backend") self.filters["subnet"].label = _("VPN Subnet") def __init__(self, *args, **kwargs): super(VPNListFilter, self).__init__(*args, **kwargs) self._set_valid_filterform_lables() class Meta(BaseConfigAPIFilter.Meta): model = Vpn fields = BaseConfigAPIFilter.Meta.fields + ["backend", "subnet"] class DeviceListFilterBackend(DjangoFilterBackend): def filter_queryset(self, request, queryset, view): """ Validate that the request parameters contain a valid configuration template uuid format """ config_template_uuid = request.query_params.get("config__templates") if config_template_uuid: try: # Attempt to convert the uuid string to a UUID object UUID(config_template_uuid) except ValueError: raise ValidationError({"config__templates": _("Invalid UUID format")}) # The filtering itself is left to DeviceListFilter: filtering the # "config__templates" relationship here as well would add a second # join to the paginated device query, and would also bypass the # other filters, which used to be ignored whenever a template was # supplied. return super().filter_queryset(request, queryset, view) class DeviceListFilter(BaseConfigAPIFilter): mac_address = filters.CharFilter( field_name="mac_address", lookup_expr="icontains", ) name = filters.CharFilter( field_name="name", lookup_expr="icontains", ) created__gte = filters.DateTimeFilter( field_name="created", lookup_expr="gte", ) created__lt = filters.DateTimeFilter( field_name="created", lookup_expr="lt", ) subnet_division_rule = filters.UUIDFilter( method="filter_subnet_division_rule", ) def filter_subnet_division_rule(self, queryset, name, value): return queryset.filter(config__subnetdivisionindex__rule=value).distinct() def _set_valid_filterform_lables(self): self.filters["group"].label = _("Device group") self.filters["config__templates"].label = _("Config template") self.filters["config__status"].label = _("Config status") self.filters["config__backend"].label = _("Config backend") self.filters["subnet_division_rule"].label = _("Subnet division rule") def __init__(self, *args, **kwargs): super(DeviceListFilter, self).__init__(*args, **kwargs) self._set_valid_filterform_lables() class Meta(BaseConfigAPIFilter.Meta): model = Device fields = BaseConfigAPIFilter.Meta.fields + [ "name", "mac_address", "config__status", "config__backend", "config__templates", "group", "subnet_division_rule", "created", ] class DeviceGroupListFilter(BaseConfigAPIFilter): # Using filter query param name `empty` # which is similar to admin filter empty = filters.BooleanFilter(field_name="device", method="filter_device") def filter_device(self, queryset, name, value): # Returns list of device groups that have devicelocation objects return queryset.exclude(device__isnull=value).distinct() def _set_valid_filterform_lables(self): self.filters["empty"].label = _("Has devices?") def __init__(self, *args, **kwargs): super(DeviceGroupListFilter, self).__init__(*args, **kwargs) self._set_valid_filterform_lables() class Meta(BaseConfigAPIFilter.Meta): model = DeviceGroup