1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #4897: Allow filtering by content type identified as <app>.<model> string

This commit is contained in:
Jeremy Stretch
2020-09-22 16:06:38 -04:00
parent 5ba4252388
commit 0c3fafdfef
5 changed files with 132 additions and 7 deletions

View File

@@ -4,7 +4,7 @@ from django.db.models import Q
from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup
from utilities.filters import BaseFilterSet
from utilities.filters import BaseFilterSet, ContentTypeFilter
from virtualization.models import Cluster, ClusterGroup
from .choices import *
from .models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, JobResult, ObjectChange, Tag
@@ -81,10 +81,11 @@ class ExportTemplateFilterSet(BaseFilterSet):
class ImageAttachmentFilterSet(BaseFilterSet):
content_type = ContentTypeFilter()
class Meta:
model = ImageAttachment
fields = ['id', 'content_type', 'object_id', 'name']
fields = ['id', 'content_type_id', 'object_id', 'name']
class TagFilterSet(BaseFilterSet):
@@ -234,11 +235,12 @@ class ObjectChangeFilterSet(BaseFilterSet):
label='Search',
)
time = django_filters.DateTimeFromToRangeFilter()
changed_object_type = ContentTypeFilter()
class Meta:
model = ObjectChange
fields = [
'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type', 'changed_object_id',
'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id',
'object_repr',
]

View File

@@ -361,8 +361,8 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
api_url='/api/users/users/',
)
)
changed_object_type = forms.ModelChoiceField(
queryset=ContentType.objects.order_by('model'),
changed_object_type_id = forms.ModelChoiceField(
queryset=ContentType.objects.order_by('app_label', 'model'),
required=False,
widget=ContentTypeSelect(),
label='Object Type'

View File

@@ -1,9 +1,14 @@
import uuid
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from dcim.models import DeviceRole, Platform, Rack, Region, Site
from extras.choices import ObjectChangeActionChoices
from extras.filters import *
from extras.models import ConfigContext, ExportTemplate, ImageAttachment, Tag
from extras.models import ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, Tag
from ipam.models import IPAddress
from tenancy.models import Tenant, TenantGroup
from virtualization.models import Cluster, ClusterGroup, ClusterType
@@ -298,4 +303,98 @@ class TagTestCase(TestCase):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
# TODO: ObjectChangeFilter test
class ObjectChangeTestCase(TestCase):
queryset = ObjectChange.objects.all()
filterset = ObjectChangeFilterSet
@classmethod
def setUpTestData(cls):
users = (
User(username='user1'),
User(username='user2'),
User(username='user3'),
)
User.objects.bulk_create(users)
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
ipaddress = IPAddress.objects.create(address='192.0.2.1/24')
object_changes = (
ObjectChange(
user=users[0],
user_name=users[0].username,
request_id=uuid.uuid4(),
action=ObjectChangeActionChoices.ACTION_CREATE,
changed_object=site,
object_repr=str(site),
object_data={'name': site.name, 'slug': site.slug}
),
ObjectChange(
user=users[0],
user_name=users[0].username,
request_id=uuid.uuid4(),
action=ObjectChangeActionChoices.ACTION_UPDATE,
changed_object=site,
object_repr=str(site),
object_data={'name': site.name, 'slug': site.slug}
),
ObjectChange(
user=users[1],
user_name=users[1].username,
request_id=uuid.uuid4(),
action=ObjectChangeActionChoices.ACTION_DELETE,
changed_object=site,
object_repr=str(site),
object_data={'name': site.name, 'slug': site.slug}
),
ObjectChange(
user=users[1],
user_name=users[1].username,
request_id=uuid.uuid4(),
action=ObjectChangeActionChoices.ACTION_CREATE,
changed_object=ipaddress,
object_repr=str(ipaddress),
object_data={'address': ipaddress.address, 'status': ipaddress.status}
),
ObjectChange(
user=users[2],
user_name=users[2].username,
request_id=uuid.uuid4(),
action=ObjectChangeActionChoices.ACTION_UPDATE,
changed_object=ipaddress,
object_repr=str(ipaddress),
object_data={'address': ipaddress.address, 'status': ipaddress.status}
),
ObjectChange(
user=users[2],
user_name=users[2].username,
request_id=uuid.uuid4(),
action=ObjectChangeActionChoices.ACTION_DELETE,
changed_object=ipaddress,
object_repr=str(ipaddress),
object_data={'address': ipaddress.address, 'status': ipaddress.status}
),
)
ObjectChange.objects.bulk_create(object_changes)
def test_id(self):
params = {'id': self.queryset.values_list('pk', flat=True)[:3]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
# def test_user(self):
# params = {'user_id': User.objects.filter(username__in=['user1', 'user2']).values_list('pk', flat=True)}
# self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
# params = {'user': ['user1', 'user2']}
# self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_user_name(self):
params = {'user_name': ['user1', 'user2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_changed_object_type(self):
params = {'changed_object_type': 'dcim.site'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_changed_object_type_id(self):
params = {'changed_object_type_id': ContentType.objects.get(app_label='dcim', model='site').pk}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)

View File

@@ -1,4 +1,5 @@
import django_filters
from django_filters.constants import EMPTY_VALUES
from copy import deepcopy
from dcim.forms import MACAddressField
from django import forms
@@ -115,6 +116,26 @@ class NumericArrayFilter(django_filters.NumberFilter):
return super().filter(qs, value)
class ContentTypeFilter(django_filters.CharFilter):
"""
Allow specifying a ContentType by <app_label>.<model> (e.g. "dcim.site").
"""
def filter(self, qs, value):
if value in EMPTY_VALUES:
return qs
try:
app_label, model = value.lower().split('.')
except ValueError:
return qs.none()
return qs.filter(
**{
f'{self.field_name}__app_label': app_label,
f'{self.field_name}__model': model
}
)
#
# FilterSets
#