mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-17 04:58:16 -06:00
Merge branch 'develop' into dev_csvcustomfields
This commit is contained in:
commit
401370b127
112
CHANGELOG.md
112
CHANGELOG.md
@ -1,11 +1,121 @@
|
|||||||
v2.5.8 (FUTURE)
|
2.5.13 (FUTURE)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#2813](https://github.com/digitalocean/netbox/issues/2813) - Add tenant group filters
|
||||||
|
* [#3085](https://github.com/digitalocean/netbox/issues/3085) - Catch all exceptions during export template rendering
|
||||||
|
* [#3138](https://github.com/digitalocean/netbox/issues/3138) - Add 2.5GE and 5GE interface form factors
|
||||||
|
* [#3183](https://github.com/digitalocean/netbox/issues/3183) - Enable bulk deletion of sites
|
||||||
|
* [#3186](https://github.com/digitalocean/netbox/issues/3186) - Add interface name filter for IP addresses
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#3132](https://github.com/digitalocean/netbox/issues/3132) - Circuit termination missing from available cable termination types
|
||||||
|
* [#3150](https://github.com/digitalocean/netbox/issues/3150) - Fix formatting of cable length during cable trace
|
||||||
|
* [#3190](https://github.com/digitalocean/netbox/issues/3190) - Fix custom field rendering for Jinja2 export templates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
2.5.12 (2019-05-01)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#3127](https://github.com/digitalocean/netbox/issues/3127) - Fix natural ordering of device components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
2.5.11 (2019-04-29)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
This release upgrades the Django framework to version 2.2.
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#2986](https://github.com/digitalocean/netbox/issues/2986) - Improve natural ordering of device components
|
||||||
|
* [#3023](https://github.com/digitalocean/netbox/issues/3023) - Add support for filtering cables by connected device
|
||||||
|
* [#3070](https://github.com/digitalocean/netbox/issues/3070) - Add decommissioning status for devices
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#2621](https://github.com/digitalocean/netbox/issues/2621) - Upgrade Django requirement to 2.2 to fix object deletion issue in the changelog middleware
|
||||||
|
* [#3072](https://github.com/digitalocean/netbox/issues/3072) - Preserve multiselect filter values when updating per-page count for list views
|
||||||
|
* [#3112](https://github.com/digitalocean/netbox/issues/3112) - Fix ordering of interface connections list by termination B name/device
|
||||||
|
* [#3116](https://github.com/digitalocean/netbox/issues/3116) - Fix `tagged_items` count in tags API endpoint
|
||||||
|
* [#3118](https://github.com/digitalocean/netbox/issues/3118) - Disable `last_login` update on login when maintenance mode is enabled
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
v2.5.10 (2019-04-08)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#3052](https://github.com/digitalocean/netbox/issues/3052) - Add Jinja2 support for export templates
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#2937](https://github.com/digitalocean/netbox/issues/2937) - Redirect to list view after editing an object from list view
|
||||||
|
* [#3036](https://github.com/digitalocean/netbox/issues/3036) - DCIM interfaces API endpoint should not include VM interfaces
|
||||||
|
* [#3039](https://github.com/digitalocean/netbox/issues/3039) - Fix exception when retrieving change object for a component template via API
|
||||||
|
* [#3041](https://github.com/digitalocean/netbox/issues/3041) - Fix form widget for bulk cable label update
|
||||||
|
* [#3044](https://github.com/digitalocean/netbox/issues/3044) - Ignore site/rack fields when connecting a new cable via device search
|
||||||
|
* [#3046](https://github.com/digitalocean/netbox/issues/3046) - Fix exception at reports API endpoint
|
||||||
|
* [#3047](https://github.com/digitalocean/netbox/issues/3047) - Fix exception when writing mac address for an interface via API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
v2.5.9 (2019-04-01)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#2933](https://github.com/digitalocean/netbox/issues/2933) - Add username to outbound webhook requests
|
||||||
|
* [#3011](https://github.com/digitalocean/netbox/issues/3011) - Add SSL support for django-rq (requires django-rq v1.3.1+)
|
||||||
|
* [#3025](https://github.com/digitalocean/netbox/issues/3025) - Add request ID to outbound webhook requests (for correlating all changes part of a single request)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#2207](https://github.com/digitalocean/netbox/issues/2207) - Fixes deterministic ordering of interfaces
|
||||||
|
* [#2577](https://github.com/digitalocean/netbox/issues/2577) - Clarification of wording in API regarding filtering
|
||||||
|
* [#2924](https://github.com/digitalocean/netbox/issues/2924) - Add interface type for QSFP28 50GE
|
||||||
|
* [#2936](https://github.com/digitalocean/netbox/issues/2936) - Fix device role selection showing duplicate first entry
|
||||||
|
* [#2998](https://github.com/digitalocean/netbox/issues/2998) - Limit device query to non-racked devices if no rack selected when creating a cable
|
||||||
|
* [#3001](https://github.com/digitalocean/netbox/issues/3001) - Fix API representation of ObjectChange `action` and add `changed_object_type`
|
||||||
|
* [#3014](https://github.com/digitalocean/netbox/issues/3014) - Fixes VM Role filtering
|
||||||
|
* [#3019](https://github.com/digitalocean/netbox/issues/3019) - Fix tag population when running NetBox within a path
|
||||||
|
* [#3022](https://github.com/digitalocean/netbox/issues/3022) - Add missing cable termination types to DCIM `_choices` endpoint
|
||||||
|
* [#3026](https://github.com/digitalocean/netbox/issues/3026) - Tweak prefix/IP filter forms to filter using VRF ID rather than route distinguisher
|
||||||
|
* [#3027](https://github.com/digitalocean/netbox/issues/3027) - Ignore empty local context data when rendering config contexts
|
||||||
|
* [#3032](https://github.com/digitalocean/netbox/issues/3032) - Save assigned tags when creating a new secret
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
v2.5.8 (2019-03-11)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#2435](https://github.com/digitalocean/netbox/issues/2435) - Printer friendly CSS
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#2065](https://github.com/digitalocean/netbox/issues/2065) - Correct documentation for VM interface serializer
|
||||||
* [#2705](https://github.com/digitalocean/netbox/issues/2705) - Fix endpoint grouping in API docs
|
* [#2705](https://github.com/digitalocean/netbox/issues/2705) - Fix endpoint grouping in API docs
|
||||||
|
* [#2781](https://github.com/digitalocean/netbox/issues/2781) - Fix filtering of sites/devices/VMs by multiple regions
|
||||||
* [#2923](https://github.com/digitalocean/netbox/issues/2923) - Provider filter form's site field should be blank by default
|
* [#2923](https://github.com/digitalocean/netbox/issues/2923) - Provider filter form's site field should be blank by default
|
||||||
* [#2938](https://github.com/digitalocean/netbox/issues/2938) - Enforce deterministic ordering of device components returned by API
|
* [#2938](https://github.com/digitalocean/netbox/issues/2938) - Enforce deterministic ordering of device components returned by API
|
||||||
* [#2939](https://github.com/digitalocean/netbox/issues/2939) - Exclude circuit terminations from API interface connections endpoint
|
* [#2939](https://github.com/digitalocean/netbox/issues/2939) - Exclude circuit terminations from API interface connections endpoint
|
||||||
|
* [#2940](https://github.com/digitalocean/netbox/issues/2940) - Allow CSV import of prefixes/IPs to VRF without an RD assigned
|
||||||
|
* [#2944](https://github.com/digitalocean/netbox/issues/2944) - Record the deletion of an IP address in the changelog of its parent interface (if any)
|
||||||
|
* [#2952](https://github.com/digitalocean/netbox/issues/2952) - Added the `slug` field to the Tenant filter for use in the API and search function
|
||||||
|
* [#2954](https://github.com/digitalocean/netbox/issues/2954) - Remove trailing slashes to fix root/template paths on Windows
|
||||||
|
* [#2961](https://github.com/digitalocean/netbox/issues/2961) - Prevent exception when exporting inventory items belonging to unnamed devices
|
||||||
|
* [#2962](https://github.com/digitalocean/netbox/issues/2962) - Increase ExportTemplate `mime_type` field length
|
||||||
|
* [#2966](https://github.com/digitalocean/netbox/issues/2966) - Accept `null` cable length_unit via API
|
||||||
|
* [#2972](https://github.com/digitalocean/netbox/issues/2972) - Improve ContentTypeField serializer to elegantly handle invalid data
|
||||||
|
* [#2976](https://github.com/digitalocean/netbox/issues/2976) - Add delete button to tag view
|
||||||
|
* [#2980](https://github.com/digitalocean/netbox/issues/2980) - Improve rendering time for API docs
|
||||||
|
* [#2982](https://github.com/digitalocean/netbox/issues/2982) - Correct CSS class assignment on color picker
|
||||||
|
* [#2984](https://github.com/digitalocean/netbox/issues/2984) - Fix logging of unlabeled cable ID on cable deletion
|
||||||
|
* [#2985](https://github.com/digitalocean/netbox/issues/2985) - Fix pagination page length for rack elevations
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -45,13 +45,13 @@ and run `upgrade.sh`.
|
|||||||
|
|
||||||
## Supported SDK
|
## Supported SDK
|
||||||
|
|
||||||
- [pynetbox](https://github.com/digitalocean/pynetbox) Python API client library for Netbox.
|
- [pynetbox](https://github.com/digitalocean/pynetbox) - A Python API client library for Netbox
|
||||||
|
|
||||||
## Community SDK
|
## Community SDK
|
||||||
|
|
||||||
- [netbox-client-ruby](https://github.com/ninech/netbox-client-ruby) A ruby client library for Netbox v2.
|
- [netbox-client-ruby](https://github.com/ninech/netbox-client-ruby) - A Ruby client library for Netbox
|
||||||
|
- [powerbox](https://github.com/BatmanAMA/powerbox) - A PowerShell library for Netbox
|
||||||
|
|
||||||
## Ansible Inventory
|
## Ansible Inventory
|
||||||
|
|
||||||
- [netbox-as-ansible-inventory](https://github.com/AAbouZaid/netbox-as-ansible-inventory) Ansible dynamic inventory script for Netbox.
|
- [netbox-as-ansible-inventory](https://github.com/AAbouZaid/netbox-as-ansible-inventory) - Ansible dynamic inventory script for Netbox
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ psql -c 'create database netbox'
|
|||||||
psql netbox < netbox.sql
|
psql netbox < netbox.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
Keep in mind that PostgreSQL user accounts and permissions are not included with the dump: You will need to create those manually if you want to fully replicate the original database (see the [installation docs](installation/1-postgresql.md)). When setting up a development instance of NetBox, it's strongly recommended to use different credentials anyway.
|
Keep in mind that PostgreSQL user accounts and permissions are not included with the dump: You will need to create those manually if you want to fully replicate the original database (see the [installation docs](../installation/1-postgresql.md)). When setting up a development instance of NetBox, it's strongly recommended to use different credentials anyway.
|
||||||
|
|
||||||
## Export the Database Schema
|
## Export the Database Schema
|
||||||
|
|
||||||
|
@ -261,7 +261,7 @@ A list of objects retrieved via the API can be filtered by passing one or more q
|
|||||||
GET /api/ipam/prefixes/?status=1
|
GET /api/ipam/prefixes/?status=1
|
||||||
```
|
```
|
||||||
|
|
||||||
The same filter can be incldued multiple times. These will effect a logical OR and return objects matching any of the given values. For example, the following will return all active and reserved prefixes:
|
Certain filters can be included multiple times within a single request. These will effect a logical OR and return objects matching any of the given values. For example, the following will return all active and reserved prefixes:
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/ipam/prefixes/?status=1&status=2
|
GET /api/ipam/prefixes/?status=1&status=2
|
||||||
|
@ -283,6 +283,7 @@ REDIS = {
|
|||||||
'PASSWORD': '',
|
'PASSWORD': '',
|
||||||
'DATABASE': 0,
|
'DATABASE': 0,
|
||||||
'DEFAULT_TIMEOUT': 300,
|
'DEFAULT_TIMEOUT': 300,
|
||||||
|
'SSL': False,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -315,3 +316,9 @@ The TCP port to use when connecting to the Redis server.
|
|||||||
Default: None
|
Default: None
|
||||||
|
|
||||||
The password to use when authenticating to the Redis server (optional).
|
The password to use when authenticating to the Redis server (optional).
|
||||||
|
|
||||||
|
### SSL
|
||||||
|
|
||||||
|
Default: False
|
||||||
|
|
||||||
|
Use secure sockets layer to encrypt the connections to the Redis server.
|
||||||
|
@ -3,13 +3,13 @@ from django.db.models import Q
|
|||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
|
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
|
||||||
from .constants import CIRCUIT_STATUS_CHOICES
|
from .constants import CIRCUIT_STATUS_CHOICES
|
||||||
from .models import Provider, Circuit, CircuitTermination, CircuitType
|
from .models import Provider, Circuit, CircuitTermination, CircuitType
|
||||||
|
|
||||||
|
|
||||||
class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class ProviderFilter(CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -54,7 +54,7 @@ class CircuitTypeFilter(NameSlugSearchFilterSet):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -87,16 +87,6 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
choices=CIRCUIT_STATUS_CHOICES,
|
choices=CIRCUIT_STATUS_CHOICES,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='terminations__site',
|
field_name='terminations__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
|
@ -4,6 +4,7 @@ from taggit.forms import TagField
|
|||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
|
from tenancy.forms import TenancyFilterForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
|
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
|
||||||
@ -265,8 +266,9 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Circuit
|
model = Circuit
|
||||||
|
field_order = ['q', 'type', 'provider', 'status', 'site', 'tenant_group', 'tenant', 'commit_rate']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
@ -292,16 +294,6 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
|
@ -11,7 +11,7 @@ CIRCUITTYPE_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.circuit.change_circuittype %}
|
{% if perms.circuit.change_circuittype %}
|
||||||
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ class CircuitTypeTable(BaseTable):
|
|||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
circuit_count = tables.Column(verbose_name='Circuits')
|
circuit_count = tables.Column(verbose_name='Circuits')
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=CIRCUITTYPE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
|
template_code=CIRCUITTYPE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.validators import UniqueTogetherValidator
|
from rest_framework.validators import UniqueTogetherValidator
|
||||||
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
||||||
@ -502,12 +503,16 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class CableSerializer(ValidatedModelSerializer):
|
class CableSerializer(ValidatedModelSerializer):
|
||||||
termination_a_type = ContentTypeField()
|
termination_a_type = ContentTypeField(
|
||||||
termination_b_type = ContentTypeField()
|
queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES)
|
||||||
|
)
|
||||||
|
termination_b_type = ContentTypeField(
|
||||||
|
queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES)
|
||||||
|
)
|
||||||
termination_a = serializers.SerializerMethodField(read_only=True)
|
termination_a = serializers.SerializerMethodField(read_only=True)
|
||||||
termination_b = serializers.SerializerMethodField(read_only=True)
|
termination_b = serializers.SerializerMethodField(read_only=True)
|
||||||
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
||||||
length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False)
|
length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import F, Q
|
from django.db.models import F
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
@ -35,7 +35,7 @@ from .exceptions import MissingFilterException
|
|||||||
|
|
||||||
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
||||||
fields = (
|
fields = (
|
||||||
(Cable, ['length_unit', 'status', 'type']),
|
(Cable, ['length_unit', 'status', 'termination_a_type', 'termination_b_type', 'type']),
|
||||||
(ConsolePort, ['connection_status']),
|
(ConsolePort, ['connection_status']),
|
||||||
(Device, ['face', 'status']),
|
(Device, ['face', 'status']),
|
||||||
(DeviceType, ['subdevice_role']),
|
(DeviceType, ['subdevice_role']),
|
||||||
@ -419,7 +419,9 @@ class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = Interface.objects.select_related(
|
queryset = Interface.objects.filter(
|
||||||
|
device__isnull=False
|
||||||
|
).select_related(
|
||||||
'device', '_connected_interface', '_connected_circuittermination', 'cable'
|
'device', '_connected_interface', '_connected_circuittermination', 'cable'
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'ip_addresses', 'tags'
|
'ip_addresses', 'tags'
|
||||||
|
@ -75,6 +75,8 @@ IFACE_FF_100ME_FIXED = 800
|
|||||||
IFACE_FF_1GE_FIXED = 1000
|
IFACE_FF_1GE_FIXED = 1000
|
||||||
IFACE_FF_1GE_GBIC = 1050
|
IFACE_FF_1GE_GBIC = 1050
|
||||||
IFACE_FF_1GE_SFP = 1100
|
IFACE_FF_1GE_SFP = 1100
|
||||||
|
IFACE_FF_2GE_FIXED = 1120
|
||||||
|
IFACE_FF_5GE_FIXED = 1130
|
||||||
IFACE_FF_10GE_FIXED = 1150
|
IFACE_FF_10GE_FIXED = 1150
|
||||||
IFACE_FF_10GE_CX4 = 1170
|
IFACE_FF_10GE_CX4 = 1170
|
||||||
IFACE_FF_10GE_SFP_PLUS = 1200
|
IFACE_FF_10GE_SFP_PLUS = 1200
|
||||||
@ -83,6 +85,7 @@ IFACE_FF_10GE_XENPAK = 1310
|
|||||||
IFACE_FF_10GE_X2 = 1320
|
IFACE_FF_10GE_X2 = 1320
|
||||||
IFACE_FF_25GE_SFP28 = 1350
|
IFACE_FF_25GE_SFP28 = 1350
|
||||||
IFACE_FF_40GE_QSFP_PLUS = 1400
|
IFACE_FF_40GE_QSFP_PLUS = 1400
|
||||||
|
IFACE_FF_50GE_QSFP28 = 1420
|
||||||
IFACE_FF_100GE_CFP = 1500
|
IFACE_FF_100GE_CFP = 1500
|
||||||
IFACE_FF_100GE_CFP2 = 1510
|
IFACE_FF_100GE_CFP2 = 1510
|
||||||
IFACE_FF_100GE_CFP4 = 1520
|
IFACE_FF_100GE_CFP4 = 1520
|
||||||
@ -149,6 +152,8 @@ IFACE_FF_CHOICES = [
|
|||||||
[
|
[
|
||||||
[IFACE_FF_100ME_FIXED, '100BASE-TX (10/100ME)'],
|
[IFACE_FF_100ME_FIXED, '100BASE-TX (10/100ME)'],
|
||||||
[IFACE_FF_1GE_FIXED, '1000BASE-T (1GE)'],
|
[IFACE_FF_1GE_FIXED, '1000BASE-T (1GE)'],
|
||||||
|
[IFACE_FF_2GE_FIXED, '2.5GBASE-T (2.5GE)'],
|
||||||
|
[IFACE_FF_5GE_FIXED, '5GBASE-T (5GE)'],
|
||||||
[IFACE_FF_10GE_FIXED, '10GBASE-T (10GE)'],
|
[IFACE_FF_10GE_FIXED, '10GBASE-T (10GE)'],
|
||||||
[IFACE_FF_10GE_CX4, '10GBASE-CX4 (10GE)'],
|
[IFACE_FF_10GE_CX4, '10GBASE-CX4 (10GE)'],
|
||||||
]
|
]
|
||||||
@ -164,6 +169,7 @@ IFACE_FF_CHOICES = [
|
|||||||
[IFACE_FF_10GE_X2, 'X2 (10GE)'],
|
[IFACE_FF_10GE_X2, 'X2 (10GE)'],
|
||||||
[IFACE_FF_25GE_SFP28, 'SFP28 (25GE)'],
|
[IFACE_FF_25GE_SFP28, 'SFP28 (25GE)'],
|
||||||
[IFACE_FF_40GE_QSFP_PLUS, 'QSFP+ (40GE)'],
|
[IFACE_FF_40GE_QSFP_PLUS, 'QSFP+ (40GE)'],
|
||||||
|
[IFACE_FF_50GE_QSFP28, 'QSFP28 (50GE)'],
|
||||||
[IFACE_FF_100GE_CFP, 'CFP (100GE)'],
|
[IFACE_FF_100GE_CFP, 'CFP (100GE)'],
|
||||||
[IFACE_FF_100GE_CFP2, 'CFP2 (100GE)'],
|
[IFACE_FF_100GE_CFP2, 'CFP2 (100GE)'],
|
||||||
[IFACE_FF_200GE_CFP2, 'CFP2 (200GE)'],
|
[IFACE_FF_200GE_CFP2, 'CFP2 (200GE)'],
|
||||||
@ -316,6 +322,7 @@ DEVICE_STATUS_PLANNED = 2
|
|||||||
DEVICE_STATUS_STAGED = 3
|
DEVICE_STATUS_STAGED = 3
|
||||||
DEVICE_STATUS_FAILED = 4
|
DEVICE_STATUS_FAILED = 4
|
||||||
DEVICE_STATUS_INVENTORY = 5
|
DEVICE_STATUS_INVENTORY = 5
|
||||||
|
DEVICE_STATUS_DECOMMISSIONING = 6
|
||||||
DEVICE_STATUS_CHOICES = [
|
DEVICE_STATUS_CHOICES = [
|
||||||
[DEVICE_STATUS_ACTIVE, 'Active'],
|
[DEVICE_STATUS_ACTIVE, 'Active'],
|
||||||
[DEVICE_STATUS_OFFLINE, 'Offline'],
|
[DEVICE_STATUS_OFFLINE, 'Offline'],
|
||||||
@ -323,6 +330,7 @@ DEVICE_STATUS_CHOICES = [
|
|||||||
[DEVICE_STATUS_STAGED, 'Staged'],
|
[DEVICE_STATUS_STAGED, 'Staged'],
|
||||||
[DEVICE_STATUS_FAILED, 'Failed'],
|
[DEVICE_STATUS_FAILED, 'Failed'],
|
||||||
[DEVICE_STATUS_INVENTORY, 'Inventory'],
|
[DEVICE_STATUS_INVENTORY, 'Inventory'],
|
||||||
|
[DEVICE_STATUS_DECOMMISSIONING, 'Decommissioning'],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Site statuses
|
# Site statuses
|
||||||
@ -343,6 +351,7 @@ STATUS_CLASSES = {
|
|||||||
3: 'primary',
|
3: 'primary',
|
||||||
4: 'danger',
|
4: 'danger',
|
||||||
5: 'default',
|
5: 'default',
|
||||||
|
6: 'warning',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Console/power/interface connection statuses
|
# Console/power/interface connection statuses
|
||||||
@ -355,7 +364,7 @@ CONNECTION_STATUS_CHOICES = [
|
|||||||
|
|
||||||
# Cable endpoint types
|
# Cable endpoint types
|
||||||
CABLE_TERMINATION_TYPES = [
|
CABLE_TERMINATION_TYPES = [
|
||||||
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport',
|
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport', 'circuittermination',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Cable types
|
# Cable types
|
||||||
|
@ -31,7 +31,7 @@ class MACAddressField(models.Field):
|
|||||||
try:
|
try:
|
||||||
return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
|
return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
|
||||||
except AddrFormatError as e:
|
except AddrFormatError as e:
|
||||||
raise ValidationError(e)
|
raise ValidationError("Invalid MAC address format: {}".format(value))
|
||||||
|
|
||||||
def db_type(self, connection):
|
def db_type(self, connection):
|
||||||
return 'macaddr'
|
return 'macaddr'
|
||||||
|
@ -6,9 +6,12 @@ from netaddr import EUI
|
|||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.constants import COLOR_CHOICES
|
from utilities.constants import COLOR_CHOICES
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter
|
from utilities.filters import (
|
||||||
|
NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
||||||
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -36,7 +39,7 @@ class RegionFilter(NameSlugSearchFilterSet):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class SiteFilter(TenancyFilterSet, CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -49,25 +52,16 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
choices=SITE_STATUS_CHOICES,
|
choices=SITE_STATUS_CHOICES,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
region_id = django_filters.NumberFilter(
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
method='filter_region',
|
queryset=Region.objects.all(),
|
||||||
field_name='pk',
|
field_name='region__in',
|
||||||
label='Region (ID)',
|
label='Region (ID)',
|
||||||
)
|
)
|
||||||
region = django_filters.CharFilter(
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
method='filter_region',
|
queryset=Region.objects.all(),
|
||||||
field_name='slug',
|
field_name='region__in',
|
||||||
label='Region (slug)',
|
|
||||||
)
|
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Region (slug)',
|
||||||
)
|
)
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
|
|
||||||
@ -95,16 +89,6 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
pass
|
pass
|
||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
def filter_region(self, queryset, name, value):
|
|
||||||
try:
|
|
||||||
region = Region.objects.get(**{name: value})
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return queryset.none()
|
|
||||||
return queryset.filter(
|
|
||||||
Q(region=region) |
|
|
||||||
Q(region__in=region.get_descendants())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RackGroupFilter(NameSlugSearchFilterSet):
|
class RackGroupFilter(NameSlugSearchFilterSet):
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
@ -130,7 +114,7 @@ class RackRoleFilter(NameSlugSearchFilterSet):
|
|||||||
fields = ['name', 'slug', 'color']
|
fields = ['name', 'slug', 'color']
|
||||||
|
|
||||||
|
|
||||||
class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class RackFilter(TenancyFilterSet, CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -160,16 +144,6 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Group',
|
label='Group',
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=RACK_STATUS_CHOICES,
|
choices=RACK_STATUS_CHOICES,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -206,7 +180,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RackReservationFilter(django_filters.FilterSet):
|
class RackReservationFilter(TenancyFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -241,16 +215,6 @@ class RackReservationFilter(django_filters.FilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Group',
|
label='Group',
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
user_id = django_filters.ModelMultipleChoiceFilter(
|
user_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=User.objects.all(),
|
queryset=User.objects.all(),
|
||||||
label='User (ID)',
|
label='User (ID)',
|
||||||
@ -456,7 +420,7 @@ class PlatformFilter(NameSlugSearchFilterSet):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class DeviceFilter(CustomFieldFilterSet):
|
class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -491,16 +455,6 @@ class DeviceFilter(CustomFieldFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Role (slug)',
|
label='Role (slug)',
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
platform_id = django_filters.ModelMultipleChoiceFilter(
|
platform_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Platform.objects.all(),
|
queryset=Platform.objects.all(),
|
||||||
label='Platform (ID)',
|
label='Platform (ID)',
|
||||||
@ -513,14 +467,15 @@ class DeviceFilter(CustomFieldFilterSet):
|
|||||||
)
|
)
|
||||||
name = NullableCharFieldFilter()
|
name = NullableCharFieldFilter()
|
||||||
asset_tag = NullableCharFieldFilter()
|
asset_tag = NullableCharFieldFilter()
|
||||||
region_id = django_filters.NumberFilter(
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
method='filter_region',
|
queryset=Region.objects.all(),
|
||||||
field_name='pk',
|
field_name='site__region__in',
|
||||||
label='Region (ID)',
|
label='Region (ID)',
|
||||||
)
|
)
|
||||||
region = django_filters.CharFilter(
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
method='filter_region',
|
queryset=Region.objects.all(),
|
||||||
field_name='slug',
|
field_name='site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
label='Region (slug)',
|
label='Region (slug)',
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
@ -619,16 +574,6 @@ class DeviceFilter(CustomFieldFilterSet):
|
|||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
def filter_region(self, queryset, name, value):
|
|
||||||
try:
|
|
||||||
region = Region.objects.get(**{name: value})
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return queryset.none()
|
|
||||||
return queryset.filter(
|
|
||||||
Q(site__region=region) |
|
|
||||||
Q(site__region__in=region.get_descendants())
|
|
||||||
)
|
|
||||||
|
|
||||||
def _mac_address(self, queryset, name, value):
|
def _mac_address(self, queryset, name, value):
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
if not value:
|
if not value:
|
||||||
@ -984,6 +929,14 @@ class CableFilter(django_filters.FilterSet):
|
|||||||
color = django_filters.MultipleChoiceFilter(
|
color = django_filters.MultipleChoiceFilter(
|
||||||
choices=COLOR_CHOICES
|
choices=COLOR_CHOICES
|
||||||
)
|
)
|
||||||
|
device = django_filters.CharFilter(
|
||||||
|
method='filter_connected_device',
|
||||||
|
field_name='name'
|
||||||
|
)
|
||||||
|
device_id = django_filters.CharFilter(
|
||||||
|
method='filter_connected_device',
|
||||||
|
field_name='pk'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
@ -994,6 +947,16 @@ class CableFilter(django_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(label__icontains=value)
|
return queryset.filter(label__icontains=value)
|
||||||
|
|
||||||
|
def filter_connected_device(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
try:
|
||||||
|
device = Device.objects.get(**{name: value})
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return queryset.none()
|
||||||
|
cable_pks = device.get_cables(pk_list=True)
|
||||||
|
return queryset.filter(pk__in=cable_pks)
|
||||||
|
|
||||||
|
|
||||||
class ConsoleConnectionFilter(django_filters.FilterSet):
|
class ConsoleConnectionFilter(django_filters.FilterSet):
|
||||||
site = django_filters.CharFilter(
|
site = django_filters.CharFilter(
|
||||||
|
@ -13,7 +13,8 @@ from timezone_field import TimeZoneFormField
|
|||||||
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from ipam.models import IPAddress, VLAN, VLANGroup
|
from ipam.models import IPAddress, VLAN, VLANGroup
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.forms import TenancyFilterForm
|
||||||
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
||||||
BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField,
|
BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField,
|
||||||
@ -256,8 +257,9 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Site
|
model = Site
|
||||||
|
field_order = ['q', 'status', 'region', 'tenant_group', 'tenant']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
@ -276,16 +278,6 @@ class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
value_field="slug",
|
value_field="slug",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -596,8 +588,9 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Rack
|
model = Rack
|
||||||
|
field_order = ['q', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
@ -619,16 +612,6 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
null_option=True,
|
null_option=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=RACK_STATUS_CHOICES,
|
choices=RACK_STATUS_CHOICES,
|
||||||
required=False,
|
required=False,
|
||||||
@ -689,40 +672,6 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
|
|||||||
return unit_choices
|
return unit_choices
|
||||||
|
|
||||||
|
|
||||||
class RackReservationFilterForm(BootstrapMixin, forms.Form):
|
|
||||||
q = forms.CharField(
|
|
||||||
required=False,
|
|
||||||
label='Search'
|
|
||||||
)
|
|
||||||
site = FilterChoiceField(
|
|
||||||
queryset=Site.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/dcim/sites/",
|
|
||||||
value_field="slug",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
group_id = FilterChoiceField(
|
|
||||||
queryset=RackGroup.objects.select_related('site'),
|
|
||||||
label='Rack group',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/dcim/rack-groups/",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
|
class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(
|
pk = forms.ModelMultipleChoiceField(
|
||||||
queryset=RackReservation.objects.all(),
|
queryset=RackReservation.objects.all(),
|
||||||
@ -751,6 +700,31 @@ class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
nullable_fields = []
|
nullable_fields = []
|
||||||
|
|
||||||
|
|
||||||
|
class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
|
||||||
|
field_order = ['q', 'site', 'group_id', 'tenant_group', 'tenant']
|
||||||
|
q = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label='Search'
|
||||||
|
)
|
||||||
|
site = FilterChoiceField(
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
group_id = FilterChoiceField(
|
||||||
|
queryset=RackGroup.objects.select_related('site'),
|
||||||
|
label='Rack group',
|
||||||
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/rack-groups/",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Manufacturers
|
# Manufacturers
|
||||||
#
|
#
|
||||||
@ -1643,8 +1617,12 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Device
|
model = Device
|
||||||
|
field_order = [
|
||||||
|
'q', 'region', 'site', 'rack_group_id', 'rack_id', 'status', 'role', 'tenant_group', 'tenant',
|
||||||
|
'manufacturer_id', 'device_type_id', 'mac_address', 'has_primary_ip',
|
||||||
|
]
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
@ -1700,17 +1678,6 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
api_url="/api/dcim/device-roles/",
|
api_url="/api/dcim/device-roles/",
|
||||||
value_field="slug",
|
value_field="slug",
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
manufacturer_id = FilterChoiceField(
|
manufacturer_id = FilterChoiceField(
|
||||||
@ -2707,12 +2674,12 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(CONNECTION_STATUS_CHOICES),
|
choices=add_blank_choice(CONNECTION_STATUS_CHOICES),
|
||||||
required=False,
|
required=False,
|
||||||
|
widget=StaticSelect2(),
|
||||||
initial=''
|
initial=''
|
||||||
)
|
)
|
||||||
label = forms.CharField(
|
label = forms.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
required=False,
|
required=False
|
||||||
widget=StaticSelect2()
|
|
||||||
)
|
)
|
||||||
color = forms.CharField(
|
color = forms.CharField(
|
||||||
max_length=6,
|
max_length=6,
|
||||||
@ -2767,6 +2734,10 @@ class CableFilterForm(BootstrapMixin, forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
widget=ColorSelect()
|
widget=ColorSelect()
|
||||||
)
|
)
|
||||||
|
device = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label='Device name'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -3102,9 +3073,31 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tenant_group = FilterChoiceField(
|
||||||
|
queryset=TenantGroup.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/tenancy/tenant-groups/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
filter_for={
|
||||||
|
'tenant': 'group'
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --',
|
null_label='-- None --',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/tenancy/tenants/",
|
||||||
|
value_field="slug",
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
@ -14,22 +14,6 @@ CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*:(\d{{1,9}})(\.\d{{1,9}})?$')
|
|||||||
VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*\.(\d{{1,9}})$') AS integer), 0)"
|
VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*\.(\d{{1,9}})$') AS integer), 0)"
|
||||||
|
|
||||||
|
|
||||||
class DeviceComponentManager(Manager):
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
|
|
||||||
queryset = super().get_queryset()
|
|
||||||
table_name = self.model._meta.db_table
|
|
||||||
sql = r"CONCAT(REGEXP_REPLACE({}.name, '\d+$', ''), LPAD(SUBSTRING({}.name FROM '\d+$'), 8, '0'))"
|
|
||||||
|
|
||||||
# Pad any trailing digits to effect natural sorting
|
|
||||||
return queryset.extra(
|
|
||||||
select={
|
|
||||||
'name_padded': sql.format(table_name, table_name),
|
|
||||||
}
|
|
||||||
).order_by('name_padded', 'pk')
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceQuerySet(QuerySet):
|
class InterfaceQuerySet(QuerySet):
|
||||||
|
|
||||||
def connectable(self):
|
def connectable(self):
|
||||||
@ -64,11 +48,15 @@ class InterfaceManager(Manager):
|
|||||||
|
|
||||||
The original `name` field is considered in its entirety to serve as a fallback in the event interfaces do not
|
The original `name` field is considered in its entirety to serve as a fallback in the event interfaces do not
|
||||||
match any of the prescribed fields.
|
match any of the prescribed fields.
|
||||||
|
|
||||||
|
The `id` field is included to enforce deterministic ordering of interfaces in similar vein of other device
|
||||||
|
components.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sql_col = '{}.name'.format(self.model._meta.db_table)
|
sql_col = '{}.name'.format(self.model._meta.db_table)
|
||||||
ordering = [
|
ordering = [
|
||||||
'_slot', '_subslot', '_position', '_subposition', '_type', '_id', '_channel', '_vc', 'name',
|
'_slot', '_subslot', '_position', '_subposition', '_type', '_id', '_channel', '_vc', 'name', 'pk'
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
|
@ -23,7 +23,7 @@ from utilities.utils import serialize_object, to_meters
|
|||||||
from .constants import *
|
from .constants import *
|
||||||
from .exceptions import LoopDetected
|
from .exceptions import LoopDetected
|
||||||
from .fields import ASNField, MACAddressField
|
from .fields import ASNField, MACAddressField
|
||||||
from .managers import DeviceComponentManager, InterfaceManager
|
from .managers import InterfaceManager
|
||||||
|
|
||||||
|
|
||||||
class ComponentTemplateModel(models.Model):
|
class ComponentTemplateModel(models.Model):
|
||||||
@ -1004,7 +1004,7 @@ class ConsolePortTemplate(ComponentTemplateModel):
|
|||||||
max_length=50
|
max_length=50
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device_type', 'name']
|
ordering = ['device_type', 'name']
|
||||||
@ -1027,7 +1027,7 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
|
|||||||
max_length=50
|
max_length=50
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device_type', 'name']
|
ordering = ['device_type', 'name']
|
||||||
@ -1050,7 +1050,7 @@ class PowerPortTemplate(ComponentTemplateModel):
|
|||||||
max_length=50
|
max_length=50
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device_type', 'name']
|
ordering = ['device_type', 'name']
|
||||||
@ -1073,7 +1073,7 @@ class PowerOutletTemplate(ComponentTemplateModel):
|
|||||||
max_length=50
|
max_length=50
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device_type', 'name']
|
ordering = ['device_type', 'name']
|
||||||
@ -1139,7 +1139,7 @@ class FrontPortTemplate(ComponentTemplateModel):
|
|||||||
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device_type', 'name']
|
ordering = ['device_type', 'name']
|
||||||
@ -1188,7 +1188,7 @@ class RearPortTemplate(ComponentTemplateModel):
|
|||||||
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device_type', 'name']
|
ordering = ['device_type', 'name']
|
||||||
@ -1211,7 +1211,7 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
|||||||
max_length=50
|
max_length=50
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device_type', 'name']
|
ordering = ['device_type', 'name']
|
||||||
@ -1704,6 +1704,21 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False)
|
filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False)
|
||||||
return Interface.objects.filter(filter)
|
return Interface.objects.filter(filter)
|
||||||
|
|
||||||
|
def get_cables(self, pk_list=False):
|
||||||
|
"""
|
||||||
|
Return a QuerySet or PK list matching all Cables connected to a component of this Device.
|
||||||
|
"""
|
||||||
|
cable_pks = []
|
||||||
|
for component_model in [
|
||||||
|
ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, FrontPort, RearPort
|
||||||
|
]:
|
||||||
|
cable_pks += component_model.objects.filter(
|
||||||
|
device=self, cable__isnull=False
|
||||||
|
).values_list('cable', flat=True)
|
||||||
|
if pk_list:
|
||||||
|
return cable_pks
|
||||||
|
return Cable.objects.filter(pk__in=cable_pks)
|
||||||
|
|
||||||
def get_children(self):
|
def get_children(self):
|
||||||
"""
|
"""
|
||||||
Return the set of child Devices installed in DeviceBays within this Device.
|
Return the set of child Devices installed in DeviceBays within this Device.
|
||||||
@ -1742,7 +1757,7 @@ class ConsolePort(CableTermination, ComponentModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
tags = TaggableManager()
|
tags = TaggableManager()
|
||||||
|
|
||||||
csv_headers = ['device', 'name']
|
csv_headers = ['device', 'name']
|
||||||
@ -1785,7 +1800,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
tags = TaggableManager()
|
tags = TaggableManager()
|
||||||
|
|
||||||
csv_headers = ['device', 'name']
|
csv_headers = ['device', 'name']
|
||||||
@ -1834,7 +1849,7 @@ class PowerPort(CableTermination, ComponentModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
tags = TaggableManager()
|
tags = TaggableManager()
|
||||||
|
|
||||||
csv_headers = ['device', 'name']
|
csv_headers = ['device', 'name']
|
||||||
@ -1877,7 +1892,7 @@ class PowerOutlet(CableTermination, ComponentModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
tags = TaggableManager()
|
tags = TaggableManager()
|
||||||
|
|
||||||
csv_headers = ['device', 'name']
|
csv_headers = ['device', 'name']
|
||||||
@ -2198,7 +2213,7 @@ class FrontPort(CableTermination, ComponentModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
tags = TaggableManager()
|
tags = TaggableManager()
|
||||||
|
|
||||||
csv_headers = ['device', 'name', 'type', 'rear_port', 'rear_port_position', 'description']
|
csv_headers = ['device', 'name', 'type', 'rear_port', 'rear_port_position', 'description']
|
||||||
@ -2264,7 +2279,7 @@ class RearPort(CableTermination, ComponentModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
tags = TaggableManager()
|
tags = TaggableManager()
|
||||||
|
|
||||||
csv_headers = ['device', 'name', 'type', 'positions', 'description']
|
csv_headers = ['device', 'name', 'type', 'positions', 'description']
|
||||||
@ -2311,7 +2326,7 @@ class DeviceBay(ComponentModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DeviceComponentManager()
|
objects = NaturalOrderingManager()
|
||||||
tags = TaggableManager()
|
tags = TaggableManager()
|
||||||
|
|
||||||
csv_headers = ['device', 'name', 'installed_device']
|
csv_headers = ['device', 'name', 'installed_device']
|
||||||
@ -2423,7 +2438,7 @@ class InventoryItem(ComponentModel):
|
|||||||
|
|
||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return (
|
return (
|
||||||
self.device.name or '{' + self.device.pk + '}',
|
self.device.name or '{{{}}}'.format(self.device.pk),
|
||||||
self.name,
|
self.name,
|
||||||
self.manufacturer.name if self.manufacturer else None,
|
self.manufacturer.name if self.manufacturer else None,
|
||||||
self.part_id,
|
self.part_id,
|
||||||
@ -2557,16 +2572,15 @@ class Cable(ChangeLoggedModel):
|
|||||||
('termination_b_type', 'termination_b_id'),
|
('termination_b_type', 'termination_b_id'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# Create an ID string for use by __str__(). We have to save a copy of pk since it's nullified after .delete()
|
|
||||||
# is called.
|
|
||||||
self.id_string = '#{}'.format(self.pk)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.label or self.id_string
|
if self.label:
|
||||||
|
return self.label
|
||||||
|
|
||||||
|
# Save a copy of the PK on the instance since it's nullified if .delete() is called
|
||||||
|
if not hasattr(self, 'id_string'):
|
||||||
|
self.id_string = '#{}'.format(self.pk)
|
||||||
|
|
||||||
|
return self.id_string
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:cable', args=[self.pk])
|
return reverse('dcim:cable', args=[self.pk])
|
||||||
|
@ -44,7 +44,7 @@ REGION_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.change_region %}
|
{% if perms.dcim.change_region %}
|
||||||
<a href="{% url 'dcim:region_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'dcim:region_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ RACKGROUP_ACTIONS = """
|
|||||||
<i class="fa fa-eye"></i>
|
<i class="fa fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.change_rackgroup %}
|
{% if perms.dcim.change_rackgroup %}
|
||||||
<a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}" class="btn btn-xs btn-warning" title="Edit">
|
<a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning" title="Edit">
|
||||||
<i class="glyphicon glyphicon-pencil"></i>
|
<i class="glyphicon glyphicon-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -67,7 +67,7 @@ RACKROLE_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.change_rackrole %}
|
{% if perms.dcim.change_rackrole %}
|
||||||
<a href="{% url 'dcim:rackrole_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'dcim:rackrole_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ RACKRESERVATION_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.change_rackreservation %}
|
{% if perms.dcim.change_rackreservation %}
|
||||||
<a href="{% url 'dcim:rackreservation_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'dcim:rackreservation_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ MANUFACTURER_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.change_manufacturer %}
|
{% if perms.dcim.change_manufacturer %}
|
||||||
<a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ DEVICEROLE_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.change_devicerole %}
|
{% if perms.dcim.change_devicerole %}
|
||||||
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ PLATFORM_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.change_platform %}
|
{% if perms.dcim.change_platform %}
|
||||||
<a href="{% url 'dcim:platform_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'dcim:platform_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ VIRTUALCHASSIS_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.change_virtualchassis %}
|
{% if perms.dcim.change_virtualchassis %}
|
||||||
<a href="{% url 'dcim:virtualchassis_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'dcim:virtualchassis_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ class RegionTable(BaseTable):
|
|||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=REGION_ACTIONS,
|
template_code=REGION_ACTIONS,
|
||||||
attrs={'td': {'class': 'text-right'}},
|
attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name=''
|
verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -239,7 +239,7 @@ class RackGroupTable(BaseTable):
|
|||||||
slug = tables.Column()
|
slug = tables.Column()
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=RACKGROUP_ACTIONS,
|
template_code=RACKGROUP_ACTIONS,
|
||||||
attrs={'td': {'class': 'text-right'}},
|
attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name=''
|
verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -258,7 +258,7 @@ class RackRoleTable(BaseTable):
|
|||||||
rack_count = tables.Column(verbose_name='Racks')
|
rack_count = tables.Column(verbose_name='Racks')
|
||||||
color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color')
|
color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color')
|
||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(template_code=RACKROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
actions = tables.TemplateColumn(template_code=RACKROLE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name='')
|
verbose_name='')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
@ -305,11 +305,11 @@ class RackDetailTable(RackTable):
|
|||||||
|
|
||||||
class RackReservationTable(BaseTable):
|
class RackReservationTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
||||||
unit_list = tables.Column(orderable=False, verbose_name='Units')
|
unit_list = tables.Column(orderable=False, verbose_name='Units')
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=RACKRESERVATION_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
|
template_code=RACKRESERVATION_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
@ -327,7 +327,7 @@ class ManufacturerTable(BaseTable):
|
|||||||
devicetype_count = tables.Column(verbose_name='Device Types')
|
devicetype_count = tables.Column(verbose_name='Device Types')
|
||||||
platform_count = tables.Column(verbose_name='Platforms')
|
platform_count = tables.Column(verbose_name='Platforms')
|
||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(template_code=MANUFACTURER_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
actions = tables.TemplateColumn(template_code=MANUFACTURER_ACTIONS, attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name='')
|
verbose_name='')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
@ -463,7 +463,7 @@ class DeviceRoleTable(BaseTable):
|
|||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=DEVICEROLE_ACTIONS,
|
template_code=DEVICEROLE_ACTIONS,
|
||||||
attrs={'td': {'class': 'text-right'}},
|
attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name=''
|
verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -492,7 +492,7 @@ class PlatformTable(BaseTable):
|
|||||||
)
|
)
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=PLATFORM_ACTIONS,
|
template_code=PLATFORM_ACTIONS,
|
||||||
attrs={'td': {'class': 'text-right'}},
|
attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name=''
|
verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -733,18 +733,18 @@ class InterfaceConnectionTable(BaseTable):
|
|||||||
)
|
)
|
||||||
device_b = tables.LinkColumn(
|
device_b = tables.LinkColumn(
|
||||||
viewname='dcim:device',
|
viewname='dcim:device',
|
||||||
accessor=Accessor('connected_endpoint.device'),
|
accessor=Accessor('_connected_interface.device'),
|
||||||
args=[Accessor('connected_endpoint.device.pk')],
|
args=[Accessor('_connected_interface.device.pk')],
|
||||||
verbose_name='Device B'
|
verbose_name='Device B'
|
||||||
)
|
)
|
||||||
interface_b = tables.LinkColumn(
|
interface_b = tables.LinkColumn(
|
||||||
viewname='dcim:interface',
|
viewname='dcim:interface',
|
||||||
accessor=Accessor('connected_endpoint.name'),
|
accessor=Accessor('_connected_interface'),
|
||||||
args=[Accessor('connected_endpoint.pk')],
|
args=[Accessor('_connected_interface.pk')],
|
||||||
verbose_name='Interface B'
|
verbose_name='Interface B'
|
||||||
)
|
)
|
||||||
description_b = tables.Column(
|
description_b = tables.Column(
|
||||||
accessor=Accessor('connected_endpoint.description'),
|
accessor=Accessor('_connected_interface.description'),
|
||||||
verbose_name='Description'
|
verbose_name='Description'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -779,7 +779,7 @@ class VirtualChassisTable(BaseTable):
|
|||||||
member_count = tables.Column(verbose_name='Members')
|
member_count = tables.Column(verbose_name='Members')
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=VIRTUALCHASSIS_ACTIONS,
|
template_code=VIRTUALCHASSIS_ACTIONS,
|
||||||
attrs={'td': {'class': 'text-right'}},
|
attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name=''
|
verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ urlpatterns = [
|
|||||||
url(r'^sites/add/$', views.SiteCreateView.as_view(), name='site_add'),
|
url(r'^sites/add/$', views.SiteCreateView.as_view(), name='site_add'),
|
||||||
url(r'^sites/import/$', views.SiteBulkImportView.as_view(), name='site_import'),
|
url(r'^sites/import/$', views.SiteBulkImportView.as_view(), name='site_import'),
|
||||||
url(r'^sites/edit/$', views.SiteBulkEditView.as_view(), name='site_bulk_edit'),
|
url(r'^sites/edit/$', views.SiteBulkEditView.as_view(), name='site_bulk_edit'),
|
||||||
|
url(r'^sites/delete/$', views.SiteBulkDeleteView.as_view(), name='site_bulk_delete'),
|
||||||
url(r'^sites/(?P<slug>[\w-]+)/$', views.SiteView.as_view(), name='site'),
|
url(r'^sites/(?P<slug>[\w-]+)/$', views.SiteView.as_view(), name='site'),
|
||||||
url(r'^sites/(?P<slug>[\w-]+)/edit/$', views.SiteEditView.as_view(), name='site_edit'),
|
url(r'^sites/(?P<slug>[\w-]+)/edit/$', views.SiteEditView.as_view(), name='site_edit'),
|
||||||
url(r'^sites/(?P<slug>[\w-]+)/delete/$', views.SiteDeleteView.as_view(), name='site_delete'),
|
url(r'^sites/(?P<slug>[\w-]+)/delete/$', views.SiteDeleteView.as_view(), name='site_delete'),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||||
@ -246,6 +247,14 @@ class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
default_return_url = 'dcim:site_list'
|
default_return_url = 'dcim:site_list'
|
||||||
|
|
||||||
|
|
||||||
|
class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_site'
|
||||||
|
queryset = Site.objects.select_related('region', 'tenant')
|
||||||
|
filter = filters.SiteFilter
|
||||||
|
table = tables.SiteTable
|
||||||
|
default_return_url = 'dcim:site_list'
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rack groups
|
# Rack groups
|
||||||
#
|
#
|
||||||
@ -353,8 +362,9 @@ class RackElevationListView(View):
|
|||||||
total_count = racks.count()
|
total_count = racks.count()
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
paginator = EnhancedPaginator(racks, 25)
|
per_page = request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||||
page_number = request.GET.get('page', 1)
|
page_number = request.GET.get('page', 1)
|
||||||
|
paginator = EnhancedPaginator(racks, per_page)
|
||||||
try:
|
try:
|
||||||
page = paginator.page(page_number)
|
page = paginator.page(page_number)
|
||||||
except PageNotAnInteger:
|
except PageNotAnInteger:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
@ -15,7 +16,8 @@ from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantG
|
|||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from users.api.nested_serializers import NestedUserSerializer
|
from users.api.nested_serializers import NestedUserSerializer
|
||||||
from utilities.api import (
|
from utilities.api import (
|
||||||
ChoiceField, ContentTypeField, get_serializer_for_model, SerializedPKRelatedField, ValidatedModelSerializer,
|
ChoiceField, ContentTypeField, get_serializer_for_model, SerializerNotFound, SerializedPKRelatedField,
|
||||||
|
ValidatedModelSerializer,
|
||||||
)
|
)
|
||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
|
|
||||||
@ -53,10 +55,17 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ExportTemplateSerializer(ValidatedModelSerializer):
|
class ExportTemplateSerializer(ValidatedModelSerializer):
|
||||||
|
template_language = ChoiceField(
|
||||||
|
choices=TEMPLATE_LANGUAGE_CHOICES,
|
||||||
|
default=TEMPLATE_LANGUAGE_JINJA2
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
fields = ['id', 'content_type', 'name', 'description', 'template_code', 'mime_type', 'file_extension']
|
fields = [
|
||||||
|
'id', 'content_type', 'name', 'description', 'template_language', 'template_code', 'mime_type',
|
||||||
|
'file_extension',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -88,7 +97,9 @@ class TagSerializer(ValidatedModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ImageAttachmentSerializer(ValidatedModelSerializer):
|
class ImageAttachmentSerializer(ValidatedModelSerializer):
|
||||||
content_type = ContentTypeField()
|
content_type = ContentTypeField(
|
||||||
|
queryset=ContentType.objects.all()
|
||||||
|
)
|
||||||
parent = serializers.SerializerMethodField(read_only=True)
|
parent = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -205,14 +216,25 @@ class ReportDetailSerializer(ReportSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ObjectChangeSerializer(serializers.ModelSerializer):
|
class ObjectChangeSerializer(serializers.ModelSerializer):
|
||||||
user = NestedUserSerializer(read_only=True)
|
user = NestedUserSerializer(
|
||||||
content_type = ContentTypeField(read_only=True)
|
read_only=True
|
||||||
changed_object = serializers.SerializerMethodField(read_only=True)
|
)
|
||||||
|
action = ChoiceField(
|
||||||
|
choices=OBJECTCHANGE_ACTION_CHOICES,
|
||||||
|
read_only=True
|
||||||
|
)
|
||||||
|
changed_object_type = ContentTypeField(
|
||||||
|
read_only=True
|
||||||
|
)
|
||||||
|
changed_object = serializers.SerializerMethodField(
|
||||||
|
read_only=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ObjectChange
|
model = ObjectChange
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'time', 'user', 'user_name', 'request_id', 'action', 'content_type', 'changed_object', 'object_data',
|
'id', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type', 'changed_object',
|
||||||
|
'object_data',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_changed_object(self, obj):
|
def get_changed_object(self, obj):
|
||||||
@ -221,9 +243,14 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
if obj.changed_object is None:
|
if obj.changed_object is None:
|
||||||
return None
|
return None
|
||||||
serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
|
|
||||||
if serializer is None:
|
try:
|
||||||
|
serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
|
||||||
|
except SerializerNotFound:
|
||||||
return obj.object_repr
|
return obj.object_repr
|
||||||
context = {'request': self.context['request']}
|
context = {
|
||||||
|
'request': self.context['request']
|
||||||
|
}
|
||||||
data = serializer(obj.changed_object, context=context).data
|
data = serializer(obj.changed_object, context=context).data
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -10,7 +10,7 @@ from taggit.models import Tag
|
|||||||
|
|
||||||
from extras import filters
|
from extras import filters
|
||||||
from extras.models import (
|
from extras.models import (
|
||||||
ConfigContext, CustomField, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
|
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
|
||||||
)
|
)
|
||||||
from extras.reports import get_report, get_reports
|
from extras.reports import get_report, get_reports
|
||||||
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
||||||
@ -23,8 +23,9 @@ from . import serializers
|
|||||||
|
|
||||||
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
|
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
|
||||||
fields = (
|
fields = (
|
||||||
(CustomField, ['type']),
|
(ExportTemplate, ['template_language']),
|
||||||
(Graph, ['type']),
|
(Graph, ['type']),
|
||||||
|
(ObjectChange, ['action']),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -115,7 +116,9 @@ class TopologyMapViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class TagViewSet(ModelViewSet):
|
class TagViewSet(ModelViewSet):
|
||||||
queryset = Tag.objects.annotate(tagged_items=Count('taggit_taggeditem_items'))
|
queryset = Tag.objects.annotate(
|
||||||
|
tagged_items=Count('taggit_taggeditem_items', distinct=True)
|
||||||
|
)
|
||||||
serializer_class = serializers.TagSerializer
|
serializer_class = serializers.TagSerializer
|
||||||
filterset_class = filters.TagFilter
|
filterset_class = filters.TagFilter
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ class ExtrasConfig(AppConfig):
|
|||||||
port=settings.REDIS_PORT,
|
port=settings.REDIS_PORT,
|
||||||
db=settings.REDIS_DATABASE,
|
db=settings.REDIS_DATABASE,
|
||||||
password=settings.REDIS_PASSWORD or None,
|
password=settings.REDIS_PASSWORD or None,
|
||||||
|
ssl=settings.REDIS_SSL,
|
||||||
)
|
)
|
||||||
rs.ping()
|
rs.ping()
|
||||||
except redis.exceptions.ConnectionError:
|
except redis.exceptions.ConnectionError:
|
||||||
|
@ -56,6 +56,14 @@ EXPORTTEMPLATE_MODELS = [
|
|||||||
'cluster', 'virtualmachine', # Virtualization
|
'cluster', 'virtualmachine', # Virtualization
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# ExportTemplate language choices
|
||||||
|
TEMPLATE_LANGUAGE_DJANGO = 10
|
||||||
|
TEMPLATE_LANGUAGE_JINJA2 = 20
|
||||||
|
TEMPLATE_LANGUAGE_CHOICES = (
|
||||||
|
(TEMPLATE_LANGUAGE_DJANGO, 'Django'),
|
||||||
|
(TEMPLATE_LANGUAGE_JINJA2, 'Jinja2'),
|
||||||
|
)
|
||||||
|
|
||||||
# Topology map types
|
# Topology map types
|
||||||
TOPOLOGYMAP_TYPE_NETWORK = 1
|
TOPOLOGYMAP_TYPE_NETWORK = 1
|
||||||
TOPOLOGYMAP_TYPE_CONSOLE = 2
|
TOPOLOGYMAP_TYPE_CONSOLE = 2
|
||||||
|
@ -82,7 +82,7 @@ class ExportTemplateFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
fields = ['content_type', 'name']
|
fields = ['content_type', 'name', 'template_language']
|
||||||
|
|
||||||
|
|
||||||
class TagFilter(django_filters.FilterSet):
|
class TagFilter(django_filters.FilterSet):
|
||||||
|
@ -4,7 +4,6 @@ from django import forms
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from mptt.forms import TreeNodeMultipleChoiceField
|
|
||||||
from taggit.forms import TagField
|
from taggit.forms import TagField
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ def _record_object_deleted(request, instance, **kwargs):
|
|||||||
if hasattr(instance, 'log_change'):
|
if hasattr(instance, 'log_change'):
|
||||||
instance.log_change(request.user, request.id, OBJECTCHANGE_ACTION_DELETE)
|
instance.log_change(request.user, request.id, OBJECTCHANGE_ACTION_DELETE)
|
||||||
|
|
||||||
enqueue_webhooks(instance, OBJECTCHANGE_ACTION_DELETE)
|
enqueue_webhooks(instance, request.user, request.id, OBJECTCHANGE_ACTION_DELETE)
|
||||||
|
|
||||||
|
|
||||||
class ObjectChangeMiddleware(object):
|
class ObjectChangeMiddleware(object):
|
||||||
@ -83,7 +83,7 @@ class ObjectChangeMiddleware(object):
|
|||||||
obj.log_change(request.user, request.id, action)
|
obj.log_change(request.user, request.id, action)
|
||||||
|
|
||||||
# Enqueue webhooks
|
# Enqueue webhooks
|
||||||
enqueue_webhooks(obj, action)
|
enqueue_webhooks(obj, request.user, request.id, action)
|
||||||
|
|
||||||
# Housekeeping: 1% chance of clearing out expired ObjectChanges
|
# Housekeeping: 1% chance of clearing out expired ObjectChanges
|
||||||
if _thread_locals.changed_objects and settings.CHANGELOG_RETENTION and random.randint(1, 100) == 1:
|
if _thread_locals.changed_objects and settings.CHANGELOG_RETENTION and random.randint(1, 100) == 1:
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.1.7 on 2019-03-05 18:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0016_exporttemplate_add_cable'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='exporttemplate',
|
||||||
|
name='mime_type',
|
||||||
|
field=models.CharField(blank=True, max_length=50),
|
||||||
|
),
|
||||||
|
]
|
27
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py
Normal file
27
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 2.1.7 on 2019-04-08 14:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def set_template_language(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Set the language for all existing ExportTemplates to Django (Jinja2 is the default for new ExportTemplates).
|
||||||
|
"""
|
||||||
|
ExportTemplate = apps.get_model('extras', 'ExportTemplate')
|
||||||
|
ExportTemplate.objects.update(template_language=10)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0017_exporttemplate_mime_type_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exporttemplate',
|
||||||
|
name='template_language',
|
||||||
|
field=models.PositiveSmallIntegerField(default=20),
|
||||||
|
),
|
||||||
|
migrations.RunPython(set_template_language),
|
||||||
|
]
|
@ -1,7 +1,6 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
import graphviz
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -12,6 +11,8 @@ from django.db.models import F, Q
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
import graphviz
|
||||||
|
from jinja2 import Environment
|
||||||
|
|
||||||
from dcim.constants import CONNECTION_STATUS_CONNECTED
|
from dcim.constants import CONNECTION_STATUS_CONNECTED
|
||||||
from utilities.utils import deepmerge, foreground_color
|
from utilities.utils import deepmerge, foreground_color
|
||||||
@ -105,6 +106,7 @@ class CustomFieldModel(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
@property
|
||||||
def cf(self):
|
def cf(self):
|
||||||
"""
|
"""
|
||||||
Name-based CustomFieldValue accessor for use in templates
|
Name-based CustomFieldValue accessor for use in templates
|
||||||
@ -355,9 +357,13 @@ class ExportTemplate(models.Model):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
template_language = models.PositiveSmallIntegerField(
|
||||||
|
choices=TEMPLATE_LANGUAGE_CHOICES,
|
||||||
|
default=TEMPLATE_LANGUAGE_JINJA2
|
||||||
|
)
|
||||||
template_code = models.TextField()
|
template_code = models.TextField()
|
||||||
mime_type = models.CharField(
|
mime_type = models.CharField(
|
||||||
max_length=15,
|
max_length=50,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
file_extension = models.CharField(
|
file_extension = models.CharField(
|
||||||
@ -374,16 +380,36 @@ class ExportTemplate(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}: {}'.format(self.content_type, self.name)
|
return '{}: {}'.format(self.content_type, self.name)
|
||||||
|
|
||||||
|
def render(self, queryset):
|
||||||
|
"""
|
||||||
|
Render the contents of the template.
|
||||||
|
"""
|
||||||
|
context = {
|
||||||
|
'queryset': queryset
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.template_language == TEMPLATE_LANGUAGE_DJANGO:
|
||||||
|
template = Template(self.template_code)
|
||||||
|
output = template.render(Context(context))
|
||||||
|
|
||||||
|
elif self.template_language == TEMPLATE_LANGUAGE_JINJA2:
|
||||||
|
template = Environment().from_string(source=self.template_code)
|
||||||
|
output = template.render(**context)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Replace CRLF-style line terminators
|
||||||
|
output = output.replace('\r\n', '\n')
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
def render_to_response(self, queryset):
|
def render_to_response(self, queryset):
|
||||||
"""
|
"""
|
||||||
Render the template to an HTTP response, delivered as a named file attachment
|
Render the template to an HTTP response, delivered as a named file attachment
|
||||||
"""
|
"""
|
||||||
template = Template(self.template_code)
|
output = self.render(queryset)
|
||||||
mime_type = 'text/plain' if not self.mime_type else self.mime_type
|
mime_type = 'text/plain' if not self.mime_type else self.mime_type
|
||||||
output = template.render(Context({'queryset': queryset}))
|
|
||||||
|
|
||||||
# Replace CRLF-style line terminators
|
|
||||||
output = output.replace('\r\n', '\n')
|
|
||||||
|
|
||||||
# Build the response
|
# Build the response
|
||||||
response = HttpResponse(output, content_type=mime_type)
|
response = HttpResponse(output, content_type=mime_type)
|
||||||
@ -720,7 +746,7 @@ class ConfigContextModel(models.Model):
|
|||||||
data = deepmerge(data, context.data)
|
data = deepmerge(data, context.data)
|
||||||
|
|
||||||
# If the object has local config context data defined, merge it last
|
# If the object has local config context data defined, merge it last
|
||||||
if self.local_context_data is not None:
|
if self.local_context_data:
|
||||||
data = deepmerge(data, self.local_context_data)
|
data = deepmerge(data, self.local_context_data)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -1,6 +1,24 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.db.models import Q, QuerySet
|
from django.db.models import Q, QuerySet
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFieldQueryset:
|
||||||
|
"""
|
||||||
|
Annotate custom fields on objects within a QuerySet.
|
||||||
|
"""
|
||||||
|
def __init__(self, queryset, custom_fields):
|
||||||
|
self.queryset = queryset
|
||||||
|
self.model = queryset.model
|
||||||
|
self.custom_fields = custom_fields
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for obj in self.queryset:
|
||||||
|
values_dict = {cfv.field_id: cfv.value for cfv in obj.custom_field_values.all()}
|
||||||
|
obj.custom_fields = OrderedDict([(field, values_dict.get(field.pk)) for field in self.custom_fields])
|
||||||
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextQuerySet(QuerySet):
|
class ConfigContextQuerySet(QuerySet):
|
||||||
|
|
||||||
def get_for_object(self, obj):
|
def get_for_object(self, obj):
|
||||||
|
@ -68,7 +68,7 @@ class TagTable(BaseTable):
|
|||||||
)
|
)
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=TAG_ACTIONS,
|
template_code=TAG_ACTIONS,
|
||||||
attrs={'td': {'class': 'text-right'}},
|
attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name=''
|
verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ from .tables import ConfigContextTable, ObjectChangeTable, TagTable, TaggedItemT
|
|||||||
|
|
||||||
class TagListView(ObjectListView):
|
class TagListView(ObjectListView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=Count('taggit_taggeditem_items')
|
items=Count('taggit_taggeditem_items', distinct=True)
|
||||||
).order_by(
|
).order_by(
|
||||||
'name'
|
'name'
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,7 @@ from utilities.api import get_serializer_for_model
|
|||||||
from .constants import WEBHOOK_MODELS
|
from .constants import WEBHOOK_MODELS
|
||||||
|
|
||||||
|
|
||||||
def enqueue_webhooks(instance, action):
|
def enqueue_webhooks(instance, user, request_id, action):
|
||||||
"""
|
"""
|
||||||
Find Webhook(s) assigned to this instance + action and enqueue them
|
Find Webhook(s) assigned to this instance + action and enqueue them
|
||||||
to be processed
|
to be processed
|
||||||
@ -47,5 +47,7 @@ def enqueue_webhooks(instance, action):
|
|||||||
serializer.data,
|
serializer.data,
|
||||||
instance._meta.model_name,
|
instance._meta.model_name,
|
||||||
action,
|
action,
|
||||||
str(datetime.datetime.now())
|
str(datetime.datetime.now()),
|
||||||
|
user.username,
|
||||||
|
request_id
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ from extras.constants import WEBHOOK_CT_JSON, WEBHOOK_CT_X_WWW_FORM_ENCODED, OBJ
|
|||||||
|
|
||||||
|
|
||||||
@job('default')
|
@job('default')
|
||||||
def process_webhook(webhook, data, model_name, event, timestamp):
|
def process_webhook(webhook, data, model_name, event, timestamp, username, request_id):
|
||||||
"""
|
"""
|
||||||
Make a POST request to the defined Webhook
|
Make a POST request to the defined Webhook
|
||||||
"""
|
"""
|
||||||
@ -18,6 +18,8 @@ def process_webhook(webhook, data, model_name, event, timestamp):
|
|||||||
'event': dict(OBJECTCHANGE_ACTION_CHOICES)[event].lower(),
|
'event': dict(OBJECTCHANGE_ACTION_CHOICES)[event].lower(),
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'model': model_name,
|
'model': model_name,
|
||||||
|
'username': username,
|
||||||
|
'request_id': request_id,
|
||||||
'data': data
|
'data': data
|
||||||
}
|
}
|
||||||
headers = {
|
headers = {
|
||||||
|
@ -128,6 +128,7 @@ class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
|
family = ChoiceField(choices=AF_CHOICES, read_only=True)
|
||||||
site = NestedSiteSerializer(required=False, allow_null=True)
|
site = NestedSiteSerializer(required=False, allow_null=True)
|
||||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
@ -189,6 +190,7 @@ class IPAddressInterfaceSerializer(WritableNestedSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
|
family = ChoiceField(choices=AF_CHOICES, read_only=True)
|
||||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False)
|
||||||
|
@ -6,14 +6,14 @@ from netaddr.core import AddrFormatError
|
|||||||
|
|
||||||
from dcim.models import Site, Device, Interface
|
from dcim.models import Site, Device, Interface
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
|
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES
|
from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
|
|
||||||
|
|
||||||
class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class VRFFilter(TenancyFilterSet, CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -22,16 +22,6 @@ class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
@ -59,7 +49,7 @@ class RIRFilter(NameSlugSearchFilterSet):
|
|||||||
fields = ['name', 'slug', 'is_private']
|
fields = ['name', 'slug', 'is_private']
|
||||||
|
|
||||||
|
|
||||||
class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class AggregateFilter(CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -107,7 +97,7 @@ class RoleFilter(NameSlugSearchFilterSet):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -146,16 +136,6 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
label='VRF (RD)',
|
label='VRF (RD)',
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
@ -254,7 +234,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
return queryset.filter(prefix__net_mask_length=value)
|
return queryset.filter(prefix__net_mask_length=value)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -285,16 +265,6 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
label='VRF (RD)',
|
label='VRF (RD)',
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
device = django_filters.CharFilter(
|
device = django_filters.CharFilter(
|
||||||
method='filter_device',
|
method='filter_device',
|
||||||
field_name='name',
|
field_name='name',
|
||||||
@ -316,6 +286,12 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
label='Virtual machine (name)',
|
label='Virtual machine (name)',
|
||||||
)
|
)
|
||||||
|
interface = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='interface__name',
|
||||||
|
queryset=Interface.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label='Interface (ID)',
|
||||||
|
)
|
||||||
interface_id = django_filters.ModelMultipleChoiceFilter(
|
interface_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
label='Interface (ID)',
|
label='Interface (ID)',
|
||||||
@ -394,7 +370,7 @@ class VLANGroupFilter(NameSlugSearchFilterSet):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class VLANFilter(TenancyFilterSet, CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
@ -423,16 +399,6 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Group',
|
label='Group',
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant (ID)',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Role.objects.all(),
|
queryset=Role.objects.all(),
|
||||||
label='Role (ID)',
|
label='Role (ID)',
|
||||||
|
@ -6,6 +6,7 @@ from taggit.forms import TagField
|
|||||||
from dcim.models import Site, Rack, Device, Interface
|
from dcim.models import Site, Rack, Device, Interface
|
||||||
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
|
from tenancy.forms import TenancyFilterForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
|
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
|
||||||
@ -97,22 +98,13 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = VRF
|
model = VRF
|
||||||
|
field_order = ['q', 'tenant_group', 'tenant']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -349,11 +341,11 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
|
|
||||||
class PrefixCSVForm(forms.ModelForm):
|
class PrefixCSVForm(forms.ModelForm):
|
||||||
vrf = forms.ModelChoiceField(
|
vrf = FlexibleModelChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
|
||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
help_text='Route distinguisher of parent VRF',
|
required=False,
|
||||||
|
help_text='Route distinguisher of parent VRF (or {ID})',
|
||||||
error_messages={
|
error_messages={
|
||||||
'invalid_choice': 'VRF not found.',
|
'invalid_choice': 'VRF not found.',
|
||||||
}
|
}
|
||||||
@ -497,8 +489,12 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Prefix
|
model = Prefix
|
||||||
|
field_order = [
|
||||||
|
'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'status', 'site', 'role', 'tenant_group', 'tenant',
|
||||||
|
'is_pool', 'expand',
|
||||||
|
]
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
@ -524,24 +520,12 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
label='Mask length',
|
label='Mask length',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
vrf = FilterChoiceField(
|
vrf_id = FilterChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
to_field_name='rd',
|
|
||||||
label='VRF',
|
label='VRF',
|
||||||
null_label='-- Global --',
|
null_label='-- Global --',
|
||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
api_url="/api/ipam/vrfs/",
|
api_url="/api/ipam/vrfs/",
|
||||||
value_field="rd",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
null_option=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -764,11 +748,11 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressCSVForm(forms.ModelForm):
|
class IPAddressCSVForm(forms.ModelForm):
|
||||||
vrf = forms.ModelChoiceField(
|
vrf = FlexibleModelChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
|
||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
help_text='Route distinguisher of the assigned VRF',
|
required=False,
|
||||||
|
help_text='Route distinguisher of parent VRF (or {ID})',
|
||||||
error_messages={
|
error_messages={
|
||||||
'invalid_choice': 'VRF not found.',
|
'invalid_choice': 'VRF not found.',
|
||||||
}
|
}
|
||||||
@ -946,8 +930,11 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
|
field_order = [
|
||||||
|
'q', 'parent', 'family', 'mask_length', 'vrf_id', 'status', 'role', 'tenant_group', 'tenant',
|
||||||
|
]
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
@ -973,24 +960,12 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
label='Mask length',
|
label='Mask length',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
vrf = FilterChoiceField(
|
vrf_id = FilterChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
to_field_name='rd',
|
|
||||||
label='VRF',
|
label='VRF',
|
||||||
null_label='-- Global --',
|
null_label='-- Global --',
|
||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
api_url="/api/ipam/vrfs/",
|
api_url="/api/ipam/vrfs/",
|
||||||
value_field="rd",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
null_option=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -1225,8 +1200,9 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = VLAN
|
model = VLAN
|
||||||
|
field_order = ['q', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
@ -1250,16 +1226,6 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
null_option=True,
|
null_option=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/tenancy/tenants/",
|
|
||||||
value_field="slug",
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=VLAN_STATUS_CHOICES,
|
choices=VLAN_STATUS_CHOICES,
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import netaddr
|
import netaddr
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
@ -10,8 +10,9 @@ from django.urls import reverse
|
|||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from extras.models import CustomFieldModel
|
from extras.models import CustomFieldModel, ObjectChange
|
||||||
from utilities.models import ChangeLoggedModel
|
from utilities.models import ChangeLoggedModel
|
||||||
|
from utilities.utils import serialize_object
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .fields import IPNetworkField, IPAddressField
|
from .fields import IPNetworkField, IPAddressField
|
||||||
from .querysets import PrefixQuerySet
|
from .querysets import PrefixQuerySet
|
||||||
@ -629,6 +630,27 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
|||||||
self.family = self.address.version
|
self.family = self.address.version
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def log_change(self, user, request_id, action):
|
||||||
|
"""
|
||||||
|
Include the connected Interface (if any).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# It's possible that an IPAddress can be deleted _after_ its parent Interface, in which case trying to resolve
|
||||||
|
# the interface will raise DoesNotExist.
|
||||||
|
try:
|
||||||
|
parent_obj = self.interface
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
parent_obj = None
|
||||||
|
|
||||||
|
ObjectChange(
|
||||||
|
user=user,
|
||||||
|
request_id=request_id,
|
||||||
|
changed_object=self,
|
||||||
|
related_object=parent_obj,
|
||||||
|
action=action,
|
||||||
|
object_data=serialize_object(self)
|
||||||
|
).save()
|
||||||
|
|
||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
|
|
||||||
# Determine if this IP is primary for a Device
|
# Determine if this IP is primary for a Device
|
||||||
|
@ -30,7 +30,7 @@ RIR_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.ipam.change_rir %}
|
{% if perms.ipam.change_rir %}
|
||||||
<a href="{% url 'ipam:rir_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'ipam:rir_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ ROLE_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.ipam.change_role %}
|
{% if perms.ipam.change_role %}
|
||||||
<a href="{% url 'ipam:role_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'ipam:role_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ VLANGROUP_ACTIONS = """
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if perms.ipam.change_vlangroup %}
|
{% if perms.ipam.change_vlangroup %}
|
||||||
<a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ class RIRTable(BaseTable):
|
|||||||
name = tables.LinkColumn(verbose_name='Name')
|
name = tables.LinkColumn(verbose_name='Name')
|
||||||
is_private = BooleanColumn(verbose_name='Private')
|
is_private = BooleanColumn(verbose_name='Private')
|
||||||
aggregate_count = tables.Column(verbose_name='Aggregates')
|
aggregate_count = tables.Column(verbose_name='Aggregates')
|
||||||
actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name='')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RIR
|
model = RIR
|
||||||
@ -288,7 +288,7 @@ class RoleTable(BaseTable):
|
|||||||
orderable=False,
|
orderable=False,
|
||||||
verbose_name='VLANs'
|
verbose_name='VLANs'
|
||||||
)
|
)
|
||||||
actions = tables.TemplateColumn(template_code=ROLE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
actions = tables.TemplateColumn(template_code=ROLE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name='')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Role
|
model = Role
|
||||||
@ -319,6 +319,7 @@ class PrefixTable(BaseTable):
|
|||||||
|
|
||||||
class PrefixDetailTable(PrefixTable):
|
class PrefixDetailTable(PrefixTable):
|
||||||
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False)
|
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False)
|
||||||
|
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||||
|
|
||||||
class Meta(PrefixTable.Meta):
|
class Meta(PrefixTable.Meta):
|
||||||
fields = ('pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description')
|
fields = ('pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description')
|
||||||
@ -349,6 +350,7 @@ class IPAddressDetailTable(IPAddressTable):
|
|||||||
nat_inside = tables.LinkColumn(
|
nat_inside = tables.LinkColumn(
|
||||||
'ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False, verbose_name='NAT (Inside)'
|
'ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False, verbose_name='NAT (Inside)'
|
||||||
)
|
)
|
||||||
|
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||||
|
|
||||||
class Meta(IPAddressTable.Meta):
|
class Meta(IPAddressTable.Meta):
|
||||||
fields = (
|
fields = (
|
||||||
@ -392,7 +394,7 @@ class VLANGroupTable(BaseTable):
|
|||||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||||
vlan_count = tables.Column(verbose_name='VLANs')
|
vlan_count = tables.Column(verbose_name='VLANs')
|
||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(template_code=VLANGROUP_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
actions = tables.TemplateColumn(template_code=VLANGROUP_ACTIONS, attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name='')
|
verbose_name='')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
@ -423,6 +425,7 @@ class VLANTable(BaseTable):
|
|||||||
|
|
||||||
class VLANDetailTable(VLANTable):
|
class VLANDetailTable(VLANTable):
|
||||||
prefixes = tables.TemplateColumn(VLAN_PREFIXES, orderable=False, verbose_name='Prefixes')
|
prefixes = tables.TemplateColumn(VLAN_PREFIXES, orderable=False, verbose_name='Prefixes')
|
||||||
|
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||||
|
|
||||||
class Meta(VLANTable.Meta):
|
class Meta(VLANTable.Meta):
|
||||||
fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
|
fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
|
||||||
@ -437,7 +440,7 @@ class VLANMemberTable(BaseTable):
|
|||||||
)
|
)
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=VLAN_MEMBER_ACTIONS,
|
template_code=VLAN_MEMBER_ACTIONS,
|
||||||
attrs={'td': {'class': 'text-right'}},
|
attrs={'td': {'class': 'text-right noprint'}},
|
||||||
verbose_name=''
|
verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -147,18 +147,18 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
|||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
#
|
#
|
||||||
|
|
||||||
def get_view_name(view_cls, suffix=None):
|
def get_view_name(view, suffix=None):
|
||||||
"""
|
"""
|
||||||
Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name`.
|
Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name`.
|
||||||
"""
|
"""
|
||||||
if hasattr(view_cls, 'queryset'):
|
if hasattr(view, 'queryset'):
|
||||||
# Determine the model name from the queryset.
|
# Determine the model name from the queryset.
|
||||||
name = view_cls.queryset.model._meta.verbose_name
|
name = view.queryset.model._meta.verbose_name
|
||||||
name = ' '.join([w[0].upper() + w[1:] for w in name.split()]) # Capitalize each word
|
name = ' '.join([w[0].upper() + w[1:] for w in name.split()]) # Capitalize each word
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Replicate DRF's built-in behavior.
|
# Replicate DRF's built-in behavior.
|
||||||
name = view_cls.__name__
|
name = view.__class__.__name__
|
||||||
name = formatting.remove_trailing_string(name, 'View')
|
name = formatting.remove_trailing_string(name, 'View')
|
||||||
name = formatting.remove_trailing_string(name, 'ViewSet')
|
name = formatting.remove_trailing_string(name, 'ViewSet')
|
||||||
name = formatting.camelcase_to_spaces(name)
|
name = formatting.camelcase_to_spaces(name)
|
||||||
|
@ -132,6 +132,7 @@ REDIS = {
|
|||||||
'PASSWORD': '',
|
'PASSWORD': '',
|
||||||
'DATABASE': 0,
|
'DATABASE': 0,
|
||||||
'DEFAULT_TIMEOUT': 300,
|
'DEFAULT_TIMEOUT': 300,
|
||||||
|
'SSL': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
|
# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
|
||||||
|
@ -22,7 +22,7 @@ except ImportError:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
VERSION = '2.5.8-dev'
|
VERSION = '2.5.13-dev'
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
@ -131,6 +131,7 @@ REDIS_PORT = REDIS.get('PORT', 6379)
|
|||||||
REDIS_PASSWORD = REDIS.get('PASSWORD', '')
|
REDIS_PASSWORD = REDIS.get('PASSWORD', '')
|
||||||
REDIS_DATABASE = REDIS.get('DATABASE', 0)
|
REDIS_DATABASE = REDIS.get('DATABASE', 0)
|
||||||
REDIS_DEFAULT_TIMEOUT = REDIS.get('DEFAULT_TIMEOUT', 300)
|
REDIS_DEFAULT_TIMEOUT = REDIS.get('DEFAULT_TIMEOUT', 300)
|
||||||
|
REDIS_SSL = REDIS.get('SSL', False)
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
EMAIL_HOST = EMAIL.get('SERVER')
|
EMAIL_HOST = EMAIL.get('SERVER')
|
||||||
@ -197,7 +198,7 @@ ROOT_URLCONF = 'netbox.urls'
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [BASE_DIR + '/templates/'],
|
'DIRS': [BASE_DIR + '/templates'],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@ -223,7 +224,7 @@ USE_I18N = True
|
|||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
STATIC_ROOT = BASE_DIR + '/static/'
|
STATIC_ROOT = BASE_DIR + '/static'
|
||||||
STATIC_URL = '/{}static/'.format(BASE_PATH)
|
STATIC_URL = '/{}static/'.format(BASE_PATH)
|
||||||
STATICFILES_DIRS = (
|
STATICFILES_DIRS = (
|
||||||
os.path.join(BASE_DIR, "project-static"),
|
os.path.join(BASE_DIR, "project-static"),
|
||||||
@ -291,6 +292,7 @@ RQ_QUEUES = {
|
|||||||
'DB': REDIS_DATABASE,
|
'DB': REDIS_DATABASE,
|
||||||
'PASSWORD': REDIS_PASSWORD,
|
'PASSWORD': REDIS_PASSWORD,
|
||||||
'DEFAULT_TIMEOUT': REDIS_DEFAULT_TIMEOUT,
|
'DEFAULT_TIMEOUT': REDIS_DEFAULT_TIMEOUT,
|
||||||
|
'SSL': REDIS_SSL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,6 +317,7 @@ SWAGGER_SETTINGS = {
|
|||||||
'utilities.custom_inspectors.IdInFilterInspector',
|
'utilities.custom_inspectors.IdInFilterInspector',
|
||||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||||
],
|
],
|
||||||
|
'DEFAULT_MODEL_DEPTH': 1,
|
||||||
'DEFAULT_PAGINATOR_INSPECTORS': [
|
'DEFAULT_PAGINATOR_INSPECTORS': [
|
||||||
'utilities.custom_inspectors.NullablePaginatorInspector',
|
'utilities.custom_inspectors.NullablePaginatorInspector',
|
||||||
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
||||||
|
@ -49,6 +49,19 @@ footer p {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Printer friendly CSS class and various fixes for printing. */
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
a[href]:after {
|
||||||
|
content: none !important;
|
||||||
|
}
|
||||||
|
.noprint {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Collapse the nav menu on displays less than 960px wide */
|
/* Collapse the nav menu on displays less than 960px wide */
|
||||||
@media (max-width: 959px) {
|
@media (max-width: 959px) {
|
||||||
.navbar-header {
|
.navbar-header {
|
||||||
|
@ -90,6 +90,10 @@ $(document).ready(function() {
|
|||||||
// Assign color picker selection classes
|
// Assign color picker selection classes
|
||||||
function colorPickerClassCopy(data, container) {
|
function colorPickerClassCopy(data, container) {
|
||||||
if (data.element) {
|
if (data.element) {
|
||||||
|
// Remove any existing color-selection classes
|
||||||
|
$(container).attr('class', function(i, c) {
|
||||||
|
return c.replace(/(^|\s)color-selection-\S+/g, '');
|
||||||
|
});
|
||||||
$(container).addClass($(data.element).attr("class"));
|
$(container).addClass($(data.element).attr("class"));
|
||||||
}
|
}
|
||||||
return data.text;
|
return data.text;
|
||||||
@ -151,10 +155,14 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
filter_for_elements.each(function(index, filter_for_element) {
|
filter_for_elements.each(function(index, filter_for_element) {
|
||||||
var param_name = $(filter_for_element).attr(attr_name);
|
var param_name = $(filter_for_element).attr(attr_name);
|
||||||
|
var is_nullable = $(filter_for_element).attr("nullable");
|
||||||
|
var is_visible = $(filter_for_element).is(":visible");
|
||||||
var value = $(filter_for_element).val();
|
var value = $(filter_for_element).val();
|
||||||
|
|
||||||
if (param_name && value) {
|
if (param_name && is_visible && value) {
|
||||||
parameters[param_name] = value;
|
parameters[param_name] = value;
|
||||||
|
} else if (param_name && is_visible && is_nullable) {
|
||||||
|
parameters[param_name] = "null";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -243,7 +251,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
ajax: {
|
ajax: {
|
||||||
delay: 250,
|
delay: 250,
|
||||||
url: "/api/extras/tags/",
|
url: netbox_api_path + "extras/tags/",
|
||||||
|
|
||||||
data: function(params) {
|
data: function(params) {
|
||||||
// Paging. Note that `params.page` indexes at 1
|
// Paging. Note that `params.page` indexes at 1
|
||||||
|
@ -8,7 +8,7 @@ SECRETROLE_ACTIONS = """
|
|||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.secrets.change_secretrole %}
|
{% if perms.secrets.change_secretrole %}
|
||||||
<a href="{% url 'secrets:secretrole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'secrets:secretrole_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ class SecretRoleTable(BaseTable):
|
|||||||
secret_count = tables.Column(verbose_name='Secrets')
|
secret_count = tables.Column(verbose_name='Secrets')
|
||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(
|
actions = tables.TemplateColumn(
|
||||||
template_code=SECRETROLE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
|
template_code=SECRETROLE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name=''
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
|
@ -120,6 +120,8 @@ def secret_add(request, pk):
|
|||||||
secret.plaintext = str(form.cleaned_data['plaintext'])
|
secret.plaintext = str(form.cleaned_data['plaintext'])
|
||||||
secret.encrypt(master_key)
|
secret.encrypt(master_key)
|
||||||
secret.save()
|
secret.save()
|
||||||
|
form.save_m2m()
|
||||||
|
|
||||||
messages.success(request, "Added new secret: {}.".format(secret))
|
messages.success(request, "Added new secret: {}.".format(secret))
|
||||||
if '_addanother' in request.POST:
|
if '_addanother' in request.POST:
|
||||||
return redirect('dcim:device_addsecret', pk=device.pk)
|
return redirect('dcim:device_addsecret', pk=device.pk)
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
<div class="col-xs-4 text-center">
|
<div class="col-xs-4 text-center">
|
||||||
<p class="text-muted">{% now 'Y-m-d H:i:s T' %}</p>
|
<p class="text-muted">{% now 'Y-m-d H:i:s T' %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4 text-right">
|
<div class="col-xs-4 text-right noprint">
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
<i class="fa fa-fw fa-book text-primary"></i> <a href="http://netbox.readthedocs.io/">Docs</a> ·
|
<i class="fa fa-fw fa-book text-primary"></i> <a href="http://netbox.readthedocs.io/">Docs</a> ·
|
||||||
<i class="fa fa-fw fa-cloud text-primary"></i> <a href="{% url 'api_docs' %}">API</a> ·
|
<i class="fa fa-fw fa-cloud text-primary"></i> <a href="{% url 'api_docs' %}">API</a> ·
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% block title %}{{ circuit }}{% endblock %}
|
{% block title %}{{ circuit }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'circuits:circuit_list' %}">Circuits</a></li>
|
<li><a href="{% url 'circuits:circuit_list' %}">Circuits</a></li>
|
||||||
@ -25,7 +25,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.circuits.change_circuit %}
|
{% if perms.circuits.change_circuit %}
|
||||||
<a href="{% url 'circuits:circuit_edit' pk=circuit.pk %}" class="btn btn-warning">
|
<a href="{% url 'circuits:circuit_edit' pk=circuit.pk %}" class="btn btn-warning">
|
||||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.circuits.add_circuit %}
|
{% if perms.circuits.add_circuit %}
|
||||||
{% add_button 'circuits:circuit_add' %}
|
{% add_button 'circuits:circuit_add' %}
|
||||||
{% import_button 'circuits:circuit_import' %}
|
{% import_button 'circuits:circuit_import' %}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:circuit_bulk_edit' bulk_delete_url='circuits:circuit_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:circuit_bulk_edit' bulk_delete_url='circuits:circuit_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
{% include 'inc/tags_panel.html' %}
|
{% include 'inc/tags_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.circuits.add_circuittype %}
|
{% if perms.circuits.add_circuittype %}
|
||||||
{% add_button 'circuits:circuittype_add' %}
|
{% add_button 'circuits:circuittype_add' %}
|
||||||
{% import_button 'circuits:circuittype_import' %}
|
{% import_button 'circuits:circuittype_import' %}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{% block title %}{{ provider }}{% endblock %}
|
{% block title %}{{ provider }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'circuits:provider_list' %}">Providers</a></li>
|
<li><a href="{% url 'circuits:provider_list' %}">Providers</a></li>
|
||||||
@ -25,7 +25,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if show_graphs %}
|
{% if show_graphs %}
|
||||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ provider.name }}" data-url="{% url 'circuits-api:provider-graphs' pk=provider.pk %}" title="Show graphs">
|
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ provider.name }}" data-url="{% url 'circuits-api:provider-graphs' pk=provider.pk %}" title="Show graphs">
|
||||||
<i class="fa fa-signal" aria-hidden="true"></i>
|
<i class="fa fa-signal" aria-hidden="true"></i>
|
||||||
@ -172,7 +172,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% if perms.circuits.add_circuit %}
|
{% if perms.circuits.add_circuit %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'circuits:circuit_add' %}?provider={{ provider.pk }}" class="btn btn-xs btn-primary">
|
<a href="{% url 'circuits:circuit_add' %}?provider={{ provider.pk }}" class="btn btn-xs btn-primary">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add circuit
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add circuit
|
||||||
</a>
|
</a>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.circuits.add_provider %}
|
{% if perms.circuits.add_provider %}
|
||||||
{% add_button 'circuits:provider_add' %}
|
{% add_button 'circuits:provider_add' %}
|
||||||
{% import_button 'circuits:provider_import' %}
|
{% import_button 'circuits:provider_import' %}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:provider_bulk_edit' bulk_delete_url='circuits:provider_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:provider_bulk_edit' bulk_delete_url='circuits:provider_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
{% include 'inc/tags_panel.html' %}
|
{% include 'inc/tags_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:cable_list' %}">Cables</a></li>
|
<li><a href="{% url 'dcim:cable_list' %}">Cables</a></li>
|
||||||
@ -10,7 +10,7 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.change_cable %}
|
{% if perms.dcim.change_cable %}
|
||||||
<a href="{% url 'dcim:cable_edit' pk=cable.pk %}" class="btn btn-warning">
|
<a href="{% url 'dcim:cable_edit' pk=cable.pk %}" class="btn btn-warning">
|
||||||
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this cable
|
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this cable
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_cable %}
|
{% if perms.dcim.add_cable %}
|
||||||
{% import_button 'dcim:cable_import' %}
|
{% import_button 'dcim:cable_import' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:cable_bulk_edit' bulk_delete_url='dcim:cable_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:cable_bulk_edit' bulk_delete_url='dcim:cable_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
</h4>
|
</h4>
|
||||||
<p><span class="label label-{% if cable.status %}success{% else %}info{% endif %}">{{ cable.get_status_display }}</span></p>
|
<p><span class="label label-{% if cable.status %}success{% else %}info{% endif %}">{{ cable.get_status_display }}</span></p>
|
||||||
<p>{{ cable.get_type_display|default:"" }}</p>
|
<p>{{ cable.get_type_display|default:"" }}</p>
|
||||||
{% if cable.length %}- {{ cable.length }}{{ cable.get_length_unit_display }}{% endif %}
|
{% if cable.length %}{{ cable.length }} {{ cable.get_length_unit_display }}{% endif %}
|
||||||
<span class="label color-block center-block" style="background-color: #{{ cable.color }}"> </span>
|
<span class="label color-block center-block" style="background-color: #{{ cable.color }}"> </span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h4 class="text-muted">No Cable</h4>
|
<h4 class="text-muted">No Cable</h4>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% export_button content_type %}
|
{% export_button content_type %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Console Connections{% endblock %}</h1>
|
<h1>{% block title %}Console Connections{% endblock %}</h1>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
{% include 'responsive_table.html' %}
|
{% include 'responsive_table.html' %}
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{% block title %}{{ device }}{% endblock %}
|
{% block title %}{{ device }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:site' slug=device.site.slug %}">{{ device.site }}</a></li>
|
<li><a href="{% url 'dcim:site' slug=device.site.slug %}">{{ device.site }}</a></li>
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.change_device %}
|
{% if perms.dcim.change_device %}
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
@ -199,7 +199,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
{% if perms.dcim.change_virtualchassis %}
|
{% if perms.dcim.change_virtualchassis %}
|
||||||
<a href="{% url 'dcim:virtualchassis_add_member' pk=device.virtual_chassis.pk %}?site={{ device.site.pk }}&rack={{ device.rack.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:virtualchassis_add_member' pk=device.virtual_chassis.pk %}?site={{ device.site.pk }}&rack={{ device.rack.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Member
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Member
|
||||||
@ -317,7 +317,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
|
{% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
{% if perms.dcim.add_consoleport %}
|
{% if perms.dcim.add_consoleport %}
|
||||||
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
|
||||||
@ -352,7 +352,7 @@
|
|||||||
<form id="secret_form">
|
<form id="secret_form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
</form>
|
</form>
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:device_addsecret' pk=device.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:device_addsecret' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
Add secret
|
Add secret
|
||||||
@ -377,7 +377,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_service %}
|
{% if perms.ipam.add_service %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:device_service_assign' device=device.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:device_service_assign' device=device.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign service
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign service
|
||||||
</a>
|
</a>
|
||||||
@ -390,7 +390,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'inc/image_attachments.html' with images=device.images.all %}
|
{% include 'inc/image_attachments.html' with images=device.images.all %}
|
||||||
{% if perms.extras.add_imageattachment %}
|
{% if perms.extras.add_imageattachment %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:device_add_image' object_id=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:device_add_image' object_id=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
Attach an image
|
Attach an image
|
||||||
@ -398,7 +398,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default noprint">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Related Devices</strong>
|
<strong>Related Devices</strong>
|
||||||
</div>
|
</div>
|
||||||
@ -459,7 +459,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="panel-footer">
|
<div class="panel-footer noprint">
|
||||||
{% if device_bays and perms.dcim.change_devicebay %}
|
{% if device_bays and perms.dcim.change_devicebay %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||||
@ -493,7 +493,7 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Interfaces</strong>
|
<strong>Interfaces</strong>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
<button class="btn btn-default btn-xs toggle-ips" selected="selected">
|
<button class="btn btn-default btn-xs toggle-ips" selected="selected">
|
||||||
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
|
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
|
||||||
</button>
|
</button>
|
||||||
@ -521,7 +521,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="panel-footer">
|
<div class="panel-footer noprint">
|
||||||
{% if interfaces and perms.dcim.change_interface %}
|
{% if interfaces and perms.dcim.change_interface %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||||
@ -581,7 +581,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="panel-footer">
|
<div class="panel-footer noprint">
|
||||||
{% if consoleserverports and perms.dcim.change_consoleport %}
|
{% if consoleserverports and perms.dcim.change_consoleport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||||
@ -636,7 +636,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="panel-footer">
|
<div class="panel-footer noprint">
|
||||||
{% if poweroutlets and perms.dcim.change_powerport %}
|
{% if poweroutlets and perms.dcim.change_powerport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||||
@ -693,7 +693,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="panel-footer">
|
<div class="panel-footer noprint">
|
||||||
{% if front_ports and perms.dcim.change_frontport %}
|
{% if front_ports and perms.dcim.change_frontport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||||
@ -750,7 +750,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="panel-footer">
|
<div class="panel-footer noprint">
|
||||||
{% if rear_ports and perms.dcim.change_rearport %}
|
{% if rear_ports and perms.dcim.change_rearport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_inventoryitem %}
|
{% if perms.dcim.add_inventoryitem %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:inventoryitem_add' device=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:inventoryitem_add' device=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="fa fa-plus" aria-hidden="true"></span> Add Inventory Item
|
<span class="fa fa-plus" aria-hidden="true"></span> Add Inventory Item
|
||||||
</a>
|
</a>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_device %}
|
{% if perms.dcim.add_device %}
|
||||||
{% add_button 'dcim:device_add' %}
|
{% add_button 'dcim:device_add' %}
|
||||||
{% import_button 'dcim:device_import' %}
|
{% import_button 'dcim:device_import' %}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'dcim/inc/device_table.html' with bulk_edit_url='dcim:device_bulk_edit' bulk_delete_url='dcim:device_bulk_delete' %}
|
{% include 'dcim/inc/device_table.html' with bulk_edit_url='dcim:device_bulk_edit' bulk_delete_url='dcim:device_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
{% include 'inc/tags_panel.html' %}
|
{% include 'inc/tags_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_devicerole %}
|
{% if perms.dcim.add_devicerole %}
|
||||||
{% add_button 'dcim:devicerole_add' %}
|
{% add_button 'dcim:devicerole_add' %}
|
||||||
{% import_button 'dcim:devicerole_import' %}
|
{% import_button 'dcim:devicerole_import' %}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% block title %}{{ devicetype.manufacturer }} {{ devicetype.model }}{% endblock %}
|
{% block title %}{{ devicetype.manufacturer }} {{ devicetype.model }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:devicetype_list' %}">Device Types</a></li>
|
<li><a href="{% url 'dcim:devicetype_list' %}">Device Types</a></li>
|
||||||
@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if perms.dcim.change_devicetype or perms.dcim.delete_devicetype %}
|
{% if perms.dcim.change_devicetype or perms.dcim.delete_devicetype %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.change_devicetype %}
|
{% if perms.dcim.change_devicetype %}
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_devicetype %}
|
{% if perms.dcim.add_devicetype %}
|
||||||
{% add_button 'dcim:devicetype_add' %}
|
{% add_button 'dcim:devicetype_add' %}
|
||||||
{% import_button 'dcim:devicetype_import' %}
|
{% import_button 'dcim:devicetype_import' %}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:devicetype_bulk_edit' bulk_delete_url='dcim:devicetype_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:devicetype_bulk_edit' bulk_delete_url='dcim:devicetype_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
{% include 'inc/tags_panel.html' %}
|
{% include 'inc/tags_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Actions #}
|
{# Actions #}
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if cp.cable %}
|
{% if cp.cable %}
|
||||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=cp.cable %}
|
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=cp.cable %}
|
||||||
{% elif perms.dcim.add_cable %}
|
{% elif perms.dcim.add_cable %}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Actions #}
|
{# Actions #}
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if csp.cable %}
|
{% if csp.cable %}
|
||||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=csp.cable %}
|
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=csp.cable %}
|
||||||
{% elif perms.dcim.add_cable %}
|
{% elif perms.dcim.add_cable %}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<span class="text-muted">Vacant</span>
|
<span class="text-muted">Vacant</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if perms.dcim.change_devicebay %}
|
{% if perms.dcim.change_devicebay %}
|
||||||
{% if devicebay.installed_device %}
|
{% if devicebay.installed_device %}
|
||||||
<a href="{% url 'dcim:devicebay_depopulate' pk=devicebay.pk %}" class="btn btn-danger btn-xs">
|
<a href="{% url 'dcim:devicebay_depopulate' pk=devicebay.pk %}" class="btn btn-danger btn-xs">
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<strong>{{ title }}</strong>
|
<strong>{{ title }}</strong>
|
||||||
</div>
|
</div>
|
||||||
{% include 'responsive_table.html' %}
|
{% include 'responsive_table.html' %}
|
||||||
<div class="panel-footer">
|
<div class="panel-footer noprint">
|
||||||
{% if table.rows %}
|
{% if table.rows %}
|
||||||
{% if edit_url %}
|
{% if edit_url %}
|
||||||
<button type="submit" name="_edit" formaction="{% url edit_url pk=devicetype.pk %}?return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-warning">
|
<button type="submit" name="_edit" formaction="{% url edit_url pk=devicetype.pk %}?return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-warning">
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Actions #}
|
{# Actions #}
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if frontport.cable %}
|
{% if frontport.cable %}
|
||||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=frontport.cable %}
|
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=frontport.cable %}
|
||||||
{% elif perms.dcim.add_cable %}
|
{% elif perms.dcim.add_cable %}
|
||||||
|
@ -134,7 +134,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Buttons #}
|
{# Buttons #}
|
||||||
<td class="text-right text-nowrap">
|
<td class="text-right text-nowrap noprint">
|
||||||
{% if show_graphs %}
|
{% if show_graphs %}
|
||||||
{% if iface.connected_endpoint %}
|
{% if iface.connected_endpoint %}
|
||||||
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface-graphs' pk=iface.pk %}" title="Show graphs">
|
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface-graphs' pk=iface.pk %}" title="Show graphs">
|
||||||
@ -231,7 +231,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
{# Buttons #}
|
{# Buttons #}
|
||||||
<td class="text-right text-nowrap">
|
<td class="text-right text-nowrap noprint">
|
||||||
{% if perms.ipam.change_ipaddress %}
|
{% if perms.ipam.change_ipaddress %}
|
||||||
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
|
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
|
||||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<td>{{ item.serial }}</td>
|
<td>{{ item.serial }}</td>
|
||||||
<td>{{ item.asset_tag|default:"" }}</td>
|
<td>{{ item.asset_tag|default:"" }}</td>
|
||||||
<td>{{ item.description }}</td>
|
<td>{{ item.description }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if perms.dcim.change_inventoryitem %}
|
{% if perms.dcim.change_inventoryitem %}
|
||||||
<a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
<a href="{% url 'dcim:inventoryitem_edit' pk=item.pk %}" class="btn btn-xs btn-warning"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Actions #}
|
{# Actions #}
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if po.cable %}
|
{% if po.cable %}
|
||||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=po.cable %}
|
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=po.cable %}
|
||||||
{% elif perms.dcim.add_cable %}
|
{% elif perms.dcim.add_cable %}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Actions #}
|
{# Actions #}
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if pp.cable %}
|
{% if pp.cable %}
|
||||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=pp.cable %}
|
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=pp.cable %}
|
||||||
{% elif perms.dcim.add_cable %}
|
{% elif perms.dcim.add_cable %}
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Actions #}
|
{# Actions #}
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if rearport.cable %}
|
{% if rearport.cable %}
|
||||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=rearport.cable %}
|
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=rearport.cable %}
|
||||||
{% elif perms.dcim.add_cable %}
|
{% elif perms.dcim.add_cable %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
{% if interface.device %}
|
{% if interface.device %}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.change_interface %}
|
{% if perms.dcim.change_interface %}
|
||||||
<a href="{% if interface.device %}{% url 'dcim:interface_edit' pk=interface.pk %}{% else %}{% url 'virtualization:interface_edit' pk=interface.pk %}{% endif %}" class="btn btn-warning">
|
<a href="{% if interface.device %}{% url 'dcim:interface_edit' pk=interface.pk %}{% else %}{% url 'virtualization:interface_edit' pk=interface.pk %}{% endif %}" class="btn btn-warning">
|
||||||
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this interface
|
<span class="fa fa-pencil" aria-hidden="true"></span> Edit this interface
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% export_button content_type %}
|
{% export_button content_type %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Interface Connections{% endblock %}</h1>
|
<h1>{% block title %}Interface Connections{% endblock %}</h1>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
{% include 'responsive_table.html' %}
|
{% include 'responsive_table.html' %}
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_devicetype %}
|
{% if perms.dcim.add_devicetype %}
|
||||||
{% import_button 'dcim:inventoryitem_import' %}
|
{% import_button 'dcim:inventoryitem_import' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:inventoryitem_bulk_edit' bulk_delete_url='dcim:inventoryitem_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:inventoryitem_bulk_edit' bulk_delete_url='dcim:inventoryitem_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_manufacturer %}
|
{% if perms.dcim.add_manufacturer %}
|
||||||
{% add_button 'dcim:manufacturer_add' %}
|
{% add_button 'dcim:manufacturer_add' %}
|
||||||
{% import_button 'dcim:manufacturer_import' %}
|
{% import_button 'dcim:manufacturer_import' %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_platform %}
|
{% if perms.dcim.add_platform %}
|
||||||
{% add_button 'dcim:platform_add' %}
|
{% add_button 'dcim:platform_add' %}
|
||||||
{% import_button 'dcim:platform_import' %}
|
{% import_button 'dcim:platform_import' %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% export_button content_type %}
|
{% export_button content_type %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Power Connections{% endblock %}</h1>
|
<h1>{% block title %}Power Connections{% endblock %}</h1>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
{% include 'responsive_table.html' %}
|
{% include 'responsive_table.html' %}
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:rack_list' %}">Racks</a></li>
|
<li><a href="{% url 'dcim:rack_list' %}">Racks</a></li>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}"{% else %}disabled="disabled"{% endif %} class="btn btn-primary">
|
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}"{% else %}disabled="disabled"{% endif %} class="btn btn-primary">
|
||||||
<span class="fa fa-chevron-left" aria-hidden="true"></span> Previous Rack
|
<span class="fa fa-chevron-left" aria-hidden="true"></span> Previous Rack
|
||||||
</a>
|
</a>
|
||||||
@ -223,7 +223,7 @@
|
|||||||
<div class="panel-body text-muted">None</div>
|
<div class="panel-body text-muted">None</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_device %}
|
{% if perms.dcim.add_device %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}" class="btn btn-primary btn-xs">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
Add a non-racked device
|
Add a non-racked device
|
||||||
@ -237,7 +237,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'inc/image_attachments.html' with images=rack.images.all %}
|
{% include 'inc/image_attachments.html' with images=rack.images.all %}
|
||||||
{% if perms.extras.add_imageattachment %}
|
{% if perms.extras.add_imageattachment %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:rack_add_image' object_id=rack.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:rack_add_image' object_id=rack.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
Attach an image
|
Attach an image
|
||||||
@ -271,7 +271,7 @@
|
|||||||
{{ resv.description }}<br />
|
{{ resv.description }}<br />
|
||||||
<small>{{ resv.user }} · {{ resv.created }}</small>
|
<small>{{ resv.user }} · {{ resv.created }}</small>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if perms.dcim.change_rackreservation %}
|
{% if perms.dcim.change_rackreservation %}
|
||||||
<a href="{% url 'dcim:rackreservation_edit' pk=resv.pk %}" class="btn btn-warning btn-xs" title="Edit reservation">
|
<a href="{% url 'dcim:rackreservation_edit' pk=resv.pk %}" class="btn btn-warning btn-xs" title="Edit reservation">
|
||||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
||||||
@ -290,7 +290,7 @@
|
|||||||
<div class="panel-body text-muted">None</div>
|
<div class="panel-body text-muted">None</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_rackreservation %}
|
{% if perms.dcim.add_rackreservation %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:rack_add_reservation' rack=rack.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:rack_add_reservation' rack=rack.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
Add a reservation
|
Add a reservation
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="btn-group pull-right" role="group">
|
<div class="btn-group pull-right noprint" role="group">
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face=0 %}" class="btn btn-default{% if request.GET.face != '1' %} active{% endif %}">Front</a>
|
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face=0 %}" class="btn btn-default{% if request.GET.face != '1' %} active{% endif %}">Front</a>
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face=1 %}" class="btn btn-default{% if request.GET.face == '1' %} active{% endif %}">Rear</a>
|
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face=1 %}" class="btn btn-default{% if request.GET.face == '1' %} active{% endif %}">Rear</a>
|
||||||
</div>
|
</div>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<p>No racks found</p>
|
<p>No racks found</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_rack %}
|
{% if perms.dcim.add_rack %}
|
||||||
{% add_button 'dcim:rack_add' %}
|
{% add_button 'dcim:rack_add' %}
|
||||||
{% import_button 'dcim:rack_import' %}
|
{% import_button 'dcim:rack_import' %}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rack_bulk_edit' bulk_delete_url='dcim:rack_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rack_bulk_edit' bulk_delete_url='dcim:rack_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
{% include 'inc/tags_panel.html' %}
|
{% include 'inc/tags_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_rackgroup %}
|
{% if perms.dcim.add_rackgroup %}
|
||||||
{% add_button 'dcim:rackgroup_add' %}
|
{% add_button 'dcim:rackgroup_add' %}
|
||||||
{% import_button 'dcim:rackgroup_import' %}
|
{% import_button 'dcim:rackgroup_import' %}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:rackgroup_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:rackgroup_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rackreservation_bulk_edit' bulk_delete_url='dcim:rackreservation_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rackreservation_bulk_edit' bulk_delete_url='dcim:rackreservation_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_rackrole %}
|
{% if perms.dcim.add_rackrole %}
|
||||||
{% add_button 'dcim:rackrole_add' %}
|
{% add_button 'dcim:rackrole_add' %}
|
||||||
{% import_button 'dcim:rackrole_import' %}
|
{% import_button 'dcim:rackrole_import' %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_region %}
|
{% if perms.dcim.add_region %}
|
||||||
{% add_button 'dcim:region_add' %}
|
{% add_button 'dcim:region_add' %}
|
||||||
{% import_button 'dcim:region_import' %}
|
{% import_button 'dcim:region_import' %}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:region_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:region_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:site_list' %}">Sites</a></li>
|
<li><a href="{% url 'dcim:site_list' %}">Sites</a></li>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if show_graphs %}
|
{% if show_graphs %}
|
||||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ site.name }}" data-url="{% url 'dcim-api:site-graphs' pk=site.pk %}" title="Show graphs">
|
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ site.name }}" data-url="{% url 'dcim-api:site-graphs' pk=site.pk %}" title="Show graphs">
|
||||||
<i class="fa fa-signal" aria-hidden="true"></i>
|
<i class="fa fa-signal" aria-hidden="true"></i>
|
||||||
@ -138,7 +138,7 @@
|
|||||||
<td>Physical Address</td>
|
<td>Physical Address</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.physical_address %}
|
{% if site.physical_address %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
<a href="http://maps.google.com/?q={{ site.physical_address|oneline|urlencode }}" target="_blank" class="btn btn-primary btn-xs">
|
<a href="http://maps.google.com/?q={{ site.physical_address|oneline|urlencode }}" target="_blank" class="btn btn-primary btn-xs">
|
||||||
<i class="glyphicon glyphicon-map-marker"></i> Map it
|
<i class="glyphicon glyphicon-map-marker"></i> Map it
|
||||||
</a>
|
</a>
|
||||||
@ -157,7 +157,7 @@
|
|||||||
<td>GPS Coordinates</td>
|
<td>GPS Coordinates</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.latitude and site.longitude %}
|
{% if site.latitude and site.longitude %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
<a href="http://maps.google.com/?q={{ site.latitude }},{{ site.longitude }}" target="_blank" class="btn btn-primary btn-xs">
|
<a href="http://maps.google.com/?q={{ site.latitude }},{{ site.longitude }}" target="_blank" class="btn btn-primary btn-xs">
|
||||||
<i class="glyphicon glyphicon-map-marker"></i> Map it
|
<i class="glyphicon glyphicon-map-marker"></i> Map it
|
||||||
</a>
|
</a>
|
||||||
@ -251,7 +251,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><i class="fa fa-fw fa-folder-o"></i> <a href="{{ rg.get_absolute_url }}">{{ rg }}</a></td>
|
<td><i class="fa fa-fw fa-folder-o"></i> <a href="{{ rg.get_absolute_url }}">{{ rg }}</a></td>
|
||||||
<td>{{ rg.rack_count }}</td>
|
<td>{{ rg.rack_count }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}?group_id={{ rg.pk }}" class="btn btn-xs btn-primary" title="View elevations">
|
<a href="{% url 'dcim:rack_elevation_list' %}?group_id={{ rg.pk }}" class="btn btn-xs btn-primary" title="View elevations">
|
||||||
<i class="fa fa-eye"></i>
|
<i class="fa fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -271,7 +271,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'inc/image_attachments.html' with images=site.images.all %}
|
{% include 'inc/image_attachments.html' with images=site.images.all %}
|
||||||
{% if perms.extras.add_imageattachment %}
|
{% if perms.extras.add_imageattachment %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:site_add_image' object_id=site.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:site_add_image' object_id=site.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
Attach an image
|
Attach an image
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.dcim.add_site %}
|
{% if perms.dcim.add_site %}
|
||||||
{% add_button 'dcim:site_add' %}
|
{% add_button 'dcim:site_add' %}
|
||||||
{% import_button 'dcim:site_import' %}
|
{% import_button 'dcim:site_import' %}
|
||||||
@ -12,9 +12,9 @@
|
|||||||
<h1>{% block title %}Sites{% endblock %}</h1>
|
<h1>{% block title %}Sites{% endblock %}</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:site_bulk_edit' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:site_bulk_edit' bulk_delete_url='dcim:site_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
{% include 'inc/tags_panel.html' %}
|
{% include 'inc/tags_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% export_button content_type %}
|
{% export_button content_type %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Virtual Chassis{% endblock %}</h1>
|
<h1>{% block title %}Virtual Chassis{% endblock %}</h1>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' %}
|
{% include 'utilities/obj_table.html' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
{% include 'inc/tags_panel.html' %}
|
{% include 'inc/tags_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'extras:configcontext_list' %}">Config Contexts</a></li>
|
<li><a href="{% url 'extras:configcontext_list' %}">Config Contexts</a></li>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.extras.change_configcontext %}
|
{% if perms.extras.change_configcontext %}
|
||||||
<a href="{% url 'extras:configcontext_edit' pk=configcontext.pk %}" class="btn btn-warning">
|
<a href="{% url 'extras:configcontext_edit' pk=configcontext.pk %}" class="btn btn-warning">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.extras.add_configcontext %}
|
{% if perms.extras.add_configcontext %}
|
||||||
{% add_button 'extras:configcontext_add' %}
|
{% add_button 'extras:configcontext_add' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='extras:configcontext_bulk_edit' bulk_delete_url='extras:configcontext_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='extras:configcontext_bulk_edit' bulk_delete_url='extras:configcontext_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% block title %}{{ objectchange }}{% endblock %}
|
{% block title %}{{ objectchange }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'extras:objectchange_list' %}">Changelog</a></li>
|
<li><a href="{% url 'extras:objectchange_list' %}">Changelog</a></li>
|
||||||
@ -97,7 +97,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% include 'panel_table.html' with table=related_changes_table heading='Related Changes' %}
|
{% include 'panel_table.html' with table=related_changes_table heading='Related Changes' panel_class='noprint' %}
|
||||||
{% if related_changes_count > related_changes_table.rows|length %}
|
{% if related_changes_count > related_changes_table.rows|length %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'extras:objectchange_list' %}?request_id={{ objectchange.request_id }}" class="btn btn-primary">See all {{ related_changes_count|add:"1" }} changes</a>
|
<a href="{% url 'extras:objectchange_list' %}?request_id={{ objectchange.request_id }}" class="btn btn-primary">See all {{ related_changes_count|add:"1" }} changes</a>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load buttons %}
|
{% load buttons %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% export_button content_type %}
|
{% export_button content_type %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Changelog{% endblock %}</h1>
|
<h1>{% block title %}Changelog{% endblock %}</h1>
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' %}
|
{% include 'utilities/obj_table.html' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% block title %}{{ report.name }}{% endblock %}
|
{% block title %}{{ report.name }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'extras:report_list' %}">Reports</a></li>
|
<li><a href="{% url 'extras:report_list' %}">Reports</a></li>
|
||||||
@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if perms.extras.add_reportresult %}
|
{% if perms.extras.add_reportresult %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
<form action="{% url 'extras:report_run' name=report.full_name %}" method="post">
|
<form action="{% url 'extras:report_run' name=report.full_name %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ run_form }}
|
{{ run_form }}
|
||||||
|
@ -29,6 +29,12 @@
|
|||||||
Edit this tag
|
Edit this tag
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if perms.taggit.delete_tag %}
|
||||||
|
<a href="{% url 'extras:tag_delete' slug=tag.slug %}" class="btn btn-danger">
|
||||||
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||||
|
Delete this tag
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Tag: {{ tag }}{% endblock %}</h1>
|
<h1>{% block title %}Tag: {{ tag }}{% endblock %}</h1>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{{ attachment.size|filesizeformat }}</td>
|
<td>{{ attachment.size|filesizeformat }}</td>
|
||||||
<td>{{ attachment.created }}</td>
|
<td>{{ attachment.created }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
{% if perms.extras.change_imageattachment %}
|
{% if perms.extras.change_imageattachment %}
|
||||||
<a href="{% url 'extras:imageattachment_edit' pk=attachment.pk %}" class="btn btn-warning btn-xs" title="Edit image">
|
<a href="{% url 'extras:imageattachment_edit' pk=attachment.pk %}" class="btn btn-warning btn-xs" title="Edit image">
|
||||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
||||||
|
@ -20,9 +20,11 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<form method="get">
|
<form method="get">
|
||||||
{% for k, v in request.GET.items %}
|
{% for k, v_list in request.GET.lists %}
|
||||||
{% if k != 'per_page' %}
|
{% if k != 'per_page' %}
|
||||||
<input type="hidden" name="{{ k }}" value="{{ v }}" />
|
{% for v in v_list %}
|
||||||
|
<input type="hidden" name="{{ k }}" value="{{ v }}" />
|
||||||
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<select name="per_page" id="per_page">
|
<select name="per_page" id="per_page">
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="text-right">
|
<div class="text-right noprint">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<span class="fa fa-search" aria-hidden="true"></span> Apply
|
<span class="fa fa-search" aria-hidden="true"></span> Apply
|
||||||
</button>
|
</button>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></li>
|
<li><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></li>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.ipam.change_aggregate %}
|
{% if perms.ipam.change_aggregate %}
|
||||||
<a href="{% url 'ipam:aggregate_edit' pk=aggregate.pk %}" class="btn btn-warning">
|
<a href="{% url 'ipam:aggregate_edit' pk=aggregate.pk %}" class="btn btn-warning">
|
||||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="pull-right">
|
<div class="pull-right noprint">
|
||||||
{% if perms.ipam.add_aggregate %}
|
{% if perms.ipam.add_aggregate %}
|
||||||
{% add_button 'ipam:aggregate_add' %}
|
{% add_button 'ipam:aggregate_add' %}
|
||||||
{% import_button 'ipam:aggregate_import' %}
|
{% import_button 'ipam:aggregate_import' %}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:aggregate_bulk_edit' bulk_delete_url='ipam:aggregate_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:aggregate_bulk_edit' bulk_delete_url='ipam:aggregate_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3 noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
{% include 'inc/tags_panel.html' %}
|
{% include 'inc/tags_panel.html' %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ service.description }}</td>
|
<td>{{ service.description }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right noprint">
|
||||||
<a href="{% url 'ipam:service_changelog' pk=service.pk %}" class="btn btn-default btn-xs" title="Changelog">
|
<a href="{% url 'ipam:service_changelog' pk=service.pk %}" class="btn btn-default btn-xs" title="Changelog">
|
||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</a>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user