Skip to content

Commit

Permalink
Master (#3)
Browse files Browse the repository at this point in the history
* Fixes netbox-community#1765: Improved rendering of null options for model choice fields in filter forms

* Fixes netbox-community#1802: Typo in ldap.md

* Fixes netbox-community#1621: Tweaked LLDP interface name evaluation logic

* Fixes netbox-community#1807: Populate VRF from parent when creating a new prefix

* Fixes netbox-community#1809: Populate tenant assignment from parent when creating a new prefix

* Closes netbox-community#1824: Add virtual machine count to platforms list

* Fixes netbox-community#1818: InventoryItem API serializer no longer requires specifying a null value for items with no parent

* Evaluate device_id rather than pulling entire device (DB optimization)

* added statement and exaple for using ForeignKey ID's in write actions

* Closes netbox-community#1828: Added warning about media directory permissions

* fixed duplicate api docs example and grammar

* Closes netbox-community#1835: Consistent position of previous/next rack buttons

* Fixes netbox-community#1845: Correct display of VMs in list with no role assigned

* Closes netbox-community#1406: Display tenant description as title text in object tables

* Closes netbox-community#1366: Enable searching for regions by name/slug

* Cleaned up InventoryItem add/edit/delete links and return URL

* Closes netbox-community#1073: Include prefixes/IPs from all VRFs when viewing the children of a container prefix in the global table

* Closes netbox-community#144: Implemented list and bulk edit/delete views for InventoryItems

* Added report results to the home page

* Fixes netbox-community#1850: Fix TypeError when attempting IP address import if only unnamed devices exist

* Added warning message about automatically deleting child inventory items

* Release v.2.2.9
  • Loading branch information
funzoneq committed Feb 6, 2018
1 parent d850a96 commit cb0eac6
Show file tree
Hide file tree
Showing 41 changed files with 368 additions and 122 deletions.
7 changes: 4 additions & 3 deletions docs/api/examples.md
Expand Up @@ -82,15 +82,15 @@ $ curl -H "Accept: application/json; indent=4" http://localhost/api/dcim/sites/6

### Creating a new site

Send a `POST` request to the site list endpoint with token authentication and JSON-formatted data. Only mandatory fields are required.
Send a `POST` request to the site list endpoint with token authentication and JSON-formatted data. Only mandatory fields are required. This example includes one non required field, "region."

```
$ curl -X POST -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/ --data '{"name": "My New Site", "slug": "my-new-site"}'
$ curl -X POST -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/ --data '{"name": "My New Site", "slug": "my-new-site", "region": 5}'
{
"id": 16,
"name": "My New Site",
"slug": "my-new-site",
"region": null,
"region": 5,
"tenant": null,
"facility": "",
"asn": null,
Expand All @@ -102,6 +102,7 @@ $ curl -X POST -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0
"comments": ""
}
```
Note that in this example we are creating a site bound to a region with the ID of 5. For write API actions (`POST`, `PUT`, and `PATCH`) the integer ID value is used for `ForeignKey` (related model) relationships, instead of the nested representation that is used in the `GET` (list) action.

### Modify an existing site

Expand Down
2 changes: 1 addition & 1 deletion docs/api/overview.md
Expand Up @@ -104,7 +104,7 @@ The base serializer is used to represent the default view of a model. This inclu
}
```

Related objects (e.g. `ForeignKey` fields) are represented using a nested serializer. A nested serializer provides a minimal representation of an object, including only its URL and enough information to construct its name.
Related objects (e.g. `ForeignKey` fields) are represented using a nested serializer. A nested serializer provides a minimal representation of an object, including only its URL and enough information to construct its name. When performing write api actions (`POST`, `PUT`, and `PATCH`), any `ForeignKey` relationships do not use the nested serializer, instead you will pass just the integer ID of the related model.

When a base serializer includes one or more nested serializers, the hierarchical structure precludes it from being used for write operations. Thus, a flat representation of an object may be provided using a writable serializer. This serializer includes only raw database values and is not typically used for retrieval, except as part of the response to the creation or updating of an object.

Expand Down
2 changes: 1 addition & 1 deletion docs/installation/ldap.md
Expand Up @@ -81,7 +81,7 @@ AUTH_LDAP_USER_ATTR_MAP = {

# User Groups for Permissions
!!! info
When using Microsoft Active Directory, Support for nested Groups can be activated by using `GroupOfNamesType()` instead of `NestedGroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`.
When using Microsoft Active Directory, support for nested groups can be activated by using `NestedGroupOfNamesType()` instead of `GroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`.

```python
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
Expand Down
7 changes: 7 additions & 0 deletions docs/installation/netbox.md
Expand Up @@ -88,6 +88,13 @@ Resolving deltas: 100% (1495/1495), done.
Checking connectivity... done.
```

!!! warning
Ensure that the media directory (`/opt/netbox/netbox/media/` in this example) and all its subdirectories are writable by the user account as which NetBox runs. If the NetBox process does not have permission to write to this directory, attempts to upload files (e.g. image attachments) will fail. (The appropriate user account will vary by platform.)

```
# chown -R netbox:netbox /opt/netbox/netbox/media/
```

## Install Python Packages

Install the required Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the system dependencies listed above.)
Expand Down
2 changes: 1 addition & 1 deletion netbox/circuits/forms.py
Expand Up @@ -174,7 +174,7 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
to_field_name='slug',
null_option=(0, 'None')
null_label='-- None --'
)
site = FilterChoiceField(
queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
Expand Down
3 changes: 2 additions & 1 deletion netbox/circuits/tables.py
Expand Up @@ -4,6 +4,7 @@
from django.utils.safestring import mark_safe
from django_tables2.utils import Accessor

from tenancy.tables import COL_TENANT
from utilities.tables import BaseTable, ToggleColumn
from .models import Circuit, CircuitType, Provider

Expand Down Expand Up @@ -75,7 +76,7 @@ class CircuitTable(BaseTable):
pk = ToggleColumn()
cid = tables.LinkColumn(verbose_name='ID')
provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
tenant = tables.TemplateColumn(template_code=COL_TENANT)
termination_a = CircuitTerminationColumn(orderable=False, verbose_name='A Side')
termination_z = CircuitTerminationColumn(orderable=False, verbose_name='Z Side')

Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/api/serializers.py
Expand Up @@ -733,6 +733,8 @@ class Meta:


class WritableInventoryItemSerializer(ValidatedModelSerializer):
# Provide a default value to satisfy UniqueTogetherValidator
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)

class Meta:
model = InventoryItem
Expand Down
31 changes: 30 additions & 1 deletion netbox/dcim/filters.py
Expand Up @@ -22,6 +22,10 @@


class RegionFilter(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
label='Parent region (ID)',
Expand All @@ -37,6 +41,15 @@ class Meta:
model = Region
fields = ['name', 'slug']

def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value) |
Q(slug__icontains=value)
)
return queryset.filter(qs_filter)


class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
Expand Down Expand Up @@ -600,6 +613,10 @@ class Meta:


class InventoryItemFilter(DeviceComponentFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItem.objects.all(),
label='Parent inventory item (ID)',
Expand All @@ -618,7 +635,19 @@ class InventoryItemFilter(DeviceComponentFilterSet):

class Meta:
model = InventoryItem
fields = ['name', 'part_id', 'serial', 'discovered']
fields = ['name', 'part_id', 'serial', 'asset_tag', 'discovered']

def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value) |
Q(part_id__icontains=value) |
Q(serial__iexact=value) |
Q(asset_tag__iexact=value) |
Q(description__icontains=value)
)
return queryset.filter(qs_filter)


class ConsoleConnectionFilter(django_filters.FilterSet):
Expand Down
65 changes: 57 additions & 8 deletions netbox/dcim/forms.py
Expand Up @@ -81,6 +81,11 @@ class Meta:
}


class RegionFilterForm(BootstrapMixin, forms.Form):
model = Site
q = forms.CharField(required=False, label='Search')


#
# Sites
#
Expand Down Expand Up @@ -163,7 +168,7 @@ class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('sites')),
to_field_name='slug',
null_option=(0, 'None')
null_label='-- None --'
)


Expand Down Expand Up @@ -359,17 +364,17 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
group_id = FilterChoiceField(
queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks')),
label='Rack group',
null_option=(0, 'None')
null_label='-- None --'
)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('racks')),
to_field_name='slug',
null_option=(0, 'None')
null_label='-- None --'
)
role = FilterChoiceField(
queryset=RackRole.objects.annotate(filter_count=Count('racks')),
to_field_name='slug',
null_option=(0, 'None')
null_label='-- None --'
)


Expand Down Expand Up @@ -411,7 +416,7 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form):
group_id = FilterChoiceField(
queryset=RackGroup.objects.select_related('site').annotate(filter_count=Count('racks__reservations')),
label='Rack group',
null_option=(0, 'None')
null_label='-- None --'
)


Expand Down Expand Up @@ -1031,7 +1036,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
rack_id = FilterChoiceField(
queryset=Rack.objects.annotate(filter_count=Count('devices')),
label='Rack',
null_option=(0, 'None'),
null_label='-- None --',
)
role = FilterChoiceField(
queryset=DeviceRole.objects.annotate(filter_count=Count('devices')),
Expand All @@ -1040,7 +1045,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('devices')),
to_field_name='slug',
null_option=(0, 'None'),
null_label='-- None --',
)
manufacturer_id = FilterChoiceField(queryset=Manufacturer.objects.all(), label='Manufacturer')
device_type_id = FilterChoiceField(
Expand All @@ -1052,7 +1057,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
platform = FilterChoiceField(
queryset=Platform.objects.annotate(filter_count=Count('devices')),
to_field_name='slug',
null_option=(0, 'None'),
null_label='-- None --',
)
status = forms.MultipleChoiceField(choices=device_status_choices, required=False)
mac_address = forms.CharField(required=False, label='MAC address')
Expand Down Expand Up @@ -1923,3 +1928,47 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = InventoryItem
fields = ['name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description']


class InventoryItemCSVForm(forms.ModelForm):
device = FlexibleModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name',
help_text='Device name or ID',
error_messages={
'invalid_choice': 'Device not found.',
}
)
manufacturer = forms.ModelChoiceField(
queryset=Manufacturer.objects.all(),
to_field_name='name',
required=False,
help_text='Manufacturer name',
error_messages={
'invalid_choice': 'Invalid manufacturer.',
}
)

class Meta:
model = InventoryItem
fields = ['device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description']


class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=InventoryItem.objects.all(), widget=forms.MultipleHiddenInput)
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
part_id = forms.CharField(max_length=50, required=False, label='Part ID')
description = forms.CharField(max_length=100, required=False)

class Meta:
nullable_fields = ['manufacturer', 'part_id', 'description']


class InventoryItemFilterForm(BootstrapMixin, forms.Form):
model = InventoryItem
q = forms.CharField(required=False, label='Search')
manufacturer = FilterChoiceField(
queryset=Manufacturer.objects.annotate(filter_count=Count('inventory_items')),
to_field_name='slug',
null_label='-- None --'
)
15 changes: 15 additions & 0 deletions netbox/dcim/models.py
Expand Up @@ -1452,9 +1452,24 @@ class InventoryItem(models.Model):
discovered = models.BooleanField(default=False, verbose_name='Discovered')
description = models.CharField(max_length=100, blank=True)

csv_headers = [
'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description',
]

class Meta:
ordering = ['device__id', 'parent__id', 'name']
unique_together = ['device', 'parent', 'name']

def __str__(self):
return self.name

def to_csv(self):
return csv_format([
self.device.name or '{' + self.device.pk + '}',
self.name,
self.manufacturer.name if self.manufacturer else None,
self.part_id,
self.serial,
self.asset_tag,
self.description
])

0 comments on commit cb0eac6

Please sign in to comment.