Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v1.6.1 #569

Merged
merged 26 commits into from
Sep 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0708942
Fixed csv reader to handle special characters
Aug 19, 2016
4fa536b
Post-release version bump
jeremystretch Sep 13, 2016
4406108
fixes permissions on docker-build.sh
Sep 13, 2016
5601be8
Merge pull request #551 from digitalocean/docker-build-perms
jeremystretch Sep 13, 2016
9eec975
change ldap.py to ldap_config.py
rfdrake Sep 8, 2016
9718895
add django-auth-ldap to Dockerfile
rfdrake Sep 9, 2016
824d2d8
Implemented FilterChoiceField and get_filter_choices() to reduce filt…
jeremystretch Sep 14, 2016
5e4fce2
Fixes #558: Update slug field when name is populated without a key press
jeremystretch Sep 15, 2016
2567412
Fixes #531: Order prefixes by VRF assignment
jeremystretch Sep 15, 2016
daadf7a
Fixes #557: Add 'global' choice to VRF filter for prefixes and IP add…
jeremystretch Sep 15, 2016
9dea565
Added 'none' options to filters for optional fields
jeremystretch Sep 15, 2016
2015d08
Merge pull request #555 from rfdrake/develop
jeremystretch Sep 16, 2016
2c7c0ce
Merge pull request #493 from stianvi/csv_reader_unicode
jeremystretch Sep 16, 2016
814a0e7
Tweak to #493
jeremystretch Sep 16, 2016
ce9d853
Closes #415: Added an expand/collapse toggle button to the prefix list
jeremystretch Sep 16, 2016
64326e7
Closes #552: Added a None filter option for custom select fields
jeremystretch Sep 16, 2016
513408f
Fixes #562: Fixed bulk interface creation
jeremystretch Sep 19, 2016
d0c92b4
Removed obsolete dependency
jeremystretch Sep 19, 2016
b10e29a
Closes #561: Make custom fields accessible from within export templates
jeremystretch Sep 19, 2016
687e68d
Fixes #564: Display custom fields for all applicable objects
jeremystretch Sep 19, 2016
e3f0a12
PEP8 fix
jeremystretch Sep 19, 2016
e618bf4
Reimplemented FilterChoiceField
jeremystretch Sep 20, 2016
6ccc624
Corrected PrefixFilterForm
jeremystretch Sep 20, 2016
b2684ae
status filter fields should not be required
jeremystretch Sep 20, 2016
0444ac7
Introduced NullableModelMultipleChoiceField to allow null filtering w…
jeremystretch Sep 20, 2016
75d8852
Release v1.6.1
jeremystretch Sep 21, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ WORKDIR /opt/netbox
ARG BRANCH=master
ARG URL=https://github.com/digitalocean/netbox.git
RUN git clone --depth 1 $URL -b $BRANCH . && \
pip install gunicorn==17.5 && pip install -r requirements.txt
apt-get update -qq && apt-get install -y libldap2-dev libsasl2-dev libssl-dev && \
pip install gunicorn==17.5 && \
pip install django-auth-ldap && \
pip install -r requirements.txt

ADD docker/docker-entrypoint.sh /docker-entrypoint.sh
ADD netbox/netbox/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py
Expand Down
2 changes: 2 additions & 0 deletions docs/data-model/extras.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Each export template is associated with a certain type of object. For instance,

