mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-19 09:53:34 -06:00
commit
af5dba2e0d
@ -1,39 +1,52 @@
|
|||||||
# Contributing to NetBox
|
## Getting Help
|
||||||
|
|
||||||
Thank you for your interest in contributing to NetBox! This document contains some quick pointers on reporting bugs and
|
If you encounter any issues installing or using NetBox, try one of the following resources to get assistance. Please
|
||||||
requesting new features.
|
**do not** open an issue on GitHub except to report bugs or request features.
|
||||||
|
|
||||||
## Reporting Issues
|
### Freenode IRC
|
||||||
|
|
||||||
* First, ensure that you've installed the latest stable version of NetBox. If you're running an older version, it's
|
Join the #netbox channel on [Freenode IRC](https://freenode.net/). You can connect to Freenode at irc.freenode.net using
|
||||||
possible that the bug has already been fixed.
|
an IRC client, or you can use their [webchat client](https://webchat.freenode.net/).
|
||||||
|
|
||||||
* Check the [issues list](https://github.com/digitalocean/netbox/issues) to see if the bug you've found has already been
|
### Reddit
|
||||||
reported. If you think you may be experiencing a reported issue, please add a quick comment to it with a "+1" and a
|
|
||||||
quick description of how it's affecting your installation.
|
|
||||||
|
|
||||||
* If you're having trouble installing NetBox, please join #netbox on irc.freenode.net and ask for help before creating
|
We have established [/r/netbox](https://www.reddit.com/r/netbox) on Reddit for NetBox issues and general discussion.
|
||||||
an issue on GitHub. Many installation problems are simple fixes. The issues list should be reserved for bug reports and
|
Reddit registration is free and does not require providing an email address (although it is encouraged).
|
||||||
feature requests.
|
|
||||||
|
|
||||||
* When submitting an issue, please be as descriptive as possible. Be sure to describe:
|
## Reporting Bugs
|
||||||
|
|
||||||
|
* First, ensure that you've installed the [latest stable version](https://github.com/digitalocean/netbox/releases) of
|
||||||
|
NetBox. If you're running an older version, it's possible that the bug has already been fixed.
|
||||||
|
|
||||||
|
* Next, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the bug you've found has
|
||||||
|
already been reported. If you think you may be experiencing a reported issue that hasn't already been resolved, please
|
||||||
|
click "add a reaction" in the top right corner of the issue and add a thumbs up (+1). You might also want to add a
|
||||||
|
comment describing how it's affecting your installation. This will allow us to prioritize bugs based on how many users
|
||||||
|
are affected.
|
||||||
|
|
||||||
|
* If you haven't found an existing issue that describes your suspected bug, please inquire about it on IRC or Reddit.
|
||||||
|
**Do not** file an issue until you have received confirmation that it is in fact a bug. Invalid issues are very
|
||||||
|
distracting and slow the pace at which NetBox is developed.
|
||||||
|
|
||||||
|
* When submitting an issue, please be as descriptive as possible. Be sure to include:
|
||||||
|
|
||||||
* The environment in which NetBox is running
|
* The environment in which NetBox is running
|
||||||
* The exact steps that can be taken to reproduce the issue (if applicable)
|
* The exact steps that can be taken to reproduce the issue (if applicable)
|
||||||
* Any error messages returned
|
* Any error messages returned
|
||||||
|
* Screenshots (if applicable)
|
||||||
|
|
||||||
* Keep in mind that we prioritize bugs based on their severity and how much work is required to resolve them. It may
|
* Keep in mind that we prioritize bugs based on their severity and how much work is required to resolve them. It may
|
||||||
take some time for someone to address your issue. If it's been longer than a week with no updates, please ping us on
|
take some time for someone to address your issue.
|
||||||
IRC.
|
|
||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
|
|
||||||
* First, check the [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're requesting
|
* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're
|
||||||
has already been requested (and possibly rejected). If it has, click "add a reaction" in the top right corner of the
|
requesting is already listed. (Be sure to search closed issues as well, since some feature requests are rejected.) If
|
||||||
issue and add a thumbs up (+1). This ensures that the issue has a better chance of making it onto the roadmap. Also feel
|
the feature you'd like to see has already been requested, click "add a reaction" in the top right corner of the issue
|
||||||
free to add a comment with any additional justification for the feature.
|
and add a thumbs up (+1). This ensures that the issue has a better chance of making it onto the roadmap. Also feel free
|
||||||
|
to add a comment with any additional justification for the feature.
|
||||||
|
|
||||||
* While discussion of new features is welcome, it's important to limit the scope of NetBox's feature set to avoid
|
* While suggestions for new features are welcome, it's important to limit the scope of NetBox's feature set to avoid
|
||||||
feature creep. For example, the following features would be firmly out of scope for NetBox:
|
feature creep. For example, the following features would be firmly out of scope for NetBox:
|
||||||
|
|
||||||
* Ticket management
|
* Ticket management
|
||||||
@ -41,17 +54,18 @@ feature creep. For example, the following features would be firmly out of scope
|
|||||||
* Acting as a DNS server
|
* Acting as a DNS server
|
||||||
* Acting as an authentication server
|
* Acting as an authentication server
|
||||||
|
|
||||||
* Feature requests must be very narrowly defined. The more effort you put into writing a feature request, the better its
|
* Before filing a new feature request, propose it on IRC or Reddit first. Feedback you receive there will help validate
|
||||||
chances are of being implemented. Overly broad feature requests will be closed.
|
and shape the proposed feature before filing a formal issue.
|
||||||
|
|
||||||
* If you're not sure whether the feature you want is a good fit for NetBox, please ask in #netbox on irc.freenode.net.
|
* Good feature requests are very narrowly defined. Be sure to enumerate specific functionality and data schema. The more
|
||||||
Even if it's not quite right for NetBox, we may be able to point you to a tool better suited for the job.
|
effort you put into writing a feature request, the better its chances are of being implemented. Overly broad feature
|
||||||
|
requests will be closed.
|
||||||
|
|
||||||
* When submitting a feature request, be sure to include the following:
|
* When submitting a feature request on GitHub, be sure to include the following:
|
||||||
|
|
||||||
* A detailed description of the functionality
|
* A detailed description of the proposed functionality
|
||||||
* A use case for the feature; who would use it and what value it would add to NetBox
|
* A use case for the feature; who would use it and what value it would add to NetBox
|
||||||
* A rough description of any changes necessary to the database schema (if applicable)
|
* A rough description of any changes necessary to the database schema
|
||||||
* Any third-party libraries or other resources which would be involved
|
* Any third-party libraries or other resources which would be involved
|
||||||
|
|
||||||
## Submitting Pull Requests
|
## Submitting Pull Requests
|
||||||
@ -60,9 +74,8 @@ Even if it's not quite right for NetBox, we may be able to point you to a tool b
|
|||||||
before beginning work. This will help prevent wasting time on something that might we might not be able to implement.
|
before beginning work. This will help prevent wasting time on something that might we might not be able to implement.
|
||||||
When suggesting a new feature, also make sure it won't conflict with any work that's already in progress.
|
When suggesting a new feature, also make sure it won't conflict with any work that's already in progress.
|
||||||
|
|
||||||
* When submitting a pull request, please be sure to work off of branch `develop`, rather than branch `master`.
|
* When submitting a pull request, please be sure to work off of the `develop` branch, rather than `master`. In NetBox,
|
||||||
In NetBox, the `develop` branch is used for ongoing development, while `master` is used for tagging new
|
the `develop` branch is used for ongoing development, while `master` is used for tagging new stable releases.
|
||||||
stable releases.
|
|
||||||
|
|
||||||
* All code submissions should meet the following criteria (CI will enforce these checks):
|
* All code submissions should meet the following criteria (CI will enforce these checks):
|
||||||
|
|
||||||
|
@ -25,6 +25,6 @@ Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net*
|
|||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
Please see docs/getting-started.md for instructions on installing NetBox.
|
Please see [the documentation](http://netbox.readthedocs.io/en/latest/) for instructions on installing NetBox.
|
||||||
|
|
||||||
To upgrade NetBox, please download the [latest release](https://github.com/digitalocean/netbox/releases) and run `upgrade.sh`.
|
To upgrade NetBox, please download the [latest release](https://github.com/digitalocean/netbox/releases) and run `upgrade.sh`.
|
||||||
|
@ -2,7 +2,7 @@ NetBox's local configuration is held in `netbox/netbox/configuration.py`. An exa
|
|||||||
|
|
||||||
## ALLOWED_HOSTS
|
## ALLOWED_HOSTS
|
||||||
|
|
||||||
This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
|
This is a list of valid fully-qualified domain names (FQDNs) that is used to reach the NetBox service. Usually this is the same as the hostname for the NetBox server, but can also be different (e.g. when using a reverse proxy serving the NetBox website under a different FQDN than the hostname of the NetBox server). NetBox will not permit access to the server via any other hostnames (or IPs). The value of this option is also used to set `CSRF_TRUSTED_ORIGINS`, which restricts `HTTP POST` to the same set of hosts (more about this [here](https://docs.djangoproject.com/en/1.9/ref/settings/#std:setting-CSRF_TRUSTED_ORIGINS)). Keep in mind that NetBox, by default, has `USE_X_FORWARDED_HOST = True` (in `netbox/netbox/settings.py`) which means that if you're using a reverse proxy, it's the FQDN used to reach that reverse proxy which needs to be in this list (more about this [here](https://docs.djangoproject.com/en/1.9/ref/settings/#allowed-hosts)).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -50,4 +50,4 @@ NetBox is built on the [Django](https://djangoproject.com/) Python framework and
|
|||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
See the [getting started](getting-started.md) guide for help with getting NetBox up and running quickly.
|
See the [installation guide](installation/postgresql.md) for help getting NetBox up and running quickly.
|
||||||
|
@ -3,8 +3,7 @@ from django.db.models import Count
|
|||||||
|
|
||||||
from dcim.models import Site, Device, Interface, Rack, IFACE_FF_VIRTUAL
|
from dcim.models import Site, Device, Interface, Rack, IFACE_FF_VIRTUAL
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, BootstrapMixin, BulkImportForm, CommentField, ConfirmationForm, CSVDataField, Livesearch, SmallTextarea,
|
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, Livesearch, SmallTextarea, SlugField,
|
||||||
SlugField,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import Circuit, CircuitType, Provider
|
from .models import Circuit, CircuitType, Provider
|
||||||
@ -55,10 +54,6 @@ class ProviderBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
|
||||||
class ProviderBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def provider_site_choices():
|
def provider_site_choices():
|
||||||
site_choices = Site.objects.all()
|
site_choices = Site.objects.all()
|
||||||
return [(s.slug, s.name) for s in site_choices]
|
return [(s.slug, s.name) for s in site_choices]
|
||||||
@ -81,10 +76,6 @@ class CircuitTypeForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class CircuitTypeBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=CircuitType.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Circuits
|
# Circuits
|
||||||
#
|
#
|
||||||
@ -191,10 +182,6 @@ class CircuitBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
|
||||||
class CircuitBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def circuit_type_choices():
|
def circuit_type_choices():
|
||||||
type_choices = CircuitType.objects.annotate(circuit_count=Count('circuits'))
|
type_choices = CircuitType.objects.annotate(circuit_count=Count('circuits'))
|
||||||
return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in type_choices]
|
return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in type_choices]
|
||||||
|
@ -76,7 +76,6 @@ class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'circuits.delete_provider'
|
permission_required = 'circuits.delete_provider'
|
||||||
cls = Provider
|
cls = Provider
|
||||||
form = forms.ProviderBulkDeleteForm
|
|
||||||
default_redirect_url = 'circuits:provider_list'
|
default_redirect_url = 'circuits:provider_list'
|
||||||
|
|
||||||
|
|
||||||
@ -102,7 +101,6 @@ class CircuitTypeEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'circuits.delete_circuittype'
|
permission_required = 'circuits.delete_circuittype'
|
||||||
cls = CircuitType
|
cls = CircuitType
|
||||||
form = forms.CircuitTypeBulkDeleteForm
|
|
||||||
default_redirect_url = 'circuits:circuittype_list'
|
default_redirect_url = 'circuits:circuittype_list'
|
||||||
|
|
||||||
|
|
||||||
@ -171,5 +169,4 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'circuits.delete_circuit'
|
permission_required = 'circuits.delete_circuit'
|
||||||
cls = Circuit
|
cls = Circuit
|
||||||
form = forms.CircuitBulkDeleteForm
|
|
||||||
default_redirect_url = 'circuits:circuit_list'
|
default_redirect_url = 'circuits:circuit_list'
|
||||||
|
@ -78,8 +78,8 @@ class DeviceTypeAdmin(admin.ModelAdmin):
|
|||||||
InterfaceTemplateAdmin,
|
InterfaceTemplateAdmin,
|
||||||
DeviceBayTemplateAdmin,
|
DeviceBayTemplateAdmin,
|
||||||
]
|
]
|
||||||
list_display = ['model', 'manufacturer', 'slug', 'u_height', 'console_ports', 'console_server_ports', 'power_ports',
|
list_display = ['model', 'manufacturer', 'slug', 'part_number', 'u_height', 'console_ports', 'console_server_ports',
|
||||||
'power_outlets', 'interfaces', 'device_bays']
|
'power_ports', 'power_outlets', 'interfaces', 'device_bays']
|
||||||
list_filter = ['manufacturer']
|
list_filter = ['manufacturer']
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
|
@ -111,8 +111,8 @@ class DeviceTypeSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = ['id', 'manufacturer', 'model', 'slug', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
fields = ['id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
||||||
'is_network_device']
|
'is_console_server', 'is_pdu', 'is_network_device']
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeNestedSerializer(DeviceTypeSerializer):
|
class DeviceTypeNestedSerializer(DeviceTypeSerializer):
|
||||||
@ -164,9 +164,9 @@ class DeviceTypeDetailSerializer(DeviceTypeSerializer):
|
|||||||
interface_templates = InterfaceTemplateNestedSerializer(many=True, read_only=True)
|
interface_templates = InterfaceTemplateNestedSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta(DeviceTypeSerializer.Meta):
|
class Meta(DeviceTypeSerializer.Meta):
|
||||||
fields = ['id', 'manufacturer', 'model', 'slug', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
fields = ['id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
||||||
'is_network_device', 'console_port_templates', 'cs_port_templates', 'power_port_templates',
|
'is_console_server', 'is_pdu', 'is_network_device', 'console_port_templates', 'cs_port_templates',
|
||||||
'power_outlet_templates', 'interface_templates']
|
'power_port_templates', 'power_outlet_templates', 'interface_templates']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -419,53 +419,36 @@ class RelatedConnectionsView(APIView):
|
|||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise MissingFilterException(detail='Must specify search parameters (peer-device and peer-interface).')
|
raise MissingFilterException(detail='Must specify search parameters "peer-device" and "peer-interface".')
|
||||||
|
|
||||||
# Initialize response skeleton
|
# Initialize response skeleton
|
||||||
response = dict()
|
response = {
|
||||||
response['device'] = serializers.DeviceSerializer(device).data
|
'device': serializers.DeviceSerializer(device).data,
|
||||||
response['console-ports'] = []
|
'console-ports': [],
|
||||||
response['power-ports'] = []
|
'power-ports': [],
|
||||||
response['interfaces'] = []
|
'interfaces': [],
|
||||||
|
}
|
||||||
|
|
||||||
# Build console connections
|
# Console connections
|
||||||
console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device')
|
console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device')
|
||||||
for cp in console_ports:
|
for cp in console_ports:
|
||||||
cp_info = dict()
|
data = serializers.ConsolePortSerializer(instance=cp).data
|
||||||
cp_info['name'] = cp.name
|
del(data['device'])
|
||||||
if cp.cs_port:
|
response['console-ports'].append(data)
|
||||||
cp_info['console-server'] = cp.cs_port.device.name
|
|
||||||
cp_info['port'] = cp.cs_port.name
|
|
||||||
else:
|
|
||||||
cp_info['console-server'] = None
|
|
||||||
cp_info['port'] = None
|
|
||||||
response['console-ports'].append(cp_info)
|
|
||||||
|
|
||||||
# Build power connections
|
# Power connections
|
||||||
power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device')
|
power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device')
|
||||||
for pp in power_ports:
|
for pp in power_ports:
|
||||||
pp_info = dict()
|
data = serializers.PowerPortSerializer(instance=pp).data
|
||||||
pp_info['name'] = pp.name
|
del(data['device'])
|
||||||
if pp.power_outlet:
|
response['power-ports'].append(data)
|
||||||
pp_info['pdu'] = pp.power_outlet.device.name
|
|
||||||
pp_info['outlet'] = pp.power_outlet.name
|
|
||||||
else:
|
|
||||||
pp_info['pdu'] = None
|
|
||||||
pp_info['outlet'] = None
|
|
||||||
response['power-ports'].append(pp_info)
|
|
||||||
|
|
||||||
# Built interface connections
|
# Interface connections
|
||||||
interfaces = Interface.objects.filter(device=device)
|
interfaces = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b',
|
||||||
|
'circuit')
|
||||||
for iface in interfaces:
|
for iface in interfaces:
|
||||||
iface_info = dict()
|
data = serializers.InterfaceDetailSerializer(instance=iface).data
|
||||||
iface_info['name'] = iface.name
|
del(data['device'])
|
||||||
peer_interface = iface.get_connected_interface()
|
response['interfaces'].append(data)
|
||||||
if peer_interface:
|
|
||||||
iface_info['device'] = peer_interface.device.name
|
|
||||||
iface_info['interface'] = peer_interface.name
|
|
||||||
else:
|
|
||||||
iface_info['device'] = None
|
|
||||||
iface_info['interface'] = None
|
|
||||||
response['interfaces'].append(iface_info)
|
|
||||||
|
|
||||||
return Response(response)
|
return Response(response)
|
||||||
|
@ -102,7 +102,7 @@ class DeviceTypeFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = ['manufacturer_id', 'manufacturer', 'model', 'u_height', 'is_console_server', 'is_pdu',
|
fields = ['manufacturer_id', 'manufacturer', 'model', 'part_number', 'u_height', 'is_console_server', 'is_pdu',
|
||||||
'is_network_device']
|
'is_network_device']
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from django.db.models import Count, Q
|
|||||||
|
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, BootstrapMixin, BulkImportForm, CommentField, ConfirmationForm, CSVDataField, ExpandableNameField,
|
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
|
||||||
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -85,10 +85,6 @@ class RackGroupForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['site', 'name', 'slug']
|
fields = ['site', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class RackGroupBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=RackGroup.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def rackgroup_site_choices():
|
def rackgroup_site_choices():
|
||||||
site_choices = Site.objects.annotate(rack_count=Count('rack_groups'))
|
site_choices = Site.objects.annotate(rack_count=Count('rack_groups'))
|
||||||
return [(s.slug, u'{} ({})'.format(s.name, s.rack_count)) for s in site_choices]
|
return [(s.slug, u'{} ({})'.format(s.name, s.rack_count)) for s in site_choices]
|
||||||
@ -169,10 +165,6 @@ class RackBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
|
||||||
class RackBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def rack_site_choices():
|
def rack_site_choices():
|
||||||
site_choices = Site.objects.annotate(rack_count=Count('racks'))
|
site_choices = Site.objects.annotate(rack_count=Count('racks'))
|
||||||
return [(s.slug, u'{} ({})'.format(s.name, s.rack_count)) for s in site_choices]
|
return [(s.slug, u'{} ({})'.format(s.name, s.rack_count)) for s in site_choices]
|
||||||
@ -202,10 +194,6 @@ class ManufacturerForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class ManufacturerBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Manufacturer.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device types
|
# Device types
|
||||||
#
|
#
|
||||||
@ -215,7 +203,7 @@ class DeviceTypeForm(forms.ModelForm, BootstrapMixin):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = ['manufacturer', 'model', 'slug', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
fields = ['manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
||||||
'is_network_device', 'subdevice_role']
|
'is_network_device', 'subdevice_role']
|
||||||
|
|
||||||
|
|
||||||
@ -225,10 +213,6 @@ class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
u_height = forms.IntegerField(min_value=1, required=False)
|
u_height = forms.IntegerField(min_value=1, required=False)
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def devicetype_manufacturer_choices():
|
def devicetype_manufacturer_choices():
|
||||||
manufacturer_choices = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
|
manufacturer_choices = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
|
||||||
return [(m.slug, u'{} ({})'.format(m.name, m.devicetype_count)) for m in manufacturer_choices]
|
return [(m.slug, u'{} ({})'.format(m.name, m.devicetype_count)) for m in manufacturer_choices]
|
||||||
@ -303,10 +287,6 @@ class DeviceRoleForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['name', 'slug', 'color']
|
fields = ['name', 'slug', 'color']
|
||||||
|
|
||||||
|
|
||||||
class DeviceRoleBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=DeviceRole.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Platforms
|
# Platforms
|
||||||
#
|
#
|
||||||
@ -319,10 +299,6 @@ class PlatformForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class PlatformBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Platform.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Devices
|
# Devices
|
||||||
#
|
#
|
||||||
@ -542,10 +518,6 @@ class DeviceBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
serial = forms.CharField(max_length=50, required=False, label='Serial Number')
|
serial = forms.CharField(max_length=50, required=False, label='Serial Number')
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def device_site_choices():
|
def device_site_choices():
|
||||||
site_choices = Site.objects.annotate(device_count=Count('racks__devices'))
|
site_choices = Site.objects.annotate(device_count=Count('racks__devices'))
|
||||||
return [(s.slug, u'{} ({})'.format(s.name, s.device_count)) for s in site_choices]
|
return [(s.slug, u'{} ({})'.format(s.name, s.device_count)) for s in site_choices]
|
||||||
|
20
netbox/dcim/migrations/0011_devicetype_part_number.py
Normal file
20
netbox/dcim/migrations/0011_devicetype_part_number.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.8 on 2016-07-26 15:05
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0010_devicebay_installed_device_set_null'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicetype',
|
||||||
|
name='part_number',
|
||||||
|
field=models.CharField(blank=True, help_text=b'Discrete part number (optional)', max_length=50),
|
||||||
|
),
|
||||||
|
]
|
@ -409,6 +409,7 @@ class DeviceType(models.Model):
|
|||||||
manufacturer = models.ForeignKey('Manufacturer', related_name='device_types', on_delete=models.PROTECT)
|
manufacturer = models.ForeignKey('Manufacturer', related_name='device_types', on_delete=models.PROTECT)
|
||||||
model = models.CharField(max_length=50)
|
model = models.CharField(max_length=50)
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
|
part_number = models.CharField(max_length=50, blank=True, help_text="Discrete part number (optional)")
|
||||||
u_height = models.PositiveSmallIntegerField(verbose_name='Height (U)', default=1)
|
u_height = models.PositiveSmallIntegerField(verbose_name='Height (U)', default=1)
|
||||||
is_full_depth = models.BooleanField(default=True, verbose_name="Is full depth",
|
is_full_depth = models.BooleanField(default=True, verbose_name="Is full depth",
|
||||||
help_text="Device consumes both front and rear rack faces")
|
help_text="Device consumes both front and rear rack faces")
|
||||||
|
@ -102,13 +102,25 @@ class RackTable(BaseTable):
|
|||||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||||
facility_id = tables.Column(verbose_name='Facility ID')
|
facility_id = tables.Column(verbose_name='Facility ID')
|
||||||
u_height = tables.Column(verbose_name='Height (U)')
|
u_height = tables.Column(verbose_name='Height (U)')
|
||||||
|
devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
|
||||||
u_consumed = tables.Column(accessor=Accessor('u_consumed'), verbose_name='Used (U)')
|
u_consumed = tables.Column(accessor=Accessor('u_consumed'), verbose_name='Used (U)')
|
||||||
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
|
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
|
||||||
devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ('pk', 'name', 'site', 'group', 'facility_id', 'u_height', 'devices')
|
fields = ('pk', 'name', 'site', 'group', 'facility_id', 'u_height', 'devices', 'u_consumed', 'utilization')
|
||||||
|
|
||||||
|
|
||||||
|
class RackImportTable(BaseTable):
|
||||||
|
name = tables.LinkColumn('dcim:rack', args=[Accessor('pk')], verbose_name='Name')
|
||||||
|
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||||
|
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||||
|
facility_id = tables.Column(verbose_name='Facility ID')
|
||||||
|
u_height = tables.Column(verbose_name='Height (U)')
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = Rack
|
||||||
|
fields = ('site', 'group', 'name', 'facility_id', 'u_height')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -133,93 +145,77 @@ class ManufacturerTable(BaseTable):
|
|||||||
|
|
||||||
class DeviceTypeTable(BaseTable):
|
class DeviceTypeTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
manufacturer = tables.Column(verbose_name='Manufacturer')
|
||||||
model = tables.LinkColumn('dcim:devicetype', args=[Accessor('pk')], verbose_name='Device Type')
|
model = tables.LinkColumn('dcim:devicetype', args=[Accessor('pk')], verbose_name='Device Type')
|
||||||
|
part_number = tables.Column(verbose_name='Part Number')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = ('pk', 'model', 'manufacturer', 'u_height')
|
fields = ('pk', 'model', 'manufacturer', 'part_number', 'u_height')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device type components
|
# Device type components
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePortTemplateTable(tables.Table):
|
class ConsolePortTemplateTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
|
||||||
class Meta:
|
class Meta(BaseTable.Meta):
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = ('pk', 'name')
|
fields = ('pk', 'name')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
show_header = False
|
show_header = False
|
||||||
attrs = {
|
|
||||||
'class': 'table table-hover',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateTable(tables.Table):
|
class ConsoleServerPortTemplateTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
|
||||||
class Meta:
|
class Meta(BaseTable.Meta):
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = ('pk', 'name')
|
fields = ('pk', 'name')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
show_header = False
|
show_header = False
|
||||||
attrs = {
|
|
||||||
'class': 'table table-hover',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateTable(tables.Table):
|
class PowerPortTemplateTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
|
||||||
class Meta:
|
class Meta(BaseTable.Meta):
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = ('pk', 'name')
|
fields = ('pk', 'name')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
show_header = False
|
show_header = False
|
||||||
attrs = {
|
|
||||||
'class': 'table table-hover',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateTable(tables.Table):
|
class PowerOutletTemplateTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
|
||||||
class Meta:
|
class Meta(BaseTable.Meta):
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
fields = ('pk', 'name')
|
fields = ('pk', 'name')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
show_header = False
|
show_header = False
|
||||||
attrs = {
|
|
||||||
'class': 'table table-hover',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateTable(tables.Table):
|
class InterfaceTemplateTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
|
||||||
class Meta:
|
class Meta(BaseTable.Meta):
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = ('pk', 'name')
|
fields = ('pk', 'name', 'form_factor')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
show_header = False
|
show_header = False
|
||||||
attrs = {
|
|
||||||
'class': 'table table-hover panel-body',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateTable(tables.Table):
|
class DeviceBayTemplateTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
|
||||||
class Meta:
|
class Meta(BaseTable.Meta):
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
fields = ('pk', 'name')
|
fields = ('pk', 'name')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
show_header = False
|
show_header = False
|
||||||
attrs = {
|
|
||||||
'class': 'table table-hover panel-body',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -204,6 +204,7 @@ class DeviceTypeTest(APITestCase):
|
|||||||
'manufacturer',
|
'manufacturer',
|
||||||
'model',
|
'model',
|
||||||
'slug',
|
'slug',
|
||||||
|
'part_number',
|
||||||
'u_height',
|
'u_height',
|
||||||
'is_full_depth',
|
'is_full_depth',
|
||||||
'is_console_server',
|
'is_console_server',
|
||||||
|
@ -50,31 +50,29 @@ urlpatterns = [
|
|||||||
url(r'^device-types/(?P<pk>\d+)/edit/$', views.DeviceTypeEditView.as_view(), name='devicetype_edit'),
|
url(r'^device-types/(?P<pk>\d+)/edit/$', views.DeviceTypeEditView.as_view(), name='devicetype_edit'),
|
||||||
url(r'^device-types/(?P<pk>\d+)/delete/$', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
|
url(r'^device-types/(?P<pk>\d+)/delete/$', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
|
||||||
|
|
||||||
# Component templates
|
# Console port templates
|
||||||
url(r'^device-types/(?P<pk>\d+)/console-ports/add/$', views.ConsolePortTemplateAddView.as_view(),
|
url(r'^device-types/(?P<pk>\d+)/console-ports/add/$', views.ConsolePortTemplateAddView.as_view(), name='devicetype_add_consoleport'),
|
||||||
name='devicetype_add_consoleport'),
|
url(r'^device-types/(?P<pk>\d+)/console-ports/delete/$', views.ConsolePortTemplateBulkDeleteView.as_view(), name='devicetype_delete_consoleport'),
|
||||||
url(r'^device-types/(?P<pk>\d+)/console-ports/delete/$', views.component_template_delete,
|
|
||||||
{'model': ConsolePortTemplate}, name='devicetype_delete_consoleport'),
|
# Console server port templates
|
||||||
url(r'^device-types/(?P<pk>\d+)/console-server-ports/add/$', views.ConsoleServerPortTemplateAddView.as_view(),
|
url(r'^device-types/(?P<pk>\d+)/console-server-ports/add/$', views.ConsoleServerPortTemplateAddView.as_view(), name='devicetype_add_consoleserverport'),
|
||||||
name='devicetype_add_consoleserverport'),
|
url(r'^device-types/(?P<pk>\d+)/console-server-ports/delete/$', views.ConsoleServerPortTemplateBulkDeleteView.as_view(), name='devicetype_delete_consoleserverport'),
|
||||||
url(r'^device-types/(?P<pk>\d+)/console-server-ports/delete/$', views.component_template_delete,
|
|
||||||
{'model': ConsoleServerPortTemplate}, name='devicetype_delete_consoleserverport'),
|
# Power port templates
|
||||||
url(r'^device-types/(?P<pk>\d+)/power-ports/add/$', views.PowerPortTemplateAddView.as_view(),
|
url(r'^device-types/(?P<pk>\d+)/power-ports/add/$', views.PowerPortTemplateAddView.as_view(), name='devicetype_add_powerport'),
|
||||||
name='devicetype_add_powerport'),
|
url(r'^device-types/(?P<pk>\d+)/power-ports/delete/$', views.PowerPortTemplateBulkDeleteView.as_view(), name='devicetype_delete_powerport'),
|
||||||
url(r'^device-types/(?P<pk>\d+)/power-ports/delete/$', views.component_template_delete,
|
|
||||||
{'model': PowerPortTemplate}, name='devicetype_delete_powerport'),
|
# Power outlet templates
|
||||||
url(r'^device-types/(?P<pk>\d+)/power-outlets/add/$', views.PowerOutletTemplateAddView.as_view(),
|
url(r'^device-types/(?P<pk>\d+)/power-outlets/add/$', views.PowerOutletTemplateAddView.as_view(), name='devicetype_add_poweroutlet'),
|
||||||
name='devicetype_add_poweroutlet'),
|
url(r'^device-types/(?P<pk>\d+)/power-outlets/delete/$', views.PowerOutletTemplateBulkDeleteView.as_view(), name='devicetype_delete_poweroutlet'),
|
||||||
url(r'^device-types/(?P<pk>\d+)/power-outlets/delete/$', views.component_template_delete,
|
|
||||||
{'model': PowerOutletTemplate}, name='devicetype_delete_poweroutlet'),
|
# Interface templates
|
||||||
url(r'^device-types/(?P<pk>\d+)/interfaces/add/$', views.InterfaceTemplateAddView.as_view(),
|
url(r'^device-types/(?P<pk>\d+)/interfaces/add/$', views.InterfaceTemplateAddView.as_view(), name='devicetype_add_interface'),
|
||||||
name='devicetype_add_interface'),
|
url(r'^device-types/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceTemplateBulkDeleteView.as_view(), name='devicetype_delete_interface'),
|
||||||
url(r'^device-types/(?P<pk>\d+)/interfaces/delete/$', views.component_template_delete,
|
|
||||||
{'model': InterfaceTemplate}, name='devicetype_delete_interface'),
|
# Device bay templates
|
||||||
url(r'^device-types/(?P<pk>\d+)/device-bays/add/$', views.DeviceBayTemplateAddView.as_view(),
|
url(r'^device-types/(?P<pk>\d+)/device-bays/add/$', views.DeviceBayTemplateAddView.as_view(), name='devicetype_add_devicebay'),
|
||||||
name='devicetype_add_devicebay'),
|
url(r'^device-types/(?P<pk>\d+)/device-bays/delete/$', views.DeviceBayTemplateBulkDeleteView.as_view(), name='devicetype_delete_devicebay'),
|
||||||
url(r'^device-types/(?P<pk>\d+)/device-bays/delete/$', views.component_template_delete,
|
|
||||||
{'model': DeviceBayTemplate}, name='devicetype_delete_devicebay'),
|
|
||||||
|
|
||||||
# Device roles
|
# Device roles
|
||||||
url(r'^device-roles/$', views.DeviceRoleListView.as_view(), name='devicerole_list'),
|
url(r'^device-roles/$', views.DeviceRoleListView.as_view(), name='devicerole_list'),
|
||||||
@ -105,6 +103,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Console ports
|
# Console ports
|
||||||
url(r'^devices/(?P<pk>\d+)/console-ports/add/$', views.consoleport_add, name='consoleport_add'),
|
url(r'^devices/(?P<pk>\d+)/console-ports/add/$', views.consoleport_add, name='consoleport_add'),
|
||||||
|
url(r'^devices/(?P<pk>\d+)/console-ports/delete/$', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'),
|
||||||
url(r'^console-ports/(?P<pk>\d+)/connect/$', views.consoleport_connect, name='consoleport_connect'),
|
url(r'^console-ports/(?P<pk>\d+)/connect/$', views.consoleport_connect, name='consoleport_connect'),
|
||||||
url(r'^console-ports/(?P<pk>\d+)/disconnect/$', views.consoleport_disconnect, name='consoleport_disconnect'),
|
url(r'^console-ports/(?P<pk>\d+)/disconnect/$', views.consoleport_disconnect, name='consoleport_disconnect'),
|
||||||
url(r'^console-ports/(?P<pk>\d+)/edit/$', views.consoleport_edit, name='consoleport_edit'),
|
url(r'^console-ports/(?P<pk>\d+)/edit/$', views.consoleport_edit, name='consoleport_edit'),
|
||||||
@ -112,6 +111,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Console server ports
|
# Console server ports
|
||||||
url(r'^devices/(?P<pk>\d+)/console-server-ports/add/$', views.consoleserverport_add, name='consoleserverport_add'),
|
url(r'^devices/(?P<pk>\d+)/console-server-ports/add/$', views.consoleserverport_add, name='consoleserverport_add'),
|
||||||
|
url(r'^devices/(?P<pk>\d+)/console-server-ports/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'),
|
||||||
url(r'^console-server-ports/(?P<pk>\d+)/connect/$', views.consoleserverport_connect, name='consoleserverport_connect'),
|
url(r'^console-server-ports/(?P<pk>\d+)/connect/$', views.consoleserverport_connect, name='consoleserverport_connect'),
|
||||||
url(r'^console-server-ports/(?P<pk>\d+)/disconnect/$', views.consoleserverport_disconnect, name='consoleserverport_disconnect'),
|
url(r'^console-server-ports/(?P<pk>\d+)/disconnect/$', views.consoleserverport_disconnect, name='consoleserverport_disconnect'),
|
||||||
url(r'^console-server-ports/(?P<pk>\d+)/edit/$', views.consoleserverport_edit, name='consoleserverport_edit'),
|
url(r'^console-server-ports/(?P<pk>\d+)/edit/$', views.consoleserverport_edit, name='consoleserverport_edit'),
|
||||||
@ -119,6 +119,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Power ports
|
# Power ports
|
||||||
url(r'^devices/(?P<pk>\d+)/power-ports/add/$', views.powerport_add, name='powerport_add'),
|
url(r'^devices/(?P<pk>\d+)/power-ports/add/$', views.powerport_add, name='powerport_add'),
|
||||||
|
url(r'^devices/(?P<pk>\d+)/power-ports/delete/$', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'),
|
||||||
url(r'^power-ports/(?P<pk>\d+)/connect/$', views.powerport_connect, name='powerport_connect'),
|
url(r'^power-ports/(?P<pk>\d+)/connect/$', views.powerport_connect, name='powerport_connect'),
|
||||||
url(r'^power-ports/(?P<pk>\d+)/disconnect/$', views.powerport_disconnect, name='powerport_disconnect'),
|
url(r'^power-ports/(?P<pk>\d+)/disconnect/$', views.powerport_disconnect, name='powerport_disconnect'),
|
||||||
url(r'^power-ports/(?P<pk>\d+)/edit/$', views.powerport_edit, name='powerport_edit'),
|
url(r'^power-ports/(?P<pk>\d+)/edit/$', views.powerport_edit, name='powerport_edit'),
|
||||||
@ -126,6 +127,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Power outlets
|
# Power outlets
|
||||||
url(r'^devices/(?P<pk>\d+)/power-outlets/add/$', views.poweroutlet_add, name='poweroutlet_add'),
|
url(r'^devices/(?P<pk>\d+)/power-outlets/add/$', views.poweroutlet_add, name='poweroutlet_add'),
|
||||||
|
url(r'^devices/(?P<pk>\d+)/power-outlets/delete/$', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'),
|
||||||
url(r'^power-outlets/(?P<pk>\d+)/connect/$', views.poweroutlet_connect, name='poweroutlet_connect'),
|
url(r'^power-outlets/(?P<pk>\d+)/connect/$', views.poweroutlet_connect, name='poweroutlet_connect'),
|
||||||
url(r'^power-outlets/(?P<pk>\d+)/disconnect/$', views.poweroutlet_disconnect, name='poweroutlet_disconnect'),
|
url(r'^power-outlets/(?P<pk>\d+)/disconnect/$', views.poweroutlet_disconnect, name='poweroutlet_disconnect'),
|
||||||
url(r'^power-outlets/(?P<pk>\d+)/edit/$', views.poweroutlet_edit, name='poweroutlet_edit'),
|
url(r'^power-outlets/(?P<pk>\d+)/edit/$', views.poweroutlet_edit, name='poweroutlet_edit'),
|
||||||
@ -133,6 +135,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Device bays
|
# Device bays
|
||||||
url(r'^devices/(?P<pk>\d+)/bays/add/$', views.devicebay_add, name='devicebay_add'),
|
url(r'^devices/(?P<pk>\d+)/bays/add/$', views.devicebay_add, name='devicebay_add'),
|
||||||
|
url(r'^devices/(?P<pk>\d+)/bays/delete/$', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'),
|
||||||
url(r'^device-bays/(?P<pk>\d+)/edit/$', views.devicebay_edit, name='devicebay_edit'),
|
url(r'^device-bays/(?P<pk>\d+)/edit/$', views.devicebay_edit, name='devicebay_edit'),
|
||||||
url(r'^device-bays/(?P<pk>\d+)/delete/$', views.devicebay_delete, name='devicebay_delete'),
|
url(r'^device-bays/(?P<pk>\d+)/delete/$', views.devicebay_delete, name='devicebay_delete'),
|
||||||
url(r'^device-bays/(?P<pk>\d+)/populate/$', views.devicebay_populate, name='devicebay_populate'),
|
url(r'^device-bays/(?P<pk>\d+)/populate/$', views.devicebay_populate, name='devicebay_populate'),
|
||||||
@ -147,8 +150,9 @@ urlpatterns = [
|
|||||||
url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
|
url(r'^interface-connections/import/$', views.InterfaceConnectionsBulkImportView.as_view(), name='interface_connections_import'),
|
||||||
|
|
||||||
# Interfaces
|
# Interfaces
|
||||||
url(r'^devices/interfaces/add/$', views.InterfaceBulkAddView.as_view(), name='interface_bulk_add'),
|
url(r'^devices/interfaces/add/$', views.InterfaceBulkAddView.as_view(), name='interface_add_multi'),
|
||||||
url(r'^devices/(?P<pk>\d+)/interfaces/add/$', views.interface_add, name='interface_add'),
|
url(r'^devices/(?P<pk>\d+)/interfaces/add/$', views.interface_add, name='interface_add'),
|
||||||
|
url(r'^devices/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
|
||||||
url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.interfaceconnection_add, name='interfaceconnection_add'),
|
url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.interfaceconnection_add, name='interfaceconnection_add'),
|
||||||
url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.interfaceconnection_delete, name='interfaceconnection_delete'),
|
url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.interfaceconnection_delete, name='interfaceconnection_delete'),
|
||||||
url(r'^interfaces/(?P<pk>\d+)/edit/$', views.interface_edit, name='interface_edit'),
|
url(r'^interfaces/(?P<pk>\d+)/edit/$', views.interface_edit, name='interface_edit'),
|
||||||
|
@ -7,8 +7,7 @@ from django.contrib.auth.decorators import permission_required
|
|||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db.models import Count, ProtectedError, Sum
|
from django.db.models import Count, Sum
|
||||||
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
@ -17,7 +16,6 @@ from django.views.generic import View
|
|||||||
from ipam.models import Prefix, IPAddress, VLAN
|
from ipam.models import Prefix, IPAddress, VLAN
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from extras.models import TopologyMap
|
from extras.models import TopologyMap
|
||||||
from utilities.error_handlers import handle_protectederror
|
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
@ -135,7 +133,6 @@ class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_rackgroup'
|
permission_required = 'dcim.delete_rackgroup'
|
||||||
cls = RackGroup
|
cls = RackGroup
|
||||||
form = forms.RackGroupBulkDeleteForm
|
|
||||||
default_redirect_url = 'dcim:rackgroup_list'
|
default_redirect_url = 'dcim:rackgroup_list'
|
||||||
|
|
||||||
|
|
||||||
@ -188,7 +185,7 @@ class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
|
class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||||
permission_required = 'dcim.add_rack'
|
permission_required = 'dcim.add_rack'
|
||||||
form = forms.RackImportForm
|
form = forms.RackImportForm
|
||||||
table = tables.RackTable
|
table = tables.RackImportTable
|
||||||
template_name = 'dcim/rack_import.html'
|
template_name = 'dcim/rack_import.html'
|
||||||
obj_list_url = 'dcim:rack_list'
|
obj_list_url = 'dcim:rack_list'
|
||||||
|
|
||||||
@ -213,7 +210,6 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_rack'
|
permission_required = 'dcim.delete_rack'
|
||||||
cls = Rack
|
cls = Rack
|
||||||
form = forms.RackBulkDeleteForm
|
|
||||||
default_redirect_url = 'dcim:rack_list'
|
default_redirect_url = 'dcim:rack_list'
|
||||||
|
|
||||||
|
|
||||||
@ -239,7 +235,6 @@ class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_manufacturer'
|
permission_required = 'dcim.delete_manufacturer'
|
||||||
cls = Manufacturer
|
cls = Manufacturer
|
||||||
form = forms.ManufacturerBulkDeleteForm
|
|
||||||
default_redirect_url = 'dcim:manufacturer_list'
|
default_redirect_url = 'dcim:manufacturer_list'
|
||||||
|
|
||||||
|
|
||||||
@ -334,7 +329,6 @@ class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_devicetype'
|
permission_required = 'dcim.delete_devicetype'
|
||||||
cls = DeviceType
|
cls = DeviceType
|
||||||
form = forms.DeviceTypeBulkDeleteForm
|
|
||||||
default_redirect_url = 'dcim:devicetype_list'
|
default_redirect_url = 'dcim:devicetype_list'
|
||||||
|
|
||||||
|
|
||||||
@ -396,68 +390,65 @@ class ConsolePortTemplateAddView(ComponentTemplateCreateView):
|
|||||||
form = forms.ConsolePortTemplateForm
|
form = forms.ConsolePortTemplateForm
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_consoleporttemplate'
|
||||||
|
cls = ConsolePortTemplate
|
||||||
|
parent_cls = DeviceType
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateAddView(ComponentTemplateCreateView):
|
class ConsoleServerPortTemplateAddView(ComponentTemplateCreateView):
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
form = forms.ConsoleServerPortTemplateForm
|
form = forms.ConsoleServerPortTemplateForm
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_consoleserverporttemplate'
|
||||||
|
cls = ConsoleServerPortTemplate
|
||||||
|
parent_cls = DeviceType
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateAddView(ComponentTemplateCreateView):
|
class PowerPortTemplateAddView(ComponentTemplateCreateView):
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
form = forms.PowerPortTemplateForm
|
form = forms.PowerPortTemplateForm
|
||||||
|
|
||||||
|
|
||||||
|
class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_powerporttemplate'
|
||||||
|
cls = PowerPortTemplate
|
||||||
|
parent_cls = DeviceType
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateAddView(ComponentTemplateCreateView):
|
class PowerOutletTemplateAddView(ComponentTemplateCreateView):
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
form = forms.PowerOutletTemplateForm
|
form = forms.PowerOutletTemplateForm
|
||||||
|
|
||||||
|
|
||||||
|
class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_poweroutlettemplate'
|
||||||
|
cls = PowerOutletTemplate
|
||||||
|
parent_cls = DeviceType
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateAddView(ComponentTemplateCreateView):
|
class InterfaceTemplateAddView(ComponentTemplateCreateView):
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
form = forms.InterfaceTemplateForm
|
form = forms.InterfaceTemplateForm
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_interfacetemplate'
|
||||||
|
cls = InterfaceTemplate
|
||||||
|
parent_cls = DeviceType
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateAddView(ComponentTemplateCreateView):
|
class DeviceBayTemplateAddView(ComponentTemplateCreateView):
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
form = forms.DeviceBayTemplateForm
|
form = forms.DeviceBayTemplateForm
|
||||||
|
|
||||||
|
|
||||||
def component_template_delete(request, pk, model):
|
class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_devicebaytemplate'
|
||||||
devicetype = get_object_or_404(DeviceType, pk=pk)
|
cls = DeviceBayTemplate
|
||||||
|
parent_cls = DeviceType
|
||||||
class ComponentTemplateBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = ModelMultipleChoiceField(queryset=model.objects.all(), widget=MultipleHiddenInput)
|
|
||||||
|
|
||||||
if '_confirm' in request.POST:
|
|
||||||
form = ComponentTemplateBulkDeleteForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
# Delete component templates
|
|
||||||
objects_to_delete = model.objects.filter(pk__in=[v.id for v in form.cleaned_data['pk']])
|
|
||||||
try:
|
|
||||||
deleted_count = objects_to_delete.count()
|
|
||||||
objects_to_delete.delete()
|
|
||||||
except ProtectedError, e:
|
|
||||||
handle_protectederror(list(objects_to_delete), request, e)
|
|
||||||
return redirect('dcim:devicetype', {'pk': devicetype.pk})
|
|
||||||
|
|
||||||
messages.success(request, "Deleted {} {}".format(deleted_count, model._meta.verbose_name_plural))
|
|
||||||
return redirect('dcim:devicetype', pk=devicetype.pk)
|
|
||||||
|
|
||||||
else:
|
|
||||||
form = ComponentTemplateBulkDeleteForm(initial={'pk': request.POST.getlist('pk')})
|
|
||||||
|
|
||||||
selected_objects = model.objects.filter(pk__in=request.POST.getlist('pk'))
|
|
||||||
if not selected_objects:
|
|
||||||
messages.warning(request, "No {} were selected for deletion.".format(model._meta.verbose_name_plural))
|
|
||||||
return redirect('dcim:devicetype', pk=devicetype.pk)
|
|
||||||
|
|
||||||
return render(request, 'dcim/component_template_delete.html', {
|
|
||||||
'devicetype': devicetype,
|
|
||||||
'form': form,
|
|
||||||
'selected_objects': selected_objects,
|
|
||||||
'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -482,7 +473,6 @@ class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_devicerole'
|
permission_required = 'dcim.delete_devicerole'
|
||||||
cls = DeviceRole
|
cls = DeviceRole
|
||||||
form = forms.DeviceRoleBulkDeleteForm
|
|
||||||
default_redirect_url = 'dcim:devicerole_list'
|
default_redirect_url = 'dcim:devicerole_list'
|
||||||
|
|
||||||
|
|
||||||
@ -508,7 +498,6 @@ class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_platform'
|
permission_required = 'dcim.delete_platform'
|
||||||
cls = Platform
|
cls = Platform
|
||||||
form = forms.PlatformBulkDeleteForm
|
|
||||||
default_redirect_url = 'dcim:platform_list'
|
default_redirect_url = 'dcim:platform_list'
|
||||||
|
|
||||||
|
|
||||||
@ -653,7 +642,6 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_device'
|
permission_required = 'dcim.delete_device'
|
||||||
cls = Device
|
cls = Device
|
||||||
form = forms.DeviceBulkDeleteForm
|
|
||||||
default_redirect_url = 'dcim:device_list'
|
default_redirect_url = 'dcim:device_list'
|
||||||
|
|
||||||
|
|
||||||
@ -825,6 +813,12 @@ def consoleport_delete(request, pk):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_consoleport'
|
||||||
|
cls = ConsolePort
|
||||||
|
parent_cls = Device
|
||||||
|
|
||||||
|
|
||||||
class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
|
class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||||
permission_required = 'dcim.change_consoleport'
|
permission_required = 'dcim.change_consoleport'
|
||||||
form = forms.ConsoleConnectionImportForm
|
form = forms.ConsoleConnectionImportForm
|
||||||
@ -980,6 +974,12 @@ def consoleserverport_delete(request, pk):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_consoleserverport'
|
||||||
|
cls = ConsoleServerPort
|
||||||
|
parent_cls = Device
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Power ports
|
# Power ports
|
||||||
#
|
#
|
||||||
@ -1125,6 +1125,12 @@ def powerport_delete(request, pk):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_powerport'
|
||||||
|
cls = PowerPort
|
||||||
|
parent_cls = Device
|
||||||
|
|
||||||
|
|
||||||
class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
|
class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||||
permission_required = 'dcim.change_powerport'
|
permission_required = 'dcim.change_powerport'
|
||||||
form = forms.PowerConnectionImportForm
|
form = forms.PowerConnectionImportForm
|
||||||
@ -1278,6 +1284,12 @@ def poweroutlet_delete(request, pk):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_poweroutlet'
|
||||||
|
cls = PowerOutlet
|
||||||
|
parent_cls = Device
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Interfaces
|
# Interfaces
|
||||||
#
|
#
|
||||||
@ -1372,7 +1384,7 @@ class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
|
|||||||
permission_required = 'dcim.add_interface'
|
permission_required = 'dcim.add_interface'
|
||||||
cls = Device
|
cls = Device
|
||||||
form = forms.InterfaceBulkCreateForm
|
form = forms.InterfaceBulkCreateForm
|
||||||
template_name = 'dcim/interface_bulk_add.html'
|
template_name = 'dcim/interface_add_multi.html'
|
||||||
default_redirect_url = 'dcim:device_list'
|
default_redirect_url = 'dcim:device_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
def update_objects(self, pk_list, form):
|
||||||
@ -1401,6 +1413,12 @@ class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
|
|||||||
len(selected_devices)))
|
len(selected_devices)))
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_interface'
|
||||||
|
cls = Interface
|
||||||
|
parent_cls = Device
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device bays
|
# Device bays
|
||||||
#
|
#
|
||||||
@ -1538,6 +1556,12 @@ def devicebay_depopulate(request, pk):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_devicebay'
|
||||||
|
cls = DeviceBay
|
||||||
|
parent_cls = Device
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Interface connections
|
# Interface connections
|
||||||
#
|
#
|
||||||
|
@ -77,7 +77,7 @@ class ExportTemplate(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "{}: {}".format(self.content_type, self.name)
|
return u'{}: {}'.format(self.content_type, self.name)
|
||||||
|
|
||||||
def to_response(self, context_dict, filename):
|
def to_response(self, context_dict, filename):
|
||||||
"""
|
"""
|
||||||
@ -176,8 +176,8 @@ class UserAction(models.Model):
|
|||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
if self.message:
|
if self.message:
|
||||||
return ' '.join([self.user, self.message])
|
return u'{} {}'.format(self.user, self.message)
|
||||||
return ' '.join([self.user, self.get_action_display(), self.content_type])
|
return u'{} {} {}'.format(self.user, self.get_action_display(), self.content_type)
|
||||||
|
|
||||||
def icon(self):
|
def icon(self):
|
||||||
if self.action in [ACTION_CREATE, ACTION_IMPORT]:
|
if self.action in [ACTION_CREATE, ACTION_IMPORT]:
|
||||||
|
@ -102,7 +102,7 @@ class VLANSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fields = ['id', 'site', 'group', 'vid', 'name', 'status', 'role', 'display_name']
|
fields = ['id', 'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'display_name']
|
||||||
|
|
||||||
|
|
||||||
class VLANNestedSerializer(VLANSerializer):
|
class VLANNestedSerializer(VLANSerializer):
|
||||||
|
@ -4,9 +4,7 @@ from django import forms
|
|||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from dcim.models import Site, Device, Interface
|
from dcim.models import Site, Device, Interface
|
||||||
from utilities.forms import (
|
from utilities.forms import BootstrapMixin, APISelect, Livesearch, CSVDataField, BulkImportForm, SlugField
|
||||||
BootstrapMixin, ConfirmationForm, APISelect, Livesearch, CSVDataField, BulkImportForm, SlugField,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Aggregate, IPAddress, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, VLAN, VLANGroup, VLAN_STATUS_CHOICES, VRF,
|
Aggregate, IPAddress, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, VLAN, VLANGroup, VLAN_STATUS_CHOICES, VRF,
|
||||||
@ -50,10 +48,6 @@ class VRFBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
|
||||||
class VRFBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# RIRs
|
# RIRs
|
||||||
#
|
#
|
||||||
@ -66,10 +60,6 @@ class RIRForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class RIRBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=RIR.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Aggregates
|
# Aggregates
|
||||||
#
|
#
|
||||||
@ -103,11 +93,7 @@ class AggregateBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
pk = forms.ModelMultipleChoiceField(queryset=Aggregate.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Aggregate.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
rir = forms.ModelChoiceField(queryset=RIR.objects.all(), required=False, label='RIR')
|
rir = forms.ModelChoiceField(queryset=RIR.objects.all(), required=False, label='RIR')
|
||||||
date_added = forms.DateField(required=False)
|
date_added = forms.DateField(required=False)
|
||||||
description = forms.CharField(max_length=50, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
|
||||||
class AggregateBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Aggregate.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def aggregate_rir_choices():
|
def aggregate_rir_choices():
|
||||||
@ -132,10 +118,6 @@ class RoleForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class RoleBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Role.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Prefixes
|
# Prefixes
|
||||||
#
|
#
|
||||||
@ -251,11 +233,7 @@ class PrefixBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
||||||
status = forms.ChoiceField(choices=FORM_PREFIX_STATUS_CHOICES, required=False)
|
status = forms.ChoiceField(choices=FORM_PREFIX_STATUS_CHOICES, required=False)
|
||||||
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
||||||
description = forms.CharField(max_length=50, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
|
||||||
class PrefixBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def prefix_vrf_choices():
|
def prefix_vrf_choices():
|
||||||
@ -415,11 +393,7 @@ class IPAddressBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF',
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF',
|
||||||
help_text="Select the VRF to assign, or check below to remove VRF assignment")
|
help_text="Select the VRF to assign, or check below to remove VRF assignment")
|
||||||
vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
||||||
description = forms.CharField(max_length=50, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def ipaddress_family_choices():
|
def ipaddress_family_choices():
|
||||||
@ -449,10 +423,6 @@ class VLANGroupForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['site', 'name', 'slug']
|
fields = ['site', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=VLANGroup.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def vlangroup_site_choices():
|
def vlangroup_site_choices():
|
||||||
site_choices = Site.objects.annotate(vlangroup_count=Count('vlan_groups'))
|
site_choices = Site.objects.annotate(vlangroup_count=Count('vlan_groups'))
|
||||||
return [(s.slug, u'{} ({})'.format(s.name, s.vlangroup_count)) for s in site_choices]
|
return [(s.slug, u'{} ({})'.format(s.name, s.vlangroup_count)) for s in site_choices]
|
||||||
@ -474,7 +444,7 @@ class VLANForm(forms.ModelForm, BootstrapMixin):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fields = ['site', 'group', 'vid', 'name', 'status', 'role']
|
fields = ['site', 'group', 'vid', 'name', 'description', 'status', 'role']
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'site': "The site at which this VLAN exists",
|
'site': "The site at which this VLAN exists",
|
||||||
'group': "VLAN group (optional)",
|
'group': "VLAN group (optional)",
|
||||||
@ -511,7 +481,7 @@ class VLANFromCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fields = ['site', 'group', 'vid', 'name', 'status_name', 'role']
|
fields = ['site', 'group', 'vid', 'name', 'status_name', 'role', 'description']
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
m = super(VLANFromCSVForm, self).save(commit=False)
|
m = super(VLANFromCSVForm, self).save(commit=False)
|
||||||
@ -532,10 +502,7 @@ class VLANBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
|
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
|
||||||
status = forms.ChoiceField(choices=FORM_VLAN_STATUS_CHOICES, required=False)
|
status = forms.ChoiceField(choices=FORM_VLAN_STATUS_CHOICES, required=False)
|
||||||
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
||||||
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
class VLANBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def vlan_site_choices():
|
def vlan_site_choices():
|
||||||
|
25
netbox/ipam/migrations/0005_auto_20160725_1842.py
Normal file
25
netbox/ipam/migrations/0005_auto_20160725_1842.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.8 on 2016-07-25 18:42
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0004_ipam_vlangroup_uniqueness'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='description',
|
||||||
|
field=models.CharField(blank=True, max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=64),
|
||||||
|
),
|
||||||
|
]
|
@ -406,7 +406,8 @@ class VLAN(CreatedUpdatedModel):
|
|||||||
MinValueValidator(1),
|
MinValueValidator(1),
|
||||||
MaxValueValidator(4094)
|
MaxValueValidator(4094)
|
||||||
])
|
])
|
||||||
name = models.CharField(max_length=30)
|
name = models.CharField(max_length=64)
|
||||||
|
description = models.CharField(max_length=100, blank=True)
|
||||||
status = models.PositiveSmallIntegerField('Status', choices=VLAN_STATUS_CHOICES, default=1)
|
status = models.PositiveSmallIntegerField('Status', choices=VLAN_STATUS_CHOICES, default=1)
|
||||||
role = models.ForeignKey('Role', related_name='vlans', on_delete=models.SET_NULL, blank=True, null=True)
|
role = models.ForeignKey('Role', related_name='vlans', on_delete=models.SET_NULL, blank=True, null=True)
|
||||||
|
|
||||||
@ -434,10 +435,12 @@ class VLAN(CreatedUpdatedModel):
|
|||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return ','.join([
|
return ','.join([
|
||||||
self.site.name,
|
self.site.name,
|
||||||
|
self.group.name if self.group else '',
|
||||||
str(self.vid),
|
str(self.vid),
|
||||||
self.name,
|
self.name,
|
||||||
self.get_status_display(),
|
self.get_status_display(),
|
||||||
self.role.name if self.role else '',
|
self.role.name if self.role else '',
|
||||||
|
self.description,
|
||||||
])
|
])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -95,7 +95,6 @@ class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_vrf'
|
permission_required = 'ipam.delete_vrf'
|
||||||
cls = VRF
|
cls = VRF
|
||||||
form = forms.VRFBulkDeleteForm
|
|
||||||
default_redirect_url = 'ipam:vrf_list'
|
default_redirect_url = 'ipam:vrf_list'
|
||||||
|
|
||||||
|
|
||||||
@ -121,7 +120,6 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_rir'
|
permission_required = 'ipam.delete_rir'
|
||||||
cls = RIR
|
cls = RIR
|
||||||
form = forms.RIRBulkDeleteForm
|
|
||||||
default_redirect_url = 'ipam:rir_list'
|
default_redirect_url = 'ipam:rir_list'
|
||||||
|
|
||||||
|
|
||||||
@ -217,7 +215,6 @@ class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_aggregate'
|
permission_required = 'ipam.delete_aggregate'
|
||||||
cls = Aggregate
|
cls = Aggregate
|
||||||
form = forms.AggregateBulkDeleteForm
|
|
||||||
default_redirect_url = 'ipam:aggregate_list'
|
default_redirect_url = 'ipam:aggregate_list'
|
||||||
|
|
||||||
|
|
||||||
@ -243,7 +240,6 @@ class RoleEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_role'
|
permission_required = 'ipam.delete_role'
|
||||||
cls = Role
|
cls = Role
|
||||||
form = forms.RoleBulkDeleteForm
|
|
||||||
default_redirect_url = 'ipam:role_list'
|
default_redirect_url = 'ipam:role_list'
|
||||||
|
|
||||||
|
|
||||||
@ -354,7 +350,6 @@ class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_prefix'
|
permission_required = 'ipam.delete_prefix'
|
||||||
cls = Prefix
|
cls = Prefix
|
||||||
form = forms.PrefixBulkDeleteForm
|
|
||||||
default_redirect_url = 'ipam:prefix_list'
|
default_redirect_url = 'ipam:prefix_list'
|
||||||
|
|
||||||
|
|
||||||
@ -479,7 +474,6 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_ipaddress'
|
permission_required = 'ipam.delete_ipaddress'
|
||||||
cls = IPAddress
|
cls = IPAddress
|
||||||
form = forms.IPAddressBulkDeleteForm
|
|
||||||
default_redirect_url = 'ipam:ipaddress_list'
|
default_redirect_url = 'ipam:ipaddress_list'
|
||||||
|
|
||||||
|
|
||||||
@ -506,7 +500,6 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_vlangroup'
|
permission_required = 'ipam.delete_vlangroup'
|
||||||
cls = VLANGroup
|
cls = VLANGroup
|
||||||
form = forms.VLANGroupBulkDeleteForm
|
|
||||||
default_redirect_url = 'ipam:vlangroup_list'
|
default_redirect_url = 'ipam:vlangroup_list'
|
||||||
|
|
||||||
|
|
||||||
@ -565,7 +558,7 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
def update_objects(self, pk_list, form):
|
def update_objects(self, pk_list, form):
|
||||||
|
|
||||||
fields_to_update = {}
|
fields_to_update = {}
|
||||||
for field in ['site', 'group', 'status', 'role']:
|
for field in ['site', 'group', 'status', 'role', 'description']:
|
||||||
if form.cleaned_data[field]:
|
if form.cleaned_data[field]:
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
fields_to_update[field] = form.cleaned_data[field]
|
||||||
|
|
||||||
@ -575,5 +568,4 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_vlan'
|
permission_required = 'ipam.delete_vlan'
|
||||||
cls = VLAN
|
cls = VLAN
|
||||||
form = forms.VLANBulkDeleteForm
|
|
||||||
default_redirect_url = 'ipam:vlan_list'
|
default_redirect_url = 'ipam:vlan_list'
|
||||||
|
@ -12,7 +12,7 @@ except ImportError:
|
|||||||
"the documentation.")
|
"the documentation.")
|
||||||
|
|
||||||
|
|
||||||
VERSION = '1.3.1'
|
VERSION = '1.3.2'
|
||||||
|
|
||||||
# Import local configuration
|
# Import local configuration
|
||||||
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
||||||
|
@ -10,15 +10,16 @@ $(document).ready(function() {
|
|||||||
$('#privkey_modal').modal('show');
|
$('#privkey_modal').modal('show');
|
||||||
} else {
|
} else {
|
||||||
unlock_secret(secret_id, private_key);
|
unlock_secret(secret_id, private_key);
|
||||||
$(this).hide();
|
|
||||||
$(this).siblings('button.lock-secret').show();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Locking a secret
|
// Locking a secret
|
||||||
$('button.lock-secret').click(function (event) {
|
$('button.lock-secret').click(function (event) {
|
||||||
var secret_id = $(this).attr('secret-id');
|
var secret_id = $(this).attr('secret-id');
|
||||||
$('#secret_' + secret_id).html('********');
|
var secret_div = $('#secret_' + secret_id);
|
||||||
|
|
||||||
|
// Delete the plaintext
|
||||||
|
secret_div.html('********');
|
||||||
$(this).hide();
|
$(this).hide();
|
||||||
$(this).siblings('button.unlock-secret').show();
|
$(this).siblings('button.unlock-secret').show();
|
||||||
});
|
});
|
||||||
@ -81,13 +82,16 @@ $(document).ready(function() {
|
|||||||
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
||||||
},
|
},
|
||||||
success: function (response, status) {
|
success: function (response, status) {
|
||||||
var secret_plaintext = response.plaintext;
|
$('#secret_' + secret_id).html(response.plaintext);
|
||||||
$('#secret_' + secret_id).html(secret_plaintext);
|
$('button.unlock-secret').hide();
|
||||||
return true;
|
$('button.lock-secret').show();
|
||||||
},
|
},
|
||||||
error: function (xhr, ajaxOptions, thrownError) {
|
error: function (xhr, ajaxOptions, thrownError) {
|
||||||
if (xhr.status == 403) {
|
if (xhr.status == 403) {
|
||||||
alert("Decryption failed: " + xhr.statusText);
|
alert("Permission denied");
|
||||||
|
} else {
|
||||||
|
var json = jQuery.parseJSON(xhr.responseText);
|
||||||
|
alert("Decryption failed: " + json['error']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@ from django import forms
|
|||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from utilities.forms import BootstrapMixin, BulkImportForm, ConfirmationForm, CSVDataField, SlugField
|
from utilities.forms import BootstrapMixin, BulkImportForm, CSVDataField, SlugField
|
||||||
|
|
||||||
from .models import Secret, SecretRole, UserKey
|
from .models import Secret, SecretRole, UserKey
|
||||||
|
|
||||||
@ -42,10 +42,6 @@ class SecretRoleForm(forms.ModelForm, BootstrapMixin):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class SecretRoleBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=SecretRole.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Secrets
|
# Secrets
|
||||||
#
|
#
|
||||||
@ -97,10 +93,6 @@ class SecretBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
name = forms.CharField(max_length=100, required=False)
|
name = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
|
||||||
class SecretBulkDeleteForm(ConfirmationForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
|
|
||||||
|
|
||||||
|
|
||||||
def secret_role_choices():
|
def secret_role_choices():
|
||||||
role_choices = SecretRole.objects.annotate(secret_count=Count('secrets'))
|
role_choices = SecretRole.objects.annotate(secret_count=Count('secrets'))
|
||||||
return [(r.slug, u'{} ({})'.format(r.name, r.secret_count)) for r in role_choices]
|
return [(r.slug, u'{} ({})'.format(r.name, r.secret_count)) for r in role_choices]
|
||||||
|
@ -37,7 +37,6 @@ class SecretRoleEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'secrets.delete_secretrole'
|
permission_required = 'secrets.delete_secretrole'
|
||||||
cls = SecretRole
|
cls = SecretRole
|
||||||
form = forms.SecretRoleBulkDeleteForm
|
|
||||||
default_redirect_url = 'secrets:secretrole_list'
|
default_redirect_url = 'secrets:secretrole_list'
|
||||||
|
|
||||||
|
|
||||||
@ -219,5 +218,4 @@ class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'secrets.delete_secret'
|
permission_required = 'secrets.delete_secret'
|
||||||
cls = Secret
|
cls = Secret
|
||||||
form = forms.SecretBulkDeleteForm
|
|
||||||
default_redirect_url = 'secrets:secret_list'
|
default_redirect_url = 'secrets:secret_list'
|
||||||
|
@ -289,100 +289,180 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% if device_bays or device.device_type.is_parent_device %}
|
{% if device_bays or device.device_type.is_parent_device %}
|
||||||
|
{% if perms.dcim.delete_devicebay %}
|
||||||
|
<form method="post" action="{% url 'dcim:devicebay_bulk_delete' pk=device.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% endif %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Device Bays</strong>
|
<strong>Device Bays</strong>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for devicebay in device_bays %}
|
{% for devicebay in device_bays %}
|
||||||
{% include 'dcim/inc/_devicebay.html' %}
|
{% include 'dcim/inc/_devicebay.html' with selectable=True %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No device bays defined</td>
|
<td colspan="4">No device bays defined</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_devicebay %}
|
{% if perms.dcim.add_devicebay or perms.dcim.delete_devicebay %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer">
|
||||||
<a href="{% url 'dcim:devicebay_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<div class="row">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<div class="col-md-6">
|
||||||
Add device bays
|
{% if device_bays and perms.dcim.delete_devicebay %}
|
||||||
</a>
|
<button type="submit" class="btn btn-xs btn-danger">
|
||||||
</div>
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
{% if perms.dcim.add_devicebay %}
|
||||||
|
<a href="{% url 'dcim:devicebay_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
|
Add device bay
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if perms.dcim.delete_devicebay %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if interfaces or device.device_type.is_network_device %}
|
{% if interfaces or device.device_type.is_network_device %}
|
||||||
|
{% if perms.dcim.delete_interface %}
|
||||||
|
<form method="post" action="{% url 'dcim:interface_bulk_delete' pk=device.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% endif %}
|
||||||
<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>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for iface in interfaces %}
|
{% for iface in interfaces %}
|
||||||
{% include 'dcim/inc/_interface.html' %}
|
{% include 'dcim/inc/_interface.html' with selectable=True %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No interfaces defined</td>
|
<td colspan="4">No interfaces defined</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface or perms.dcim.delete_interface %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer">
|
||||||
<a href="{% url 'dcim:interface_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<div class="row">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<div class="col-md-6">
|
||||||
Add interface
|
{% if interfaces and perms.dcim.delete_interface %}
|
||||||
</a>
|
<button type="submit" class="btn btn-xs btn-danger">
|
||||||
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
{% if perms.dcim.add_interface %}
|
||||||
|
<a href="{% url 'dcim:interface_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
|
Add interface
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if perms.dcim.delete_interface %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if cs_ports or device.device_type.is_console_server %}
|
{% if cs_ports or device.device_type.is_console_server %}
|
||||||
|
{% if perms.dcim.delete_consoleserverport %}
|
||||||
|
<form method="post" action="{% url 'dcim:consoleserverport_bulk_delete' pk=device.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% endif %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Console Server Ports</strong>
|
<strong>Console Server Ports</strong>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for csp in cs_ports %}
|
{% for csp in cs_ports %}
|
||||||
{% include 'dcim/inc/_consoleserverport.html' %}
|
{% include 'dcim/inc/_consoleserverport.html' with selectable=True %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No console server ports defined</td>
|
<td colspan="4">No console server ports defined</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
{% if perms.dcim.add_consoleserverport or perms.dcim.delete_consoleserverport %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer">
|
||||||
<a href="{% url 'dcim:consoleserverport_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<div class="row">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<div class="col-md-6">
|
||||||
Add console server ports
|
{% if cs_ports and perms.dcim.delete_consoleserverport %}
|
||||||
</a>
|
<button type="submit" class="btn btn-xs btn-danger">
|
||||||
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
{% if perms.dcim.add_consoleserverport %}
|
||||||
|
<a href="{% url 'dcim:consoleserverport_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
|
Add console server ports
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if perms.dcim.delete_consoleserverport %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if power_outlets or device.device_type.is_pdu %}
|
{% if power_outlets or device.device_type.is_pdu %}
|
||||||
|
{% if perms.dcim.delete_poweroutlet %}
|
||||||
|
<form method="post" action="{% url 'dcim:poweroutlet_bulk_delete' pk=device.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% endif %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Power Outlets</strong>
|
<strong>Power Outlets</strong>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for po in power_outlets %}
|
{% for po in power_outlets %}
|
||||||
{% include 'dcim/inc/_poweroutlet.html' %}
|
{% include 'dcim/inc/_poweroutlet.html' with selectable=True %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No power outlets defined</td>
|
<td colspan="4">No power outlets defined</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
{% if perms.dcim.add_poweroutlet or perms.dcim.delete_poweroutlet %}
|
||||||
<div class="panel-footer text-right">
|
<div class="panel-footer">
|
||||||
<a href="{% url 'dcim:poweroutlet_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<div class="row">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<div class="col-md-6">
|
||||||
Add power outlets
|
{% if power_outlets and perms.dcim.delete_poweroutlet %}
|
||||||
</a>
|
<button type="submit" class="btn btn-xs btn-danger">
|
||||||
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
{% if perms.dcim.add_poweroutlet %}
|
||||||
|
<a href="{% url 'dcim:poweroutlet_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
|
Add power outlets
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if perms.dcim.delete_poweroutlet %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,6 +48,16 @@
|
|||||||
<td>Model Name</td>
|
<td>Model Name</td>
|
||||||
<td>{{ devicetype.model }}</td>
|
<td>{{ devicetype.model }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Part Number</td>
|
||||||
|
<td>
|
||||||
|
{% if devicetype.part_number %}
|
||||||
|
{{ devicetype.part_number }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">N/A</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Height (U)</td>
|
<td>Height (U)</td>
|
||||||
<td>{{ devicetype.u_height }}</td>
|
<td>{{ devicetype.u_height }}</td>
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<tr{% if cp.cs_port and not cp.connection_status %} class="info"{% endif %}>
|
<tr{% if cp.cs_port and not cp.connection_status %} class="info"{% endif %}>
|
||||||
|
{% if selectable and perms.dcim.delete_consoleport %}
|
||||||
|
<td class="pk">
|
||||||
|
<input name="pk" type="checkbox" value="{{ cp.pk }}" />
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-keyboard-o"></i> {{ cp.name }}
|
<i class="fa fa-fw fa-keyboard-o"></i> {{ cp.name }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<tr{% if csp.connected_console and not csp.connected_console.connection_status %} class="info"{% endif %}>
|
<tr{% if csp.connected_console and not csp.connected_console.connection_status %} class="info"{% endif %}>
|
||||||
|
{% if selectable and perms.dcim.delete_consoleserverport %}
|
||||||
|
<td class="pk">
|
||||||
|
<input name="pk" type="checkbox" value="{{ csp.pk }}" />
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-keyboard-o"></i> {{ csp.name }}
|
<i class="fa fa-fw fa-keyboard-o"></i> {{ csp.name }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
|
{% if selectable and perms.dcim.delete_devicebay %}
|
||||||
|
<td class="pk">
|
||||||
|
<input name="pk" type="checkbox" value="{{ devicebay.pk }}" />
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-{% if devicebay.installed_device %}dot-circle-o{% else %}circle-o{% endif %}"></i> {{ devicebay.name }}
|
<i class="fa fa-fw fa-{% if devicebay.installed_device %}dot-circle-o{% else %}circle-o{% endif %}"></i> {{ devicebay.name }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<tr{% if iface.connection and not iface.connection.connection_status %} class="info"{% endif %}>
|
<tr{% if iface.connection and not iface.connection.connection_status %} class="info"{% endif %}>
|
||||||
|
{% if selectable and perms.dcim.delete_interface %}
|
||||||
|
<td class="pk">
|
||||||
|
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-{{ icon|default:"exchange" }}"></i> <span title="{{ iface.get_form_factor_display }}">{{ iface.name }}</span>
|
<i class="fa fa-fw fa-{{ icon|default:"exchange" }}"></i> <span title="{{ iface.get_form_factor_display }}">{{ iface.name }}</span>
|
||||||
{% if iface.description %}
|
{% if iface.description %}
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<tr{% if po.connected_port and not po.connected_port.connection_status %} class="info"{% endif %}>
|
<tr{% if po.connected_port and not po.connected_port.connection_status %} class="info"{% endif %}>
|
||||||
|
{% if selectable and perms.dcim.delete_poweroutlet %}
|
||||||
|
<td class="pk">
|
||||||
|
<input name="pk" type="checkbox" value="{{ po.pk }}" />
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-bolt"></i> {{ po.name }}
|
<i class="fa fa-fw fa-bolt"></i> {{ po.name }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
<tr{% if pp.power_outlet and not pp.connection_status %} class="info"{% endif %}>
|
<tr{% if pp.power_outlet and not pp.connection_status %} class="info"{% endif %}>
|
||||||
|
{% if selectable and perms.dcim.delete_powerport %}
|
||||||
|
<td class="pk">
|
||||||
|
<input name="pk" type="checkbox" value="{{ pp.pk }}" />
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-bolt"></i> {{ pp.name }}
|
<i class="fa fa-fw fa-bolt"></i> {{ pp.name }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<input type="hidden" name="pk_all" value="{% for row in table.rows %}{{ row.record.pk|default:'' }}{% if not forloop.last %},{% endif %}{% endfor %}" />
|
<input type="hidden" name="pk_all" value="{% for row in table.rows %}{{ row.record.pk|default:'' }}{% if not forloop.last %},{% endif %}{% endfor %}" />
|
||||||
{% render_table table table_template|default:'table.html' %}
|
{% render_table table table_template|default:'table.html' %}
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_add' %}" class="btn btn-primary btn-sm">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_add_multi' %}" class="btn btn-primary btn-sm">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
Add Interfaces
|
Add Interfaces
|
||||||
</button>
|
</button>
|
||||||
|
@ -69,6 +69,16 @@
|
|||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ vlan.name }}</td>
|
<td>{{ vlan.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Description</td>
|
||||||
|
<td>
|
||||||
|
{% if vlan.description %}
|
||||||
|
{{ vlan.description }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<td>{{ vlan.site }}</td>
|
<td>{{ vlan.site }}</td>
|
||||||
<td>{{ vlan.status }}</td>
|
<td>{{ vlan.status }}</td>
|
||||||
<td>{{ vlan.role }}</td>
|
<td>{{ vlan.role }}</td>
|
||||||
|
<td>{{ vlan.description }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -58,10 +58,15 @@
|
|||||||
<td>Functional role (optional)</td>
|
<td>Functional role (optional)</td>
|
||||||
<td>Security</td>
|
<td>Security</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Description</td>
|
||||||
|
<td>Short description (optional)</td>
|
||||||
|
<td>Security team only</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<h4>Example</h4>
|
<h4>Example</h4>
|
||||||
<pre>LAS2,Backend Network,1400,Cameras,Active,Security</pre>
|
<pre>LAS2,Backend Network,1400,Cameras,Active,Security,Security team only</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -17,7 +17,10 @@
|
|||||||
<textarea class="form-control" id="user_privkey" style="height: 300px;"></textarea>
|
<textarea class="form-control" id="user_privkey" style="height: 300px;"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group text-right">
|
<div class="form-group text-right">
|
||||||
<button id="submit_privkey" class="btn btn-primary unlock-secret" data-dismiss="modal">Submit RSA Key</button>
|
<button id="submit_privkey" class="btn btn-primary unlock-secret" data-dismiss="modal">
|
||||||
|
<i class="fa fa-save" aria-hidden="True"></i>
|
||||||
|
Save RSA Key
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,11 +5,15 @@
|
|||||||
|
|
||||||
{% block message %}
|
{% block message %}
|
||||||
<p>
|
<p>
|
||||||
Are you sure you want to delete these {{ obj_type_plural|default:"objects" }}?
|
Are you sure you want to delete these {{ obj_type_plural|default:"objects" }}{% if parent_obj %} from <a href="{{ parent_obj.get_absolute_url }}">{{ parent_obj }}</a>{% endif %}?
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for obj in selected_objects %}
|
{% for obj in selected_objects %}
|
||||||
<li><a href="{{ obj.get_absolute_url }}">{{ obj }}</a></li>
|
{% if obj.get_absolute_url %}
|
||||||
|
<li><a href="{{ obj.get_absolute_url }}">{{ obj }}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li>{{ obj }}</li>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,9 +3,11 @@ from django_tables2 import RequestConfig
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import transaction, IntegrityError
|
from django.db import transaction, IntegrityError
|
||||||
from django.db.models import ProtectedError
|
from django.db.models import ProtectedError
|
||||||
|
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.template import TemplateSyntaxError
|
from django.template import TemplateSyntaxError
|
||||||
@ -309,6 +311,7 @@ class BulkEditView(View):
|
|||||||
|
|
||||||
class BulkDeleteView(View):
|
class BulkDeleteView(View):
|
||||||
cls = None
|
cls = None
|
||||||
|
parent_cls = None
|
||||||
form = None
|
form = None
|
||||||
template_name = 'utilities/confirm_bulk_delete.html'
|
template_name = 'utilities/confirm_bulk_delete.html'
|
||||||
default_redirect_url = None
|
default_redirect_url = None
|
||||||
@ -317,24 +320,35 @@ class BulkDeleteView(View):
|
|||||||
def dispatch(self, *args, **kwargs):
|
def dispatch(self, *args, **kwargs):
|
||||||
return super(BulkDeleteView, self).dispatch(*args, **kwargs)
|
return super(BulkDeleteView, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return redirect(self.default_redirect_url)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
# Attempt to derive parent object if a parent class has been given
|
||||||
|
if self.parent_cls:
|
||||||
|
parent_obj = get_object_or_404(self.parent_cls, **kwargs)
|
||||||
|
else:
|
||||||
|
parent_obj = None
|
||||||
|
|
||||||
|
# Determine URL to redirect users upon deletion of objects
|
||||||
posted_redirect_url = request.POST.get('redirect_url')
|
posted_redirect_url = request.POST.get('redirect_url')
|
||||||
if posted_redirect_url and is_safe_url(url=posted_redirect_url, host=request.get_host()):
|
if posted_redirect_url and is_safe_url(url=posted_redirect_url, host=request.get_host()):
|
||||||
redirect_url = posted_redirect_url
|
redirect_url = posted_redirect_url
|
||||||
else:
|
elif parent_obj:
|
||||||
|
redirect_url = parent_obj.get_absolute_url()
|
||||||
|
elif self.default_redirect_url:
|
||||||
redirect_url = reverse(self.default_redirect_url)
|
redirect_url = reverse(self.default_redirect_url)
|
||||||
|
else:
|
||||||
|
raise ImproperlyConfigured('No redirect URL has been provided.')
|
||||||
|
|
||||||
|
# Are we deleting *all* objects in the queryset or just a selected subset?
|
||||||
if request.POST.get('_all'):
|
if request.POST.get('_all'):
|
||||||
pk_list = [x for x in request.POST.get('pk_all').split(',') if x]
|
pk_list = [x for x in request.POST.get('pk_all').split(',') if x]
|
||||||
else:
|
else:
|
||||||
pk_list = request.POST.getlist('pk')
|
pk_list = request.POST.getlist('pk')
|
||||||
|
|
||||||
|
form_cls = self.get_form()
|
||||||
|
|
||||||
if '_confirm' in request.POST:
|
if '_confirm' in request.POST:
|
||||||
form = self.form(request.POST)
|
form = form_cls(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
||||||
# Delete objects
|
# Delete objects
|
||||||
@ -351,7 +365,7 @@ class BulkDeleteView(View):
|
|||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = self.form(initial={'pk': pk_list})
|
form = form_cls(initial={'pk': pk_list})
|
||||||
|
|
||||||
selected_objects = self.cls.objects.filter(pk__in=pk_list)
|
selected_objects = self.cls.objects.filter(pk__in=pk_list)
|
||||||
if not selected_objects:
|
if not selected_objects:
|
||||||
@ -360,7 +374,18 @@ class BulkDeleteView(View):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'form': form,
|
'form': form,
|
||||||
|
'parent_obj': parent_obj,
|
||||||
'obj_type_plural': self.cls._meta.verbose_name_plural,
|
'obj_type_plural': self.cls._meta.verbose_name_plural,
|
||||||
'selected_objects': selected_objects,
|
'selected_objects': selected_objects,
|
||||||
'cancel_url': redirect_url,
|
'cancel_url': redirect_url,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
"""Provide a standard bulk delete form if none has been specified for the view"""
|
||||||
|
|
||||||
|
class BulkDeleteForm(ConfirmationForm):
|
||||||
|
pk = ModelMultipleChoiceField(queryset=self.cls.objects.all(), widget=MultipleHiddenInput)
|
||||||
|
|
||||||
|
if self.form:
|
||||||
|
return self.form
|
||||||
|
return BulkDeleteForm
|
||||||
|
Loading…
Reference in New Issue
Block a user