Просмотр исходного кода

Closes #3456: Enable bulk editing of tag color

Jeremy Stretch 6 лет назад
Родитель
Сommit
8ff3d2cbf6
5 измененных файлов с 42 добавлено и 15 удалено
  1. 1 0
      CHANGELOG.md
  2. 18 2
      netbox/extras/forms.py
  3. 1 0
      netbox/extras/urls.py
  4. 21 12
      netbox/extras/views.py
  5. 1 1
      netbox/templates/extras/tag_list.html

+ 1 - 0
CHANGELOG.md

@@ -16,6 +16,7 @@ v2.6.3 (FUTURE)
 * [#3405](https://github.com/netbox-community/netbox/issues/3405) - Fix population of power port/outlet details on device creation
 * [#3405](https://github.com/netbox-community/netbox/issues/3405) - Fix population of power port/outlet details on device creation
 * [#3422](https://github.com/netbox-community/netbox/issues/3422) - Prevent navigation menu from overlapping page content
 * [#3422](https://github.com/netbox-community/netbox/issues/3422) - Prevent navigation menu from overlapping page content
 * [#3430](https://github.com/netbox-community/netbox/issues/3430) - Linkify platform field on device view
 * [#3430](https://github.com/netbox-community/netbox/issues/3430) - Linkify platform field on device view
+* [#3456](https://github.com/netbox-community/netbox/issues/3456) - Enable bulk editing of tag color
 
 
 ---
 ---
 
 

+ 18 - 2
netbox/extras/forms.py

@@ -8,9 +8,10 @@ from taggit.forms import TagField
 
 
 from dcim.models import DeviceRole, Platform, Region, Site
 from dcim.models import DeviceRole, Platform, Region, Site
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
+from utilities.constants import COLOR_CHOICES
 from utilities.forms import (
 from utilities.forms import (
-    add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, CommentField,
-    ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField,
+    add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
+    CommentField, ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField,
 )
 )
 from .constants import (
 from .constants import (
     CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
     CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
@@ -219,6 +220,21 @@ class TagFilterForm(BootstrapMixin, forms.Form):
     )
     )
 
 
 
 
+class TagBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=Tag.objects.all(),
+        widget=forms.MultipleHiddenInput
+    )
+    color = forms.CharField(
+        max_length=6,
+        required=False,
+        widget=ColorSelect()
+    )
+
+    class Meta:
+        nullable_fields = []
+
+
 #
 #
 # Config contexts
 # Config contexts
 #
 #

+ 1 - 0
netbox/extras/urls.py

@@ -9,6 +9,7 @@ urlpatterns = [
 
 
     # Tags
     # Tags
     path(r'tags/', views.TagListView.as_view(), name='tag_list'),
     path(r'tags/', views.TagListView.as_view(), name='tag_list'),
+    path(r'tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'),
     path(r'tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
     path(r'tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
     path(r'tags/<slug:slug>/', views.TagView.as_view(), name='tag'),
     path(r'tags/<slug:slug>/', views.TagView.as_view(), name='tag'),
     path(r'tags/<slug:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),
     path(r'tags/<slug:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),

+ 21 - 12
netbox/extras/views.py

@@ -13,11 +13,7 @@ from django_tables2 import RequestConfig
 from utilities.forms import ConfirmationForm
 from utilities.forms import ConfirmationForm
 from utilities.paginator import EnhancedPaginator
 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 .forms import (
-    ConfigContextForm, ConfigContextBulkEditForm, ConfigContextFilterForm, ImageAttachmentForm, ObjectChangeFilterForm,
-    TagFilterForm, TagForm,
-)
+from . import filters, forms
 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
@@ -36,7 +32,7 @@ class TagListView(PermissionRequiredMixin, ObjectListView):
         'name'
         'name'
     )
     )
     filter = filters.TagFilter
     filter = filters.TagFilter
-    filter_form = TagFilterForm
+    filter_form = forms.TagFilterForm
     table = TagTable
     table = TagTable
     template_name = 'extras/tag_list.html'
     template_name = 'extras/tag_list.html'
 
 
@@ -70,7 +66,7 @@ class TagView(View):
 class TagEditView(PermissionRequiredMixin, ObjectEditView):
 class TagEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'extras.change_tag'
     permission_required = 'extras.change_tag'
     model = Tag
     model = Tag
-    model_form = TagForm
+    model_form = forms.TagForm
     default_return_url = 'extras:tag_list'
     default_return_url = 'extras:tag_list'
     template_name = 'extras/tag_edit.html'
     template_name = 'extras/tag_edit.html'
 
 
@@ -81,6 +77,19 @@ class TagDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     default_return_url = 'extras:tag_list'
     default_return_url = 'extras:tag_list'
 
 
 
 
+class TagBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'extras.change_tag'
+    queryset = Tag.objects.annotate(
+        items=Count('extras_taggeditem_items', distinct=True)
+    ).order_by(
+        'name'
+    )
+    # filter = filters.ProviderFilter
+    table = TagTable
+    form = forms.TagBulkEditForm
+    default_return_url = 'circuits:provider_list'
+
+
 class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'extras.delete_tag'
     permission_required = 'extras.delete_tag'
     queryset = Tag.objects.annotate(
     queryset = Tag.objects.annotate(
@@ -100,7 +109,7 @@ class ConfigContextListView(PermissionRequiredMixin, ObjectListView):
     permission_required = 'extras.view_configcontext'
     permission_required = 'extras.view_configcontext'
     queryset = ConfigContext.objects.all()
     queryset = ConfigContext.objects.all()
     filter = filters.ConfigContextFilter
     filter = filters.ConfigContextFilter
-    filter_form = ConfigContextFilterForm
+    filter_form = forms.ConfigContextFilterForm
     table = ConfigContextTable
     table = ConfigContextTable
     template_name = 'extras/configcontext_list.html'
     template_name = 'extras/configcontext_list.html'
 
 
@@ -120,7 +129,7 @@ class ConfigContextView(PermissionRequiredMixin, View):
 class ConfigContextCreateView(PermissionRequiredMixin, ObjectEditView):
 class ConfigContextCreateView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'extras.add_configcontext'
     permission_required = 'extras.add_configcontext'
     model = ConfigContext
     model = ConfigContext
-    model_form = ConfigContextForm
+    model_form = forms.ConfigContextForm
     default_return_url = 'extras:configcontext_list'
     default_return_url = 'extras:configcontext_list'
     template_name = 'extras/configcontext_edit.html'
     template_name = 'extras/configcontext_edit.html'
 
 
@@ -134,7 +143,7 @@ class ConfigContextBulkEditView(PermissionRequiredMixin, BulkEditView):
     queryset = ConfigContext.objects.all()
     queryset = ConfigContext.objects.all()
     filter = filters.ConfigContextFilter
     filter = filters.ConfigContextFilter
     table = ConfigContextTable
     table = ConfigContextTable
-    form = ConfigContextBulkEditForm
+    form = forms.ConfigContextBulkEditForm
     default_return_url = 'extras:configcontext_list'
     default_return_url = 'extras:configcontext_list'
 
 
 
 
@@ -179,7 +188,7 @@ class ObjectChangeListView(PermissionRequiredMixin, ObjectListView):
     permission_required = 'extras.view_objectchange'
     permission_required = 'extras.view_objectchange'
     queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
     queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
     filter = filters.ObjectChangeFilter
     filter = filters.ObjectChangeFilter
-    filter_form = ObjectChangeFilterForm
+    filter_form = forms.ObjectChangeFilterForm
     table = ObjectChangeTable
     table = ObjectChangeTable
     template_name = 'extras/objectchange_list.html'
     template_name = 'extras/objectchange_list.html'
 
 
@@ -258,7 +267,7 @@ class ObjectChangeLogView(View):
 class ImageAttachmentEditView(PermissionRequiredMixin, ObjectEditView):
 class ImageAttachmentEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'extras.change_imageattachment'
     permission_required = 'extras.change_imageattachment'
     model = ImageAttachment
     model = ImageAttachment
-    model_form = ImageAttachmentForm
+    model_form = forms.ImageAttachmentForm
 
 
     def alter_obj(self, imageattachment, request, args, kwargs):
     def alter_obj(self, imageattachment, request, args, kwargs):
         if not imageattachment.pk:
         if not imageattachment.pk:

+ 1 - 1
netbox/templates/extras/tag_list.html

@@ -5,7 +5,7 @@
 <h1>{% block title %}Tags{% endblock %}</h1>
 <h1>{% block title %}Tags{% endblock %}</h1>
 <div class="row">
 <div class="row">
 	<div class="col-md-9">
 	<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_edit_url='extras:tag_bulk_edit' bulk_delete_url='extras:tag_bulk_delete' %}
     </div>
     </div>
     <div class="col-md-3">
     <div class="col-md-3">
 		{% include 'inc/search_panel.html' %}
 		{% include 'inc/search_panel.html' %}