1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

initial pass on migrating to custom tag model with color and comments fields

This commit is contained in:
John Anderson
2019-02-20 03:52:47 -05:00
parent e521508de9
commit fc2bb724fa
32 changed files with 524 additions and 63 deletions

View File

@ -10,6 +10,11 @@ context data may observe a performance drop when returning multiple objects. To
Config Context is not needed, the query parameter `?exclude=config_context` may be added to the request as to remove
the Config Context from being included in any results.
## Enhancements
* [#2324](https://github.com/digitalocean/netbox/issues/2324) - Add color option for tags
* [#2791](https://github.com/digitalocean/netbox/issues/2791) - Add a comment field for tags
---
v2.5.7 (FUTURE)

View File

@ -0,0 +1,25 @@
# Generated by Django 2.1.4 on 2019-02-20 06:56
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('circuits', '0014_circuittermination_description'),
('extras', '0018_rename_tag_tables'),
]
operations = [
migrations.AlterField(
model_name='circuit',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='provider',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
]

View File

@ -6,7 +6,7 @@ from taggit.managers import TaggableManager
from dcim.constants import CONNECTION_STATUS_CHOICES, STATUS_CLASSES
from dcim.fields import ASNField
from dcim.models import CableTermination
from extras.models import CustomFieldModel, ObjectChange
from extras.models import CustomFieldModel, ObjectChange, TaggedItem
from utilities.models import ChangeLoggedModel
from utilities.utils import serialize_object
from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES
@ -55,7 +55,7 @@ class Provider(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
@ -165,7 +165,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',

View File

@ -0,0 +1,85 @@
# Generated by Django 2.1.4 on 2019-02-20 06:56
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('dcim', '0069_deprecate_nullablecharfield'),
('extras', '0018_rename_tag_tables'),
]
operations = [
migrations.AlterField(
model_name='consoleport',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='consoleserverport',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='device',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='devicebay',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='devicetype',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='frontport',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='interface',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='inventoryitem',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='poweroutlet',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='powerport',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='rack',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='rearport',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='site',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='virtualchassis',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
]

View File

@ -15,7 +15,7 @@ from mptt.models import MPTTModel, TreeForeignKey
from taggit.managers import TaggableManager
from timezone_field import TimeZoneField
from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange
from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem
from utilities.fields import ColorField
from utilities.managers import NaturalOrderingManager
from utilities.models import ChangeLoggedModel
@ -319,7 +319,7 @@ class Site(ChangeLoggedModel, CustomFieldModel):
)
objects = NaturalOrderingManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address',
@ -566,7 +566,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
)
objects = NaturalOrderingManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'site', 'group_name', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width',
@ -914,7 +914,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'comments',
@ -1455,7 +1455,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
)
objects = NaturalOrderingManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status',
@ -1743,7 +1743,7 @@ class ConsolePort(CableTermination, ComponentModel):
)
objects = DeviceComponentManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name']
@ -1786,7 +1786,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
)
objects = DeviceComponentManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name']
@ -1835,7 +1835,7 @@ class PowerPort(CableTermination, ComponentModel):
)
objects = DeviceComponentManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name']
@ -1878,7 +1878,7 @@ class PowerOutlet(CableTermination, ComponentModel):
)
objects = DeviceComponentManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name']
@ -1998,7 +1998,7 @@ class Interface(CableTermination, ComponentModel):
)
objects = InterfaceManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'device', 'virtual_machine', 'name', 'lag', 'form_factor', 'enabled', 'mac_address', 'mtu', 'mgmt_only',
@ -2199,7 +2199,7 @@ class FrontPort(CableTermination, ComponentModel):
)
objects = DeviceComponentManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'rear_port', 'rear_port_position', 'description']
@ -2265,7 +2265,7 @@ class RearPort(CableTermination, ComponentModel):
)
objects = DeviceComponentManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'positions', 'description']
@ -2312,7 +2312,7 @@ class DeviceBay(ComponentModel):
)
objects = DeviceComponentManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'installed_device']
@ -2405,7 +2405,7 @@ class InventoryItem(ComponentModel):
blank=True
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
@ -2452,7 +2452,7 @@ class VirtualChassis(ChangeLoggedModel):
blank=True
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['master', 'domain']

