mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
Standardize add, import, and export functionality for tags
This commit is contained in:
parent
057a022205
commit
da906f48d9
@ -1,6 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
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.utils.safestring import mark_safe
|
||||||
from mptt.forms import TreeNodeMultipleChoiceField
|
from mptt.forms import TreeNodeMultipleChoiceField
|
||||||
from taggit.forms import TagField as TagField_
|
from taggit.forms import TagField as TagField_
|
||||||
|
|
||||||
@ -161,6 +162,17 @@ class TagForm(BootstrapMixin, forms.ModelForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TagCSVForm(CSVModelForm):
|
||||||
|
slug = SlugField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
fields = Tag.csv_headers
|
||||||
|
help_texts = {
|
||||||
|
'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AddRemoveTagsForm(forms.Form):
|
class AddRemoveTagsForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -24,6 +24,8 @@ class Tag(TagBase, ChangeLoggedModel):
|
|||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
csv_headers = ['name', 'slug', 'color', 'description']
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('extras:tag', args=[self.slug])
|
return reverse('extras:tag', args=[self.slug])
|
||||||
|
|
||||||
@ -34,6 +36,14 @@ class Tag(TagBase, ChangeLoggedModel):
|
|||||||
slug += "_%d" % i
|
slug += "_%d" % i
|
||||||
return slug
|
return slug
|
||||||
|
|
||||||
|
def to_csv(self):
|
||||||
|
return (
|
||||||
|
self.name,
|
||||||
|
self.slug,
|
||||||
|
self.color,
|
||||||
|
self.description
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TaggedItem(GenericTaggedItemBase):
|
class TaggedItem(GenericTaggedItemBase):
|
||||||
tag = models.ForeignKey(
|
tag = models.ForeignKey(
|
||||||
|
@ -10,16 +10,7 @@ from extras.models import ConfigContext, ObjectChange, Tag
|
|||||||
from utilities.testing import ViewTestCases, TestCase
|
from utilities.testing import ViewTestCases, TestCase
|
||||||
|
|
||||||
|
|
||||||
# TODO: Change base class to PrimaryObjectViewTestCase
|
class TagTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
# Blocked by #3703
|
|
||||||
class TagTestCase(
|
|
||||||
ViewTestCases.GetObjectViewTestCase,
|
|
||||||
ViewTestCases.EditObjectViewTestCase,
|
|
||||||
ViewTestCases.DeleteObjectViewTestCase,
|
|
||||||
ViewTestCases.ListObjectsViewTestCase,
|
|
||||||
ViewTestCases.BulkEditObjectsViewTestCase,
|
|
||||||
ViewTestCases.BulkDeleteObjectsViewTestCase
|
|
||||||
):
|
|
||||||
model = Tag
|
model = Tag
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -38,6 +29,13 @@ class TagTestCase(
|
|||||||
'comments': 'Some comments',
|
'comments': 'Some comments',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cls.csv_data = (
|
||||||
|
"name,slug,color,description",
|
||||||
|
"Tag 4,tag-4,ff0000,Fourth tag",
|
||||||
|
"Tag 5,tag-5,00ff00,Fifth tag",
|
||||||
|
"Tag 6,tag-6,0000ff,Sixth tag",
|
||||||
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'color': '00ff00',
|
'color': '00ff00',
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Tags
|
# Tags
|
||||||
path('tags/', views.TagListView.as_view(), name='tag_list'),
|
path('tags/', views.TagListView.as_view(), name='tag_list'),
|
||||||
|
path('tags/add/', views.TagEditView.as_view(), name='tag_add'),
|
||||||
|
path('tags/import/', views.TagBulkImportView.as_view(), name='tag_import'),
|
||||||
path('tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'),
|
path('tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'),
|
||||||
path('tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
path('tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
||||||
path('tags/<str:slug>/', views.TagView.as_view(), name='tag'),
|
path('tags/<str:slug>/', views.TagView.as_view(), name='tag'),
|
||||||
|
@ -13,14 +13,13 @@ from utilities.forms import ConfirmationForm
|
|||||||
from utilities.paginator import EnhancedPaginator
|
from utilities.paginator import EnhancedPaginator
|
||||||
from utilities.utils import shallow_compare_dict
|
from utilities.utils import shallow_compare_dict
|
||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
BulkDeleteView, BulkEditView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
ObjectPermissionRequiredMixin,
|
ObjectPermissionRequiredMixin,
|
||||||
)
|
)
|
||||||
from . import filters, forms
|
from . import filters, forms, tables
|
||||||
from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult, Tag, TaggedItem
|
from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult, Tag, TaggedItem
|
||||||
from .reports import get_report, get_reports
|
from .reports import get_report, get_reports
|
||||||
from .scripts import get_scripts, run_script
|
from .scripts import get_scripts, run_script
|
||||||
from .tables import ConfigContextTable, ObjectChangeTable, TagTable, TaggedItemTable
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -35,8 +34,7 @@ class TagListView(ObjectListView):
|
|||||||
)
|
)
|
||||||
filterset = filters.TagFilterSet
|
filterset = filters.TagFilterSet
|
||||||
filterset_form = forms.TagFilterForm
|
filterset_form = forms.TagFilterForm
|
||||||
table = TagTable
|
table = tables.TagTable
|
||||||
action_buttons = ()
|
|
||||||
|
|
||||||
|
|
||||||
class TagView(ObjectView):
|
class TagView(ObjectView):
|
||||||
@ -52,7 +50,7 @@ class TagView(ObjectView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Generate a table of all items tagged with this Tag
|
# Generate a table of all items tagged with this Tag
|
||||||
items_table = TaggedItemTable(tagged_items)
|
items_table = tables.TaggedItemTable(tagged_items)
|
||||||
paginate = {
|
paginate = {
|
||||||
'paginator_class': EnhancedPaginator,
|
'paginator_class': EnhancedPaginator,
|
||||||
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||||
@ -78,13 +76,20 @@ class TagDeleteView(ObjectDeleteView):
|
|||||||
default_return_url = 'extras:tag_list'
|
default_return_url = 'extras:tag_list'
|
||||||
|
|
||||||
|
|
||||||
|
class TagBulkImportView(BulkImportView):
|
||||||
|
queryset = Tag.objects.all()
|
||||||
|
model_form = forms.TagCSVForm
|
||||||
|
table = tables.TagTable
|
||||||
|
default_return_url = 'extras:tag_list'
|
||||||
|
|
||||||
|
|
||||||
class TagBulkEditView(BulkEditView):
|
class TagBulkEditView(BulkEditView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=Count('extras_taggeditem_items', distinct=True)
|
items=Count('extras_taggeditem_items', distinct=True)
|
||||||
).order_by(
|
).order_by(
|
||||||
'name'
|
'name'
|
||||||
)
|
)
|
||||||
table = TagTable
|
table = tables.TagTable
|
||||||
form = forms.TagBulkEditForm
|
form = forms.TagBulkEditForm
|
||||||
default_return_url = 'extras:tag_list'
|
default_return_url = 'extras:tag_list'
|
||||||
|
|
||||||
@ -95,7 +100,7 @@ class TagBulkDeleteView(BulkDeleteView):
|
|||||||
).order_by(
|
).order_by(
|
||||||
'name'
|
'name'
|
||||||
)
|
)
|
||||||
table = TagTable
|
table = tables.TagTable
|
||||||
default_return_url = 'extras:tag_list'
|
default_return_url = 'extras:tag_list'
|
||||||
|
|
||||||
|
|
||||||
@ -107,7 +112,7 @@ class ConfigContextListView(ObjectListView):
|
|||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
filterset = filters.ConfigContextFilterSet
|
filterset = filters.ConfigContextFilterSet
|
||||||
filterset_form = forms.ConfigContextFilterForm
|
filterset_form = forms.ConfigContextFilterForm
|
||||||
table = ConfigContextTable
|
table = tables.ConfigContextTable
|
||||||
action_buttons = ('add',)
|
action_buttons = ('add',)
|
||||||
|
|
||||||
|
|
||||||
@ -143,7 +148,7 @@ class ConfigContextEditView(ObjectEditView):
|
|||||||
class ConfigContextBulkEditView(BulkEditView):
|
class ConfigContextBulkEditView(BulkEditView):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
filterset = filters.ConfigContextFilterSet
|
filterset = filters.ConfigContextFilterSet
|
||||||
table = ConfigContextTable
|
table = tables.ConfigContextTable
|
||||||
form = forms.ConfigContextBulkEditForm
|
form = forms.ConfigContextBulkEditForm
|
||||||
default_return_url = 'extras:configcontext_list'
|
default_return_url = 'extras:configcontext_list'
|
||||||
|
|
||||||
@ -155,7 +160,7 @@ class ConfigContextDeleteView(ObjectDeleteView):
|
|||||||
|
|
||||||
class ConfigContextBulkDeleteView(BulkDeleteView):
|
class ConfigContextBulkDeleteView(BulkDeleteView):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
table = ConfigContextTable
|
table = tables.ConfigContextTable
|
||||||
default_return_url = 'extras:configcontext_list'
|
default_return_url = 'extras:configcontext_list'
|
||||||
|
|
||||||
|
|
||||||
@ -197,7 +202,7 @@ class ObjectChangeListView(ObjectListView):
|
|||||||
queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
|
queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
|
||||||
filterset = filters.ObjectChangeFilterSet
|
filterset = filters.ObjectChangeFilterSet
|
||||||
filterset_form = forms.ObjectChangeFilterForm
|
filterset_form = forms.ObjectChangeFilterForm
|
||||||
table = ObjectChangeTable
|
table = tables.ObjectChangeTable
|
||||||
template_name = 'extras/objectchange_list.html'
|
template_name = 'extras/objectchange_list.html'
|
||||||
action_buttons = ('export',)
|
action_buttons = ('export',)
|
||||||
|
|
||||||
@ -214,7 +219,7 @@ class ObjectChangeView(ObjectView):
|
|||||||
).exclude(
|
).exclude(
|
||||||
pk=objectchange.pk
|
pk=objectchange.pk
|
||||||
)
|
)
|
||||||
related_changes_table = ObjectChangeTable(
|
related_changes_table = tables.ObjectChangeTable(
|
||||||
data=related_changes[:50],
|
data=related_changes[:50],
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
@ -267,7 +272,7 @@ class ObjectChangeLogView(View):
|
|||||||
Q(changed_object_type=content_type, changed_object_id=obj.pk) |
|
Q(changed_object_type=content_type, changed_object_id=obj.pk) |
|
||||||
Q(related_object_type=content_type, related_object_id=obj.pk)
|
Q(related_object_type=content_type, related_object_id=obj.pk)
|
||||||
)
|
)
|
||||||
objectchanges_table = ObjectChangeTable(
|
objectchanges_table = tables.ObjectChangeTable(
|
||||||
data=objectchanges,
|
data=objectchanges,
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>
|
<td>
|
||||||
{{ tag.description }}
|
{{ tag.description|placeholder }}
|
||||||
</td>
|
</td>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -101,6 +101,12 @@
|
|||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header">Tags</li>
|
<li class="dropdown-header">Tags</li>
|
||||||
<li{% if not perms.extras.view_tag %} class="disabled"{% endif %}>
|
<li{% if not perms.extras.view_tag %} class="disabled"{% endif %}>
|
||||||
|
{% if perms.extras.add_tag %}
|
||||||
|
<div class="buttons pull-right">
|
||||||
|
<a href="{% url 'extras:tag_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||||
|
<a href="{% url 'extras:tag_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<a href="{% url 'extras:tag_list' %}">Tags</a>
|
<a href="{% url 'extras:tag_list' %}">Tags</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
Loading…
Reference in New Issue
Block a user