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

Merge branch 'develop-2.6' of github.com:digitalocean/netbox into develop-2.6

This commit is contained in:
John Anderson
2019-03-03 19:06:05 -05:00
47 changed files with 810 additions and 137 deletions

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,43 @@
# 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'),
]
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,
},
),
migrations.AlterIndexTogether(
name='taggeditem',
index_together={('content_type', 'object_id')},
),
]

View File

@@ -0,0 +1,65 @@
# 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
def copy_tags(apps, schema_editor):
"""
Copy data from taggit_tag to extras_tag
"""
TaggitTag = apps.get_model('taggit', 'Tag')
ExtrasTag = apps.get_model('extras', 'Tag')
tags_values = TaggitTag.objects.all().values('id', 'name', 'slug')
tags = [ExtrasTag(**tag) for tag in tags_values]
ExtrasTag.objects.bulk_create(tags)
def copy_taggeditems(apps, schema_editor):
"""
Copy data from taggit_taggeditem to extras_taggeditem
"""
TaggitTaggedItem = apps.get_model('taggit', 'TaggedItem')
ExtrasTaggedItem = apps.get_model('extras', 'TaggedItem')
tagged_items_values = TaggitTaggedItem.objects.all().values('id', 'object_id', 'content_type_id', 'tag_id')
tagged_items = [ExtrasTaggedItem(**tagged_item) for tagged_item in tagged_items_values]
ExtrasTaggedItem.objects.bulk_create(tagged_items)
def delete_taggit_taggeditems(apps, schema_editor):
"""
Delete all TaggedItem instances from taggit_taggeditem
"""
TaggitTaggedItem = apps.get_model('taggit', 'TaggedItem')
TaggitTaggedItem.objects.all().delete()
def delete_taggit_tags(apps, schema_editor):
"""
Delete all Tag instances from taggit_tag
"""
TaggitTag = apps.get_model('taggit', 'Tag')
TaggitTag.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('extras', '0017_tag_taggeditem'),
('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'),
]
operations = [
migrations.RunPython(copy_tags),
migrations.RunPython(copy_taggeditems),
migrations.RunPython(delete_taggit_taggeditems),
migrations.RunPython(delete_taggit_tags),
]

View File

@@ -0,0 +1,34 @@
# 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', '0018_tag_data'),
]
operations = [
migrations.AddField(
model_name='tag',
name='color',
field=utilities.fields.ColorField(max_length=6, default='9e9e9e'),
),
migrations.AddField(
model_name='tag',
name='comments',
field=models.TextField(blank=True, default=''),
),
migrations.AddField(
model_name='tag',
name='created',
field=models.DateField(auto_now_add=True, null=True),
),
migrations.AddField(
model_name='tag',
name='last_updated',
field=models.DateTimeField(auto_now=True, null=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,37 @@ class ObjectChange(models.Model):
self.object_repr,
self.object_data,
)
#
# Tags
#
# TODO: figure out a way around this circular import for ObjectChange
from utilities.models import ChangeLoggedModel # noqa: E402
class Tag(TagBase, ChangeLoggedModel):
color = ColorField(
default='9e9e9e'
)
comments = models.TextField(
blank=True,
default=''
)
def get_absolute_url(self):
return reverse('extras:tag', args=[self.slug])
class TaggedItem(GenericTaggedItemBase):
tag = models.ForeignKey(
to=Tag,
related_name="%(app_label)s_%(class)s_items",
on_delete=models.CASCADE
)
class Meta:
index_together = (
("content_type", "object_id")
)

View File

@@ -1,11 +1,13 @@
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 = """
<a href="{% url 'extras:tag_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Changelog">
<i class="fa fa-history"></i>
</a>
{% if perms.taggit.change_tag %}
<a href="{% url 'extras:tag_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
{% endif %}
@@ -71,10 +73,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

@@ -1,6 +1,8 @@
from django.conf.urls import url
from extras import views
from extras.models import Tag
app_name = 'extras'
urlpatterns = [
@@ -11,6 +13,7 @@ urlpatterns = [
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-]+)/delete/$', views.TagDeleteView.as_view(), name='tag_delete'),
url(r'^tags/(?P<slug>[\w-]+)/changelog/$', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}),
# Config contexts
url(r'^config-contexts/$', views.ConfigContextListView.as_view(), name='configcontext_list'),

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'
)