View File

@ -1,6 +1,5 @@
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
from taggit.models import Tag
from dcim.api.nested_serializers import (
NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedRackSerializer,
@ -10,6 +9,7 @@ from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
from extras.constants import *
from extras.models import (
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
Tag
)
from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
from tenancy.models import Tenant, TenantGroup
@ -80,7 +80,7 @@ class TagSerializer(ValidatedModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug', 'tagged_items']
fields = ['id', 'name', 'slug', 'color', 'comments', 'tagged_items']
#

View File

@ -6,11 +6,11 @@ from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from taggit.models import Tag
from extras import filters
from extras.models import (
ConfigContext, CustomField, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
Tag
)
from extras.reports import get_report, get_reports
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
@ -115,7 +115,7 @@ class TopologyMapViewSet(ModelViewSet):
#
class TagViewSet(ModelViewSet):
queryset = Tag.objects.annotate(tagged_items=Count('taggit_taggeditem_items'))
queryset = Tag.objects.annotate(tagged_items=Count('extras_taggeditem_items'))
serializer_class = serializers.TagSerializer
filterset_class = filters.TagFilter

View File

@ -1,12 +1,11 @@
import django_filters
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from taggit.models import Tag
from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup
from .constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_TYPE_BOOLEAN, CF_TYPE_SELECT
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, TopologyMap
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag, TopologyMap
class CustomFieldFilter(django_filters.Filter):

View File

@ -6,19 +6,18 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from mptt.forms import TreeNodeMultipleChoiceField
from taggit.forms import TagField
from taggit.models import Tag
from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup
from utilities.forms import (
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect,
FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField,
FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField, CommentField
)
from .constants import (
CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
OBJECTCHANGE_ACTION_CHOICES,
)
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag
#
@ -190,11 +189,12 @@ class CustomFieldFilterForm(forms.Form):
class TagForm(BootstrapMixin, forms.ModelForm):
slug = SlugField()
comments = CommentField()
class Meta:
model = Tag
fields = [
'name', 'slug',
'name', 'slug', 'color', 'comments'
]

View File

@ -0,0 +1,46 @@
# Generated by Django 2.1.4 on 2019-02-20 06:56
from django.db import migrations, models
import django.db.models.deletion
import utilities.fields
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('extras', '0016_exporttemplate_add_cable'),
]
state_operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('name', models.CharField(max_length=100, unique=True)),
('slug', models.SlugField(max_length=100, unique=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TaggedItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('object_id', models.IntegerField(db_index=True)),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='extras_taggeditem_tagged_items', to='contenttypes.ContentType')),
('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='extras_taggeditem_items', to='extras.Tag')),
],
options={
'abstract': False,
},
),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=None,
state_operations=state_operations
)
]

View File

@ -0,0 +1,46 @@
# Generated by Django 2.1.4 on 2019-02-20 06:59
from django.db import migrations
class AppTaggitAlterModelTable(migrations.AlterModelTable):
"""
A special subclass of AlterModelTable which hardcodes the app_label to 'taggit'
This is needed because the migration deals with models which belong to the taggit
app, however because taggit is a 3rd party app, we cannot create our own migrations
there.
"""
def state_forwards(self, app_label, state):
super().state_forwards('taggit', state)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
super().database_forwards('taggit', schema_editor, from_state, to_state)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
super().database_backwards('taggit', schema_editor, from_state, to_state)
def reduce(self, operation, app_label=None):
if app_label:
app_label = 'taggit'
super().reduce(operation, app_label=app_label)
class Migration(migrations.Migration):
dependencies = [
('taggit', '0001_initial'),
('extras', '0017_tag_taggeditem'),
]
operations = [
AppTaggitAlterModelTable(
name='Tag',
table='extras_tag'
),
AppTaggitAlterModelTable(
name='TaggedItem',
table='extras_taggeditem'
),
]

View File

