mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 02:48:38 -06:00
Merge branch 'feature' into 18417-rack-height
This commit is contained in:
commit
55aadb69ae
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
## ALLOW_TOKEN_RETRIEVAL
|
## ALLOW_TOKEN_RETRIEVAL
|
||||||
|
|
||||||
Default: True
|
Default: False
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The default value of this parameter changed from true to false in NetBox v4.3.0.
|
||||||
|
|
||||||
If disabled, the values of API tokens will not be displayed after each token's initial creation. A user **must** record the value of a token prior to its creation, or it will be lost. Note that this affects _all_ users, regardless of assigned permissions.
|
If disabled, the values of API tokens will not be displayed after each token's initial creation. A user **must** record the value of a token prior to its creation, or it will be lost. Note that this affects _all_ users, regardless of assigned permissions.
|
||||||
|
|
||||||
|
@ -24,6 +24,12 @@ Jinja2 template code for rendering the exported data.
|
|||||||
|
|
||||||
The MIME type to indicate in the response when rendering the export template (optional). Defaults to `text/plain`.
|
The MIME type to indicate in the response when rendering the export template (optional). Defaults to `text/plain`.
|
||||||
|
|
||||||
|
### File Name
|
||||||
|
|
||||||
|
The file name to give to the rendered export file (optional).
|
||||||
|
|
||||||
|
!!! info "This field was introduced in NetBox v4.3."
|
||||||
|
|
||||||
### File Extension
|
### File Extension
|
||||||
|
|
||||||
The file extension to append to the file name in the response (optional).
|
The file extension to append to the file name in the response (optional).
|
||||||
|
@ -16,6 +16,12 @@ A unique URL-friendly identifier. (This value will be used for filtering.) This
|
|||||||
|
|
||||||
The color to use when displaying the tag in the NetBox UI.
|
The color to use when displaying the tag in the NetBox UI.
|
||||||
|
|
||||||
|
### Weight
|
||||||
|
|
||||||
|
A numeric weight employed to influence the ordering of tags. Tags with a lower weight will be listed before those with higher weights. Values must be within the range **0** to **32767**.
|
||||||
|
|
||||||
|
!!! info "This field was introduced in NetBox v4.3."
|
||||||
|
|
||||||
### Object Types
|
### Object Types
|
||||||
|
|
||||||
The assignment of a tag may be limited to a prescribed set of objects. For example, it may be desirable to limit the application of a specific tag to only devices and virtual machines.
|
The assignment of a tag may be limited to a prescribed set of objects. For example, it may be desirable to limit the application of a specific tag to only devices and virtual machines.
|
||||||
|
@ -4,9 +4,11 @@ A contact represents an individual or group that has been associated with an obj
|
|||||||
|
|
||||||
## Fields
|
## Fields
|
||||||
|
|
||||||
### Group
|
### Groups
|
||||||
|
|
||||||
The [contact group](./contactgroup.md) to which this contact is assigned (if any).
|
The [contact groups](./contactgroup.md) to which this contact is assigned (if any).
|
||||||
|
|
||||||
|
!!! info "This field was renamed from `group` to `groups` in NetBox v4.3, and now supports the assignment of a contact to more than one group."
|
||||||
|
|
||||||
### Name
|
### Name
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class RegionSerializer(NestedGroupModelSerializer):
|
|||||||
model = Region
|
model = Region
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated', 'site_count', 'prefix_count', '_depth',
|
'created', 'last_updated', 'site_count', 'prefix_count', 'comments', '_depth',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'site_count', '_depth')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'site_count', '_depth')
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class SiteGroupSerializer(NestedGroupModelSerializer):
|
|||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated', 'site_count', 'prefix_count', '_depth',
|
'created', 'last_updated', 'site_count', 'prefix_count', 'comments', '_depth',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'site_count', '_depth')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'site_count', '_depth')
|
||||||
|
|
||||||
@ -93,6 +93,6 @@ class LocationSerializer(NestedGroupModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'facility',
|
'id', 'url', 'display_url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'facility',
|
||||||
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count',
|
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count',
|
||||||
'prefix_count', '_depth',
|
'prefix_count', 'comments', '_depth',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'rack_count', '_depth')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'rack_count', '_depth')
|
||||||
|
@ -11,7 +11,8 @@ from ipam.filtersets import PrimaryIPFilterSet
|
|||||||
from ipam.models import ASN, IPAddress, VLANTranslationPolicy, VRF
|
from ipam.models import ASN, IPAddress, VLANTranslationPolicy, VRF
|
||||||
from netbox.choices import ColorChoices
|
from netbox.choices import ColorChoices
|
||||||
from netbox.filtersets import (
|
from netbox.filtersets import (
|
||||||
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet,
|
BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet,
|
||||||
|
OrganizationalModelFilterSet,
|
||||||
)
|
)
|
||||||
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
|
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
|
||||||
from tenancy.models import *
|
from tenancy.models import *
|
||||||
@ -81,7 +82,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RegionFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
|
class RegionFilterSet(NestedGroupModelFilterSet, ContactModelFilterSet):
|
||||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
label=_('Parent region (ID)'),
|
label=_('Parent region (ID)'),
|
||||||
@ -111,7 +112,7 @@ class RegionFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
|
|||||||
fields = ('id', 'name', 'slug', 'description')
|
fields = ('id', 'name', 'slug', 'description')
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
|
class SiteGroupFilterSet(NestedGroupModelFilterSet, ContactModelFilterSet):
|
||||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
label=_('Parent site group (ID)'),
|
label=_('Parent site group (ID)'),
|
||||||
@ -205,7 +206,7 @@ class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
|
|||||||
return queryset.filter(qs_filter).distinct()
|
return queryset.filter(qs_filter).distinct()
|
||||||
|
|
||||||
|
|
||||||
class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalModelFilterSet):
|
class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, NestedGroupModelFilterSet):
|
||||||
region_id = TreeNodeMultipleChoiceFilter(
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
field_name='site__region',
|
field_name='site__region',
|
||||||
@ -275,13 +276,13 @@ class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalM
|
|||||||
fields = ('id', 'name', 'slug', 'facility', 'description')
|
fields = ('id', 'name', 'slug', 'facility', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
# extended in order to include querying on Location.facility
|
||||||
|
queryset = super().search(queryset, name, value)
|
||||||
|
|
||||||
|
if value.strip():
|
||||||
|
queryset = queryset | queryset.model.objects.filter(facility__icontains=value)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
|
||||||
Q(name__icontains=value) |
|
|
||||||
Q(facility__icontains=value) |
|
|
||||||
Q(description__icontains=value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RackRoleFilterSet(OrganizationalModelFilterSet):
|
class RackRoleFilterSet(OrganizationalModelFilterSet):
|
||||||
|
@ -78,12 +78,13 @@ class RegionBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
model = Region
|
model = Region
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'description'),
|
FieldSet('parent', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('parent', 'description')
|
nullable_fields = ('parent', 'description', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
|
class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
@ -97,12 +98,13 @@ class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'description'),
|
FieldSet('parent', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('parent', 'description')
|
nullable_fields = ('parent', 'description', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class SiteBulkEditForm(NetBoxModelBulkEditForm):
|
class SiteBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
@ -197,12 +199,13 @@ class LocationBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
model = Location
|
model = Location
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('site', 'parent', 'status', 'tenant', 'description'),
|
FieldSet('site', 'parent', 'status', 'tenant', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('parent', 'tenant', 'description')
|
nullable_fields = ('parent', 'tenant', 'description', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
|
class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
@ -68,7 +68,7 @@ class RegionImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Region
|
model = Region
|
||||||
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
fields = ('name', 'slug', 'parent', 'description', 'tags', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupImportForm(NetBoxModelImportForm):
|
class SiteGroupImportForm(NetBoxModelImportForm):
|
||||||
@ -82,7 +82,7 @@ class SiteGroupImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fields = ('name', 'slug', 'parent', 'description')
|
fields = ('name', 'slug', 'parent', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class SiteImportForm(NetBoxModelImportForm):
|
class SiteImportForm(NetBoxModelImportForm):
|
||||||
@ -160,7 +160,10 @@ class LocationImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Location
|
model = Location
|
||||||
fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'facility', 'description', 'tags')
|
fields = (
|
||||||
|
'site', 'parent', 'name', 'slug', 'status', 'tenant', 'facility', 'description',
|
||||||
|
'tags', 'comments',
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(data, *args, **kwargs)
|
super().__init__(data, *args, **kwargs)
|
||||||
|
@ -78,6 +78,7 @@ class RegionForm(NetBoxModelForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
||||||
@ -86,7 +87,7 @@ class RegionForm(NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Region
|
model = Region
|
||||||
fields = (
|
fields = (
|
||||||
'parent', 'name', 'slug', 'description', 'tags',
|
'parent', 'name', 'slug', 'description', 'tags', 'comments',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -97,6 +98,7 @@ class SiteGroupForm(NetBoxModelForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
||||||
@ -105,7 +107,7 @@ class SiteGroupForm(NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fields = (
|
fields = (
|
||||||
'parent', 'name', 'slug', 'description', 'tags',
|
'parent', 'name', 'slug', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -179,6 +181,7 @@ class LocationForm(TenancyForm, NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('site', 'parent', 'name', 'slug', 'status', 'facility', 'description', 'tags', name=_('Location')),
|
FieldSet('site', 'parent', 'name', 'slug', 'status', 'facility', 'description', 'tags', name=_('Location')),
|
||||||
@ -188,7 +191,8 @@ class LocationForm(TenancyForm, NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Location
|
model = Location
|
||||||
fields = (
|
fields = (
|
||||||
'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'facility', 'tags',
|
'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant',
|
||||||
|
'facility', 'tags', 'comments',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0201_add_power_outlet_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='location',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='region',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sitegroup',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
]
|
@ -144,6 +144,7 @@ class LocationIndex(SearchIndex):
|
|||||||
('facility', 100),
|
('facility', 100),
|
||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('site', 'status', 'tenant', 'facility', 'description')
|
display_attrs = ('site', 'status', 'tenant', 'facility', 'description')
|
||||||
|
|
||||||
@ -317,6 +318,7 @@ class RegionIndex(SearchIndex):
|
|||||||
('name', 100),
|
('name', 100),
|
||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('parent', 'description')
|
display_attrs = ('parent', 'description')
|
||||||
|
|
||||||
@ -343,6 +345,7 @@ class SiteGroupIndex(SearchIndex):
|
|||||||
('name', 100),
|
('name', 100),
|
||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('parent', 'description')
|
display_attrs = ('parent', 'description')
|
||||||
|
|
||||||
|
@ -32,12 +32,15 @@ class RegionTable(ContactsColumnMixin, NetBoxTable):
|
|||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='dcim:region_list'
|
url_name='dcim:region_list'
|
||||||
)
|
)
|
||||||
|
comments = columns.MarkdownColumn(
|
||||||
|
verbose_name=_('Comments'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Region
|
model = Region
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'slug', 'site_count', 'description', 'contacts', 'tags', 'created', 'last_updated',
|
'pk', 'id', 'name', 'slug', 'site_count', 'description', 'comments', 'contacts', 'tags',
|
||||||
'actions',
|
'created', 'last_updated', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'site_count', 'description')
|
default_columns = ('pk', 'name', 'site_count', 'description')
|
||||||
|
|
||||||
@ -59,12 +62,15 @@ class SiteGroupTable(ContactsColumnMixin, NetBoxTable):
|
|||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='dcim:sitegroup_list'
|
url_name='dcim:sitegroup_list'
|
||||||
)
|
)
|
||||||
|
comments = columns.MarkdownColumn(
|
||||||
|
verbose_name=_('Comments'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'slug', 'site_count', 'description', 'contacts', 'tags', 'created', 'last_updated',
|
'pk', 'id', 'name', 'slug', 'site_count', 'description', 'comments', 'contacts', 'tags',
|
||||||
'actions',
|
'created', 'last_updated', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'site_count', 'description')
|
default_columns = ('pk', 'name', 'site_count', 'description')
|
||||||
|
|
||||||
@ -153,12 +159,15 @@ class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||||||
actions = columns.ActionsColumn(
|
actions = columns.ActionsColumn(
|
||||||
extra_buttons=LOCATION_BUTTONS
|
extra_buttons=LOCATION_BUTTONS
|
||||||
)
|
)
|
||||||
|
comments = columns.MarkdownColumn(
|
||||||
|
verbose_name=_('Comments'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Location
|
model = Location
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'site', 'status', 'facility', 'tenant', 'tenant_group', 'rack_count', 'device_count',
|
'pk', 'id', 'name', 'site', 'status', 'facility', 'tenant', 'tenant_group', 'rack_count', 'device_count',
|
||||||
'description', 'slug', 'contacts', 'tags', 'actions', 'created', 'last_updated',
|
'description', 'slug', 'comments', 'contacts', 'tags', 'actions', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'site', 'status', 'facility', 'tenant', 'rack_count', 'device_count', 'description'
|
'pk', 'name', 'site', 'status', 'facility', 'tenant', 'rack_count', 'device_count', 'description'
|
||||||
|
@ -74,6 +74,7 @@ class RegionTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'Region 4',
|
'name': 'Region 4',
|
||||||
'slug': 'region-4',
|
'slug': 'region-4',
|
||||||
|
'comments': 'this is region 4, not region 5',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Region 5',
|
'name': 'Region 5',
|
||||||
@ -86,13 +87,14 @@ class RegionTest(APIViewTestCases.APIViewTestCase):
|
|||||||
]
|
]
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'New comments',
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
Region.objects.create(name='Region 1', slug='region-1')
|
Region.objects.create(name='Region 1', slug='region-1')
|
||||||
Region.objects.create(name='Region 2', slug='region-2')
|
Region.objects.create(name='Region 2', slug='region-2', comments='what in the world is happening?')
|
||||||
Region.objects.create(name='Region 3', slug='region-3')
|
Region.objects.create(name='Region 3', slug='region-3')
|
||||||
|
|
||||||
|
|
||||||
@ -103,26 +105,30 @@ class SiteGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'Site Group 4',
|
'name': 'Site Group 4',
|
||||||
'slug': 'site-group-4',
|
'slug': 'site-group-4',
|
||||||
|
'comments': '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Site Group 5',
|
'name': 'Site Group 5',
|
||||||
'slug': 'site-group-5',
|
'slug': 'site-group-5',
|
||||||
|
'comments': 'not actually empty',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Site Group 6',
|
'name': 'Site Group 6',
|
||||||
'slug': 'site-group-6',
|
'slug': 'site-group-6',
|
||||||
|
'comments': 'Do I really exist?',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'I do exist!',
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
SiteGroup.objects.create(name='Site Group 1', slug='site-group-1')
|
SiteGroup.objects.create(name='Site Group 1', slug='site-group-1')
|
||||||
SiteGroup.objects.create(name='Site Group 2', slug='site-group-2')
|
SiteGroup.objects.create(name='Site Group 2', slug='site-group-2', comments='')
|
||||||
SiteGroup.objects.create(name='Site Group 3', slug='site-group-3')
|
SiteGroup.objects.create(name='Site Group 3', slug='site-group-3', comments='Hi!')
|
||||||
|
|
||||||
|
|
||||||
class SiteTest(APIViewTestCases.APIViewTestCase):
|
class SiteTest(APIViewTestCases.APIViewTestCase):
|
||||||
@ -212,12 +218,14 @@ class LocationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
name='Parent Location 1',
|
name='Parent Location 1',
|
||||||
slug='parent-location-1',
|
slug='parent-location-1',
|
||||||
status=LocationStatusChoices.STATUS_ACTIVE,
|
status=LocationStatusChoices.STATUS_ACTIVE,
|
||||||
|
comments='First!'
|
||||||
),
|
),
|
||||||
Location.objects.create(
|
Location.objects.create(
|
||||||
site=sites[1],
|
site=sites[1],
|
||||||
name='Parent Location 2',
|
name='Parent Location 2',
|
||||||
slug='parent-location-2',
|
slug='parent-location-2',
|
||||||
status=LocationStatusChoices.STATUS_ACTIVE,
|
status=LocationStatusChoices.STATUS_ACTIVE,
|
||||||
|
comments='Second!'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -227,6 +235,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
slug='location-1',
|
slug='location-1',
|
||||||
parent=parent_locations[0],
|
parent=parent_locations[0],
|
||||||
status=LocationStatusChoices.STATUS_ACTIVE,
|
status=LocationStatusChoices.STATUS_ACTIVE,
|
||||||
|
comments='Third!'
|
||||||
)
|
)
|
||||||
Location.objects.create(
|
Location.objects.create(
|
||||||
site=sites[0],
|
site=sites[0],
|
||||||
@ -250,6 +259,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'parent': parent_locations[1].pk,
|
'parent': parent_locations[1].pk,
|
||||||
'status': LocationStatusChoices.STATUS_PLANNED,
|
'status': LocationStatusChoices.STATUS_PLANNED,
|
||||||
|
'comments': '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Location 5',
|
'name': 'Test Location 5',
|
||||||
@ -257,6 +267,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'parent': parent_locations[1].pk,
|
'parent': parent_locations[1].pk,
|
||||||
'status': LocationStatusChoices.STATUS_PLANNED,
|
'status': LocationStatusChoices.STATUS_PLANNED,
|
||||||
|
'comments': 'Somebody should check on this location',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Location 6',
|
'name': 'Test Location 6',
|
||||||
|
@ -67,9 +67,15 @@ class RegionTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
parent_regions = (
|
parent_regions = (
|
||||||
Region(name='Region 1', slug='region-1', description='foobar1'),
|
Region(
|
||||||
Region(name='Region 2', slug='region-2', description='foobar2'),
|
name='Region 1', slug='region-1', description='foobar1', comments="There's nothing that",
|
||||||
Region(name='Region 3', slug='region-3', description='foobar3'),
|
),
|
||||||
|
Region(
|
||||||
|
name='Region 2', slug='region-2', description='foobar2', comments='a hundred men or more',
|
||||||
|
),
|
||||||
|
Region(
|
||||||
|
name='Region 3', slug='region-3', description='foobar3', comments='could ever do'
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for region in parent_regions:
|
for region in parent_regions:
|
||||||
region.save()
|
region.save()
|
||||||
@ -100,6 +106,13 @@ class RegionTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_q_comments(self):
|
||||||
|
params = {'q': 'there'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
params = {'q': 'hundred men could'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Region 1', 'Region 2']}
|
params = {'name': ['Region 1', 'Region 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -148,13 +161,17 @@ class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
SiteGroup(name='Site Group 2A', slug='site-group-2a', parent=parent_groups[1]),
|
SiteGroup(name='Site Group 2A', slug='site-group-2a', parent=parent_groups[1]),
|
||||||
SiteGroup(name='Site Group 2B', slug='site-group-2b', parent=parent_groups[1]),
|
SiteGroup(name='Site Group 2B', slug='site-group-2b', parent=parent_groups[1]),
|
||||||
SiteGroup(name='Site Group 3A', slug='site-group-3a', parent=parent_groups[2]),
|
SiteGroup(name='Site Group 3A', slug='site-group-3a', parent=parent_groups[2]),
|
||||||
SiteGroup(name='Site Group 3B', slug='site-group-3b', parent=parent_groups[2]),
|
SiteGroup(
|
||||||
|
name='Site Group 3B', slug='site-group-3b', parent=parent_groups[2], comments='this is a parent group',
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for site_group in groups:
|
for site_group in groups:
|
||||||
site_group.save()
|
site_group.save()
|
||||||
|
|
||||||
child_groups = (
|
child_groups = (
|
||||||
SiteGroup(name='Site Group 1A1', slug='site-group-1a1', parent=groups[0]),
|
SiteGroup(
|
||||||
|
name='Site Group 1A1', slug='site-group-1a1', parent=groups[0], comments='this is a child group',
|
||||||
|
),
|
||||||
SiteGroup(name='Site Group 1B1', slug='site-group-1b1', parent=groups[1]),
|
SiteGroup(name='Site Group 1B1', slug='site-group-1b1', parent=groups[1]),
|
||||||
SiteGroup(name='Site Group 2A1', slug='site-group-2a1', parent=groups[2]),
|
SiteGroup(name='Site Group 2A1', slug='site-group-2a1', parent=groups[2]),
|
||||||
SiteGroup(name='Site Group 2B1', slug='site-group-2b1', parent=groups[3]),
|
SiteGroup(name='Site Group 2B1', slug='site-group-2b1', parent=groups[3]),
|
||||||
@ -168,6 +185,13 @@ class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_q_comments(self):
|
||||||
|
params = {'q': 'this'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
params = {'q': 'child'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Site Group 1', 'Site Group 2']}
|
params = {'name': ['Site Group 1', 'Site Group 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -401,6 +425,7 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
status=LocationStatusChoices.STATUS_PLANNED,
|
status=LocationStatusChoices.STATUS_PLANNED,
|
||||||
facility='Facility 1',
|
facility='Facility 1',
|
||||||
description='foobar1',
|
description='foobar1',
|
||||||
|
comments='',
|
||||||
),
|
),
|
||||||
Location(
|
Location(
|
||||||
name='Location 2A',
|
name='Location 2A',
|
||||||
@ -410,6 +435,7 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
status=LocationStatusChoices.STATUS_STAGING,
|
status=LocationStatusChoices.STATUS_STAGING,
|
||||||
facility='Facility 2',
|
facility='Facility 2',
|
||||||
description='foobar2',
|
description='foobar2',
|
||||||
|
comments='First comment!',
|
||||||
),
|
),
|
||||||
Location(
|
Location(
|
||||||
name='Location 3A',
|
name='Location 3A',
|
||||||
@ -419,6 +445,7 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
status=LocationStatusChoices.STATUS_DECOMMISSIONING,
|
status=LocationStatusChoices.STATUS_DECOMMISSIONING,
|
||||||
facility='Facility 3',
|
facility='Facility 3',
|
||||||
description='foobar3',
|
description='foobar3',
|
||||||
|
comments='_This_ is a **bold comment**',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for location in locations:
|
for location in locations:
|
||||||
@ -436,6 +463,13 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_q_comments(self):
|
||||||
|
params = {'q': 'this'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
params = {'q': 'comment'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Location 1', 'Location 2']}
|
params = {'name': ['Location 1', 'Location 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -25,8 +25,10 @@ class RegionTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
|
|
||||||
# Create three Regions
|
# Create three Regions
|
||||||
regions = (
|
regions = (
|
||||||
Region(name='Region 1', slug='region-1'),
|
Region(name='Region 1', slug='region-1', comments=''),
|
||||||
Region(name='Region 2', slug='region-2'),
|
Region(
|
||||||
|
name='Region 2', slug='region-2', comments="It's going to take a lot to drag me away from you"
|
||||||
|
),
|
||||||
Region(name='Region 3', slug='region-3'),
|
Region(name='Region 3', slug='region-3'),
|
||||||
)
|
)
|
||||||
for region in regions:
|
for region in regions:
|
||||||
@ -40,13 +42,14 @@ class RegionTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
'parent': regions[2].pk,
|
'parent': regions[2].pk,
|
||||||
'description': 'A new region',
|
'description': 'A new region',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
|
'comments': 'This comment is really exciting!',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,slug,description",
|
"name,slug,description,comments",
|
||||||
"Region 4,region-4,Fourth region",
|
"Region 4,region-4,Fourth region,",
|
||||||
"Region 5,region-5,Fifth region",
|
"Region 5,region-5,Fifth region,hi guys",
|
||||||
"Region 6,region-6,Sixth region",
|
"Region 6,region-6,Sixth region,bye guys",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
@ -58,6 +61,7 @@ class RegionTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'This comment is super exciting!!!',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -69,7 +73,7 @@ class SiteGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
|
|
||||||
# Create three SiteGroups
|
# Create three SiteGroups
|
||||||
sitegroups = (
|
sitegroups = (
|
||||||
SiteGroup(name='Site Group 1', slug='site-group-1'),
|
SiteGroup(name='Site Group 1', slug='site-group-1', comments='Still here'),
|
||||||
SiteGroup(name='Site Group 2', slug='site-group-2'),
|
SiteGroup(name='Site Group 2', slug='site-group-2'),
|
||||||
SiteGroup(name='Site Group 3', slug='site-group-3'),
|
SiteGroup(name='Site Group 3', slug='site-group-3'),
|
||||||
)
|
)
|
||||||
@ -84,24 +88,26 @@ class SiteGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
'parent': sitegroups[2].pk,
|
'parent': sitegroups[2].pk,
|
||||||
'description': 'A new site group',
|
'description': 'A new site group',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
|
'comments': 'still here',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,slug,description",
|
"name,slug,description,comments",
|
||||||
"Site Group 4,site-group-4,Fourth site group",
|
"Site Group 4,site-group-4,Fourth site group,",
|
||||||
"Site Group 5,site-group-5,Fifth site group",
|
"Site Group 5,site-group-5,Fifth site group,still hear",
|
||||||
"Site Group 6,site-group-6,Sixth site group",
|
"Site Group 6,site-group-6,Sixth site group,"
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,name,description",
|
"id,name,description,comments",
|
||||||
f"{sitegroups[0].pk},Site Group 7,Fourth site group7",
|
f"{sitegroups[0].pk},Site Group 7,Fourth site group7,",
|
||||||
f"{sitegroups[1].pk},Site Group 8,Fifth site group8",
|
f"{sitegroups[1].pk},Site Group 8,Fifth site group8,when will it end",
|
||||||
f"{sitegroups[2].pk},Site Group 0,Sixth site group9",
|
f"{sitegroups[2].pk},Site Group 0,Sixth site group9,",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'the end',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -202,6 +208,7 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
site=site,
|
site=site,
|
||||||
status=LocationStatusChoices.STATUS_ACTIVE,
|
status=LocationStatusChoices.STATUS_ACTIVE,
|
||||||
tenant=tenant,
|
tenant=tenant,
|
||||||
|
comments='',
|
||||||
),
|
),
|
||||||
Location(
|
Location(
|
||||||
name='Location 2',
|
name='Location 2',
|
||||||
@ -209,6 +216,7 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
site=site,
|
site=site,
|
||||||
status=LocationStatusChoices.STATUS_ACTIVE,
|
status=LocationStatusChoices.STATUS_ACTIVE,
|
||||||
tenant=tenant,
|
tenant=tenant,
|
||||||
|
comments='First comment!',
|
||||||
),
|
),
|
||||||
Location(
|
Location(
|
||||||
name='Location 3',
|
name='Location 3',
|
||||||
@ -216,6 +224,7 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
site=site,
|
site=site,
|
||||||
status=LocationStatusChoices.STATUS_ACTIVE,
|
status=LocationStatusChoices.STATUS_ACTIVE,
|
||||||
tenant=tenant,
|
tenant=tenant,
|
||||||
|
comments='_This_ is a **bold comment**',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for location in locations:
|
for location in locations:
|
||||||
@ -232,24 +241,26 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
'tenant': tenant.pk,
|
'tenant': tenant.pk,
|
||||||
'description': 'A new location',
|
'description': 'A new location',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
|
'comments': 'This comment is really boring',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"site,tenant,name,slug,status,description",
|
"site,tenant,name,slug,status,description,comments",
|
||||||
"Site 1,Tenant 1,Location 4,location-4,planned,Fourth location",
|
"Site 1,Tenant 1,Location 4,location-4,planned,Fourth location,",
|
||||||
"Site 1,Tenant 1,Location 5,location-5,planned,Fifth location",
|
"Site 1,Tenant 1,Location 5,location-5,planned,Fifth location,",
|
||||||
"Site 1,Tenant 1,Location 6,location-6,planned,Sixth location",
|
"Site 1,Tenant 1,Location 6,location-6,planned,Sixth location,hi!",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,name,description",
|
"id,name,description,comments",
|
||||||
f"{locations[0].pk},Location 7,Fourth location7",
|
f"{locations[0].pk},Location 7,Fourth location7,Useful comment",
|
||||||
f"{locations[1].pk},Location 8,Fifth location8",
|
f"{locations[1].pk},Location 8,Fifth location8,unuseful comment",
|
||||||
f"{locations[2].pk},Location 0,Sixth location9",
|
f"{locations[2].pk},Location 0,Sixth location9,",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'This comment is also really boring',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class ExportTemplateSerializer(ValidatedModelSerializer):
|
|||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'description', 'template_code', 'mime_type',
|
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'description', 'template_code', 'mime_type',
|
||||||
'file_extension', 'as_attachment', 'data_source', 'data_path', 'data_file', 'data_synced', 'created',
|
'file_name', 'file_extension', 'as_attachment', 'data_source', 'data_path', 'data_file', 'data_synced',
|
||||||
'last_updated',
|
'created', 'last_updated',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||||
|
@ -27,8 +27,8 @@ class TagSerializer(ValidatedModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'object_types',
|
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'weight',
|
||||||
'tagged_items', 'created', 'last_updated',
|
'object_types', 'tagged_items', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'color', 'description')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'color', 'description')
|
||||||
|
|
||||||
|
@ -258,8 +258,8 @@ class ExportTemplateFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
fields = (
|
fields = (
|
||||||
'id', 'name', 'description', 'mime_type', 'file_extension', 'as_attachment', 'auto_sync_enabled',
|
'id', 'name', 'description', 'mime_type', 'file_name', 'file_extension', 'as_attachment',
|
||||||
'data_synced',
|
'auto_sync_enabled', 'data_synced',
|
||||||
)
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
@ -267,7 +267,8 @@ class ExportTemplateFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(name__icontains=value) |
|
Q(name__icontains=value) |
|
||||||
Q(description__icontains=value)
|
Q(description__icontains=value) |
|
||||||
|
Q(file_name__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +451,7 @@ class TagFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ('id', 'name', 'slug', 'color', 'description', 'object_types')
|
fields = ('id', 'name', 'slug', 'color', 'weight', 'description', 'object_types')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -155,6 +155,10 @@ class ExportTemplateBulkEditForm(BulkEditForm):
|
|||||||
max_length=50,
|
max_length=50,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
file_name = forms.CharField(
|
||||||
|
label=_('File name'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
file_extension = forms.CharField(
|
file_extension = forms.CharField(
|
||||||
label=_('File extension'),
|
label=_('File extension'),
|
||||||
max_length=15,
|
max_length=15,
|
||||||
@ -166,7 +170,7 @@ class ExportTemplateBulkEditForm(BulkEditForm):
|
|||||||
widget=BulkEditNullBooleanSelect()
|
widget=BulkEditNullBooleanSelect()
|
||||||
)
|
)
|
||||||
|
|
||||||
nullable_fields = ('description', 'mime_type', 'file_extension')
|
nullable_fields = ('description', 'mime_type', 'file_name', 'file_extension')
|
||||||
|
|
||||||
|
|
||||||
class SavedFilterBulkEditForm(BulkEditForm):
|
class SavedFilterBulkEditForm(BulkEditForm):
|
||||||
@ -275,6 +279,10 @@ class TagBulkEditForm(BulkEditForm):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
weight = forms.IntegerField(
|
||||||
|
label=_('Weight'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
nullable_fields = ('description',)
|
nullable_fields = ('description',)
|
||||||
|
|
||||||
|
@ -144,7 +144,8 @@ class ExportTemplateImportForm(CSVModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'object_types', 'description', 'mime_type', 'file_extension', 'as_attachment', 'template_code',
|
'name', 'object_types', 'description', 'mime_type', 'file_name', 'file_extension', 'as_attachment',
|
||||||
|
'template_code',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -232,10 +233,14 @@ class EventRuleImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class TagImportForm(CSVModelForm):
|
class TagImportForm(CSVModelForm):
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
weight = forms.IntegerField(
|
||||||
|
label=_('Weight'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ('name', 'slug', 'color', 'description')
|
fields = ('name', 'slug', 'color', 'weight', 'description')
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryImportForm(NetBoxModelImportForm):
|
class JournalEntryImportForm(NetBoxModelImportForm):
|
||||||
|
@ -162,7 +162,7 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id'),
|
FieldSet('q', 'filter_id'),
|
||||||
FieldSet('data_source_id', 'data_file_id', name=_('Data')),
|
FieldSet('data_source_id', 'data_file_id', name=_('Data')),
|
||||||
FieldSet('object_type_id', 'mime_type', 'file_extension', 'as_attachment', name=_('Attributes')),
|
FieldSet('object_type_id', 'mime_type', 'file_name', 'file_extension', 'as_attachment', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
data_source_id = DynamicModelMultipleChoiceField(
|
data_source_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=DataSource.objects.all(),
|
queryset=DataSource.objects.all(),
|
||||||
@ -186,6 +186,10 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('MIME type')
|
label=_('MIME type')
|
||||||
)
|
)
|
||||||
|
file_name = forms.CharField(
|
||||||
|
label=_('File name'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
file_extension = forms.CharField(
|
file_extension = forms.CharField(
|
||||||
label=_('File extension'),
|
label=_('File extension'),
|
||||||
required=False
|
required=False
|
||||||
|
@ -246,7 +246,7 @@ class ExportTemplateForm(SyncedDataMixin, forms.ModelForm):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('name', 'object_types', 'description', 'template_code', name=_('Export Template')),
|
FieldSet('name', 'object_types', 'description', 'template_code', name=_('Export Template')),
|
||||||
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||||
FieldSet('mime_type', 'file_extension', 'as_attachment', name=_('Rendering')),
|
FieldSet('mime_type', 'file_name', 'file_extension', 'as_attachment', name=_('Rendering')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -490,15 +490,19 @@ class TagForm(forms.ModelForm):
|
|||||||
queryset=ObjectType.objects.with_feature('tags'),
|
queryset=ObjectType.objects.with_feature('tags'),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
weight = forms.IntegerField(
|
||||||
|
label=_('Weight'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('name', 'slug', 'color', 'description', 'object_types', name=_('Tag')),
|
FieldSet('name', 'slug', 'color', 'weight', 'description', 'object_types', name=_('Tag')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'slug', 'color', 'description', 'object_types',
|
'name', 'slug', 'color', 'weight', 'description', 'object_types',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0123_remove_staging'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='tag',
|
||||||
|
options={'ordering': ('weight', 'name')},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='weight',
|
||||||
|
field=models.PositiveSmallIntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
16
netbox/extras/migrations/0125_exporttemplate_file_name.py
Normal file
16
netbox/extras/migrations/0125_exporttemplate_file_name.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0124_alter_tag_options_tag_weight'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exporttemplate',
|
||||||
|
name='file_name',
|
||||||
|
field=models.CharField(blank=True, max_length=200),
|
||||||
|
),
|
||||||
|
]
|
@ -16,7 +16,7 @@ from core.models import ObjectType
|
|||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.conditions import ConditionSet
|
from extras.conditions import ConditionSet
|
||||||
from extras.constants import *
|
from extras.constants import *
|
||||||
from extras.utils import image_upload
|
from extras.utils import filename_from_model, image_upload
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.events import get_event_type_choices
|
from netbox.events import get_event_type_choices
|
||||||
from netbox.models import ChangeLoggedModel
|
from netbox.models import ChangeLoggedModel
|
||||||
@ -409,6 +409,11 @@ class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, Change
|
|||||||
verbose_name=_('MIME type'),
|
verbose_name=_('MIME type'),
|
||||||
help_text=_('Defaults to <code>text/plain; charset=utf-8</code>')
|
help_text=_('Defaults to <code>text/plain; charset=utf-8</code>')
|
||||||
)
|
)
|
||||||
|
file_name = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True,
|
||||||
|
help_text=_('Filename to give to the rendered export file')
|
||||||
|
)
|
||||||
file_extension = models.CharField(
|
file_extension = models.CharField(
|
||||||
verbose_name=_('file extension'),
|
verbose_name=_('file extension'),
|
||||||
max_length=15,
|
max_length=15,
|
||||||
@ -422,7 +427,7 @@ class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, Change
|
|||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'object_types', 'template_code', 'mime_type', 'file_extension', 'as_attachment',
|
'object_types', 'template_code', 'mime_type', 'file_name', 'file_extension', 'as_attachment',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -480,10 +485,10 @@ class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, Change
|
|||||||
response = HttpResponse(output, content_type=mime_type)
|
response = HttpResponse(output, content_type=mime_type)
|
||||||
|
|
||||||
if self.as_attachment:
|
if self.as_attachment:
|
||||||
basename = queryset.model._meta.verbose_name_plural.replace(' ', '_')
|
|
||||||
extension = f'.{self.file_extension}' if self.file_extension else ''
|
extension = f'.{self.file_extension}' if self.file_extension else ''
|
||||||
filename = f'netbox_{basename}{extension}'
|
filename = self.file_name or filename_from_model(queryset.model)
|
||||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
full_filename = f'{filename}{extension}'
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="{full_filename}"'
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -40,13 +40,17 @@ class Tag(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, TagBase):
|
|||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("The object type(s) to which this tag can be applied.")
|
help_text=_("The object type(s) to which this tag can be applied.")
|
||||||
)
|
)
|
||||||
|
weight = models.PositiveSmallIntegerField(
|
||||||
|
verbose_name=_('weight'),
|
||||||
|
default=0,
|
||||||
|
)
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'color', 'description', 'object_types',
|
'color', 'description', 'object_types',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ('weight', 'name')
|
||||||
verbose_name = _('tag')
|
verbose_name = _('tag')
|
||||||
verbose_name_plural = _('tags')
|
verbose_name_plural = _('tags')
|
||||||
|
|
||||||
|
@ -203,11 +203,12 @@ class ExportTemplateTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'object_types', 'description', 'mime_type', 'file_extension', 'as_attachment',
|
'pk', 'id', 'name', 'object_types', 'description', 'mime_type', 'file_name', 'file_extension',
|
||||||
'data_source', 'data_file', 'data_synced', 'created', 'last_updated',
|
'as_attachment', 'data_source', 'data_file', 'data_synced', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'object_types', 'description', 'mime_type', 'file_extension', 'as_attachment', 'is_synced',
|
'pk', 'name', 'object_types', 'description', 'mime_type', 'file_name', 'file_extension',
|
||||||
|
'as_attachment', 'is_synced',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -449,8 +450,8 @@ class TagTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'object_types', 'created', 'last_updated',
|
'pk', 'id', 'name', 'items', 'slug', 'color', 'weight', 'description', 'object_types',
|
||||||
'actions',
|
'created', 'last_updated', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description')
|
default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description')
|
||||||
|
|
||||||
|
@ -479,6 +479,7 @@ class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'object_types': ['dcim.device'],
|
'object_types': ['dcim.device'],
|
||||||
'name': 'Test Export Template 6',
|
'name': 'Test Export Template 6',
|
||||||
'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
|
'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
|
||||||
|
'file_name': 'test_export_template_6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
@ -494,7 +495,9 @@ class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
),
|
),
|
||||||
ExportTemplate(
|
ExportTemplate(
|
||||||
name='Export Template 2',
|
name='Export Template 2',
|
||||||
template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}'
|
template_code='{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
|
||||||
|
file_name='export_template_2',
|
||||||
|
file_extension='test',
|
||||||
),
|
),
|
||||||
ExportTemplate(
|
ExportTemplate(
|
||||||
name='Export Template 3',
|
name='Export Template 3',
|
||||||
@ -502,8 +505,10 @@ class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
ExportTemplate.objects.bulk_create(export_templates)
|
ExportTemplate.objects.bulk_create(export_templates)
|
||||||
|
|
||||||
|
device_object_type = ObjectType.objects.get_for_model(Device)
|
||||||
for et in export_templates:
|
for et in export_templates:
|
||||||
et.object_types.set([ObjectType.objects.get_for_model(Device)])
|
et.object_types.set([device_object_type])
|
||||||
|
|
||||||
|
|
||||||
class TagTest(APIViewTestCases.APIViewTestCase):
|
class TagTest(APIViewTestCases.APIViewTestCase):
|
||||||
@ -513,6 +518,7 @@ class TagTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'Tag 4',
|
'name': 'Tag 4',
|
||||||
'slug': 'tag-4',
|
'slug': 'tag-4',
|
||||||
|
'weight': 1000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Tag 5',
|
'name': 'Tag 5',
|
||||||
@ -533,7 +539,7 @@ class TagTest(APIViewTestCases.APIViewTestCase):
|
|||||||
tags = (
|
tags = (
|
||||||
Tag(name='Tag 1', slug='tag-1'),
|
Tag(name='Tag 1', slug='tag-1'),
|
||||||
Tag(name='Tag 2', slug='tag-2'),
|
Tag(name='Tag 2', slug='tag-2'),
|
||||||
Tag(name='Tag 3', slug='tag-3'),
|
Tag(name='Tag 3', slug='tag-3', weight=26),
|
||||||
)
|
)
|
||||||
Tag.objects.bulk_create(tags)
|
Tag.objects.bulk_create(tags)
|
||||||
|
|
||||||
|
@ -624,8 +624,11 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
|
|
||||||
export_templates = (
|
export_templates = (
|
||||||
ExportTemplate(name='Export Template 1', template_code='TESTING', description='foobar1'),
|
ExportTemplate(name='Export Template 1', template_code='TESTING', description='foobar1'),
|
||||||
ExportTemplate(name='Export Template 2', template_code='TESTING', description='foobar2'),
|
ExportTemplate(
|
||||||
ExportTemplate(name='Export Template 3', template_code='TESTING'),
|
name='Export Template 2', template_code='TESTING', description='foobar2',
|
||||||
|
file_name='export_template_2', file_extension='nagios',
|
||||||
|
),
|
||||||
|
ExportTemplate(name='Export Template 3', template_code='TESTING', file_name='export_filename'),
|
||||||
)
|
)
|
||||||
ExportTemplate.objects.bulk_create(export_templates)
|
ExportTemplate.objects.bulk_create(export_templates)
|
||||||
for i, et in enumerate(export_templates):
|
for i, et in enumerate(export_templates):
|
||||||
@ -635,6 +638,9 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
params = {'q': 'export_filename'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Export Template 1', 'Export Template 2']}
|
params = {'name': ['Export Template 1', 'Export Template 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -649,6 +655,20 @@ class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'description': ['foobar1', 'foobar2']}
|
params = {'description': ['foobar1', 'foobar2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_file_name(self):
|
||||||
|
params = {'file_name': ['export_filename']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_file_extension(self):
|
||||||
|
params = {'file_extension': ['nagios']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
params = {'file_name': ['export_template_2'], 'file_extension': ['nagios']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
params = {'file_name': 'export_filename', 'file_extension': ['nagios']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
|
||||||
|
|
||||||
|
|
||||||
class ImageAttachmentTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ImageAttachmentTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ImageAttachment.objects.all()
|
queryset = ImageAttachment.objects.all()
|
||||||
@ -1196,7 +1216,7 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
tags = (
|
tags = (
|
||||||
Tag(name='Tag 1', slug='tag-1', color='ff0000', description='foobar1'),
|
Tag(name='Tag 1', slug='tag-1', color='ff0000', description='foobar1'),
|
||||||
Tag(name='Tag 2', slug='tag-2', color='00ff00', description='foobar2'),
|
Tag(name='Tag 2', slug='tag-2', color='00ff00', description='foobar2'),
|
||||||
Tag(name='Tag 3', slug='tag-3', color='0000ff'),
|
Tag(name='Tag 3', slug='tag-3', color='0000ff', weight=1000),
|
||||||
)
|
)
|
||||||
Tag.objects.bulk_create(tags)
|
Tag.objects.bulk_create(tags)
|
||||||
tags[0].object_types.add(object_types['site'])
|
tags[0].object_types.add(object_types['site'])
|
||||||
@ -1249,6 +1269,13 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
['Tag 2', 'Tag 3']
|
['Tag 2', 'Tag 3']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_weight(self):
|
||||||
|
params = {'weight': [1000]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
params = {'weight': [0]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
|
||||||
class TaggedItemFilterSetTestCase(TestCase):
|
class TaggedItemFilterSetTestCase(TestCase):
|
||||||
queryset = TaggedItem.objects.all()
|
queryset = TaggedItem.objects.all()
|
||||||
|
@ -10,6 +10,40 @@ from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMac
|
|||||||
|
|
||||||
class TagTest(TestCase):
|
class TagTest(TestCase):
|
||||||
|
|
||||||
|
def test_default_ordering_weight_then_name_is_set(self):
|
||||||
|
Tag.objects.create(name='Tag 1', slug='tag-1', weight=100)
|
||||||
|
Tag.objects.create(name='Tag 2', slug='tag-2')
|
||||||
|
Tag.objects.create(name='Tag 3', slug='tag-3', weight=10)
|
||||||
|
Tag.objects.create(name='Tag 4', slug='tag-4', weight=10)
|
||||||
|
|
||||||
|
tags = Tag.objects.all()
|
||||||
|
|
||||||
|
self.assertEqual(tags[0].slug, 'tag-2')
|
||||||
|
self.assertEqual(tags[1].slug, 'tag-3')
|
||||||
|
self.assertEqual(tags[2].slug, 'tag-4')
|
||||||
|
self.assertEqual(tags[3].slug, 'tag-1')
|
||||||
|
|
||||||
|
def test_tag_related_manager_ordering_weight_then_name(self):
|
||||||
|
tags = [
|
||||||
|
Tag.objects.create(name='Tag 1', slug='tag-1', weight=100),
|
||||||
|
Tag.objects.create(name='Tag 2', slug='tag-2'),
|
||||||
|
Tag.objects.create(name='Tag 3', slug='tag-3', weight=10),
|
||||||
|
Tag.objects.create(name='Tag 4', slug='tag-4', weight=10),
|
||||||
|
]
|
||||||
|
|
||||||
|
site = Site.objects.create(name='Site 1')
|
||||||
|
for tag in tags:
|
||||||
|
site.tags.add(tag)
|
||||||
|
site.save()
|
||||||
|
|
||||||
|
site = Site.objects.first()
|
||||||
|
tags = site.tags.all()
|
||||||
|
|
||||||
|
self.assertEqual(tags[0].slug, 'tag-2')
|
||||||
|
self.assertEqual(tags[1].slug, 'tag-3')
|
||||||
|
self.assertEqual(tags[2].slug, 'tag-4')
|
||||||
|
self.assertEqual(tags[3].slug, 'tag-1')
|
||||||
|
|
||||||
def test_create_tag_unicode(self):
|
def test_create_tag_unicode(self):
|
||||||
tag = Tag(name='Testing Unicode: 台灣')
|
tag = Tag(name='Testing Unicode: 台灣')
|
||||||
tag.save()
|
tag.save()
|
||||||
|
19
netbox/extras/tests/test_utils.py
Normal file
19
netbox/extras/tests/test_utils.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from extras.models import ExportTemplate
|
||||||
|
from extras.utils import filename_from_model
|
||||||
|
from tenancy.models import ContactGroup, TenantGroup
|
||||||
|
from wireless.models import WirelessLANGroup
|
||||||
|
|
||||||
|
|
||||||
|
class FilenameFromModelTests(TestCase):
|
||||||
|
def test_expected_output(self):
|
||||||
|
cases = (
|
||||||
|
(ExportTemplate, 'netbox_export_templates'),
|
||||||
|
(ContactGroup, 'netbox_contact_groups'),
|
||||||
|
(TenantGroup, 'netbox_tenant_groups'),
|
||||||
|
(WirelessLANGroup, 'netbox_wireless_lan_groups'),
|
||||||
|
)
|
||||||
|
|
||||||
|
for model, expected in cases:
|
||||||
|
self.assertEqual(filename_from_model(model), expected)
|
@ -305,7 +305,7 @@ class ExportTemplateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
export_templates = (
|
export_templates = (
|
||||||
ExportTemplate(name='Export Template 1', template_code=TEMPLATE_CODE),
|
ExportTemplate(name='Export Template 1', template_code=TEMPLATE_CODE),
|
||||||
ExportTemplate(name='Export Template 2', template_code=TEMPLATE_CODE),
|
ExportTemplate(name='Export Template 2', template_code=TEMPLATE_CODE),
|
||||||
ExportTemplate(name='Export Template 3', template_code=TEMPLATE_CODE),
|
ExportTemplate(name='Export Template 3', template_code=TEMPLATE_CODE, file_name='export_template_3')
|
||||||
)
|
)
|
||||||
ExportTemplate.objects.bulk_create(export_templates)
|
ExportTemplate.objects.bulk_create(export_templates)
|
||||||
for et in export_templates:
|
for et in export_templates:
|
||||||
@ -315,13 +315,14 @@ class ExportTemplateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'name': 'Export Template X',
|
'name': 'Export Template X',
|
||||||
'object_types': [site_type.pk],
|
'object_types': [site_type.pk],
|
||||||
'template_code': TEMPLATE_CODE,
|
'template_code': TEMPLATE_CODE,
|
||||||
|
'file_name': 'template_x',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,object_types,template_code",
|
"name,object_types,template_code,file_name",
|
||||||
f"Export Template 4,dcim.site,{TEMPLATE_CODE}",
|
f"Export Template 4,dcim.site,{TEMPLATE_CODE},",
|
||||||
f"Export Template 5,dcim.site,{TEMPLATE_CODE}",
|
f"Export Template 5,dcim.site,{TEMPLATE_CODE},template_5",
|
||||||
f"Export Template 6,dcim.site,{TEMPLATE_CODE}",
|
f"Export Template 6,dcim.site,{TEMPLATE_CODE},",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
@ -441,8 +442,8 @@ class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
|
|
||||||
tags = (
|
tags = (
|
||||||
Tag(name='Tag 1', slug='tag-1'),
|
Tag(name='Tag 1', slug='tag-1'),
|
||||||
Tag(name='Tag 2', slug='tag-2'),
|
Tag(name='Tag 2', slug='tag-2', weight=1),
|
||||||
Tag(name='Tag 3', slug='tag-3'),
|
Tag(name='Tag 3', slug='tag-3', weight=32767),
|
||||||
)
|
)
|
||||||
Tag.objects.bulk_create(tags)
|
Tag.objects.bulk_create(tags)
|
||||||
|
|
||||||
@ -451,13 +452,14 @@ class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
'slug': 'tag-x',
|
'slug': 'tag-x',
|
||||||
'color': 'c0c0c0',
|
'color': 'c0c0c0',
|
||||||
'comments': 'Some comments',
|
'comments': 'Some comments',
|
||||||
|
'weight': 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,slug,color,description",
|
"name,slug,color,description,weight",
|
||||||
"Tag 4,tag-4,ff0000,Fourth tag",
|
"Tag 4,tag-4,ff0000,Fourth tag,0",
|
||||||
"Tag 5,tag-5,00ff00,Fifth tag",
|
"Tag 5,tag-5,00ff00,Fifth tag,1111",
|
||||||
"Tag 6,tag-6,0000ff,Sixth tag",
|
"Tag 6,tag-6,0000ff,Sixth tag,0",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.db import models
|
||||||
from taggit.managers import _TaggableManager
|
from taggit.managers import _TaggableManager
|
||||||
|
|
||||||
from netbox.context import current_request
|
from netbox.context import current_request
|
||||||
@ -15,6 +16,12 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def filename_from_model(model: models.Model) -> str:
|
||||||
|
"""Standardises how we generate filenames from model class for exports"""
|
||||||
|
base = model._meta.verbose_name_plural.lower().replace(' ', '_')
|
||||||
|
return f'netbox_{base}'
|
||||||
|
|
||||||
|
|
||||||
def is_taggable(obj):
|
def is_taggable(obj):
|
||||||
"""
|
"""
|
||||||
Return True if the instance can have Tags assigned to it; False otherwise.
|
Return True if the instance can have Tags assigned to it; False otherwise.
|
||||||
|
@ -43,6 +43,8 @@ SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|||||||
|
|
||||||
DEFAULT_PERMISSIONS = {}
|
DEFAULT_PERMISSIONS = {}
|
||||||
|
|
||||||
|
ALLOW_TOKEN_RETRIEVAL = True
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': True
|
'disable_existing_loggers': True
|
||||||
|
@ -329,3 +329,19 @@ class OrganizationalModelFilterSet(NetBoxModelFilterSet):
|
|||||||
models.Q(slug__icontains=value) |
|
models.Q(slug__icontains=value) |
|
||||||
models.Q(description__icontains=value)
|
models.Q(description__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NestedGroupModelFilterSet(NetBoxModelFilterSet):
|
||||||
|
"""
|
||||||
|
A base FilterSet for models that inherit from NestedGroupModel
|
||||||
|
"""
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if value.strip():
|
||||||
|
queryset = queryset.filter(
|
||||||
|
models.Q(name__icontains=value) |
|
||||||
|
models.Q(slug__icontains=value) |
|
||||||
|
models.Q(description__icontains=value) |
|
||||||
|
models.Q(comments__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
@ -150,6 +150,10 @@ class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
comments = models.TextField(
|
||||||
|
verbose_name=_('comments'),
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
objects = TreeManager()
|
objects = TreeManager()
|
||||||
|
|
||||||
|
@ -455,7 +455,8 @@ class TagsMixin(models.Model):
|
|||||||
which is a `TaggableManager` instance.
|
which is a `TaggableManager` instance.
|
||||||
"""
|
"""
|
||||||
tags = TaggableManager(
|
tags = TaggableManager(
|
||||||
through='extras.TaggedItem'
|
through='extras.TaggedItem',
|
||||||
|
ordering=('weight', 'name'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -64,7 +64,7 @@ elif hasattr(configuration, 'DATABASE') and hasattr(configuration, 'DATABASES'):
|
|||||||
|
|
||||||
# Set static config parameters
|
# Set static config parameters
|
||||||
ADMINS = getattr(configuration, 'ADMINS', [])
|
ADMINS = getattr(configuration, 'ADMINS', [])
|
||||||
ALLOW_TOKEN_RETRIEVAL = getattr(configuration, 'ALLOW_TOKEN_RETRIEVAL', True)
|
ALLOW_TOKEN_RETRIEVAL = getattr(configuration, 'ALLOW_TOKEN_RETRIEVAL', False)
|
||||||
ALLOWED_HOSTS = getattr(configuration, 'ALLOWED_HOSTS') # Required
|
ALLOWED_HOSTS = getattr(configuration, 'ALLOWED_HOSTS') # Required
|
||||||
AUTH_PASSWORD_VALIDATORS = getattr(configuration, 'AUTH_PASSWORD_VALIDATORS', [
|
AUTH_PASSWORD_VALIDATORS = getattr(configuration, 'AUTH_PASSWORD_VALIDATORS', [
|
||||||
{
|
{
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
@ -23,6 +23,10 @@
|
|||||||
<th scope="row">{% trans "MIME Type" %}</th>
|
<th scope="row">{% trans "MIME Type" %}</th>
|
||||||
<td>{{ object.mime_type|placeholder }}</td>
|
<td>{{ object.mime_type|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "File Name" %}</th>
|
||||||
|
<td>{{ object.file_name|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "File Extension" %}</th>
|
<th scope="row">{% trans "File Extension" %}</th>
|
||||||
<td>{{ object.file_extension|placeholder }}</td>
|
<td>{{ object.file_extension|placeholder }}</td>
|
||||||
|
@ -28,6 +28,10 @@
|
|||||||
<span class="color-label" style="background-color: #{{ object.color }}"> </span>
|
<span class="color-label" style="background-color: #{{ object.color }}"> </span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Weight" %}</th>
|
||||||
|
<td>{{ object.weight }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Tagged Items" %}</th>
|
<th scope="row">{% trans "Tagged Items" %}</th>
|
||||||
<td>
|
<td>
|
||||||
|
@ -18,8 +18,18 @@
|
|||||||
<h2 class="card-header">{% trans "Contact" %}</h2>
|
<h2 class="card-header">{% trans "Contact" %}</h2>
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Group" %}</th>
|
<th scope="row">{% trans "Groups" %}</th>
|
||||||
<td>{{ object.group|linkify|placeholder }}</td>
|
<td>
|
||||||
|
{% if object.groups.all|length > 0 %}
|
||||||
|
<ol class="list-unstyled mb-0">
|
||||||
|
{% for group in object.groups.all %}
|
||||||
|
<li>{{ group|linkify|placeholder }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
{% else %}
|
||||||
|
{{ ''|placeholder }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Name" %}</th>
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
@ -3,7 +3,7 @@ from drf_spectacular.types import OpenApiTypes
|
|||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from netbox.api.fields import ChoiceField, ContentTypeField
|
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
||||||
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
|
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
|
||||||
from tenancy.choices import ContactPriorityChoices
|
from tenancy.choices import ContactPriorityChoices
|
||||||
from tenancy.models import ContactAssignment, Contact, ContactGroup, ContactRole
|
from tenancy.models import ContactAssignment, Contact, ContactGroup, ContactRole
|
||||||
@ -26,7 +26,7 @@ class ContactGroupSerializer(NestedGroupModelSerializer):
|
|||||||
model = ContactGroup
|
model = ContactGroup
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated', 'contact_count', '_depth',
|
'created', 'last_updated', 'contact_count', 'comments', '_depth',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'contact_count', '_depth')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'contact_count', '_depth')
|
||||||
|
|
||||||
@ -43,12 +43,17 @@ class ContactRoleSerializer(NetBoxModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ContactSerializer(NetBoxModelSerializer):
|
class ContactSerializer(NetBoxModelSerializer):
|
||||||
group = ContactGroupSerializer(nested=True, required=False, allow_null=True, default=None)
|
groups = SerializedPKRelatedField(
|
||||||
|
queryset=ContactGroup.objects.all(),
|
||||||
|
serializer=ContactGroupSerializer,
|
||||||
|
required=False,
|
||||||
|
many=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Contact
|
model = Contact
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'group', 'name', 'title', 'phone', 'email', 'address', 'link',
|
'id', 'url', 'display_url', 'display', 'groups', 'name', 'title', 'phone', 'email', 'address', 'link',
|
||||||
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||||
|
@ -19,7 +19,7 @@ class TenantGroupSerializer(NestedGroupModelSerializer):
|
|||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated', 'tenant_count', '_depth',
|
'created', 'last_updated', 'tenant_count', 'comments', '_depth',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'tenant_count', '_depth')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'tenant_count', '_depth')
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class ContactGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
|||||||
queryset = ContactGroup.objects.add_related_count(
|
queryset = ContactGroup.objects.add_related_count(
|
||||||
ContactGroup.objects.all(),
|
ContactGroup.objects.all(),
|
||||||
Contact,
|
Contact,
|
||||||
'group',
|
'groups',
|
||||||
'contact_count',
|
'contact_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@ import django_filters
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet
|
from netbox.filtersets import NestedGroupModelFilterSet, NetBoxModelFilterSet, OrganizationalModelFilterSet
|
||||||
from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
|
from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ __all__ = (
|
|||||||
# Contacts
|
# Contacts
|
||||||
#
|
#
|
||||||
|
|
||||||
class ContactGroupFilterSet(OrganizationalModelFilterSet):
|
class ContactGroupFilterSet(NestedGroupModelFilterSet):
|
||||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
label=_('Parent contact group (ID)'),
|
label=_('Parent contact group (ID)'),
|
||||||
@ -46,6 +46,11 @@ class ContactGroupFilterSet(OrganizationalModelFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Contact group (slug)'),
|
label=_('Contact group (slug)'),
|
||||||
)
|
)
|
||||||
|
contact_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='contact',
|
||||||
|
queryset=Contact.objects.all(),
|
||||||
|
label=_('Contact (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContactGroup
|
model = ContactGroup
|
||||||
@ -62,15 +67,15 @@ class ContactRoleFilterSet(OrganizationalModelFilterSet):
|
|||||||
class ContactFilterSet(NetBoxModelFilterSet):
|
class ContactFilterSet(NetBoxModelFilterSet):
|
||||||
group_id = TreeNodeMultipleChoiceFilter(
|
group_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
field_name='group',
|
field_name='groups',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label=_('Contact group (ID)'),
|
label=_('Contact group (ID)'),
|
||||||
)
|
)
|
||||||
group = TreeNodeMultipleChoiceFilter(
|
group = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
field_name='group',
|
field_name='groups',
|
||||||
lookup_expr='in',
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
|
lookup_expr='in',
|
||||||
label=_('Contact group (slug)'),
|
label=_('Contact group (slug)'),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -105,13 +110,13 @@ class ContactAssignmentFilterSet(NetBoxModelFilterSet):
|
|||||||
)
|
)
|
||||||
group_id = TreeNodeMultipleChoiceFilter(
|
group_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
field_name='contact__group',
|
field_name='contact__groups',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label=_('Contact group (ID)'),
|
label=_('Contact group (ID)'),
|
||||||
)
|
)
|
||||||
group = TreeNodeMultipleChoiceFilter(
|
group = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
field_name='contact__group',
|
field_name='contact__groups',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Contact group (slug)'),
|
label=_('Contact group (slug)'),
|
||||||
@ -153,7 +158,7 @@ class ContactModelFilterSet(django_filters.FilterSet):
|
|||||||
)
|
)
|
||||||
contact_group = TreeNodeMultipleChoiceFilter(
|
contact_group = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
field_name='contacts__contact__group',
|
field_name='contacts__contact__groups',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label=_('Contact group'),
|
label=_('Contact group'),
|
||||||
)
|
)
|
||||||
@ -163,7 +168,7 @@ class ContactModelFilterSet(django_filters.FilterSet):
|
|||||||
# Tenancy
|
# Tenancy
|
||||||
#
|
#
|
||||||
|
|
||||||
class TenantGroupFilterSet(OrganizationalModelFilterSet):
|
class TenantGroupFilterSet(NestedGroupModelFilterSet):
|
||||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=TenantGroup.objects.all(),
|
queryset=TenantGroup.objects.all(),
|
||||||
label=_('Parent tenant group (ID)'),
|
label=_('Parent tenant group (ID)'),
|
||||||
|
@ -5,7 +5,7 @@ from netbox.forms import NetBoxModelBulkEditForm
|
|||||||
from tenancy.choices import ContactPriorityChoices
|
from tenancy.choices import ContactPriorityChoices
|
||||||
from tenancy.models import *
|
from tenancy.models import *
|
||||||
from utilities.forms import add_blank_choice
|
from utilities.forms import add_blank_choice
|
||||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField
|
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||||
from utilities.forms.rendering import FieldSet
|
from utilities.forms.rendering import FieldSet
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -33,9 +33,10 @@ class TenantGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
nullable_fields = ('parent', 'description')
|
nullable_fields = ('parent', 'description', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class TenantBulkEditForm(NetBoxModelBulkEditForm):
|
class TenantBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
@ -67,12 +68,13 @@ class ContactGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
model = ContactGroup
|
model = ContactGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'description'),
|
FieldSet('parent', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('parent', 'description')
|
nullable_fields = ('parent', 'description', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class ContactRoleBulkEditForm(NetBoxModelBulkEditForm):
|
class ContactRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
@ -90,8 +92,13 @@ class ContactRoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
|
|
||||||
class ContactBulkEditForm(NetBoxModelBulkEditForm):
|
class ContactBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
group = DynamicModelChoiceField(
|
add_groups = DynamicModelMultipleChoiceField(
|
||||||
label=_('Group'),
|
label=_('Add groups'),
|
||||||
|
queryset=ContactGroup.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
remove_groups = DynamicModelMultipleChoiceField(
|
||||||
|
label=_('Remove groups'),
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@ -127,9 +134,13 @@ class ContactBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Contact
|
model = Contact
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('group', 'title', 'phone', 'email', 'address', 'link', 'description'),
|
FieldSet('title', 'phone', 'email', 'address', 'link', 'description'),
|
||||||
|
FieldSet('add_groups', 'remove_groups', name=_('Groups')),
|
||||||
|
)
|
||||||
|
|
||||||
|
nullable_fields = (
|
||||||
|
'add_groups', 'remove_groups', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments'
|
||||||
)
|
)
|
||||||
nullable_fields = ('group', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments')
|
|
||||||
|
|
||||||
|
|
||||||
class ContactAssignmentBulkEditForm(NetBoxModelBulkEditForm):
|
class ContactAssignmentBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import *
|
from tenancy.models import *
|
||||||
from utilities.forms.fields import CSVContentTypeField, CSVModelChoiceField, SlugField
|
from utilities.forms.fields import CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ContactAssignmentImportForm',
|
'ContactAssignmentImportForm',
|
||||||
@ -31,7 +31,7 @@ class TenantGroupImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
fields = ('name', 'slug', 'parent', 'description', 'tags', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class TenantImportForm(NetBoxModelImportForm):
|
class TenantImportForm(NetBoxModelImportForm):
|
||||||
@ -65,7 +65,7 @@ class ContactGroupImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContactGroup
|
model = ContactGroup
|
||||||
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
fields = ('name', 'slug', 'parent', 'description', 'tags', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class ContactRoleImportForm(NetBoxModelImportForm):
|
class ContactRoleImportForm(NetBoxModelImportForm):
|
||||||
@ -77,17 +77,16 @@ class ContactRoleImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
|
|
||||||
class ContactImportForm(NetBoxModelImportForm):
|
class ContactImportForm(NetBoxModelImportForm):
|
||||||
group = CSVModelChoiceField(
|
groups = CSVModelMultipleChoiceField(
|
||||||
label=_('Group'),
|
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text=_('Assigned group')
|
help_text=_('Group names separated by commas, encased with double quotes (e.g. "Group 1,Group 2")')
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Contact
|
model = Contact
|
||||||
fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'group', 'description', 'comments', 'tags')
|
fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'groups', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ContactAssignmentImportForm(NetBoxModelImportForm):
|
class ContactAssignmentImportForm(NetBoxModelImportForm):
|
||||||
|
@ -75,7 +75,7 @@ class ContactFilterForm(NetBoxModelFilterSetForm):
|
|||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
null_option='None',
|
null_option='None',
|
||||||
label=_('Group')
|
label=_('Groups')
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from tenancy.models import *
|
from tenancy.models import *
|
||||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, SlugField
|
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
|
||||||
from utilities.forms.rendering import FieldSet, ObjectAttribute
|
from utilities.forms.rendering import FieldSet, ObjectAttribute
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -27,6 +27,7 @@ class TenantGroupForm(NetBoxModelForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Tenant Group')),
|
FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Tenant Group')),
|
||||||
@ -35,7 +36,7 @@ class TenantGroupForm(NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
fields = [
|
fields = [
|
||||||
'parent', 'name', 'slug', 'description', 'tags',
|
'parent', 'name', 'slug', 'description', 'tags', 'comments'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ class ContactGroupForm(NetBoxModelForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Contact Group')),
|
FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Contact Group')),
|
||||||
@ -77,7 +79,7 @@ class ContactGroupForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContactGroup
|
model = ContactGroup
|
||||||
fields = ('parent', 'name', 'slug', 'description', 'tags')
|
fields = ('parent', 'name', 'slug', 'description', 'tags', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class ContactRoleForm(NetBoxModelForm):
|
class ContactRoleForm(NetBoxModelForm):
|
||||||
@ -93,8 +95,8 @@ class ContactRoleForm(NetBoxModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class ContactForm(NetBoxModelForm):
|
class ContactForm(NetBoxModelForm):
|
||||||
group = DynamicModelChoiceField(
|
groups = DynamicModelMultipleChoiceField(
|
||||||
label=_('Group'),
|
label=_('Groups'),
|
||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@ -102,7 +104,7 @@ class ContactForm(NetBoxModelForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'group', 'name', 'title', 'phone', 'email', 'address', 'link', 'description', 'tags',
|
'groups', 'name', 'title', 'phone', 'email', 'address', 'link', 'description', 'tags',
|
||||||
name=_('Contact')
|
name=_('Contact')
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -110,7 +112,7 @@ class ContactForm(NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Contact
|
model = Contact
|
||||||
fields = (
|
fields = (
|
||||||
'group', 'name', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments', 'tags',
|
'groups', 'name', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
widgets = {
|
widgets = {
|
||||||
'address': forms.Textarea(attrs={'rows': 3}),
|
'address': forms.Textarea(attrs={'rows': 3}),
|
||||||
@ -123,7 +125,7 @@ class ContactAssignmentForm(NetBoxModelForm):
|
|||||||
queryset=ContactGroup.objects.all(),
|
queryset=ContactGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
initial_params={
|
initial_params={
|
||||||
'contacts': '$contact'
|
'contact': '$contact'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
contact = DynamicModelChoiceField(
|
contact = DynamicModelChoiceField(
|
||||||
|
@ -97,7 +97,7 @@ class TenantGroupType(OrganizationalObjectType):
|
|||||||
|
|
||||||
@strawberry_django.type(models.Contact, fields='__all__', filters=ContactFilter)
|
@strawberry_django.type(models.Contact, fields='__all__', filters=ContactFilter)
|
||||||
class ContactType(ContactAssignmentsMixin, NetBoxObjectType):
|
class ContactType(ContactAssignmentsMixin, NetBoxObjectType):
|
||||||
group: Annotated['ContactGroupType', strawberry.lazy('tenancy.graphql.types')] | None
|
groups: List[Annotated['ContactGroupType', strawberry.lazy('tenancy.graphql.types')]]
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(models.ContactRole, fields='__all__', filters=ContactRoleFilter)
|
@strawberry_django.type(models.ContactRole, fields='__all__', filters=ContactRoleFilter)
|
||||||
|
68
netbox/tenancy/migrations/0018_contact_groups.py
Normal file
68
netbox/tenancy/migrations/0018_contact_groups.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_contact_groups(apps, schema_editor):
|
||||||
|
Contacts = apps.get_model('tenancy', 'Contact')
|
||||||
|
|
||||||
|
qs = Contacts.objects.filter(group__isnull=False)
|
||||||
|
for contact in qs:
|
||||||
|
contact.groups.add(contact.group)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tenancy', '0017_natural_ordering'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContactGroupMembership',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'contact group membership',
|
||||||
|
'verbose_name_plural': 'contact group memberships',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveConstraint(
|
||||||
|
model_name='contact',
|
||||||
|
name='tenancy_contact_unique_group_name',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contactgroupmembership',
|
||||||
|
name='contact',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, related_name='+', to='tenancy.contact'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contactgroupmembership',
|
||||||
|
name='group',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, related_name='+', to='tenancy.contactgroup'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contact',
|
||||||
|
name='groups',
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name='contacts',
|
||||||
|
related_query_name='contact',
|
||||||
|
through='tenancy.ContactGroupMembership',
|
||||||
|
to='tenancy.contactgroup',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='contactgroupmembership',
|
||||||
|
constraint=models.UniqueConstraint(fields=('group', 'contact'), name='unique_group_name'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(code=migrate_contact_groups, reverse_code=migrations.RunPython.noop),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='contact',
|
||||||
|
name='group',
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,21 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tenancy', '0018_contact_groups'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contactgroup',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tenantgroup',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
]
|
@ -13,6 +13,7 @@ __all__ = (
|
|||||||
'ContactAssignment',
|
'ContactAssignment',
|
||||||
'Contact',
|
'Contact',
|
||||||
'ContactGroup',
|
'ContactGroup',
|
||||||
|
'ContactGroupMembership',
|
||||||
'ContactRole',
|
'ContactRole',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,12 +48,12 @@ class Contact(PrimaryModel):
|
|||||||
"""
|
"""
|
||||||
Contact information for a particular object(s) in NetBox.
|
Contact information for a particular object(s) in NetBox.
|
||||||
"""
|
"""
|
||||||
group = models.ForeignKey(
|
groups = models.ManyToManyField(
|
||||||
to='tenancy.ContactGroup',
|
to='tenancy.ContactGroup',
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
related_name='contacts',
|
related_name='contacts',
|
||||||
blank=True,
|
through='tenancy.ContactGroupMembership',
|
||||||
null=True
|
related_query_name='contact',
|
||||||
|
blank=True
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
verbose_name=_('name'),
|
||||||
@ -84,17 +85,11 @@ class Contact(PrimaryModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'group', 'name', 'title', 'phone', 'email', 'address', 'link',
|
'groups', 'name', 'title', 'phone', 'email', 'address', 'link',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
constraints = (
|
|
||||||
models.UniqueConstraint(
|
|
||||||
fields=('group', 'name'),
|
|
||||||
name='%(app_label)s_%(class)s_unique_group_name'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
verbose_name = _('contact')
|
verbose_name = _('contact')
|
||||||
verbose_name_plural = _('contacts')
|
verbose_name_plural = _('contacts')
|
||||||
|
|
||||||
@ -102,6 +97,18 @@ class Contact(PrimaryModel):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class ContactGroupMembership(models.Model):
|
||||||
|
group = models.ForeignKey(ContactGroup, related_name="+", on_delete=models.CASCADE)
|
||||||
|
contact = models.ForeignKey(Contact, related_name="+", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=['group', 'contact'], name='unique_group_name')
|
||||||
|
]
|
||||||
|
verbose_name = _('contact group membership')
|
||||||
|
verbose_name_plural = _('contact group memberships')
|
||||||
|
|
||||||
|
|
||||||
class ContactAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
|
class ContactAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
|
||||||
object_type = models.ForeignKey(
|
object_type = models.ForeignKey(
|
||||||
to='contenttypes.ContentType',
|
to='contenttypes.ContentType',
|
||||||
|
@ -25,6 +25,7 @@ class ContactGroupIndex(SearchIndex):
|
|||||||
('name', 100),
|
('name', 100),
|
||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('description',)
|
display_attrs = ('description',)
|
||||||
|
|
||||||
@ -59,5 +60,6 @@ class TenantGroupIndex(SearchIndex):
|
|||||||
('name', 100),
|
('name', 100),
|
||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('description',)
|
display_attrs = ('description',)
|
||||||
|
@ -27,11 +27,15 @@ class ContactGroupTable(NetBoxTable):
|
|||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='tenancy:contactgroup_list'
|
url_name='tenancy:contactgroup_list'
|
||||||
)
|
)
|
||||||
|
comments = columns.MarkdownColumn(
|
||||||
|
verbose_name=_('Comments'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = ContactGroup
|
model = ContactGroup
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'contact_count', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions',
|
'pk', 'name', 'contact_count', 'description', 'comments', 'slug', 'tags', 'created',
|
||||||
|
'last_updated', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'contact_count', 'description')
|
default_columns = ('pk', 'name', 'contact_count', 'description')
|
||||||
|
|
||||||
@ -56,9 +60,9 @@ class ContactTable(NetBoxTable):
|
|||||||
verbose_name=_('Name'),
|
verbose_name=_('Name'),
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
group = tables.Column(
|
groups = columns.ManyToManyColumn(
|
||||||
verbose_name=_('Group'),
|
verbose_name=_('Groups'),
|
||||||
linkify=True
|
linkify_item=('tenancy:contactgroup', {'pk': tables.A('pk')})
|
||||||
)
|
)
|
||||||
phone = tables.Column(
|
phone = tables.Column(
|
||||||
verbose_name=_('Phone'),
|
verbose_name=_('Phone'),
|
||||||
@ -79,10 +83,10 @@ class ContactTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Contact
|
model = Contact
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'group', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments',
|
'pk', 'name', 'groups', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments',
|
||||||
'assignment_count', 'tags', 'created', 'last_updated',
|
'assignment_count', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'group', 'assignment_count', 'title', 'phone', 'email')
|
default_columns = ('pk', 'name', 'groups', 'assignment_count', 'title', 'phone', 'email')
|
||||||
|
|
||||||
|
|
||||||
class ContactAssignmentTable(NetBoxTable):
|
class ContactAssignmentTable(NetBoxTable):
|
||||||
|
@ -24,11 +24,15 @@ class TenantGroupTable(NetBoxTable):
|
|||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='tenancy:tenantgroup_list'
|
url_name='tenancy:tenantgroup_list'
|
||||||
)
|
)
|
||||||
|
comments = columns.MarkdownColumn(
|
||||||
|
verbose_name=_('Comments'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'tenant_count', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions',
|
'pk', 'id', 'name', 'tenant_count', 'description', 'comments', 'slug', 'tags', 'created',
|
||||||
|
'last_updated', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'tenant_count', 'description')
|
default_columns = ('pk', 'name', 'tenant_count', 'description')
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ class TenantGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
brief_fields = ['_depth', 'description', 'display', 'id', 'name', 'slug', 'tenant_count', 'url']
|
brief_fields = ['_depth', 'description', 'display', 'id', 'name', 'slug', 'tenant_count', 'url']
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'New Comment',
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -28,12 +29,17 @@ class TenantGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
parent_tenant_groups = (
|
parent_tenant_groups = (
|
||||||
TenantGroup.objects.create(name='Parent Tenant Group 1', slug='parent-tenant-group-1'),
|
TenantGroup.objects.create(name='Parent Tenant Group 1', slug='parent-tenant-group-1'),
|
||||||
TenantGroup.objects.create(name='Parent Tenant Group 2', slug='parent-tenant-group-2'),
|
TenantGroup.objects.create(
|
||||||
|
name='Parent Tenant Group 2', slug='parent-tenant-group-2', comments='Parent Group 2 comment',
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
TenantGroup.objects.create(name='Tenant Group 1', slug='tenant-group-1', parent=parent_tenant_groups[0])
|
TenantGroup.objects.create(name='Tenant Group 1', slug='tenant-group-1', parent=parent_tenant_groups[0])
|
||||||
TenantGroup.objects.create(name='Tenant Group 2', slug='tenant-group-2', parent=parent_tenant_groups[0])
|
TenantGroup.objects.create(name='Tenant Group 2', slug='tenant-group-2', parent=parent_tenant_groups[0])
|
||||||
TenantGroup.objects.create(name='Tenant Group 3', slug='tenant-group-3', parent=parent_tenant_groups[0])
|
TenantGroup.objects.create(
|
||||||
|
name='Tenant Group 3', slug='tenant-group-3', parent=parent_tenant_groups[0],
|
||||||
|
comments='Tenant Group 3 comment'
|
||||||
|
)
|
||||||
|
|
||||||
cls.create_data = [
|
cls.create_data = [
|
||||||
{
|
{
|
||||||
@ -50,6 +56,7 @@ class TenantGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'name': 'Tenant Group 6',
|
'name': 'Tenant Group 6',
|
||||||
'slug': 'tenant-group-6',
|
'slug': 'tenant-group-6',
|
||||||
'parent': parent_tenant_groups[1].pk,
|
'parent': parent_tenant_groups[1].pk,
|
||||||
|
'comments': 'Tenant Group 6 comment',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -107,13 +114,18 @@ class ContactGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
parent_contact_groups = (
|
parent_contact_groups = (
|
||||||
ContactGroup.objects.create(name='Parent Contact Group 1', slug='parent-contact-group-1'),
|
ContactGroup.objects.create(
|
||||||
|
name='Parent Contact Group 1', slug='parent-contact-group-1', comments='Parent 1 comment'
|
||||||
|
),
|
||||||
ContactGroup.objects.create(name='Parent Contact Group 2', slug='parent-contact-group-2'),
|
ContactGroup.objects.create(name='Parent Contact Group 2', slug='parent-contact-group-2'),
|
||||||
)
|
)
|
||||||
|
|
||||||
ContactGroup.objects.create(name='Contact Group 1', slug='contact-group-1', parent=parent_contact_groups[0])
|
ContactGroup.objects.create(name='Contact Group 1', slug='contact-group-1', parent=parent_contact_groups[0])
|
||||||
ContactGroup.objects.create(name='Contact Group 2', slug='contact-group-2', parent=parent_contact_groups[0])
|
ContactGroup.objects.create(name='Contact Group 2', slug='contact-group-2', parent=parent_contact_groups[0])
|
||||||
ContactGroup.objects.create(name='Contact Group 3', slug='contact-group-3', parent=parent_contact_groups[0])
|
ContactGroup.objects.create(
|
||||||
|
name='Contact Group 3', slug='contact-group-3', parent=parent_contact_groups[0],
|
||||||
|
comments='Child Group 3 comment',
|
||||||
|
)
|
||||||
|
|
||||||
cls.create_data = [
|
cls.create_data = [
|
||||||
{
|
{
|
||||||
@ -125,11 +137,13 @@ class ContactGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'name': 'Contact Group 5',
|
'name': 'Contact Group 5',
|
||||||
'slug': 'contact-group-5',
|
'slug': 'contact-group-5',
|
||||||
'parent': parent_contact_groups[1].pk,
|
'parent': parent_contact_groups[1].pk,
|
||||||
|
'comments': '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Contact Group 6',
|
'name': 'Contact Group 6',
|
||||||
'slug': 'contact-group-6',
|
'slug': 'contact-group-6',
|
||||||
'parent': parent_contact_groups[1].pk,
|
'parent': parent_contact_groups[1].pk,
|
||||||
|
'comments': 'Child Group 6 comment',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -170,7 +184,7 @@ class ContactTest(APIViewTestCases.APIViewTestCase):
|
|||||||
model = Contact
|
model = Contact
|
||||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'group': None,
|
'groups': [],
|
||||||
'comments': 'New comments',
|
'comments': 'New comments',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,20 +197,22 @@ class ContactTest(APIViewTestCases.APIViewTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
contacts = (
|
contacts = (
|
||||||
Contact(name='Contact 1', group=contact_groups[0]),
|
Contact(name='Contact 1'),
|
||||||
Contact(name='Contact 2', group=contact_groups[0]),
|
Contact(name='Contact 2'),
|
||||||
Contact(name='Contact 3', group=contact_groups[0]),
|
Contact(name='Contact 3'),
|
||||||
)
|
)
|
||||||
Contact.objects.bulk_create(contacts)
|
Contact.objects.bulk_create(contacts)
|
||||||
|
contacts[0].groups.add(contact_groups[0])
|
||||||
|
contacts[1].groups.add(contact_groups[0])
|
||||||
|
contacts[2].groups.add(contact_groups[0])
|
||||||
|
|
||||||
cls.create_data = [
|
cls.create_data = [
|
||||||
{
|
{
|
||||||
'name': 'Contact 4',
|
'name': 'Contact 4',
|
||||||
'group': contact_groups[1].pk,
|
'groups': [contact_groups[1].pk],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Contact 5',
|
'name': 'Contact 5',
|
||||||
'group': contact_groups[1].pk,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Contact 6',
|
'name': 'Contact 6',
|
||||||
|
@ -16,7 +16,7 @@ class TenantGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
|
|
||||||
parent_tenant_groups = (
|
parent_tenant_groups = (
|
||||||
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
|
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
|
||||||
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
|
TenantGroup(name='Tenant Group 2', slug='tenant-group-2', comments='Parent group 2 comment'),
|
||||||
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
|
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
|
||||||
)
|
)
|
||||||
for tenant_group in parent_tenant_groups:
|
for tenant_group in parent_tenant_groups:
|
||||||
@ -27,7 +27,8 @@ class TenantGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
name='Tenant Group 1A',
|
name='Tenant Group 1A',
|
||||||
slug='tenant-group-1a',
|
slug='tenant-group-1a',
|
||||||
parent=parent_tenant_groups[0],
|
parent=parent_tenant_groups[0],
|
||||||
description='foobar1'
|
description='foobar1',
|
||||||
|
comments='Tenant Group 1A comment',
|
||||||
),
|
),
|
||||||
TenantGroup(
|
TenantGroup(
|
||||||
name='Tenant Group 2A',
|
name='Tenant Group 2A',
|
||||||
@ -48,7 +49,10 @@ class TenantGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
child_tenant_groups = (
|
child_tenant_groups = (
|
||||||
TenantGroup(name='Tenant Group 1A1', slug='tenant-group-1a1', parent=tenant_groups[0]),
|
TenantGroup(name='Tenant Group 1A1', slug='tenant-group-1a1', parent=tenant_groups[0]),
|
||||||
TenantGroup(name='Tenant Group 2A1', slug='tenant-group-2a1', parent=tenant_groups[1]),
|
TenantGroup(name='Tenant Group 2A1', slug='tenant-group-2a1', parent=tenant_groups[1]),
|
||||||
TenantGroup(name='Tenant Group 3A1', slug='tenant-group-3a1', parent=tenant_groups[2]),
|
TenantGroup(
|
||||||
|
name='Tenant Group 3A1', slug='tenant-group-3a1', parent=tenant_groups[2],
|
||||||
|
comments='Tenant Group 3A1 comment',
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for tenant_group in child_tenant_groups:
|
for tenant_group in child_tenant_groups:
|
||||||
tenant_group.save()
|
tenant_group.save()
|
||||||
@ -57,6 +61,13 @@ class TenantGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_q_comments(self):
|
||||||
|
params = {'q': 'parent'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
params = {'q': 'comment'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Tenant Group 1', 'Tenant Group 2']}
|
params = {'name': ['Tenant Group 1', 'Tenant Group 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -139,7 +150,7 @@ class ContactGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
|
|
||||||
parent_contact_groups = (
|
parent_contact_groups = (
|
||||||
ContactGroup(name='Contact Group 1', slug='contact-group-1'),
|
ContactGroup(name='Contact Group 1', slug='contact-group-1'),
|
||||||
ContactGroup(name='Contact Group 2', slug='contact-group-2'),
|
ContactGroup(name='Contact Group 2', slug='contact-group-2', comments='Parent group 2'),
|
||||||
ContactGroup(name='Contact Group 3', slug='contact-group-3'),
|
ContactGroup(name='Contact Group 3', slug='contact-group-3'),
|
||||||
)
|
)
|
||||||
for contact_group in parent_contact_groups:
|
for contact_group in parent_contact_groups:
|
||||||
@ -162,14 +173,18 @@ class ContactGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
name='Contact Group 3A',
|
name='Contact Group 3A',
|
||||||
slug='contact-group-3a',
|
slug='contact-group-3a',
|
||||||
parent=parent_contact_groups[2],
|
parent=parent_contact_groups[2],
|
||||||
description='foobar3'
|
description='foobar3',
|
||||||
|
comments='Contact Group 3A comment, not a parent',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for contact_group in contact_groups:
|
for contact_group in contact_groups:
|
||||||
contact_group.save()
|
contact_group.save()
|
||||||
|
|
||||||
child_contact_groups = (
|
child_contact_groups = (
|
||||||
ContactGroup(name='Contact Group 1A1', slug='contact-group-1a1', parent=contact_groups[0]),
|
ContactGroup(
|
||||||
|
name='Contact Group 1A1', slug='contact-group-1a1', parent=contact_groups[0],
|
||||||
|
comments='Contact Group 1A1 comment',
|
||||||
|
),
|
||||||
ContactGroup(name='Contact Group 2A1', slug='contact-group-2a1', parent=contact_groups[1]),
|
ContactGroup(name='Contact Group 2A1', slug='contact-group-2a1', parent=contact_groups[1]),
|
||||||
ContactGroup(name='Contact Group 3A1', slug='contact-group-3a1', parent=contact_groups[2]),
|
ContactGroup(name='Contact Group 3A1', slug='contact-group-3a1', parent=contact_groups[2]),
|
||||||
)
|
)
|
||||||
@ -180,6 +195,13 @@ class ContactGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_q_comments(self):
|
||||||
|
params = {'q': 'parent'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
params = {'q': '1A1'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Contact Group 1', 'Contact Group 2']}
|
params = {'name': ['Contact Group 1', 'Contact Group 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -241,6 +263,7 @@ class ContactRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class ContactTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ContactTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Contact.objects.all()
|
queryset = Contact.objects.all()
|
||||||
filterset = ContactFilterSet
|
filterset = ContactFilterSet
|
||||||
|
ignore_fields = ('groups',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -254,11 +277,14 @@ class ContactTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
contactgroup.save()
|
contactgroup.save()
|
||||||
|
|
||||||
contacts = (
|
contacts = (
|
||||||
Contact(name='Contact 1', group=contact_groups[0], description='foobar1'),
|
Contact(name='Contact 1', description='foobar1'),
|
||||||
Contact(name='Contact 2', group=contact_groups[1], description='foobar2'),
|
Contact(name='Contact 2', description='foobar2'),
|
||||||
Contact(name='Contact 3', group=contact_groups[2], description='foobar3'),
|
Contact(name='Contact 3', description='foobar3'),
|
||||||
)
|
)
|
||||||
Contact.objects.bulk_create(contacts)
|
Contact.objects.bulk_create(contacts)
|
||||||
|
contacts[0].groups.add(contact_groups[0])
|
||||||
|
contacts[1].groups.add(contact_groups[1])
|
||||||
|
contacts[2].groups.add(contact_groups[2])
|
||||||
|
|
||||||
def test_q(self):
|
def test_q(self):
|
||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
@ -311,11 +337,14 @@ class ContactAssignmentTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
ContactRole.objects.bulk_create(contact_roles)
|
ContactRole.objects.bulk_create(contact_roles)
|
||||||
|
|
||||||
contacts = (
|
contacts = (
|
||||||
Contact(name='Contact 1', group=contact_groups[0]),
|
Contact(name='Contact 1'),
|
||||||
Contact(name='Contact 2', group=contact_groups[1]),
|
Contact(name='Contact 2'),
|
||||||
Contact(name='Contact 3', group=contact_groups[2]),
|
Contact(name='Contact 3'),
|
||||||
)
|
)
|
||||||
Contact.objects.bulk_create(contacts)
|
Contact.objects.bulk_create(contacts)
|
||||||
|
contacts[0].groups.add(contact_groups[0])
|
||||||
|
contacts[1].groups.add(contact_groups[1])
|
||||||
|
contacts[2].groups.add(contact_groups[2])
|
||||||
|
|
||||||
assignments = (
|
assignments = (
|
||||||
ContactAssignment(object=sites[0], contact=contacts[0], role=contact_roles[0]),
|
ContactAssignment(object=sites[0], contact=contacts[0], role=contact_roles[0]),
|
||||||
|
@ -15,7 +15,7 @@ class TenantGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
|
|
||||||
tenant_groups = (
|
tenant_groups = (
|
||||||
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
|
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
|
||||||
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
|
TenantGroup(name='Tenant Group 2', slug='tenant-group-2', comments='Tenant Group 2 comment'),
|
||||||
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
|
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
|
||||||
)
|
)
|
||||||
for tenanantgroup in tenant_groups:
|
for tenanantgroup in tenant_groups:
|
||||||
@ -28,24 +28,26 @@ class TenantGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
'slug': 'tenant-group-x',
|
'slug': 'tenant-group-x',
|
||||||
'description': 'A new tenant group',
|
'description': 'A new tenant group',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
|
'comments': 'Tenant Group X comment',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,slug,description",
|
"name,slug,description,comments",
|
||||||
"Tenant Group 4,tenant-group-4,Fourth tenant group",
|
"Tenant Group 4,tenant-group-4,Fourth tenant group,",
|
||||||
"Tenant Group 5,tenant-group-5,Fifth tenant group",
|
"Tenant Group 5,tenant-group-5,Fifth tenant group,",
|
||||||
"Tenant Group 6,tenant-group-6,Sixth tenant group",
|
"Tenant Group 6,tenant-group-6,Sixth tenant group,Sixth tenant group comment",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,name,description",
|
"id,name,description,comments",
|
||||||
f"{tenant_groups[0].pk},Tenant Group 7,Fourth tenant group7",
|
f"{tenant_groups[0].pk},Tenant Group 7,Fourth tenant group7,Group 7 comment",
|
||||||
f"{tenant_groups[1].pk},Tenant Group 8,Fifth tenant group8",
|
f"{tenant_groups[1].pk},Tenant Group 8,Fifth tenant group8,",
|
||||||
f"{tenant_groups[2].pk},Tenant Group 0,Sixth tenant group9",
|
f"{tenant_groups[2].pk},Tenant Group 0,Sixth tenant group9,",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'New comment',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -106,7 +108,7 @@ class ContactGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
contact_groups = (
|
contact_groups = (
|
||||||
ContactGroup(name='Contact Group 1', slug='contact-group-1'),
|
ContactGroup(name='Contact Group 1', slug='contact-group-1', comments='Comment 1'),
|
||||||
ContactGroup(name='Contact Group 2', slug='contact-group-2'),
|
ContactGroup(name='Contact Group 2', slug='contact-group-2'),
|
||||||
ContactGroup(name='Contact Group 3', slug='contact-group-3'),
|
ContactGroup(name='Contact Group 3', slug='contact-group-3'),
|
||||||
)
|
)
|
||||||
@ -120,24 +122,26 @@ class ContactGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
'slug': 'contact-group-x',
|
'slug': 'contact-group-x',
|
||||||
'description': 'A new contact group',
|
'description': 'A new contact group',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
|
'comments': 'Form data comment',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,slug,description",
|
"name,slug,description,comments",
|
||||||
"Contact Group 4,contact-group-4,Fourth contact group",
|
"Contact Group 4,contact-group-4,Fourth contact group,",
|
||||||
"Contact Group 5,contact-group-5,Fifth contact group",
|
"Contact Group 5,contact-group-5,Fifth contact group,Fifth comment",
|
||||||
"Contact Group 6,contact-group-6,Sixth contact group",
|
"Contact Group 6,contact-group-6,Sixth contact group,",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,name,description",
|
"id,name,description,comments",
|
||||||
f"{contact_groups[0].pk},Contact Group 7,Fourth contact group7",
|
f"{contact_groups[0].pk},Contact Group 7,Fourth contact group7,",
|
||||||
f"{contact_groups[1].pk},Contact Group 8,Fifth contact group8",
|
f"{contact_groups[1].pk},Contact Group 8,Fifth contact group8,Group 8 comment",
|
||||||
f"{contact_groups[2].pk},Contact Group 0,Sixth contact group9",
|
f"{contact_groups[2].pk},Contact Group 0,Sixth contact group9,",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'Bulk update comment',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -196,37 +200,40 @@ class ContactTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
contactgroup.save()
|
contactgroup.save()
|
||||||
|
|
||||||
contacts = (
|
contacts = (
|
||||||
Contact(name='Contact 1', group=contact_groups[0]),
|
Contact(name='Contact 1'),
|
||||||
Contact(name='Contact 2', group=contact_groups[0]),
|
Contact(name='Contact 2'),
|
||||||
Contact(name='Contact 3', group=contact_groups[0]),
|
Contact(name='Contact 3'),
|
||||||
)
|
)
|
||||||
Contact.objects.bulk_create(contacts)
|
Contact.objects.bulk_create(contacts)
|
||||||
|
contacts[0].groups.add(contact_groups[0])
|
||||||
|
contacts[1].groups.add(contact_groups[1])
|
||||||
|
|
||||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'name': 'Contact X',
|
'name': 'Contact X',
|
||||||
'group': contact_groups[1].pk,
|
'groups': [contact_groups[1].pk],
|
||||||
'comments': 'Some comments',
|
'comments': 'Some comments',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"group,name",
|
"name",
|
||||||
"Contact Group 1,Contact 4",
|
"groups",
|
||||||
"Contact Group 1,Contact 5",
|
"Contact 4",
|
||||||
"Contact Group 1,Contact 6",
|
"Contact 5",
|
||||||
|
"Contact 6",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,name,comments",
|
"id,name,groups,comments",
|
||||||
f"{contacts[0].pk},Contact Group 7,New comments 7",
|
f'{contacts[0].pk},Contact 7,"Contact Group 1,Contact Group 2",New comments 7',
|
||||||
f"{contacts[1].pk},Contact Group 8,New comments 8",
|
f'{contacts[1].pk},Contact 8,"Contact Group 1",New comments 8',
|
||||||
f"{contacts[2].pk},Contact Group 9,New comments 9",
|
f'{contacts[2].pk},Contact 9,"Contact Group 1",New comments 9',
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'group': contact_groups[1].pk,
|
'description': "New description",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ class ContactGroupListView(generic.ObjectListView):
|
|||||||
queryset = ContactGroup.objects.add_related_count(
|
queryset = ContactGroup.objects.add_related_count(
|
||||||
ContactGroup.objects.all(),
|
ContactGroup.objects.all(),
|
||||||
Contact,
|
Contact,
|
||||||
'group',
|
'groups',
|
||||||
'contact_count',
|
'contact_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
@ -214,7 +214,7 @@ class ContactGroupBulkEditView(generic.BulkEditView):
|
|||||||
queryset = ContactGroup.objects.add_related_count(
|
queryset = ContactGroup.objects.add_related_count(
|
||||||
ContactGroup.objects.all(),
|
ContactGroup.objects.all(),
|
||||||
Contact,
|
Contact,
|
||||||
'group',
|
'groups',
|
||||||
'contact_count',
|
'contact_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
@ -228,7 +228,7 @@ class ContactGroupBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = ContactGroup.objects.add_related_count(
|
queryset = ContactGroup.objects.add_related_count(
|
||||||
ContactGroup.objects.all(),
|
ContactGroup.objects.all(),
|
||||||
Contact,
|
Contact,
|
||||||
'group',
|
'groups',
|
||||||
'contact_count',
|
'contact_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
@ -337,6 +337,15 @@ class ContactBulkEditView(generic.BulkEditView):
|
|||||||
table = tables.ContactTable
|
table = tables.ContactTable
|
||||||
form = forms.ContactBulkEditForm
|
form = forms.ContactBulkEditForm
|
||||||
|
|
||||||
|
def post_save_operations(self, form, obj):
|
||||||
|
super().post_save_operations(form, obj)
|
||||||
|
|
||||||
|
# Add/remove groups
|
||||||
|
if form.cleaned_data.get('add_groups', None):
|
||||||
|
obj.groups.add(*form.cleaned_data['add_groups'])
|
||||||
|
if form.cleaned_data.get('remove_groups', None):
|
||||||
|
obj.groups.remove(*form.cleaned_data['remove_groups'])
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Contact, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Contact, 'bulk_delete', path='delete', detail=False)
|
||||||
class ContactBulkDeleteView(generic.BulkDeleteView):
|
class ContactBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
@ -144,8 +144,8 @@ class BaseFilterSetTests:
|
|||||||
# Check that the filter class is correct
|
# Check that the filter class is correct
|
||||||
filter = filters[filter_name]
|
filter = filters[filter_name]
|
||||||
if filter_class is not None:
|
if filter_class is not None:
|
||||||
self.assertIs(
|
self.assertIsInstance(
|
||||||
type(filter),
|
filter,
|
||||||
filter_class,
|
filter_class,
|
||||||
f"Invalid filter class {type(filter)} for {filter_name} (should be {filter_class})!"
|
f"Invalid filter class {type(filter)} for {filter_name} (should be {filter_class})!"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +26,7 @@ class WirelessLANGroupSerializer(NestedGroupModelSerializer):
|
|||||||
model = WirelessLANGroup
|
model = WirelessLANGroup
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated', 'wirelesslan_count', '_depth',
|
'created', 'last_updated', 'wirelesslan_count', 'comments', '_depth',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'wirelesslan_count', '_depth')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'wirelesslan_count', '_depth')
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from dcim.choices import LinkStatusChoices
|
|||||||
from dcim.base_filtersets import ScopedFilterSet
|
from dcim.base_filtersets import ScopedFilterSet
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
|
from netbox.filtersets import NestedGroupModelFilterSet, NetBoxModelFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import TreeNodeMultipleChoiceFilter
|
from utilities.filters import TreeNodeMultipleChoiceFilter
|
||||||
from .choices import *
|
from .choices import *
|
||||||
@ -18,7 +18,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANGroupFilterSet(OrganizationalModelFilterSet):
|
class WirelessLANGroupFilterSet(NestedGroupModelFilterSet):
|
||||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=WirelessLANGroup.objects.all()
|
queryset=WirelessLANGroup.objects.all()
|
||||||
)
|
)
|
||||||
|
@ -32,12 +32,13 @@ class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
model = WirelessLANGroup
|
model = WirelessLANGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'description'),
|
FieldSet('parent', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('parent', 'description')
|
nullable_fields = ('parent', 'description', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
|
class WirelessLANBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
|
||||||
|
@ -30,7 +30,7 @@ class WirelessLANGroupImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WirelessLANGroup
|
model = WirelessLANGroup
|
||||||
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
fields = ('name', 'slug', 'parent', 'description', 'tags', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANImportForm(ScopedImportForm, NetBoxModelImportForm):
|
class WirelessLANImportForm(ScopedImportForm, NetBoxModelImportForm):
|
||||||
|
@ -24,6 +24,7 @@ class WirelessLANGroupForm(NetBoxModelForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Wireless LAN Group')),
|
FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Wireless LAN Group')),
|
||||||
@ -32,7 +33,7 @@ class WirelessLANGroupForm(NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = WirelessLANGroup
|
model = WirelessLANGroup
|
||||||
fields = [
|
fields = [
|
||||||
'parent', 'name', 'slug', 'description', 'tags',
|
'parent', 'name', 'slug', 'description', 'tags', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
16
netbox/wireless/migrations/0014_wirelesslangroup_comments.py
Normal file
16
netbox/wireless/migrations/0014_wirelesslangroup_comments.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wireless', '0013_natural_ordering'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='wirelesslangroup',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
]
|
@ -21,6 +21,7 @@ class WirelessLANGroupIndex(SearchIndex):
|
|||||||
('name', 100),
|
('name', 100),
|
||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('description',)
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
@ -24,10 +24,12 @@ class WirelessLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'Wireless LAN Group 4',
|
'name': 'Wireless LAN Group 4',
|
||||||
'slug': 'wireless-lan-group-4',
|
'slug': 'wireless-lan-group-4',
|
||||||
|
'comments': '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Wireless LAN Group 5',
|
'name': 'Wireless LAN Group 5',
|
||||||
'slug': 'wireless-lan-group-5',
|
'slug': 'wireless-lan-group-5',
|
||||||
|
'comments': 'LAN Group 5 comment',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Wireless LAN Group 6',
|
'name': 'Wireless LAN Group 6',
|
||||||
@ -36,6 +38,7 @@ class WirelessLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
]
|
]
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'New comment',
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -21,7 +21,10 @@ class WirelessLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
parent_groups = (
|
parent_groups = (
|
||||||
WirelessLANGroup(name='Wireless LAN Group 1', slug='wireless-lan-group-1', description='A'),
|
WirelessLANGroup(name='Wireless LAN Group 1', slug='wireless-lan-group-1', description='A'),
|
||||||
WirelessLANGroup(name='Wireless LAN Group 2', slug='wireless-lan-group-2', description='B'),
|
WirelessLANGroup(name='Wireless LAN Group 2', slug='wireless-lan-group-2', description='B'),
|
||||||
WirelessLANGroup(name='Wireless LAN Group 3', slug='wireless-lan-group-3', description='C'),
|
WirelessLANGroup(
|
||||||
|
name='Wireless LAN Group 3', slug='wireless-lan-group-3', description='C',
|
||||||
|
comments='Parent Group 3 comment',
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for group in parent_groups:
|
for group in parent_groups:
|
||||||
group.save()
|
group.save()
|
||||||
@ -38,10 +41,15 @@ class WirelessLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
slug='wireless-lan-group-1b',
|
slug='wireless-lan-group-1b',
|
||||||
parent=parent_groups[0],
|
parent=parent_groups[0],
|
||||||
description='foobar2',
|
description='foobar2',
|
||||||
|
comments='Child Group 1B comment',
|
||||||
),
|
),
|
||||||
WirelessLANGroup(name='Wireless LAN Group 2A', slug='wireless-lan-group-2a', parent=parent_groups[1]),
|
WirelessLANGroup(name='Wireless LAN Group 2A', slug='wireless-lan-group-2a', parent=parent_groups[1]),
|
||||||
WirelessLANGroup(name='Wireless LAN Group 2B', slug='wireless-lan-group-2b', parent=parent_groups[1]),
|
WirelessLANGroup(name='Wireless LAN Group 2B', slug='wireless-lan-group-2b', parent=parent_groups[1]),
|
||||||
WirelessLANGroup(name='Wireless LAN Group 3A', slug='wireless-lan-group-3a', parent=parent_groups[2]),
|
WirelessLANGroup(
|
||||||
|
name='Wireless LAN Group 3A', slug='wireless-lan-group-3a', parent=parent_groups[2],
|
||||||
|
comments='Wireless LAN Group 3A comment',
|
||||||
|
|
||||||
|
),
|
||||||
WirelessLANGroup(name='Wireless LAN Group 3B', slug='wireless-lan-group-3b', parent=parent_groups[2]),
|
WirelessLANGroup(name='Wireless LAN Group 3B', slug='wireless-lan-group-3b', parent=parent_groups[2]),
|
||||||
)
|
)
|
||||||
for group in groups:
|
for group in groups:
|
||||||
@ -62,6 +70,13 @@ class WirelessLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_q_comments(self):
|
||||||
|
params = {'q': 'parent'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
params = {'q': 'comment'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Wireless LAN Group 1', 'Wireless LAN Group 2']}
|
params = {'name': ['Wireless LAN Group 1', 'Wireless LAN Group 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -16,7 +16,9 @@ class WirelessLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
|
|
||||||
groups = (
|
groups = (
|
||||||
WirelessLANGroup(name='Wireless LAN Group 1', slug='wireless-lan-group-1'),
|
WirelessLANGroup(name='Wireless LAN Group 1', slug='wireless-lan-group-1'),
|
||||||
WirelessLANGroup(name='Wireless LAN Group 2', slug='wireless-lan-group-2'),
|
WirelessLANGroup(
|
||||||
|
name='Wireless LAN Group 2', slug='wireless-lan-group-2', comments='LAN Group 2 comment',
|
||||||
|
),
|
||||||
WirelessLANGroup(name='Wireless LAN Group 3', slug='wireless-lan-group-3'),
|
WirelessLANGroup(name='Wireless LAN Group 3', slug='wireless-lan-group-3'),
|
||||||
)
|
)
|
||||||
for group in groups:
|
for group in groups:
|
||||||
@ -30,24 +32,26 @@ class WirelessLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
'parent': groups[2].pk,
|
'parent': groups[2].pk,
|
||||||
'description': 'A new wireless LAN group',
|
'description': 'A new wireless LAN group',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
|
'comments': 'LAN Group X comment',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,slug,description",
|
"name,slug,description,comments",
|
||||||
"Wireless LAN Group 4,wireless-lan-group-4,Fourth wireless LAN group",
|
"Wireless LAN Group 4,wireless-lan-group-4,Fourth wireless LAN group,",
|
||||||
"Wireless LAN Group 5,wireless-lan-group-5,Fifth wireless LAN group",
|
"Wireless LAN Group 5,wireless-lan-group-5,Fifth wireless LAN group,",
|
||||||
"Wireless LAN Group 6,wireless-lan-group-6,Sixth wireless LAN group",
|
"Wireless LAN Group 6,wireless-lan-group-6,Sixth wireless LAN group,LAN Group 6 comment",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,name,description",
|
"id,name,description,comments",
|
||||||
f"{groups[0].pk},Wireless LAN Group 7,Fourth wireless LAN group7",
|
f"{groups[0].pk},Wireless LAN Group 7,Fourth wireless LAN group7,Group 7 comment",
|
||||||
f"{groups[1].pk},Wireless LAN Group 8,Fifth wireless LAN group8",
|
f"{groups[1].pk},Wireless LAN Group 8,Fifth wireless LAN group8,",
|
||||||
f"{groups[2].pk},Wireless LAN Group 0,Sixth wireless LAN group9",
|
f"{groups[2].pk},Wireless LAN Group 0,Sixth wireless LAN group9,",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'comments': 'New Comments',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user