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 %} + + + + Tags + {{ tag }} + + + + + + + + + + + + + + + + + {% 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 %}