@ -0,0 +1,52 @@
# Generated by Django 2.1.4 on 2019-02-20 07:05
from django.db import migrations
class AppTaggitDeleteModel(migrations.DeleteModel):
"""
A special subclass of DeleteModel which hardcodes the app_label to 'taggit'
This is needed because the migration deals with models which belong to the taggit
app, however because taggit is a 3rd party app, we cannot create our own migrations
there.
"""
def state_forwards(self, app_label, state):
super().state_forwards('taggit', state)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
super().database_forwards('taggit', schema_editor, from_state, to_state)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
super().database_backwards('taggit', schema_editor, from_state, to_state)
class Migration(migrations.Migration):
dependencies = [
('extras', '0018_rename_tag_tables'),
('circuits', '0015_custom_tag_models'),
('dcim', '0070_custom_tag_models'),
('ipam', '0025_custom_tag_models'),
('secrets', '0006_custom_tag_models'),
('tenancy', '0006_custom_tag_models'),
('virtualization', '0009_custom_tag_models'),
]
state_operations = [
AppTaggitDeleteModel(
name='Tag',
),
AppTaggitDeleteModel(
name='TaggedItem',
),
]
database_operations = []
operations = [
migrations.SeparateDatabaseAndState(
database_operations=None,
state_operations=state_operations
)
]

View File

@ -0,0 +1,24 @@
# Generated by Django 2.1.4 on 2019-02-20 07:38
from django.db import migrations, models
import utilities.fields
class Migration(migrations.Migration):
dependencies = [
('extras', '0019_delete_taggit_models'),
]
operations = [
migrations.AddField(
model_name='tag',
name='color',
field=utilities.fields.ColorField(max_length=6),
),
migrations.AddField(
model_name='tag',
name='comments',
field=models.TextField(blank=True),
),
]

View File

@ -12,8 +12,10 @@ from django.db.models import F, Q
from django.http import HttpResponse
from django.template import Template, Context
from django.urls import reverse
from taggit.models import TagBase, GenericTaggedItemBase
from dcim.constants import CONNECTION_STATUS_CONNECTED
from utilities.fields import ColorField
from utilities.utils import deepmerge, foreground_color
from .constants import *
from .querysets import ConfigContextQuerySet
@ -860,3 +862,23 @@ class ObjectChange(models.Model):
self.object_repr,
self.object_data,
)
#
# Tags
#
class Tag(TagBase):
color = ColorField()
comments = models.TextField(
blank=True
)
class TaggedItem(GenericTaggedItemBase):
tag = models.ForeignKey(
to=Tag,
related_name="%(app_label)s_%(class)s_items",
on_delete=models.CASCADE
)

View File

