mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-19 09:53:34 -06:00
Added API views & tests for tags
This commit is contained in:
parent
03a1c48b54
commit
dc2f1d7c64
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from taggit.models import Tag
|
||||||
|
|
||||||
from dcim.api.serializers import NestedDeviceSerializer, NestedRackSerializer, NestedSiteSerializer
|
from dcim.api.serializers import NestedDeviceSerializer, NestedRackSerializer, NestedSiteSerializer
|
||||||
from dcim.models import Device, Rack, Site
|
from dcim.models import Device, Rack, Site
|
||||||
@ -62,6 +63,18 @@ class TopologyMapSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'name', 'slug', 'site', 'device_patterns', 'description']
|
fields = ['id', 'name', 'slug', 'site', 'device_patterns', 'description']
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tags
|
||||||
|
#
|
||||||
|
|
||||||
|
class TagSerializer(ValidatedModelSerializer):
|
||||||
|
tagged_items = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
fields = ['id', 'name', 'slug', 'tagged_items']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Image attachments
|
# Image attachments
|
||||||
#
|
#
|
||||||
|
@ -28,6 +28,9 @@ router.register(r'export-templates', views.ExportTemplateViewSet)
|
|||||||
# Topology maps
|
# Topology maps
|
||||||
router.register(r'topology-maps', views.TopologyMapViewSet)
|
router.register(r'topology-maps', views.TopologyMapViewSet)
|
||||||
|
|
||||||
|
# Tags
|
||||||
|
router.register(r'tags', views.TagViewSet)
|
||||||
|
|
||||||
# Image attachments
|
# Image attachments
|
||||||
router.register(r'image-attachments', views.ImageAttachmentViewSet)
|
router.register(r'image-attachments', views.ImageAttachmentViewSet)
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db.models import Count
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.decorators import detail_route
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
||||||
|
from taggit.models import Tag
|
||||||
|
|
||||||
from extras import filters
|
from extras import filters
|
||||||
from extras.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction
|
from extras.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction
|
||||||
@ -109,6 +111,16 @@ class TopologyMapViewSet(ModelViewSet):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tags
|
||||||
|
#
|
||||||
|
|
||||||
|
class TagViewSet(ModelViewSet):
|
||||||
|
queryset = Tag.objects.annotate(tagged_items=Count('taggit_taggeditem_items'))
|
||||||
|
serializer_class = serializers.TagSerializer
|
||||||
|
filter_class = filters.TagFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Image attachments
|
# Image attachments
|
||||||
#
|
#
|
||||||
|
@ -3,6 +3,8 @@ from __future__ import unicode_literals
|
|||||||
import django_filters
|
import django_filters
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db.models import Q
|
||||||
|
from taggit.models import Tag
|
||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from .constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_TYPE_BOOLEAN, CF_TYPE_SELECT
|
from .constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_TYPE_BOOLEAN, CF_TYPE_SELECT
|
||||||
@ -85,6 +87,25 @@ class ExportTemplateFilter(django_filters.FilterSet):
|
|||||||
fields = ['content_type', 'name']
|
fields = ['content_type', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
class TagFilter(django_filters.FilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(name__icontains=value) |
|
||||||
|
Q(slug__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TopologyMapFilter(django_filters.FilterSet):
|
class TopologyMapFilter(django_filters.FilterSet):
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='site',
|
name='site',
|
||||||
|
@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
from taggit.models import Tag
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.constants import GRAPH_TYPE_SITE
|
from extras.constants import GRAPH_TYPE_SITE
|
||||||
@ -226,3 +227,96 @@ class ExportTemplateTest(HttpStatusMixin, APITestCase):
|
|||||||
|
|
||||||
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
||||||
self.assertEqual(ExportTemplate.objects.count(), 2)
|
self.assertEqual(ExportTemplate.objects.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
class TagTest(HttpStatusMixin, APITestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
user = User.objects.create(username='testuser', is_superuser=True)
|
||||||
|
token = Token.objects.create(user=user)
|
||||||
|
self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token.key)}
|
||||||
|
|
||||||
|
self.tag1 = Tag.objects.create(name='Test Tag 1', slug='test-tag-1')
|
||||||
|
self.tag2 = Tag.objects.create(name='Test Tag 2', slug='test-tag-2')
|
||||||
|
self.tag3 = Tag.objects.create(name='Test Tag 3', slug='test-tag-3')
|
||||||
|
|
||||||
|
def test_get_tag(self):
|
||||||
|
|
||||||
|
url = reverse('extras-api:tag-detail', kwargs={'pk': self.tag1.pk})
|
||||||
|
response = self.client.get(url, **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['name'], self.tag1.name)
|
||||||
|
|
||||||
|
def test_list_tags(self):
|
||||||
|
|
||||||
|
url = reverse('extras-api:tag-list')
|
||||||
|
response = self.client.get(url, **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_create_tag(self):
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'name': 'Test Tag 4',
|
||||||
|
'slug': 'test-tag-4',
|
||||||
|
}
|
||||||
|
|
||||||
|
url = reverse('extras-api:tag-list')
|
||||||
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
|
|
||||||
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||||
|
self.assertEqual(Tag.objects.count(), 4)
|
||||||
|
tag4 = Tag.objects.get(pk=response.data['id'])
|
||||||
|
self.assertEqual(tag4.name, data['name'])
|
||||||
|
self.assertEqual(tag4.slug, data['slug'])
|
||||||
|
|
||||||
|
def test_create_tag_bulk(self):
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
'name': 'Test Tag 4',
|
||||||
|
'slug': 'test-tag-4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Test Tag 5',
|
||||||
|
'slug': 'test-tag-5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Test Tag 6',
|
||||||
|
'slug': 'test-tag-6',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
url = reverse('extras-api:tag-list')
|
||||||
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
|
|
||||||
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||||
|
self.assertEqual(Tag.objects.count(), 6)
|
||||||
|
self.assertEqual(response.data[0]['name'], data[0]['name'])
|
||||||
|
self.assertEqual(response.data[1]['name'], data[1]['name'])
|
||||||
|
self.assertEqual(response.data[2]['name'], data[2]['name'])
|
||||||
|
|
||||||
|
def test_update_tag(self):
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'name': 'Test Tag X',
|
||||||
|
'slug': 'test-tag-x',
|
||||||
|
}
|
||||||
|
|
||||||
|
url = reverse('extras-api:tag-detail', kwargs={'pk': self.tag1.pk})
|
||||||
|
response = self.client.put(url, data, format='json', **self.header)
|
||||||
|
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(Tag.objects.count(), 3)
|
||||||
|
tag1 = Tag.objects.get(pk=response.data['id'])
|
||||||
|
self.assertEqual(tag1.name, data['name'])
|
||||||
|
self.assertEqual(tag1.slug, data['slug'])
|
||||||
|
|
||||||
|
def test_delete_tag(self):
|
||||||
|
|
||||||
|
url = reverse('extras-api:tag-detail', kwargs={'pk': self.tag1.pk})
|
||||||
|
response = self.client.delete(url, **self.header)
|
||||||
|
|
||||||
|
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
||||||
|
self.assertEqual(Tag.objects.count(), 2)
|
||||||
|
Loading…
Reference in New Issue
Block a user