Export templates are written in [Django's template language](https://docs.djangoproject.com/en/1.9/ref/templates/language/), which is very similar to Jinja2. The list of objects returned from the database is stored in the `queryset` variable. Typically, you'll want to iterate through this list using a for loop.

To access custom fields of an object within a template, use the `cf` attribute. For example, `{{ obj.cf.color }}` will return the value (if any) for a custom field named `color` on `obj`.

A MIME type and file extension can optionally be defined for each export template. The default MIME type is `text/plain`.

## Example
Expand Down
6 changes: 4 additions & 2 deletions netbox/circuits/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from dcim.models import Site
from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter

from .models import Provider, Circuit, CircuitType


Expand Down Expand Up @@ -64,12 +66,12 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Circuit type (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
tenant_id = NullableModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
tenant = NullableModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
to_field_name='slug',
Expand Down
45 changes: 10 additions & 35 deletions netbox/circuits/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from tenancy.forms import bulkedit_tenant_choices
from tenancy.models import Tenant
from utilities.forms import (
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, Livesearch, SmallTextarea, SlugField,
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, FilterChoiceField, Livesearch, SmallTextarea,
SlugField,
)

from .models import Circuit, CircuitType, Provider
Expand Down Expand Up @@ -57,15 +58,9 @@ class ProviderBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
comments = CommentField()


def provider_site_choices():
site_choices = Site.objects.all()
return [(s.slug, s.name) for s in site_choices]


class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Provider
site = forms.MultipleChoiceField(required=False, choices=provider_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
site = FilterChoiceField(queryset=Site.objects.all(), to_field_name='slug')


#
Expand Down Expand Up @@ -189,32 +184,12 @@ class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
comments = CommentField()


def circuit_type_choices():
type_choices = CircuitType.objects.annotate(circuit_count=Count('circuits'))
return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in type_choices]


def circuit_provider_choices():
provider_choices = Provider.objects.annotate(circuit_count=Count('circuits'))
return [(p.slug, u'{} ({})'.format(p.name, p.circuit_count)) for p in provider_choices]


def circuit_tenant_choices():
tenant_choices = Tenant.objects.annotate(circuit_count=Count('circuits'))
return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in tenant_choices]


def circuit_site_choices():
site_choices = Site.objects.annotate(circuit_count=Count('circuits'))
return [(s.slug, u'{} ({})'.format(s.name, s.circuit_count)) for s in site_choices]


class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Circuit
type = forms.MultipleChoiceField(required=False, choices=circuit_type_choices)
provider = forms.MultipleChoiceField(required=False, choices=circuit_provider_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
tenant = forms.MultipleChoiceField(required=False, choices=circuit_tenant_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
site = forms.MultipleChoiceField(required=False, choices=circuit_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
type = FilterChoiceField(queryset=CircuitType.objects.annotate(filter_count=Count('circuits')),
to_field_name='slug')
provider = FilterChoiceField(queryset=Provider.objects.annotate(filter_count=Count('circuits')),
to_field_name='slug')
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('circuits')), to_field_name='slug',
null_option=(0, 'None'))
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('circuits')), to_field_name='slug')
25 changes: 13 additions & 12 deletions netbox/dcim/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter
from .models import (
ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, Interface, InterfaceConnection, Manufacturer,
Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, Site,
Expand All @@ -15,12 +16,12 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
action='search',
label='Search',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
tenant_id = NullableModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
tenant = NullableModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
to_field_name='slug',
Expand Down Expand Up @@ -75,34 +76,34 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Site (slug)',
)
group_id = django_filters.ModelMultipleChoiceFilter(
group_id = NullableModelMultipleChoiceFilter(
name='group',
queryset=RackGroup.objects.all(),
label='Group (ID)',
)
group = django_filters.ModelMultipleChoiceFilter(
group = NullableModelMultipleChoiceFilter(
name='group',
queryset=RackGroup.objects.all(),
to_field_name='slug',
label='Group',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
tenant_id = NullableModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
tenant = NullableModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
role_id = django_filters.ModelMultipleChoiceFilter(
role_id = NullableModelMultipleChoiceFilter(
name='role',
queryset=RackRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
role = NullableModelMultipleChoiceFilter(
name='role',
queryset=RackRole.objects.all(),
to_field_name='slug',
Expand Down Expand Up @@ -177,12 +178,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Role (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
tenant_id = NullableModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
tenant = NullableModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
to_field_name='slug',
Expand Down Expand Up @@ -210,12 +211,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Device model (slug)',
)
platform_id = django_filters.ModelMultipleChoiceFilter(
platform_id = NullableModelMultipleChoiceFilter(
name='platform',
queryset=Platform.objects.all(),
label='Platform (ID)',
)
platform = django_filters.ModelMultipleChoiceFilter(
platform = NullableModelMultipleChoiceFilter(
name='platform',
queryset=Platform.objects.all(),
to_field_name='slug',
Expand Down
114 changes: 23 additions & 91 deletions netbox/dcim/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from tenancy.models import Tenant
from utilities.forms import (
APISelect, add_blank_choice, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
)

from .models import (
Expand Down Expand Up @@ -117,15 +117,10 @@ class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')


def site_tenant_choices():
tenant_choices = Tenant.objects.annotate(site_count=Count('sites'))
return [(t.slug, u'{} ({})'.format(t.name, t.site_count)) for t in tenant_choices]


class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Site
tenant = forms.MultipleChoiceField(required=False, choices=site_tenant_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('sites')), to_field_name='slug',
null_option=(0, 'None'))


#
Expand All @@ -140,14 +135,8 @@ class Meta:
fields = ['site', 'name', 'slug']


def rackgroup_site_choices():
site_choices = Site.objects.annotate(rack_count=Count('rack_groups'))
return [(s.slug, u'{} ({})'.format(s.name, s.rack_count)) for s in site_choices]


class RackGroupFilterForm(forms.Form, BootstrapMixin):
site = forms.MultipleChoiceField(required=False, choices=rackgroup_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('rack_groups')), to_field_name='slug')


#
Expand Down Expand Up @@ -254,36 +243,15 @@ class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
comments = CommentField()


def rack_site_choices():
site_choices = Site.objects.annotate(rack_count=Count('racks'))
return [(s.slug, u'{} ({})'.format(s.name, s.rack_count)) for s in site_choices]


def rack_group_choices():
group_choices = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
return [(g.pk, u'{} ({})'.format(g, g.rack_count)) for g in group_choices]


def rack_tenant_choices():
tenant_choices = Tenant.objects.annotate(rack_count=Count('racks'))
return [(t.slug, u'{} ({})'.format(t.name, t.rack_count)) for t in tenant_choices]


def rack_role_choices():
role_choices = RackRole.objects.annotate(rack_count=Count('racks'))
return [(r.slug, u'{} ({})'.format(r.name, r.rack_count)) for r in role_choices]


class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Rack
site = forms.MultipleChoiceField(required=False, choices=rack_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
group_id = forms.MultipleChoiceField(required=False, choices=rack_group_choices, label='Rack Group',
widget=forms.SelectMultiple(attrs={'size': 8}))
tenant = forms.MultipleChoiceField(required=False, choices=rack_tenant_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
role = forms.MultipleChoiceField(required=False, choices=rack_role_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks')), to_field_name='slug')
group_id = FilterChoiceField(queryset=RackGroup.objects.select_related('site')
.annotate(filter_count=Count('racks')), label='Rack group', null_option=(0, 'None'))
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('racks')), to_field_name='slug',
null_option=(0, 'None'))
role = FilterChoiceField(queryset=RackRole.objects.annotate(filter_count=Count('racks')), to_field_name='slug',
null_option=(0, 'None'))


#
Expand Down Expand Up @@ -317,14 +285,9 @@ class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
u_height = forms.IntegerField(min_value=1, required=False)


def devicetype_manufacturer_choices():
manufacturer_choices = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
return [(m.slug, u'{} ({})'.format(m.name, m.devicetype_count)) for m in manufacturer_choices]


class DeviceTypeFilterForm(forms.Form, BootstrapMixin):
manufacturer = forms.MultipleChoiceField(required=False, choices=devicetype_manufacturer_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
manufacturer = FilterChoiceField(queryset=Manufacturer.objects.annotate(filter_count=Count('device_types')),
to_field_name='slug')


#
Expand Down Expand Up @@ -627,49 +590,18 @@ class DeviceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
serial = forms.CharField(max_length=50, required=False, label='Serial Number')


def device_site_choices():
site_choices = Site.objects.annotate(device_count=Count('racks__devices'))
return [(s.slug, u'{} ({})'.format(s.name, s.device_count)) for s in site_choices]


def device_rack_group_choices():
group_choices = RackGroup.objects.select_related('site').annotate(device_count=Count('racks__devices'))
return [(g.pk, u'{} ({})'.format(g, g.device_count)) for g in group_choices]


def device_role_choices():
role_choices = DeviceRole.objects.annotate(device_count=Count('devices'))
return [(r.slug, u'{} ({})'.format(r.name, r.device_count)) for r in role_choices]


def device_tenant_choices():
tenant_choices = Tenant.objects.annotate(device_count=Count('devices'))
return [(t.slug, u'{} ({})'.format(t.name, t.device_count)) for t in tenant_choices]


def device_type_choices():
type_choices = DeviceType.objects.select_related('manufacturer').annotate(device_count=Count('instances'))
return [(t.pk, u'{} ({})'.format(t, t.device_count)) for t in type_choices]


def device_platform_choices():
platform_choices = Platform.objects.annotate(device_count=Count('devices'))
return [(p.slug, u'{} ({})'.format(p.name, p.device_count)) for p in platform_choices]


class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Device
site = forms.MultipleChoiceField(required=False, choices=device_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
rack_group_id = forms.MultipleChoiceField(required=False, choices=device_rack_group_choices, label='Rack Group',
widget=forms.SelectMultiple(attrs={'size': 8}))
role = forms.MultipleChoiceField(required=False, choices=device_role_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
tenant = forms.MultipleChoiceField(required=False, choices=device_tenant_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
device_type_id = forms.MultipleChoiceField(required=False, choices=device_type_choices, label='Type',
widget=forms.SelectMultiple(attrs={'size': 8}))
platform = forms.MultipleChoiceField(required=False, choices=device_platform_choices)
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks__devices')), to_field_name='slug')
rack_group_id = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks__devices')),
label='Rack Group')
role = FilterChoiceField(queryset=DeviceRole.objects.annotate(filter_count=Count('devices')), to_field_name='slug')
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('devices')), to_field_name='slug',
null_option=(0, 'None'))
device_type_id = FilterChoiceField(queryset=DeviceType.objects.select_related('manufacturer')
.annotate(filter_count=Count('instances')), label='Type')
platform = FilterChoiceField(queryset=Platform.objects.annotate(filter_count=Count('devices')),
to_field_name='slug', null_option=(0, 'None'))
status = forms.NullBooleanField(required=False, widget=forms.Select(choices=FORM_STATUS_CHOICES))


Expand Down
2 changes: 1 addition & 1 deletion netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,7 @@ class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
template_name = 'dcim/interface_add_multi.html'
default_redirect_url = 'dcim:device_list'

def update_objects(self, pk_list, form):
def update_objects(self, pk_list, form, fields):

selected_devices = Device.objects.filter(pk__in=pk_list)
interfaces = []
Expand Down
Loading