@ -1,9 +1,8 @@
import django_tables2 as tables
from django_tables2.utils import Accessor
from taggit.models import Tag, TaggedItem
from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
from .models import ConfigContext, ObjectChange
from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
from .models import ConfigContext, ObjectChange, Tag, TaggedItem
TAG_ACTIONS = """
{% if perms.taggit.change_tag %}
@ -71,10 +70,11 @@ class TagTable(BaseTable):
attrs={'td': {'class': 'text-right'}},
verbose_name=''
)
color = ColorColumn()
class Meta(BaseTable.Meta):
model = Tag
fields = ('pk', 'name', 'items', 'slug', 'actions')
fields = ('pk', 'name', 'items', 'slug', 'color', 'actions')
class TaggedItemTable(BaseTable):

View File

@ -1,11 +1,10 @@
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from rest_framework import status
from taggit.models import Tag
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Region, Site
from extras.constants import GRAPH_TYPE_SITE
from extras.models import ConfigContext, Graph, ExportTemplate
from extras.models import ConfigContext, Graph, ExportTemplate, Tag
from tenancy.models import Tenant, TenantGroup
from utilities.testing import APITestCase

View File

@ -4,10 +4,9 @@ import uuid
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
from taggit.models import Tag
from dcim.models import Site
from extras.models import ConfigContext, ObjectChange
from extras.models import ConfigContext, ObjectChange, Tag
class TagTestCase(TestCase):

View File

@ -9,7 +9,6 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.utils.safestring import mark_safe
from django.views.generic import View
from django_tables2 import RequestConfig
from taggit.models import Tag, TaggedItem
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator
@ -19,7 +18,7 @@ from .forms import (
ConfigContextForm, ConfigContextBulkEditForm, ConfigContextFilterForm, ImageAttachmentForm, ObjectChangeFilterForm,
TagFilterForm, TagForm,
)
from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult
from .models import ConfigContext, ImageAttachment, ObjectChange, ReportResult, Tag, TaggedItem
from .reports import get_report, get_reports
from .tables import ConfigContextTable, ObjectChangeTable, TagTable, TaggedItemTable
@ -30,7 +29,7 @@ from .tables import ConfigContextTable, ObjectChangeTable, TagTable, TaggedItemT
class TagListView(ObjectListView):
queryset = Tag.objects.annotate(
items=Count('taggit_taggeditem_items')
items=Count('extras_taggeditem_items')
).order_by(
'name'
)
@ -69,22 +68,23 @@ class TagView(View):
class TagEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'taggit.change_tag'
permission_required = 'extras.change_tag'
model = Tag
model_form = TagForm
default_return_url = 'extras:tag_list'
template_name = 'extras/tag_edit.html'
class TagDeleteView(PermissionRequiredMixin, ObjectDeleteView):
permission_required = 'taggit.delete_tag'
permission_required = 'extras.delete_tag'
model = Tag
default_return_url = 'extras:tag_list'
class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'taggit.delete_tag'
permission_required = 'extras.delete_tag'
queryset = Tag.objects.annotate(
items=Count('taggit_taggeditem_items')
items=Count('extras_taggeditem_items')
).order_by(
'name'
)

View File

@ -0,0 +1,45 @@
# Generated by Django 2.1.4 on 2019-02-20 06:56
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('ipam', '0024_vrf_allow_null_rd'),
('extras', '0018_rename_tag_tables'),
]
operations = [
migrations.AlterField(
model_name='aggregate',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='ipaddress',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='prefix',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='service',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='vlan',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='vrf',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
]

View File

@ -10,7 +10,7 @@ from django.urls import reverse
from taggit.managers import TaggableManager
from dcim.models import Interface
from extras.models import CustomFieldModel
from extras.models import CustomFieldModel, TaggedItem
from utilities.models import ChangeLoggedModel
from .constants import *
from .fields import IPNetworkField, IPAddressField
@ -55,7 +55,7 @@ class VRF(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
@ -154,7 +154,7 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['prefix', 'rir', 'date_added', 'description']
@ -324,7 +324,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
)
objects = PrefixQuerySet.as_manager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description',
@ -583,7 +583,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
)
objects = IPAddressManager()
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface_name', 'is_primary',
@ -790,7 +790,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']
@ -892,7 +892,7 @@ class Service(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'virtual_machine', 'name', 'protocol', 'description']

View File

@ -2,8 +2,17 @@ from django.conf import settings
from django.contrib.admin import AdminSite
from django.contrib.auth.admin import GroupAdmin, UserAdmin
from django.contrib.auth.models import Group, User
from taggit.admin import TagAdmin
from taggit.models import Tag
from taggit.admin import TagAdmin, TaggedItemInline
from extras.models import Tag, TaggedItem
class NetBoxTaggedItemInline(TaggedItemInline):
model = TaggedItem
class NetBoxTagAdmin(TagAdmin):
inlines = [NetBoxTaggedItemInline]
class NetBoxAdminSite(AdminSite):
@ -20,7 +29,7 @@ admin_site = NetBoxAdminSite(name='admin')
# Register external models
admin_site.register(Group, GroupAdmin)
admin_site.register(User, UserAdmin)
admin_site.register(Tag, TagAdmin)
admin_site.register(Tag, NetBoxTagAdmin)
# Modify the template to include an RQ link if django_rq is installed (see RQ_SHOW_ADMIN_LINK)
if settings.WEBHOOKS_ENABLED:

View File

@ -0,0 +1,20 @@
# Generated by Django 2.1.4 on 2019-02-20 06:56
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('secrets', '0005_change_logging'),
('extras', '0018_rename_tag_tables'),
]
operations = [
migrations.AlterField(
model_name='secret',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
]

View File

@ -14,7 +14,7 @@ from django.urls import reverse
from django.utils.encoding import force_bytes
from taggit.managers import TaggableManager
from extras.models import CustomFieldModel
from extras.models import CustomFieldModel, TaggedItem
from utilities.models import ChangeLoggedModel
from .exceptions import InvalidKey
from .hashers import SecretValidationHasher
@ -345,7 +345,7 @@ class Secret(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
plaintext = None
csv_headers = ['device', 'role', 'name', 'plaintext']

View File

@ -59,8 +59,26 @@
{{ items_count }}
</td>
</tr>
<tr>
<td>Color</td>
<td>
<span class="label color-block" style="background-color: #{{ tag.color }}">&nbsp;</span>
</td>
</tr>
</table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Comments</strong>
</div>
<div class="panel-body rendered-markdown">
{% if tag.comments %}
{{ tag.comments|gfm }}
{% else %}
<span class="text-muted">None</span>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
{% include 'panel_table.html' with table=items_table heading='Tagged Objects' %}

View File

@ -0,0 +1,19 @@
{% extends 'utilities/obj_edit.html' %}
{% load form_helpers %}
{% block form %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Tag</strong></div>
<div class="panel-body">
{% render_field form.name %}
{% render_field form.slug %}
{% render_field form.color %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Comments</strong></div>
<div class="panel-body">
{% render_field form.comments %}
</div>
</div>
{% endblock %}

View File

@ -1,5 +1,7 @@
{% load helpers %}
{% if url_name %}
<a href="{% url url_name %}?tag={{ tag.slug }}"><span class="label label-default">{{ tag }}</span></a>
<a href="{% url url_name %}?tag={{ tag.slug }}"><span class="label label-default" style="color: {{ tag.color|fgcolor }}; background-color: #{{ tag.color }}">{{ tag }}</span></a>
{% else %}
<span class="label label-default">{{ tag }}</span>
{% endif %}

View File

@ -0,0 +1,20 @@
# Generated by Django 2.1.4 on 2019-02-20 06:56
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('tenancy', '0005_change_logging'),
('extras', '0018_rename_tag_tables'),
]
operations = [
migrations.AlterField(
model_name='tenant',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
]

View File

@ -3,7 +3,7 @@ from django.db import models
from django.urls import reverse
from taggit.managers import TaggableManager
from extras.models import CustomFieldModel
from extras.models import CustomFieldModel, TaggedItem
from utilities.models import ChangeLoggedModel
@ -70,7 +70,7 @@ class Tenant(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['name', 'slug', 'group', 'description', 'comments']

View File

@ -1,7 +1,8 @@
import django_filters
from django.conf import settings
from django.db.models import Q
from taggit.models import Tag
from extras.models import Tag
class NumericInFilter(django_filters.BaseInFilter, django_filters.NumberFilter):

View File

@ -157,7 +157,7 @@ class ObjectListView(View):
# Construct queryset for tags list
if hasattr(model, 'tags'):
tags = model.tags.annotate(count=Count('taggit_taggeditem_items')).order_by('name')
tags = model.tags.annotate(count=Count('extras_taggeditem_items')).order_by('name')
else:
tags = None

View File

@ -0,0 +1,25 @@
# Generated by Django 2.1.4 on 2019-02-20 06:56
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('virtualization', '0008_virtualmachine_local_context_data'),
('extras', '0018_rename_tag_tables'),
]
operations = [
migrations.AlterField(
model_name='cluster',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='virtualmachine',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
]

View File

@ -6,7 +6,7 @@ from django.urls import reverse
from taggit.managers import TaggableManager
from dcim.models import Device
from extras.models import ConfigContextModel, CustomFieldModel
from extras.models import ConfigContextModel, CustomFieldModel, TaggedItem
from utilities.models import ChangeLoggedModel
from .constants import DEVICE_STATUS_ACTIVE, VM_STATUS_CHOICES, VM_STATUS_CLASSES
@ -119,7 +119,7 @@ class Cluster(ChangeLoggedModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['name', 'type', 'group', 'site', 'comments']
@ -238,7 +238,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
object_id_field='obj_id'
)
tags = TaggableManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',