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

Standardize add, import, and export functionality for tags

Jeremy Stretch 5 лет назад
Родитель
Сommit
da906f48d9

+ 12 - 0
netbox/extras/forms.py

@@ -1,6 +1,7 @@
 from django import forms
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
+from django.utils.safestring import mark_safe
 from mptt.forms import TreeNodeMultipleChoiceField
 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):
 
     def __init__(self, *args, **kwargs):

+ 10 - 0
netbox/extras/models/tags.py

@@ -24,6 +24,8 @@ class Tag(TagBase, ChangeLoggedModel):
 
     objects = RestrictedQuerySet.as_manager()
 
+    csv_headers = ['name', 'slug', 'color', 'description']
+
     def get_absolute_url(self):
         return reverse('extras:tag', args=[self.slug])
 
@@ -34,6 +36,14 @@ class Tag(TagBase, ChangeLoggedModel):
             slug += "_%d" % i
         return slug
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+            self.color,
+            self.description
+        )
+
 
 class TaggedItem(GenericTaggedItemBase):
     tag = models.ForeignKey(

+ 8 - 10
netbox/extras/tests/test_views.py

@@ -10,16 +10,7 @@ from extras.models import ConfigContext, ObjectChange, Tag
 from utilities.testing import ViewTestCases, TestCase
 
 
-# TODO: Change base class to PrimaryObjectViewTestCase
-# Blocked by #3703
-class TagTestCase(
-    ViewTestCases.GetObjectViewTestCase,
-    ViewTestCases.EditObjectViewTestCase,
-    ViewTestCases.DeleteObjectViewTestCase,
-    ViewTestCases.ListObjectsViewTestCase,
-    ViewTestCases.BulkEditObjectsViewTestCase,
-    ViewTestCases.BulkDeleteObjectsViewTestCase
-):
+class TagTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     model = Tag
 
     @classmethod
@@ -38,6 +29,13 @@ class TagTestCase(
             '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 = {
             'color': '00ff00',
         }

+ 2 - 0
netbox/extras/urls.py

@@ -9,6 +9,8 @@ urlpatterns = [
 
     # Tags
     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/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
     path('tags/<str:slug>/', views.TagView.as_view(), name='tag'),

+ 19 - 14
netbox/extras/views.py

@@ -13,14 +13,13 @@ from utilities.forms import ConfirmationForm
 from utilities.paginator import EnhancedPaginator
 from utilities.utils import shallow_compare_dict
 from utilities.views import (
-    BulkDeleteView, BulkEditView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
+    BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
     ObjectPermissionRequiredMixin,
 )
-from . import filters, forms
+from . import filters, forms, tables
 from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult, Tag, TaggedItem
 from .reports import get_report, get_reports
 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_form = forms.TagFilterForm
-    table = TagTable
-    action_buttons = ()
+    table = tables.TagTable
 
 
 class TagView(ObjectView):
@@ -52,7 +50,7 @@ class TagView(ObjectView):
         )
 
         # Generate a table of all items tagged with this Tag
-        items_table = TaggedItemTable(tagged_items)
+        items_table = tables.TaggedItemTable(tagged_items)
         paginate = {
             'paginator_class': EnhancedPaginator,
             'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
@@ -78,13 +76,20 @@ class TagDeleteView(ObjectDeleteView):
     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):
     queryset = Tag.objects.annotate(
         items=Count('extras_taggeditem_items', distinct=True)
     ).order_by(
         'name'
     )
-    table = TagTable
+    table = tables.TagTable
     form = forms.TagBulkEditForm
     default_return_url = 'extras:tag_list'
 
@@ -95,7 +100,7 @@ class TagBulkDeleteView(BulkDeleteView):
     ).order_by(
         'name'
     )
-    table = TagTable
+    table = tables.TagTable
     default_return_url = 'extras:tag_list'
 
 
@@ -107,7 +112,7 @@ class ConfigContextListView(ObjectListView):
     queryset = ConfigContext.objects.all()
     filterset = filters.ConfigContextFilterSet
     filterset_form = forms.ConfigContextFilterForm
-    table = ConfigContextTable
+    table = tables.ConfigContextTable
     action_buttons = ('add',)
 
 
@@ -143,7 +148,7 @@ class ConfigContextEditView(ObjectEditView):
 class ConfigContextBulkEditView(BulkEditView):
     queryset = ConfigContext.objects.all()
     filterset = filters.ConfigContextFilterSet
-    table = ConfigContextTable
+    table = tables.ConfigContextTable
     form = forms.ConfigContextBulkEditForm
     default_return_url = 'extras:configcontext_list'
 
@@ -155,7 +160,7 @@ class ConfigContextDeleteView(ObjectDeleteView):
 
 class ConfigContextBulkDeleteView(BulkDeleteView):
     queryset = ConfigContext.objects.all()
-    table = ConfigContextTable
+    table = tables.ConfigContextTable
     default_return_url = 'extras:configcontext_list'
 
 
@@ -197,7 +202,7 @@ class ObjectChangeListView(ObjectListView):
     queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
     filterset = filters.ObjectChangeFilterSet
     filterset_form = forms.ObjectChangeFilterForm
-    table = ObjectChangeTable
+    table = tables.ObjectChangeTable
     template_name = 'extras/objectchange_list.html'
     action_buttons = ('export',)
 
@@ -214,7 +219,7 @@ class ObjectChangeView(ObjectView):
         ).exclude(
             pk=objectchange.pk
         )
-        related_changes_table = ObjectChangeTable(
+        related_changes_table = tables.ObjectChangeTable(
             data=related_changes[:50],
             orderable=False
         )
@@ -267,7 +272,7 @@ class ObjectChangeLogView(View):
             Q(changed_object_type=content_type, changed_object_id=obj.pk) |
             Q(related_object_type=content_type, related_object_id=obj.pk)
         )
-        objectchanges_table = ObjectChangeTable(
+        objectchanges_table = tables.ObjectChangeTable(
             data=objectchanges,
             orderable=False
         )

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

@@ -85,7 +85,7 @@
                     <tr>
                         <td>Description</td>
                         <td>
-                            {{ tag.description }}
+                            {{ tag.description|placeholder }}
                         </td>
                 </table>
             </div>

+ 6 - 0
netbox/templates/inc/nav_menu.html

@@ -101,6 +101,12 @@
                         <li class="divider"></li>
                         <li class="dropdown-header">Tags</li>
                         <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>
                         </li>
                     </ul>