mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
Closes #2557: Added object view for tags
This commit is contained in:
parent
23cde65add
commit
3366a6ae3d
@ -3,6 +3,7 @@ v2.4.8 (FUTURE)
|
|||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
* [#2490](https://github.com/digitalocean/netbox/issues/2490) - Added bulk editing for config contexts
|
* [#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
|
## Bug Fixes
|
||||||
|
|
||||||
|
@ -208,6 +208,11 @@ class AddRemoveTagsForm(forms.Form):
|
|||||||
self.fields['remove_tags'] = TagField(required=False)
|
self.fields['remove_tags'] = TagField(required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class TagFilterForm(BootstrapMixin, forms.Form):
|
||||||
|
model = Tag
|
||||||
|
q = forms.CharField(required=False, label='Search')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Config contexts
|
# Config contexts
|
||||||
#
|
#
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_tables2 as tables
|
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 utilities.tables import BaseTable, BooleanColumn, ToggleColumn
|
||||||
from .models import ConfigContext, ObjectChange
|
from .models import ConfigContext, ObjectChange
|
||||||
@ -15,6 +16,14 @@ TAG_ACTIONS = """
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
TAGGED_ITEM = """
|
||||||
|
{% if value.get_absolute_url %}
|
||||||
|
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ value }}
|
||||||
|
{% endif %}
|
||||||
|
"""
|
||||||
|
|
||||||
CONFIGCONTEXT_ACTIONS = """
|
CONFIGCONTEXT_ACTIONS = """
|
||||||
{% if perms.extras.change_configcontext %}
|
{% if perms.extras.change_configcontext %}
|
||||||
<a href="{% url 'extras:configcontext_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'extras:configcontext_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
@ -55,6 +64,10 @@ OBJECTCHANGE_REQUEST_ID = """
|
|||||||
|
|
||||||
class TagTable(BaseTable):
|
class TagTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
name = tables.LinkColumn(
|
||||||
|
viewname='extras:tag',
|
||||||
|
args=[Accessor('slug')]
|
||||||
|
)
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=TAG_ACTIONS,
|
template_code=TAG_ACTIONS,
|
||||||
attrs={'td': {'class': 'text-right'}},
|
attrs={'td': {'class': 'text-right'}},
|
||||||
@ -66,6 +79,21 @@ class TagTable(BaseTable):
|
|||||||
fields = ('pk', 'name', 'items', 'slug', 'actions')
|
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):
|
class ConfigContextTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
|
@ -9,6 +9,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Tags
|
# Tags
|
||||||
url(r'^tags/$', views.TagListView.as_view(), name='tag_list'),
|
url(r'^tags/$', views.TagListView.as_view(), name='tag_list'),
|
||||||
|
url(r'^tags/(?P<slug>[\w-]+)/$', views.TagView.as_view(), name='tag'),
|
||||||
url(r'^tags/(?P<slug>[\w-]+)/edit/$', views.TagEditView.as_view(), name='tag_edit'),
|
url(r'^tags/(?P<slug>[\w-]+)/edit/$', views.TagEditView.as_view(), name='tag_edit'),
|
||||||
url(r'^tags/(?P<slug>[\w-]+)/delete/$', views.TagDeleteView.as_view(), name='tag_delete'),
|
url(r'^tags/(?P<slug>[\w-]+)/delete/$', views.TagDeleteView.as_view(), name='tag_delete'),
|
||||||
url(r'^tags/delete/$', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
url(r'^tags/delete/$', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
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.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.views.generic import View
|
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.forms import ConfirmationForm
|
||||||
|
from utilities.paginator import EnhancedPaginator
|
||||||
from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView
|
from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView
|
||||||
from . import filters
|
from . import filters
|
||||||
from .forms import (
|
from .forms import (
|
||||||
ConfigContextForm, ConfigContextBulkEditForm, ConfigContextFilterForm, ImageAttachmentForm, ObjectChangeFilterForm,
|
ConfigContextForm, ConfigContextBulkEditForm, ConfigContextFilterForm, ImageAttachmentForm, ObjectChangeFilterForm,
|
||||||
TagForm,
|
TagFilterForm, TagForm,
|
||||||
)
|
)
|
||||||
from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult
|
from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult
|
||||||
from .reports import get_report, get_reports
|
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):
|
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
|
table = TagTable
|
||||||
template_name = 'extras/tag_list.html'
|
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):
|
class TagEditView(PermissionRequiredMixin, ObjectEditView):
|
||||||
permission_required = 'taggit.change_tag'
|
permission_required = 'taggit.change_tag'
|
||||||
model = Tag
|
model = Tag
|
||||||
@ -48,7 +85,11 @@ class TagDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
|
|
||||||
class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'circuits.delete_circuittype'
|
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
|
table = TagTable
|
||||||
default_return_url = 'extras:tag_list'
|
default_return_url = 'extras:tag_list'
|
||||||
|
|
||||||
|
69
netbox/templates/extras/tag.html
Normal file
69
netbox/templates/extras/tag.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-8 col-md-9">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="{% url 'extras:tag_list' %}">Tags</a></li>
|
||||||
|
<li>{{ tag }}</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-md-3">
|
||||||
|
<form action="{% url 'extras:tag_list' %}" method="get">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" name="q" class="form-control" />
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<span class="fa fa-search" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
{% if perms.taggit.change_tag %}
|
||||||
|
<a href="{% url 'extras:tag_edit' slug=tag.slug %}?return_url={% url 'extras:tag' slug=tag.slug %}" class="btn btn-warning">
|
||||||
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||||
|
Edit this tag
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<h1>{% block title %}Tag: {{ tag }}{% endblock %}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Tag</strong>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover panel-body attr-table">
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>
|
||||||
|
{{ tag.name }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Slug</td>
|
||||||
|
<td>
|
||||||
|
{{ tag.slug }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Tagged Items</td>
|
||||||
|
<td>
|
||||||
|
{{ items_count }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% include 'panel_table.html' with table=items_table heading='Tagged Objects' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -4,8 +4,11 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% block title %}Tags{% endblock %}</h1>
|
<h1>{% block title %}Tags{% endblock %}</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_delete_url='extras:tag_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_delete_url='extras:tag_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
{% include 'inc/search_panel.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user