From 2fce7ebd8fee511a0fef8b3aada11037e8ef8e9f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Nov 2018 11:02:48 -0500 Subject: [PATCH 1/8] Fixes #2565: Improved rendering of Markdown tables --- CHANGELOG.md | 1 + netbox/project-static/css/base.css | 13 +++++++++++++ netbox/templates/circuits/circuit.html | 2 +- netbox/templates/circuits/provider.html | 2 +- netbox/templates/dcim/device.html | 2 +- netbox/templates/dcim/devicetype.html | 2 +- netbox/templates/dcim/rack.html | 2 +- netbox/templates/dcim/site.html | 2 +- netbox/templates/tenancy/tenant.html | 2 +- netbox/templates/virtualization/cluster.html | 2 +- netbox/templates/virtualization/virtualmachine.html | 2 +- 11 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e02f1446..21d286875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ v2.4.8 (FUTURE) * [#2473](https://github.com/digitalocean/netbox/issues/2473) - Fix encoding of long (>127 character) secrets * [#2558](https://github.com/digitalocean/netbox/issues/2558) - Filter on all tags when multiple are passed +* [#2565](https://github.com/digitalocean/netbox/issues/2565) - Improved rendering of Markdown tables * [#2575](https://github.com/digitalocean/netbox/issues/2575) - Correct model specified for rack roles table --- diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index 6222a477d..56104515c 100644 --- a/netbox/project-static/css/base.css +++ b/netbox/project-static/css/base.css @@ -390,6 +390,19 @@ table.report th a { top: -51px; } +/* Rendered Markdown */ +.rendered-markdown table { + width: 100%; +} +.rendered-markdown th { + border-bottom: 2px solid #dddddd; + padding: 8px; +} +.rendered-markdown td { + border-top: 1px solid #dddddd; + padding: 8px; +} + /* AJAX loader */ .loading { position: fixed; diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index 5c86cb24e..5b15782c9 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -131,7 +131,7 @@
Comments
-
+
{% if circuit.comments %} {{ circuit.comments|gfm }} {% else %} diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 4ec9adee1..157c66918 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -129,7 +129,7 @@
Comments
-
+
{% if provider.comments %} {{ provider.comments|gfm }} {% else %} diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 7b56269b1..61fb649c9 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -290,7 +290,7 @@
Comments
-
+
{% if device.comments %} {{ device.comments|gfm }} {% else %} diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 652c291e6..35931b49f 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -164,7 +164,7 @@
Comments
-
+
{% if devicetype.comments %} {{ devicetype.comments|gfm }} {% else %} diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index aaebe02da..ebe9a8870 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -164,7 +164,7 @@
Comments
-
+
{% if rack.comments %} {{ rack.comments|gfm }} {% else %} diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index f4623b57b..f592434c4 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -230,7 +230,7 @@
Comments
-
+
{% if site.comments %} {{ site.comments|gfm }} {% else %} diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index 6f2131a51..6068a7102 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -87,7 +87,7 @@
Comments
-
+
{% if tenant.comments %} {{ tenant.comments|gfm }} {% else %} diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html index 69ed4e212..d3606e624 100644 --- a/netbox/templates/virtualization/cluster.html +++ b/netbox/templates/virtualization/cluster.html @@ -99,7 +99,7 @@
Comments
-
+
{% if cluster.comments %} {{ cluster.comments|gfm }} {% else %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 552aaa997..9f8ec8308 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -143,7 +143,7 @@
Comments
-
+
{% if virtualmachine.comments %} {{ virtualmachine.comments|gfm }} {% else %} From 7bed48f5fee3180dce82de2623cc4ddc30ff3091 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Nov 2018 14:18:00 -0500 Subject: [PATCH 2/8] Expanded device interfaces display to include MTU, MAC address, and tags --- netbox/dcim/views.py | 6 ++-- netbox/templates/dcim/device.html | 1 + netbox/templates/dcim/inc/interface.html | 38 +++++++++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index eb7f71a25..91b2a25a4 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -858,8 +858,10 @@ class DeviceView(View): device.device_type.interface_ordering ).select_related( 'connected_as_a__interface_b__device', 'connected_as_b__interface_a__device', - 'circuit_termination__circuit' - ).prefetch_related('ip_addresses') + 'circuit_termination__circuit__provider' + ).prefetch_related( + 'tags', 'ip_addresses' + ) # Device bays device_bays = natsorted( diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 61fb649c9..faa946f2e 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -525,6 +525,7 @@ Name LAG Description + MTU Mode Connection diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index 57390e582..8a36e74ed 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -1,4 +1,5 @@ - +{% load helpers %} + {# Checkbox #} {% if perms.dcim.change_interface or perms.dcim.delete_interface %} @@ -7,32 +8,53 @@ {% endif %} - {# Icon and name #} + {# Icon/name/MAC #} {{ iface }} + {% if iface.mac_address %} +
{{ iface.mac_address }} + {% endif %} {# LAG #} {% if iface.lag %} - {{ iface.lag }} + {{ iface.lag }} {% endif %} - {# Description #} - {{ iface.description|default:"—" }} + {# Description/tags #} + + {% if iface.description %} + {{ iface.description }}
+ {% endif %} + {% for tag in iface.tags.all %} + {% tag tag %} + {% empty %} + {% if not iface.description %}—{% endif %} + {% endfor %} + + + {# MTU #} + {{ iface.mtu|default:"—" }} {# 802.1Q mode #} - {{ iface.get_mode_display }} + {{ iface.get_mode_display|default:"—" }} {# Connection or type #} {% if iface.is_lag %} LAG interface
- {{ iface.member_interfaces.all|join:", "|default:"No members" }} + + {% for member in iface.member_interfaces.all %} + {{ member }}{% if not forloop.last %}, {% endif %} + {% empty %} + No members + {% endfor %} + {% elif iface.is_virtual %} Virtual interface @@ -138,7 +160,7 @@ {% endif %} {# IP addresses table #} - + From 83be0b5db4314e4eb2313bbd371fc4d5f21ed2a8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Nov 2018 15:08:55 -0500 Subject: [PATCH 3/8] Closes #2490: Added bulk editing for config contexts --- CHANGELOG.md | 4 +++ netbox/extras/forms.py | 26 +++++++++++++++++-- netbox/extras/urls.py | 1 + netbox/extras/views.py | 16 ++++++++++-- .../templates/extras/configcontext_list.html | 2 +- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d286875..3f9cdf168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ v2.4.8 (FUTURE) +## Enhancements + +* [#2490](https://github.com/digitalocean/netbox/issues/2490) - Added bulk editing for config contexts + ## Bug Fixes * [#2473](https://github.com/digitalocean/netbox/issues/2473) - Fix encoding of long (>127 character) secrets diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index 7dfceb390..238dd831a 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -13,8 +13,8 @@ from taggit.models import Tag from dcim.models import DeviceRole, Platform, Region, Site from tenancy.models import Tenant, TenantGroup from utilities.forms import ( - add_blank_choice, BootstrapMixin, BulkEditForm, FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, - JSONField, SlugField, + add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, FilterChoiceField, + FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField, ) from .constants import ( CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, @@ -227,6 +227,28 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm): ] +class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm): + pk = forms.ModelMultipleChoiceField( + queryset=ConfigContext.objects.all(), + widget=forms.MultipleHiddenInput + ) + weight = forms.IntegerField( + required=False, + min_value=0 + ) + is_active = forms.NullBooleanField( + required=False, + widget=BulkEditNullBooleanSelect() + ) + description = forms.CharField( + required=False, + max_length=100 + ) + + class Meta: + nullable_fields = ['description'] + + class ConfigContextFilterForm(BootstrapMixin, forms.Form): q = forms.CharField( required=False, diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index e56652280..8af4e3910 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -16,6 +16,7 @@ urlpatterns = [ # Config contexts url(r'^config-contexts/$', views.ConfigContextListView.as_view(), name='configcontext_list'), url(r'^config-contexts/add/$', views.ConfigContextCreateView.as_view(), name='configcontext_add'), + url(r'^config-contexts/edit/$', views.ConfigContextBulkEditView.as_view(), name='configcontext_bulk_edit'), url(r'^config-contexts/(?P\d+)/$', views.ConfigContextView.as_view(), name='configcontext'), url(r'^config-contexts/(?P\d+)/edit/$', views.ConfigContextEditView.as_view(), name='configcontext_edit'), url(r'^config-contexts/(?P\d+)/delete/$', views.ConfigContextDeleteView.as_view(), name='configcontext_delete'), diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 7626d4012..2b656a28b 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -12,9 +12,12 @@ from django.views.generic import View from taggit.models import Tag from utilities.forms import ConfirmationForm -from utilities.views import BulkDeleteView, ObjectDeleteView, ObjectEditView, ObjectListView +from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView from . import filters -from .forms import ConfigContextForm, ConfigContextFilterForm, ImageAttachmentForm, ObjectChangeFilterForm, TagForm +from .forms import ( + ConfigContextForm, ConfigContextBulkEditForm, ConfigContextFilterForm, ImageAttachmentForm, ObjectChangeFilterForm, + TagForm, +) from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult from .reports import get_report, get_reports from .tables import ConfigContextTable, ObjectChangeTable, TagTable @@ -85,6 +88,15 @@ class ConfigContextEditView(ConfigContextCreateView): permission_required = 'extras.change_configcontext' +class ConfigContextBulkEditView(PermissionRequiredMixin, BulkEditView): + permission_required = 'extras.change_configcontext' + queryset = ConfigContext.objects.all() + filter = filters.ConfigContextFilter + table = ConfigContextTable + form = ConfigContextBulkEditForm + default_return_url = 'extras:configcontext_list' + + class ConfigContextDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'extras.delete_configcontext' model = ConfigContext diff --git a/netbox/templates/extras/configcontext_list.html b/netbox/templates/extras/configcontext_list.html index c35ba76ff..16a1dc220 100644 --- a/netbox/templates/extras/configcontext_list.html +++ b/netbox/templates/extras/configcontext_list.html @@ -10,7 +10,7 @@

{% block title %}Config Contexts{% endblock %}

- {% include 'utilities/obj_table.html' with bulk_delete_url='extras:configcontext_bulk_delete' %} + {% include 'utilities/obj_table.html' with bulk_edit_url='extras:configcontext_bulk_edit' bulk_delete_url='extras:configcontext_bulk_delete' %}
{% include 'inc/search_panel.html' %} From 408f6326361026fa0ac13afffe89a2277bf4f17c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 14 Nov 2018 10:12:35 -0500 Subject: [PATCH 4/8] Fixes #2588: Catch all exceptions from failed NAPALM API Calls --- CHANGELOG.md | 1 + netbox/dcim/api/views.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f9cdf168..ca2f3efd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ v2.4.8 (FUTURE) * [#2558](https://github.com/digitalocean/netbox/issues/2558) - Filter on all tags when multiple are passed * [#2565](https://github.com/digitalocean/netbox/issues/2565) - Improved rendering of Markdown tables * [#2575](https://github.com/digitalocean/netbox/issues/2575) - Correct model specified for rack roles table +* [#2588](https://github.com/digitalocean/netbox/issues/2588) - Catch all exceptions from failed NAPALM API Calls --- diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 2159661ef..fd4d37096 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -263,9 +263,9 @@ class DeviceViewSet(CustomFieldModelViewSet): # Check that NAPALM is installed try: import napalm + from napalm.base.exceptions import ModuleImportError except ImportError: raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.") - from napalm.base.exceptions import ModuleImportError # Validate the configured driver try: @@ -309,7 +309,9 @@ class DeviceViewSet(CustomFieldModelViewSet): try: response[method] = getattr(d, method)() except NotImplementedError: - response[method] = {'error': 'Method not implemented for NAPALM driver {}'.format(driver)} + response[method] = {'error': 'Method {} not implemented for NAPALM driver {}'.format(method, driver)} + except Exception as e: + response[method] = {'error': 'Method {} failed: {}'.format(method, e)} d.close() return Response(response) From 23cde65add0ce5fd38a65b12eca7f3c84dbcccb3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 14 Nov 2018 10:38:53 -0500 Subject: [PATCH 5/8] Fixes #2589: Virtual machine API serializer should require cluster assignment --- CHANGELOG.md | 1 + netbox/virtualization/api/serializers.py | 2 +- netbox/virtualization/tests/test_api.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca2f3efd9..f7f928d78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ v2.4.8 (FUTURE) * [#2565](https://github.com/digitalocean/netbox/issues/2565) - Improved rendering of Markdown tables * [#2575](https://github.com/digitalocean/netbox/issues/2575) - Correct model specified for rack roles table * [#2588](https://github.com/digitalocean/netbox/issues/2588) - Catch all exceptions from failed NAPALM API Calls +* [#2589](https://github.com/digitalocean/netbox/issues/2589) - Virtual machine API serializer should require cluster assignment --- diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index b749f1e5e..80fa73002 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -93,7 +93,7 @@ class VirtualMachineIPAddressSerializer(WritableNestedSerializer): class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer): status = ChoiceField(choices=VM_STATUS_CHOICES, required=False) site = NestedSiteSerializer(read_only=True) - cluster = NestedClusterSerializer(required=False, allow_null=True) + cluster = NestedClusterSerializer() role = NestedDeviceRoleSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) platform = NestedPlatformSerializer(required=False, allow_null=True) diff --git a/netbox/virtualization/tests/test_api.py b/netbox/virtualization/tests/test_api.py index 32f56b99b..99e57b201 100644 --- a/netbox/virtualization/tests/test_api.py +++ b/netbox/virtualization/tests/test_api.py @@ -380,6 +380,18 @@ class VirtualMachineTest(APITestCase): self.assertEqual(virtualmachine4.name, data['name']) self.assertEqual(virtualmachine4.cluster.pk, data['cluster']) + def test_create_virtualmachine_without_cluster(self): + + data = { + 'name': 'Test Virtual Machine 4', + } + + url = reverse('virtualization-api:virtualmachine-list') + response = self.client.post(url, data, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + self.assertEqual(VirtualMachine.objects.count(), 3) + def test_create_virtualmachine_bulk(self): data = [ From 3366a6ae3d420365a5b0b9dd93df6d56125e1d05 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 15 Nov 2018 16:47:41 -0500 Subject: [PATCH 6/8] Closes #2557: Added object view for tags --- CHANGELOG.md | 1 + netbox/extras/forms.py | 5 ++ netbox/extras/tables.py | 30 +++++++++++- netbox/extras/urls.py | 1 + netbox/extras/views.py | 51 ++++++++++++++++++-- netbox/templates/extras/tag.html | 69 +++++++++++++++++++++++++++ netbox/templates/extras/tag_list.html | 5 +- 7 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 netbox/templates/extras/tag.html diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f928d78..bf5f1d6ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ v2.4.8 (FUTURE) ## Enhancements * [#2490](https://github.com/digitalocean/netbox/issues/2490) - Added bulk editing for config contexts +* [#2557](https://github.com/digitalocean/netbox/issues/2557) - Added object view for tags ## Bug Fixes diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index 238dd831a..6fc4b8859 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -208,6 +208,11 @@ class AddRemoveTagsForm(forms.Form): self.fields['remove_tags'] = TagField(required=False) +class TagFilterForm(BootstrapMixin, forms.Form): + model = Tag + q = forms.CharField(required=False, label='Search') + + # # Config contexts # diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index 22bf26cce..cf2b6f888 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -1,7 +1,8 @@ from __future__ import unicode_literals import django_tables2 as tables -from taggit.models import Tag +from django_tables2.utils import Accessor +from taggit.models import Tag, TaggedItem from utilities.tables import BaseTable, BooleanColumn, ToggleColumn from .models import ConfigContext, ObjectChange @@ -15,6 +16,14 @@ TAG_ACTIONS = """ {% endif %} """ +TAGGED_ITEM = """ +{% if value.get_absolute_url %} + {{ value }} +{% else %} + {{ value }} +{% endif %} +""" + CONFIGCONTEXT_ACTIONS = """ {% if perms.extras.change_configcontext %} @@ -55,6 +64,10 @@ OBJECTCHANGE_REQUEST_ID = """ class TagTable(BaseTable): pk = ToggleColumn() + name = tables.LinkColumn( + viewname='extras:tag', + args=[Accessor('slug')] + ) actions = tables.TemplateColumn( template_code=TAG_ACTIONS, attrs={'td': {'class': 'text-right'}}, @@ -66,6 +79,21 @@ class TagTable(BaseTable): fields = ('pk', 'name', 'items', 'slug', 'actions') +class TaggedItemTable(BaseTable): + content_object = tables.TemplateColumn( + template_code=TAGGED_ITEM, + orderable=False, + verbose_name='Object' + ) + content_type = tables.Column( + verbose_name='Type' + ) + + class Meta(BaseTable.Meta): + model = TaggedItem + fields = ('content_object', 'content_type') + + class ConfigContextTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index 8af4e3910..a97019a04 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ # Tags url(r'^tags/$', views.TagListView.as_view(), name='tag_list'), + url(r'^tags/(?P[\w-]+)/$', views.TagView.as_view(), name='tag'), url(r'^tags/(?P[\w-]+)/edit/$', views.TagEditView.as_view(), name='tag_edit'), url(r'^tags/(?P[\w-]+)/delete/$', views.TagDeleteView.as_view(), name='tag_delete'), url(r'^tags/delete/$', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'), diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 2b656a28b..3e9186490 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django import template +from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.contenttypes.models import ContentType @@ -9,18 +10,20 @@ from django.http import Http404 from django.shortcuts import get_object_or_404, redirect, render from django.utils.safestring import mark_safe from django.views.generic import View -from taggit.models import Tag +from django_tables2 import RequestConfig +from taggit.models import Tag, TaggedItem from utilities.forms import ConfirmationForm +from utilities.paginator import EnhancedPaginator from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView from . import filters from .forms import ( ConfigContextForm, ConfigContextBulkEditForm, ConfigContextFilterForm, ImageAttachmentForm, ObjectChangeFilterForm, - TagForm, + TagFilterForm, TagForm, ) from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult from .reports import get_report, get_reports -from .tables import ConfigContextTable, ObjectChangeTable, TagTable +from .tables import ConfigContextTable, ObjectChangeTable, TagTable, TaggedItemTable # @@ -28,11 +31,45 @@ from .tables import ConfigContextTable, ObjectChangeTable, TagTable # class TagListView(ObjectListView): - queryset = Tag.objects.annotate(items=Count('taggit_taggeditem_items')).order_by('name') + queryset = Tag.objects.annotate( + items=Count('taggit_taggeditem_items') + ).order_by( + 'name' + ) + filter = filters.TagFilter + filter_form = TagFilterForm table = TagTable template_name = 'extras/tag_list.html' +class TagView(View): + + def get(self, request, slug): + + tag = get_object_or_404(Tag, slug=slug) + tagged_items = TaggedItem.objects.filter( + tag=tag + ).select_related( + 'content_type' + ).prefetch_related( + 'content_object' + ) + + # Generate a table of all items tagged with this Tag + items_table = TaggedItemTable(tagged_items) + paginate = { + 'klass': EnhancedPaginator, + 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT) + } + RequestConfig(request, paginate).configure(items_table) + + return render(request, 'extras/tag.html', { + 'tag': tag, + 'items_count': tagged_items.count(), + 'items_table': items_table, + }) + + class TagEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'taggit.change_tag' model = Tag @@ -48,7 +85,11 @@ class TagDeleteView(PermissionRequiredMixin, ObjectDeleteView): class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'circuits.delete_circuittype' - queryset = Tag.objects.annotate(items=Count('taggit_taggeditem_items')).order_by('name') + queryset = Tag.objects.annotate( + items=Count('taggit_taggeditem_items') + ).order_by( + 'name' + ) table = TagTable default_return_url = 'extras:tag_list' diff --git a/netbox/templates/extras/tag.html b/netbox/templates/extras/tag.html new file mode 100644 index 000000000..aceb0ca94 --- /dev/null +++ b/netbox/templates/extras/tag.html @@ -0,0 +1,69 @@ +{% extends '_base.html' %} +{% load helpers %} + +{% block header %} +
+
+ +
+
+
+
+ + + + +
+ +
+
+
+ {% if perms.taggit.change_tag %} + + + Edit this tag + + {% endif %} +
+

{% block title %}Tag: {{ tag }}{% endblock %}

+{% endblock %} + +{% block content %} +
+
+
+
+ Tag +
+
+ + + + + + + + + + + + +
Name + {{ tag.name }} +
Slug + {{ tag.slug }} +
Tagged Items + {{ items_count }} +
+
+
+
+ {% include 'panel_table.html' with table=items_table heading='Tagged Objects' %} +
+
+{% endblock %} diff --git a/netbox/templates/extras/tag_list.html b/netbox/templates/extras/tag_list.html index 3136991a0..8178e5538 100644 --- a/netbox/templates/extras/tag_list.html +++ b/netbox/templates/extras/tag_list.html @@ -4,8 +4,11 @@ {% block content %}

{% block title %}Tags{% endblock %}

-
+
{% include 'utilities/obj_table.html' with bulk_delete_url='extras:tag_bulk_delete' %}
+
+ {% include 'inc/search_panel.html' %} +
{% endblock %} From 55c153c5a9585a032edca1d85921ef09393aa53f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 20 Nov 2018 11:56:14 -0500 Subject: [PATCH 7/8] Release v2.4.8 --- CHANGELOG.md | 2 +- netbox/netbox/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5f1d6ee..6560f0e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v2.4.8 (FUTURE) +v2.4.8 (2018-11-20) ## Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 1b09989c7..42451c9a2 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -22,7 +22,7 @@ if sys.version_info[0] < 3: DeprecationWarning ) -VERSION = '2.4.8-dev' +VERSION = '2.4.8' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) From 34bfb899d17c0bf152da05b7d645978851b913c4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 20 Nov 2018 11:58:19 -0500 Subject: [PATCH 8/8] Post-release version bump --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 42451c9a2..f6fbcdf70 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -22,7 +22,7 @@ if sys.version_info[0] < 3: DeprecationWarning ) -VERSION = '2.4.8' +VERSION = '2.4.9-dev' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))