mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-20 19:19:22 -06:00
commit
a5a7358d26
25
.github/ISSUE_TEMPLATE.md
vendored
25
.github/ISSUE_TEMPLATE.md
vendored
@ -4,23 +4,30 @@
|
|||||||
remove the "is:open" filter from the search bar to include closed issues.
|
remove the "is:open" filter from the search bar to include closed issues.
|
||||||
|
|
||||||
Check the appropriate type for your issue below by placing an x between the
|
Check the appropriate type for your issue below by placing an x between the
|
||||||
brackets. If none of the below apply, please raise your issue for
|
brackets. For assistance with installation issues, or for any other issues
|
||||||
discussion on our mailing list:
|
other than those listed below, please raise your topic for discussion on
|
||||||
|
our mailing list:
|
||||||
|
|
||||||
https://groups.google.com/forum/#!forum/netbox-discuss
|
https://groups.google.com/forum/#!forum/netbox-discuss
|
||||||
|
|
||||||
Please note that issues which do not fall under any of the below categories
|
Please note that issues which do not fall under any of the below categories
|
||||||
will be closed.
|
will be closed. Due to an excessive backlog of feature requests, we are
|
||||||
|
not currently accepting any proposals which extend NetBox's feature scope.
|
||||||
|
|
||||||
|
Do not prepend any sort of tag to your issue's title. An administrator will
|
||||||
|
review your issue and assign labels as appropriate.
|
||||||
--->
|
--->
|
||||||
### Issue type
|
### Issue type
|
||||||
[ ] Feature request <!-- Requesting the implementation of a new feature -->
|
[ ] Feature request <!-- An enhancement of existing functionality -->
|
||||||
[ ] Bug report <!-- Reporting unexpected or erroneous behavior -->
|
[ ] Bug report <!-- Unexpected or erroneous behavior -->
|
||||||
[ ] Documentation <!-- Proposing a modification to the documentation -->
|
[ ] Documentation <!-- A modification to the documentation -->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please describe the environment in which you are running NetBox. (Be sure
|
Please describe the environment in which you are running NetBox. (Be sure
|
||||||
to verify that you are running the latest stable release of NetBox before
|
to verify that you are running the latest stable release of NetBox before
|
||||||
submitting a bug report.)
|
submitting a bug report.) If you are submitting a bug report and have made
|
||||||
|
any changes to the code base, please first validate that your bug can be
|
||||||
|
recreated while running an official release.
|
||||||
-->
|
-->
|
||||||
### Environment
|
### Environment
|
||||||
* Python version: <!-- Example: 3.5.4 -->
|
* Python version: <!-- Example: 3.5.4 -->
|
||||||
@ -28,8 +35,8 @@
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
BUG REPORTS must include:
|
BUG REPORTS must include:
|
||||||
* A list of the steps needed to reproduce the bug
|
* A list of the steps needed for someone else to reproduce the bug
|
||||||
* A description of the expected behavior
|
* A description of the expected and observed behavior
|
||||||
* Any relevant error messages (screenshots may also help)
|
* Any relevant error messages (screenshots may also help)
|
||||||
|
|
||||||
FEATURE REQUESTS must include:
|
FEATURE REQUESTS must include:
|
||||||
|
126
CONTRIBUTING.md
126
CONTRIBUTING.md
@ -1,8 +1,8 @@
|
|||||||
## Getting Help
|
## Getting Help
|
||||||
|
|
||||||
If you encounter any issues installing or using NetBox, try one of the
|
If you encounter any issues installing or using NetBox, try one of the
|
||||||
following resources to get assistance. Please **do not** open a GitHub
|
following resources to get assistance. Please **do not** open a GitHub issue
|
||||||
issue except to report bugs or request features.
|
except to report bugs or request features.
|
||||||
|
|
||||||
### Mailing List
|
### Mailing List
|
||||||
|
|
||||||
@ -13,35 +13,32 @@ installation. You can find us [here](https://groups.google.com/forum/#!forum/net
|
|||||||
### Freenode IRC
|
### Freenode IRC
|
||||||
|
|
||||||
For real-time discussion, you can join the #netbox channel on [Freenode](https://freenode.net/).
|
For real-time discussion, you can join the #netbox channel on [Freenode](https://freenode.net/).
|
||||||
You can connect to Freenode at irc.freenode.net using an IRC client, or
|
You can connect to Freenode at irc.freenode.net using an IRC client, or you can
|
||||||
you can use their [webchat client](https://webchat.freenode.net/).
|
use their [webchat client](https://webchat.freenode.net/).
|
||||||
|
|
||||||
## Reporting Bugs
|
## Reporting Bugs
|
||||||
|
|
||||||
* First, ensure that you've installed the [latest stable version](https://github.com/digitalocean/netbox/releases) of
|
* 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
|
NetBox. If you're running an older version, it's possible that the bug has
|
||||||
has already been fixed.
|
already been fixed.
|
||||||
|
|
||||||
* Next, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the bug you've found has
|
* Next, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the bug you've found has already
|
||||||
already been reported. If you think you may be experiencing a reported
|
been reported. If you think you may be experiencing a reported issue that
|
||||||
issue that hasn't already been resolved, please click "add a reaction"
|
hasn't already been resolved, please click "add a reaction" in the top right
|
||||||
in the top right corner of the issue and add a thumbs up (+1). You might
|
corner of the issue and add a thumbs up (+1). You mightalso want to add a
|
||||||
also want to add a comment describing how it's affecting your
|
comment describing how it's affecting your installation. This will allow us to
|
||||||
installation. This will allow us to prioritize bugs based on how many
|
prioritize bugs based on how many users are affected.
|
||||||
users are affected.
|
|
||||||
|
|
||||||
* If you haven't found an existing issue that describes your suspected
|
* If you haven't found an existing issue that describes your suspected bug,
|
||||||
bug, please inquire about it on the mailing list. **Do not** file an
|
please inquire about it on the mailing list. **Do not** file an issue until you
|
||||||
issue until you have received confirmation that it is in fact a bug.
|
have received confirmation that it is in fact a bug. Invalid issues are very
|
||||||
Invalid issues are very distracting and slow the pace at which NetBox is
|
distracting and slow the pace at which NetBox is developed.
|
||||||
developed.
|
|
||||||
|
|
||||||
* When submitting an issue, please be as descriptive as possible. Be
|
* When submitting an issue, please be as descriptive as possible. Be sure to
|
||||||
sure to include:
|
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
|
* The exact steps that can be taken to reproduce the issue (if applicable)
|
||||||
applicable)
|
|
||||||
* Any error messages generated
|
* Any error messages generated
|
||||||
* Screenshots (if applicable)
|
* Screenshots (if applicable)
|
||||||
|
|
||||||
@ -49,71 +46,64 @@ sure to include:
|
|||||||
The issue will be reviewed by a moderator after submission and the appropriate
|
The issue will be reviewed by a moderator after submission and the appropriate
|
||||||
labels will be applied.
|
labels will be applied.
|
||||||
|
|
||||||
* Keep in mind that we prioritize bugs based on their severity and how
|
* Keep in mind that we prioritize bugs based on their severity and how much
|
||||||
much work is required to resolve them. It may take some time for someone
|
work is required to resolve them. It may take some time for someone to address
|
||||||
to address your issue.
|
your issue.
|
||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
|
|
||||||
* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're
|
* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're requesting
|
||||||
requesting is already listed. (Be sure to search closed issues as well,
|
is already listed. (Be sure to search closed issues as well, since some
|
||||||
since some feature requests are rejected.) If the feature you'd like to
|
feature requests have been rejected.) If the feature you'd like to see has
|
||||||
see has already been requested, click "add a reaction" in the top right
|
already been requested and is open, click "add a reaction" in the top right
|
||||||
corner of the issue and add a thumbs up (+1). This ensures that the
|
corner of the issue and add a thumbs up (+1). This ensures that the issue has
|
||||||
issue has a better chance of making it onto the roadmap. Also feel free
|
a better chance of receiving attention. Also feel free to add a comment with
|
||||||
to add a comment with any additional justification for the feature.
|
any additional justification for the feature. (However, note that comments with
|
||||||
(However, note that comments with no substance other than a "+1" will be
|
no substance other than a "+1" will be deleted. Please use GitHub's reactions
|
||||||
deleted. Please use GitHub's reactions feature to indicate your
|
feature to indicate your support.)
|
||||||
support.)
|
|
||||||
|
|
||||||
* While suggestions for new features are welcome, it's important to
|
* Due to an excessive backlog of feature requests, we are not currently
|
||||||
limit the scope of NetBox's feature set to avoid feature creep. For
|
accepting any proposals which substantially extend NetBox's functionality
|
||||||
example, the following features would be firmly out of scope for NetBox:
|
beyond its current feature set. This includes the introduction of any new views
|
||||||
|
or models which have not already been proposed in an existing feature request.
|
||||||
* Ticket management
|
|
||||||
* Network state monitoring
|
|
||||||
* Acting as a DNS server
|
|
||||||
* Acting as an authentication server
|
|
||||||
|
|
||||||
* Before filing a new feature request, consider raising your idea on the
|
* Before filing a new feature request, consider raising your idea on the
|
||||||
mailing list first. Feedback you receive there will help validate and
|
mailing list first. Feedback you receive there will help validate and shape the
|
||||||
shape the proposed feature before filing a formal issue.
|
proposed feature before filing a formal issue.
|
||||||
|
|
||||||
* Good feature requests are very narrowly defined. Be sure to enumerate
|
* Good feature requests are very narrowly defined. Be sure to thoroughly
|
||||||
specific functionality and data schema. The more effort you put into
|
describe the functionality and data model(s) being proposed. The more effort
|
||||||
writing a feature request, the better its chance is of being
|
you put into writing a feature request, the better its chance is of being
|
||||||
implemented. Overly broad feature requests will be closed.
|
implemented. Overly broad feature requests will be closed.
|
||||||
|
|
||||||
* When submitting a feature request on GitHub, be sure to include the
|
* When submitting a feature request on GitHub, be sure to include the
|
||||||
following:
|
following:
|
||||||
|
|
||||||
* A detailed description of the proposed functionality
|
* A detailed description of the proposed functionality
|
||||||
* A use case for the feature; who would use it and what value it
|
* A use case for the feature; who would use it and what value it would add
|
||||||
would add to NetBox
|
to NetBox
|
||||||
* A rough description of changes necessary to the database schema
|
* A rough description of changes necessary to the database schema (if
|
||||||
(if applicable)
|
applicable)
|
||||||
* Any third-party libraries or other resources which would be
|
* Any third-party libraries or other resources which would be involved
|
||||||
involved
|
|
||||||
|
|
||||||
* Please avoid prepending any sort of tag (e.g. "[Feature]") to the issue title.
|
* Please avoid prepending any sort of tag (e.g. "[Feature]") to the issue
|
||||||
The issue will be reviewed by a moderator after submission and the appropriate
|
title. The issue will be reviewed by a moderator after submission and the
|
||||||
labels will be applied.
|
appropriate labels will be applied.
|
||||||
|
|
||||||
## Submitting Pull Requests
|
## Submitting Pull Requests
|
||||||
|
|
||||||
* Be sure to open an issue before starting work on a pull request, and
|
* Be sure to open an issue before starting work on a pull request, and discuss
|
||||||
discuss your idea with the NetBox maintainers before beginning work.
|
your idea with the NetBox maintainers before beginning work. This will help
|
||||||
This will help prevent wasting time on something that might we might not
|
prevent wasting time on something that might we might not be able to implement.
|
||||||
be able to implement. When suggesting a new feature, also make sure it
|
When suggesting a new feature, also make sure it won't conflict with any work
|
||||||
won't conflict with any work that's already in progress.
|
that's already in progress.
|
||||||
|
|
||||||
* When submitting a pull request, please be sure to work off of the
|
* When submitting a pull request, please be sure to work off of the `develop`
|
||||||
`develop` branch, rather than `master`. In NetBox, the `develop` branch
|
branch, rather than `master`. The `develop` branch is used for ongoing
|
||||||
is used for ongoing development, while `master` is used for tagging new
|
development, while `master` is used for tagging new stable releases.
|
||||||
stable releases.
|
|
||||||
|
|
||||||
* All code submissions should meet the following criteria (CI will
|
* All code submissions should meet the following criteria (CI will enforce
|
||||||
enforce these checks):
|
these checks):
|
||||||
|
|
||||||
* Python syntax is valid
|
* Python syntax is valid
|
||||||
* All tests pass when run with `./manage.py test`
|
* All tests pass when run with `./manage.py test`
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.decorators import detail_route
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
|
|
||||||
from circuits import filters
|
from circuits import filters
|
||||||
from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
|
from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
|
||||||
from extras.models import Graph, GRAPH_TYPE_PROVIDER
|
|
||||||
from extras.api.serializers import RenderedGraphSerializer
|
from extras.api.serializers import RenderedGraphSerializer
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
|
from extras.models import Graph, GRAPH_TYPE_PROVIDER
|
||||||
from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
|
from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
|
@ -11,7 +11,6 @@ from utilities.forms import (
|
|||||||
APISelect, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, FilterChoiceField,
|
APISelect, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, FilterChoiceField,
|
||||||
SmallTextarea, SlugField,
|
SmallTextarea, SlugField,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ from django.utils.encoding import python_2_unicode_compatible
|
|||||||
from dcim.fields import ASNField
|
from dcim.fields import ASNField
|
||||||
from extras.models import CustomFieldModel, CustomFieldValue
|
from extras.models import CustomFieldModel, CustomFieldValue
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.utils import csv_format
|
|
||||||
from utilities.models import CreatedUpdatedModel
|
from utilities.models import CreatedUpdatedModel
|
||||||
|
from utilities.utils import csv_format
|
||||||
from .constants import *
|
from .constants import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django_tables2.utils import Accessor
|
|
||||||
|
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django_tables2.utils import Accessor
|
||||||
|
|
||||||
from utilities.tables import BaseTable, ToggleColumn
|
from utilities.tables import BaseTable, ToggleColumn
|
||||||
from .models import Circuit, CircuitType, Provider
|
from .models import Circuit, CircuitType, Provider
|
||||||
|
|
||||||
|
|
||||||
CIRCUITTYPE_ACTIONS = """
|
CIRCUITTYPE_ACTIONS = """
|
||||||
{% if perms.circuit.change_circuittype %}
|
{% if perms.circuit.change_circuittype %}
|
||||||
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from circuits.constants import TERM_SIDE_A, TERM_SIDE_Z
|
||||||
|
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.models import Graph, GRAPH_TYPE_PROVIDER
|
from extras.constants import GRAPH_TYPE_PROVIDER
|
||||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider, TERM_SIDE_A, TERM_SIDE_Z
|
from extras.models import Graph
|
||||||
from users.models import Token
|
from users.models import Token
|
||||||
from utilities.tests import HttpStatusMixin
|
from utilities.tests import HttpStatusMixin
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
app_name = 'circuits'
|
app_name = 'circuits'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ from utilities.views import (
|
|||||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
)
|
)
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider, TERM_SIDE_A, TERM_SIDE_Z
|
from .constants import TERM_SIDE_A, TERM_SIDE_Z
|
||||||
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.validators import UniqueTogetherValidator
|
from rest_framework.validators import UniqueTogetherValidator
|
||||||
|
|
||||||
from ipam.models import IPAddress
|
|
||||||
from circuits.models import Circuit, CircuitTermination
|
from circuits.models import Circuit, CircuitTermination
|
||||||
|
from dcim.constants import (
|
||||||
|
CONNECTION_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_ORDERING_CHOICES, RACK_FACE_CHOICES, RACK_TYPE_CHOICES,
|
||||||
|
RACK_WIDTH_CHOICES, STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
|
||||||
|
)
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
CONNECTION_STATUS_CHOICES, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBay, DeviceBayTemplate, DeviceType, DeviceRole, IFACE_FF_CHOICES, IFACE_ORDERING_CHOICES, Interface,
|
DeviceBayTemplate, DeviceType, DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
||||||
InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate,
|
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RACK_FACE_CHOICES, RACK_TYPE_CHOICES,
|
RackReservation, RackRole, Region, Site,
|
||||||
RACK_WIDTH_CHOICES, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
|
|
||||||
)
|
)
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
|
from ipam.models import IPAddress
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from tenancy.api.serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
|
@ -1,28 +1,30 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponseBadRequest, HttpResponseForbidden
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.decorators import detail_route
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.mixins import ListModelMixin
|
from rest_framework.mixins import ListModelMixin
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
|
||||||
|
|
||||||
from django.conf import settings
|
from dcim import filters
|
||||||
from django.http import HttpResponseBadRequest, HttpResponseForbidden
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
|
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
||||||
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
RackReservation, RackRole, Region, Site,
|
RackReservation, RackRole, Region, Site,
|
||||||
)
|
)
|
||||||
from dcim import filters
|
|
||||||
from extras.api.serializers import RenderedGraphSerializer
|
from extras.api.serializers import RenderedGraphSerializer
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
||||||
from utilities.api import IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ServiceUnavailable, WritableSerializerMixin
|
from utilities.api import (
|
||||||
from .exceptions import MissingFilterException
|
IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ServiceUnavailable, WritableSerializerMixin,
|
||||||
|
)
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
from .exceptions import MissingFilterException
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -256,12 +258,19 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
|
|||||||
device.platform
|
device.platform
|
||||||
))
|
))
|
||||||
|
|
||||||
# Check that NAPALM is installed and verify the configured driver
|
# Check that NAPALM is installed
|
||||||
try:
|
try:
|
||||||
import napalm
|
import napalm
|
||||||
from napalm_base.exceptions import ConnectAuthError, ModuleImportError
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.")
|
raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.")
|
||||||
|
|
||||||
|
# TODO: Remove support for NAPALM < 2.0
|
||||||
|
try:
|
||||||
|
from napalm.base.exceptions import ConnectAuthError, ModuleImportError
|
||||||
|
except ImportError:
|
||||||
|
from napalm_base.exceptions import ConnectAuthError, ModuleImportError
|
||||||
|
|
||||||
|
# Validate the configured driver
|
||||||
try:
|
try:
|
||||||
driver = napalm.get_network_driver(device.platform.napalm_driver)
|
driver = napalm.get_network_driver(device.platform.napalm_driver)
|
||||||
except ModuleImportError:
|
except ModuleImportError:
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
from netaddr import EUI
|
|
||||||
from netaddr.core import AddrFormatError
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from netaddr import EUI
|
||||||
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.filters import NullableCharFieldFilter, NumericInFilter
|
from utilities.filters import NullableCharFieldFilter, NumericInFilter
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
|
from .constants import (
|
||||||
|
IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, STATUS_CHOICES, VIRTUAL_IFACE_TYPES, WIRELESS_IFACE_TYPES,
|
||||||
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, STATUS_CHOICES, IFACE_FF_LAG, Interface, InterfaceConnection,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
||||||
InterfaceTemplate, Manufacturer, InventoryItem, NONCONNECTABLE_IFACE_TYPES, Platform, PowerOutlet,
|
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region, Site,
|
RackReservation, RackRole, Region, Site,
|
||||||
VIRTUAL_IFACE_TYPES, WIRELESS_IFACE_TYPES,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -424,7 +425,8 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Device model (slug)',
|
label='Device model (slug)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=STATUS_CHOICES
|
choices=STATUS_CHOICES,
|
||||||
|
null_value=None
|
||||||
)
|
)
|
||||||
is_full_depth = django_filters.BooleanFilter(
|
is_full_depth = django_filters.BooleanFilter(
|
||||||
name='device_type__is_full_depth',
|
name='device_type__is_full_depth',
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from netaddr import EUI, AddrFormatError
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from netaddr import EUI, AddrFormatError
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from mptt.forms import TreeNodeChoiceField
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.postgres.forms.array import SimpleArrayField
|
from django.contrib.postgres.forms.array import SimpleArrayField
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
|
from mptt.forms import TreeNodeChoiceField
|
||||||
|
|
||||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
@ -19,17 +19,19 @@ from utilities.forms import (
|
|||||||
SlugField, FilterTreeNodeMultipleChoiceField,
|
SlugField, FilterTreeNodeMultipleChoiceField,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
|
from .constants import (
|
||||||
|
CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES,
|
||||||
|
RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, RACK_WIDTH_19IN, RACK_WIDTH_23IN, STATUS_CHOICES,
|
||||||
|
SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES,
|
||||||
|
)
|
||||||
from .formfields import MACAddressFormField
|
from .formfields import MACAddressFormField
|
||||||
from .models import (
|
from .models import (
|
||||||
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, ConsolePort,
|
DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
||||||
ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType, Interface,
|
Device, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, InventoryItem,
|
||||||
IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation,
|
||||||
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_FACE_CHOICES,
|
RackRole, Region, Site,
|
||||||
RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, RACK_WIDTH_19IN, RACK_WIDTH_23IN,
|
|
||||||
Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DEVICE_BY_PK_RE = '{\d+\}'
|
DEVICE_BY_PK_RE = '{\d+\}'
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from itertools import count, groupby
|
from itertools import count, groupby
|
||||||
|
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -15,9 +13,10 @@ from django.db import models
|
|||||||
from django.db.models import Count, Q, ObjectDoesNotExist
|
from django.db.models import Count, Q, ObjectDoesNotExist
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from extras.models import CustomFieldModel, CustomField, CustomFieldValue, ImageAttachment
|
from extras.models import CustomFieldModel, CustomFieldValue, ImageAttachment
|
||||||
from extras.rpc import RPC_CLIENTS
|
from extras.rpc import RPC_CLIENTS
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.fields import ColorField, NullableCharField
|
from utilities.fields import ColorField, NullableCharField
|
||||||
@ -1118,6 +1117,15 @@ class ConsoleServerPort(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
# Check that the parent device's DeviceType is a console server
|
||||||
|
device_type = self.device.device_type
|
||||||
|
if not device_type.is_console_server:
|
||||||
|
raise ValidationError("The {} {} device type not support assignment of console server ports.".format(
|
||||||
|
device_type.manufacturer, device_type
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Power ports
|
# Power ports
|
||||||
@ -1183,6 +1191,15 @@ class PowerOutlet(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
# Check that the parent device's DeviceType is a PDU
|
||||||
|
device_type = self.device.device_type
|
||||||
|
if not device_type.is_pdu:
|
||||||
|
raise ValidationError("The {} {} device type not support assignment of power outlets.".format(
|
||||||
|
device_type.manufacturer, device_type
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Interfaces
|
# Interfaces
|
||||||
@ -1239,6 +1256,13 @@ class Interface(models.Model):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
|
# Check that the parent device's DeviceType is a network device
|
||||||
|
device_type = self.device.device_type
|
||||||
|
if not device_type.is_network_device:
|
||||||
|
raise ValidationError("The {} {} device type not support assignment of network interfaces.".format(
|
||||||
|
device_type.manufacturer, device_type
|
||||||
|
))
|
||||||
|
|
||||||
# An Interface must belong to a Device *or* to a VirtualMachine
|
# An Interface must belong to a Device *or* to a VirtualMachine
|
||||||
if self.device and self.virtual_machine:
|
if self.device and self.virtual_machine:
|
||||||
raise ValidationError("An interface cannot belong to both a device and a virtual machine.")
|
raise ValidationError("An interface cannot belong to both a device and a virtual machine.")
|
||||||
|
@ -44,10 +44,10 @@ class InterfaceQuerySet(QuerySet):
|
|||||||
|
|
||||||
TYPE_RE = r"SUBSTRING({} FROM '^([^0-9]+)')"
|
TYPE_RE = r"SUBSTRING({} FROM '^([^0-9]+)')"
|
||||||
ID_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)$') AS integer)"
|
ID_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)$') AS integer)"
|
||||||
SLOT_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)\/') AS integer)"
|
SLOT_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?([0-9]+)\/') AS integer)"
|
||||||
SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/)([0-9]+)') AS integer), 0)"
|
SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/)([0-9]+)') AS integer), 0)"
|
||||||
POSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{2}}([0-9]+)') AS integer), 0)"
|
POSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/){{2}}([0-9]+)') AS integer), 0)"
|
||||||
SUBPOSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{3}}([0-9]+)') AS integer), 0)"
|
SUBPOSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/){{3}}([0-9]+)') AS integer), 0)"
|
||||||
CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)"
|
CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)"
|
||||||
VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)"
|
VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)"
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ from .models import (
|
|||||||
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, Region, Site,
|
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, Region, Site,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
REGION_LINK = """
|
REGION_LINK = """
|
||||||
{% if record.get_children %}
|
{% if record.get_children %}
|
||||||
<span style="padding-left: {{ record.get_ancestors|length }}0px "><i class="fa fa-caret-right"></i>
|
<span style="padding-left: {{ record.get_ancestors|length }}0px "><i class="fa fa-caret-right"></i>
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from dcim.constants import IFACE_FF_LAG, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, IFACE_FF_LAG, Interface, InterfaceConnection, InterfaceTemplate,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
||||||
Manufacturer, InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
|
InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
|
||||||
RackReservation, RackRole, Region, Site, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
|
RackReservation, RackRole, Region, Site,
|
||||||
)
|
)
|
||||||
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
||||||
from users.models import Token
|
from users.models import Token
|
||||||
@ -1432,7 +1432,7 @@ class ConsoleServerPortTest(HttpStatusMixin, APITestCase):
|
|||||||
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||||
devicetype = DeviceType.objects.create(
|
devicetype = DeviceType.objects.create(
|
||||||
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
|
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1', is_console_server=True
|
||||||
)
|
)
|
||||||
devicerole = DeviceRole.objects.create(
|
devicerole = DeviceRole.objects.create(
|
||||||
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
||||||
@ -1590,7 +1590,7 @@ class PowerOutletTest(HttpStatusMixin, APITestCase):
|
|||||||
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||||
devicetype = DeviceType.objects.create(
|
devicetype = DeviceType.objects.create(
|
||||||
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
|
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1', is_pdu=True
|
||||||
)
|
)
|
||||||
devicerole = DeviceRole.objects.create(
|
devicerole = DeviceRole.objects.create(
|
||||||
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
||||||
@ -1667,7 +1667,7 @@ class InterfaceTest(HttpStatusMixin, APITestCase):
|
|||||||
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||||
devicetype = DeviceType.objects.create(
|
devicetype = DeviceType.objects.create(
|
||||||
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
|
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1', is_network_device=True
|
||||||
)
|
)
|
||||||
devicerole = DeviceRole.objects.create(
|
devicerole = DeviceRole.objects.create(
|
||||||
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
||||||
|
@ -5,9 +5,8 @@ from django.conf.urls import url
|
|||||||
from extras.views import ImageAttachmentEditView
|
from extras.views import ImageAttachmentEditView
|
||||||
from ipam.views import ServiceCreateView
|
from ipam.views import ServiceCreateView
|
||||||
from secrets.views import secret_add
|
from secrets.views import secret_add
|
||||||
from .models import Device, Rack, Site
|
|
||||||
from . import views
|
from . import views
|
||||||
|
from .models import Device, Rack, Site
|
||||||
|
|
||||||
app_name = 'dcim'
|
app_name = 'dcim'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import re
|
|
||||||
from natsort import natsorted
|
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@ -15,10 +14,11 @@ from django.utils.html import escape
|
|||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from natsort import natsorted
|
||||||
|
|
||||||
from ipam.models import Prefix, Service, VLAN
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE, UserAction
|
from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE, UserAction
|
||||||
|
from ipam.models import Prefix, Service, VLAN
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator
|
from utilities.paginator import EnhancedPaginator
|
||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
@ -26,11 +26,12 @@ from utilities.views import (
|
|||||||
ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
)
|
)
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
|
from .constants import CONNECTION_STATUS_CONNECTED
|
||||||
from .models import (
|
from .models import (
|
||||||
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
||||||
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
RackGroup, RackReservation, RackRole, Region, Site,
|
RackReservation, RackRole, Region, Site,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
from datetime import datetime
|
||||||
from rest_framework.exceptions import ValidationError
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from extras.models import (
|
from extras.constants import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT
|
||||||
CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_SELECT, CustomField, CustomFieldChoice, CustomFieldValue,
|
from extras.models import CustomField, CustomFieldChoice, CustomFieldValue
|
||||||
)
|
|
||||||
from utilities.api import ValidatedModelSerializer
|
from utilities.api import ValidatedModelSerializer
|
||||||
|
|
||||||
|
|
||||||
@ -39,6 +38,15 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
|
|||||||
# Data validation
|
# Data validation
|
||||||
if value not in [None, '']:
|
if value not in [None, '']:
|
||||||
|
|
||||||
|
# Validate integer
|
||||||
|
if cf.type == CF_TYPE_INTEGER:
|
||||||
|
try:
|
||||||
|
int(value)
|
||||||
|
except ValueError:
|
||||||
|
raise ValidationError(
|
||||||
|
"Invalid value for integer field {}: {}".format(field_name, value)
|
||||||
|
)
|
||||||
|
|
||||||
# Validate boolean
|
# Validate boolean
|
||||||
if cf.type == CF_TYPE_BOOLEAN and value not in [True, False, 1, 0]:
|
if cf.type == CF_TYPE_BOOLEAN and value not in [True, False, 1, 0]:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from dcim.api.serializers import NestedDeviceSerializer, NestedRackSerializer, NestedSiteSerializer
|
from dcim.api.serializers import NestedDeviceSerializer, NestedRackSerializer, NestedSiteSerializer
|
||||||
from dcim.models import Device, Rack, Site
|
from dcim.models import Device, Rack, Site
|
||||||
from extras.models import (
|
from extras.constants import ACTION_CHOICES, GRAPH_TYPE_CHOICES
|
||||||
ACTION_CHOICES, ExportTemplate, Graph, GRAPH_TYPE_CHOICES, ImageAttachment, ReportResult, TopologyMap, UserAction,
|
from extras.models import ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction
|
||||||
)
|
|
||||||
from users.api.serializers import NestedUserSerializer
|
from users.api.serializers import NestedUserSerializer
|
||||||
from utilities.api import ChoiceFieldSerializer, ContentTypeFieldSerializer, ValidatedModelSerializer
|
from utilities.api import ChoiceFieldSerializer, ContentTypeFieldSerializer, ValidatedModelSerializer
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from rest_framework.decorators import detail_route
|
|
||||||
from rest_framework.exceptions import PermissionDenied
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from rest_framework.decorators import detail_route
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet
|
||||||
|
|
||||||
from extras import filters
|
from extras import filters
|
||||||
from extras.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction
|
from extras.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from .models import CF_TYPE_SELECT, CustomField, Graph, ExportTemplate, TopologyMap, UserAction
|
from .constants import CF_TYPE_SELECT
|
||||||
|
from .models import CustomField, Graph, ExportTemplate, TopologyMap, UserAction
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldFilter(django_filters.Filter):
|
class CustomFieldFilter(django_filters.Filter):
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from utilities.forms import BootstrapMixin, BulkEditForm, LaxURLField
|
from utilities.forms import BootstrapMixin, BulkEditForm, LaxURLField
|
||||||
from .models import (
|
from .constants import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL
|
||||||
CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, CustomField, CustomFieldValue,
|
from .models import CustomField, CustomFieldValue, ImageAttachment
|
||||||
ImageAttachment,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=False):
|
def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=False):
|
||||||
|
@ -10,7 +10,6 @@ from django.conf import settings
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
|
||||||
|
|
||||||
APPS = ['circuits', 'dcim', 'extras', 'ipam', 'secrets', 'tenancy', 'users', 'virtualization']
|
APPS = ['circuits', 'dcim', 'extras', 'ipam', 'secrets', 'tenancy', 'users', 'virtualization']
|
||||||
|
|
||||||
BANNER_TEXT = """### NetBox interactive shell ({node})
|
BANNER_TEXT = """### NetBox interactive shell ({node})
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from ncclient.transport.errors import AuthenticationError
|
|
||||||
from paramiko import AuthenticationException
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from ncclient.transport.errors import AuthenticationError
|
||||||
|
from paramiko import AuthenticationException
|
||||||
|
|
||||||
from dcim.models import Device, InventoryItem, Site, STATUS_ACTIVE
|
from dcim.models import Device, InventoryItem, Site, STATUS_ACTIVE
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from extras.models import ReportResult
|
|
||||||
from extras.reports import get_reports
|
from extras.reports import get_reports
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# Generated by Django 1.11.4 on 2017-09-26 21:25
|
# Generated by Django 1.11.4 on 2017-09-26 21:25
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.contrib.postgres.fields.jsonb
|
import django.contrib.postgres.fields.jsonb
|
||||||
@ -18,7 +19,7 @@ def verify_postgresql_version(apps, schema_editor):
|
|||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute("SELECT VERSION()")
|
cursor.execute("SELECT VERSION()")
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
pg_version = row[0].split()[1]
|
pg_version = re.match('^PostgreSQL (\d+\.\d+(\.\d+)?)', row[0]).group(1)
|
||||||
if StrictVersion(pg_version) < StrictVersion('9.4.0'):
|
if StrictVersion(pg_version) < StrictVersion('9.4.0'):
|
||||||
raise Exception("PostgreSQL 9.4.0 or higher is required ({} found). Upgrade PostgreSQL and then run migrations again.".format(pg_version))
|
raise Exception("PostgreSQL 9.4.0 or higher is required ({} found). Upgrade PostgreSQL and then run migrations again.".format(pg_version))
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import graphviz
|
|
||||||
|
|
||||||
|
import graphviz
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from collections import OrderedDict
|
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ncclient import manager
|
|
||||||
import paramiko
|
import paramiko
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
from ncclient import manager
|
||||||
|
|
||||||
CONNECT_TIMEOUT = 5 # seconds
|
CONNECT_TIMEOUT = 5 # seconds
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.models import Graph, GRAPH_TYPE_SITE, ExportTemplate
|
from extras.constants import GRAPH_TYPE_SITE
|
||||||
|
from extras.models import Graph, ExportTemplate
|
||||||
from users.models import Token
|
from users.models import Token
|
||||||
from utilities.tests import HttpStatusMixin
|
from utilities.tests import HttpStatusMixin
|
||||||
|
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
from rest_framework import status
|
from datetime import date
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.models import (
|
from extras.constants import CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_SELECT, CF_TYPE_URL
|
||||||
CustomField, CustomFieldValue, CustomFieldChoice, CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE,
|
from extras.models import CustomField, CustomFieldValue, CustomFieldChoice
|
||||||
CF_TYPE_SELECT, CF_TYPE_URL,
|
|
||||||
)
|
|
||||||
from users.models import Token
|
from users.models import Token
|
||||||
from utilities.tests import HttpStatusMixin
|
from utilities.tests import HttpStatusMixin
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from extras import views
|
from extras import views
|
||||||
|
|
||||||
|
|
||||||
app_name = 'extras'
|
app_name = 'extras'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@ -6,10 +7,10 @@ from rest_framework.validators import UniqueTogetherValidator
|
|||||||
|
|
||||||
from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer
|
from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from ipam.models import (
|
from ipam.constants import (
|
||||||
Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, Prefix,
|
IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES,
|
||||||
PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF,
|
|
||||||
)
|
)
|
||||||
|
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from tenancy.api.serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
||||||
from virtualization.api.serializers import NestedVirtualMachineSerializer
|
from virtualization.api.serializers import NestedVirtualMachineSerializer
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import detail_route
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
|
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
|
||||||
from ipam import filters
|
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
|
from ipam import filters
|
||||||
|
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
|
from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ VLAN_STATUS_CHOICES = (
|
|||||||
(VLAN_STATUS_DEPRECATED, 'Deprecated')
|
(VLAN_STATUS_DEPRECATED, 'Deprecated')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Bootstrap CSS classes for various statuses
|
# Bootstrap CSS classes
|
||||||
STATUS_CHOICE_CLASSES = {
|
STATUS_CHOICE_CLASSES = {
|
||||||
0: 'default',
|
0: 'default',
|
||||||
1: 'primary',
|
1: 'primary',
|
||||||
@ -70,6 +70,16 @@ STATUS_CHOICE_CLASSES = {
|
|||||||
4: 'warning',
|
4: 'warning',
|
||||||
5: 'success',
|
5: 'success',
|
||||||
}
|
}
|
||||||
|
ROLE_CHOICE_CLASSES = {
|
||||||
|
10: 'default',
|
||||||
|
20: 'primary',
|
||||||
|
30: 'warning',
|
||||||
|
40: 'success',
|
||||||
|
41: 'success',
|
||||||
|
42: 'success',
|
||||||
|
43: 'success',
|
||||||
|
44: 'success',
|
||||||
|
}
|
||||||
|
|
||||||
# IP protocols (for services)
|
# IP protocols (for services)
|
||||||
IP_PROTOCOL_TCP = 6
|
IP_PROTOCOL_TCP = 6
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from netaddr import IPNetwork
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
from .formfields import IPFormField
|
from .formfields import IPFormField
|
||||||
from .lookups import (
|
from .lookups import (
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
|
from django.db.models import Q
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from dcim.models import Site, Device, Interface
|
from dcim.models import Site, Device, Interface
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.filters import NumericInFilter
|
from utilities.filters import NumericInFilter
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .models import (
|
from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES
|
||||||
Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role,
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||||
@ -102,9 +99,18 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
# TODO: Deprecate in v2.3.0
|
||||||
parent = django_filters.CharFilter(
|
parent = django_filters.CharFilter(
|
||||||
method='search_by_parent',
|
method='search_within_include',
|
||||||
label='Parent prefix',
|
label='Parent prefix (deprecated)',
|
||||||
|
)
|
||||||
|
within = django_filters.CharFilter(
|
||||||
|
method='search_within',
|
||||||
|
label='Within prefix',
|
||||||
|
)
|
||||||
|
within_include = django_filters.CharFilter(
|
||||||
|
method='search_within_include',
|
||||||
|
label='Within and including prefix',
|
||||||
)
|
)
|
||||||
mask_length = django_filters.NumberFilter(
|
mask_length = django_filters.NumberFilter(
|
||||||
method='filter_mask_length',
|
method='filter_mask_length',
|
||||||
@ -159,7 +165,8 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Role (slug)',
|
label='Role (slug)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=PREFIX_STATUS_CHOICES
|
choices=PREFIX_STATUS_CHOICES,
|
||||||
|
null_value=None
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -177,7 +184,17 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
pass
|
pass
|
||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
def search_by_parent(self, queryset, name, value):
|
def search_within(self, queryset, name, value):
|
||||||
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
try:
|
||||||
|
query = str(IPNetwork(value).cidr)
|
||||||
|
return queryset.filter(prefix__net_contained=query)
|
||||||
|
except (AddrFormatError, ValueError):
|
||||||
|
return queryset.none()
|
||||||
|
|
||||||
|
def search_within_include(self, queryset, name, value):
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
@ -254,7 +271,8 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Interface (ID)',
|
label='Interface (ID)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=IPADDRESS_STATUS_CHOICES
|
choices=IPADDRESS_STATUS_CHOICES,
|
||||||
|
null_value=None
|
||||||
)
|
)
|
||||||
role = django_filters.MultipleChoiceFilter(
|
role = django_filters.MultipleChoiceFilter(
|
||||||
choices=IPADDRESS_ROLE_CHOICES
|
choices=IPADDRESS_ROLE_CHOICES
|
||||||
@ -353,7 +371,8 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Role (slug)',
|
label='Role (slug)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=VLAN_STATUS_CHOICES
|
choices=VLAN_STATUS_CHOICES,
|
||||||
|
null_value=None
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from netaddr import IPNetwork, AddrFormatError
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from netaddr import IPNetwork, AddrFormatError
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -14,11 +14,8 @@ from utilities.forms import (
|
|||||||
add_blank_choice,
|
add_blank_choice,
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .models import (
|
from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES
|
||||||
Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role,
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
Service, VLAN, VLANGroup, VLAN_STATUS_CHOICES, VRF,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
IP_FAMILY_CHOICES = [
|
IP_FAMILY_CHOICES = [
|
||||||
('', 'All'),
|
('', 'All'),
|
||||||
@ -362,7 +359,7 @@ def prefix_status_choices():
|
|||||||
class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Prefix
|
model = Prefix
|
||||||
q = forms.CharField(required=False, label='Search')
|
q = forms.CharField(required=False, label='Search')
|
||||||
parent = forms.CharField(required=False, label='Parent prefix', widget=forms.TextInput(attrs={
|
within_include = forms.CharField(required=False, label='Search within', widget=forms.TextInput(attrs={
|
||||||
'placeholder': 'Prefix',
|
'placeholder': 'Prefix',
|
||||||
}))
|
}))
|
||||||
family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address family')
|
family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address family')
|
||||||
@ -691,6 +688,11 @@ class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
nullable_fields = ['vrf', 'role', 'tenant', 'description']
|
nullable_fields = ['vrf', 'role', 'tenant', 'description']
|
||||||
|
|
||||||
|
|
||||||
|
class IPAddressAssignForm(BootstrapMixin, forms.Form):
|
||||||
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
|
||||||
|
address = forms.CharField(label='IP Address')
|
||||||
|
|
||||||
|
|
||||||
def ipaddress_status_choices():
|
def ipaddress_status_choices():
|
||||||
status_counts = {}
|
status_counts = {}
|
||||||
for status in IPAddress.objects.values('status').annotate(count=Count('status')).order_by('status'):
|
for status in IPAddress.objects.values('status').annotate(count=Count('status')).order_by('status'):
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import netaddr
|
|
||||||
|
|
||||||
|
import netaddr
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@ -286,7 +285,7 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
"""
|
"""
|
||||||
Return all IPAddresses within this Prefix.
|
Return all IPAddresses within this Prefix.
|
||||||
"""
|
"""
|
||||||
return IPAddress.objects.filter(address__net_contained_or_equal=str(self.prefix), vrf=self.vrf)
|
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
||||||
|
|
||||||
def get_available_ips(self):
|
def get_available_ips(self):
|
||||||
"""
|
"""
|
||||||
@ -315,9 +314,7 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
|
child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
|
||||||
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
||||||
else:
|
else:
|
||||||
child_count = IPAddress.objects.filter(
|
child_count = self.get_child_ips().count()
|
||||||
address__net_contained_or_equal=str(self.prefix), vrf=self.vrf
|
|
||||||
).count()
|
|
||||||
prefix_size = self.prefix.size
|
prefix_size = self.prefix.size
|
||||||
if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
|
if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
|
||||||
prefix_size -= 2
|
prefix_size -= 2
|
||||||
@ -461,6 +458,9 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CHOICE_CLASSES[self.status]
|
return STATUS_CHOICE_CLASSES[self.status]
|
||||||
|
|
||||||
|
def get_role_class(self):
|
||||||
|
return ROLE_CHOICE_CLASSES[self.role]
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class VLANGroup(models.Model):
|
class VLANGroup(models.Model):
|
||||||
|
@ -6,7 +6,6 @@ from django_tables2.utils import Accessor
|
|||||||
from utilities.tables import BaseTable, ToggleColumn
|
from utilities.tables import BaseTable, ToggleColumn
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF
|
||||||
|
|
||||||
|
|
||||||
RIR_UTILIZATION = """
|
RIR_UTILIZATION = """
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
{% if record.stats.total %}
|
{% if record.stats.total %}
|
||||||
@ -77,6 +76,10 @@ IPADDRESS_LINK = """
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
IPADDRESS_ASSIGN_LINK = """
|
||||||
|
<a href="{% url 'ipam:ipaddress_edit' pk=record.pk %}?interface={{ request.GET.interface }}&return_url={{ request.GET.return_url }}">{{ record }}</a>
|
||||||
|
"""
|
||||||
|
|
||||||
IPADDRESS_PARENT = """
|
IPADDRESS_PARENT = """
|
||||||
{% if record.interface %}
|
{% if record.interface %}
|
||||||
<a href="{{ record.interface.parent.get_absolute_url }}">{{ record.interface.parent }}</a>
|
<a href="{{ record.interface.parent.get_absolute_url }}">{{ record.interface.parent }}</a>
|
||||||
@ -269,8 +272,8 @@ class PrefixDetailTable(PrefixTable):
|
|||||||
class IPAddressTable(BaseTable):
|
class IPAddressTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address')
|
address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address')
|
||||||
status = tables.TemplateColumn(STATUS_LABEL)
|
|
||||||
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
|
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
|
||||||
|
status = tables.TemplateColumn(STATUS_LABEL)
|
||||||
tenant = tables.TemplateColumn(TENANT_LINK)
|
tenant = tables.TemplateColumn(TENANT_LINK)
|
||||||
parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
|
parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
|
||||||
interface = tables.Column(orderable=False)
|
interface = tables.Column(orderable=False)
|
||||||
@ -294,6 +297,18 @@ class IPAddressDetailTable(IPAddressTable):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IPAddressAssignTable(BaseTable):
|
||||||
|
address = tables.TemplateColumn(IPADDRESS_ASSIGN_LINK, verbose_name='IP Address')
|
||||||
|
status = tables.TemplateColumn(STATUS_LABEL)
|
||||||
|
parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
|
||||||
|
interface = tables.Column(orderable=False)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = IPAddress
|
||||||
|
fields = ('address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface')
|
||||||
|
orderable = False
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# VLAN groups
|
# VLAN groups
|
||||||
#
|
#
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.urls import reverse
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
from ipam.models import (
|
from ipam.constants import IP_PROTOCOL_TCP, IP_PROTOCOL_UDP
|
||||||
Aggregate, IPAddress, IP_PROTOCOL_TCP, IP_PROTOCOL_UDP, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF,
|
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
)
|
|
||||||
from users.models import Token
|
from users.models import Token
|
||||||
from utilities.tests import HttpStatusMixin
|
from utilities.tests import HttpStatusMixin
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ urlpatterns = [
|
|||||||
url(r'^ip-addresses/import/$', views.IPAddressBulkImportView.as_view(), name='ipaddress_import'),
|
url(r'^ip-addresses/import/$', views.IPAddressBulkImportView.as_view(), name='ipaddress_import'),
|
||||||
url(r'^ip-addresses/edit/$', views.IPAddressBulkEditView.as_view(), name='ipaddress_bulk_edit'),
|
url(r'^ip-addresses/edit/$', views.IPAddressBulkEditView.as_view(), name='ipaddress_bulk_edit'),
|
||||||
url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'),
|
url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'),
|
||||||
|
url(r'^ip-addresses/assign/$', views.IPAddressAssignView.as_view(), name='ipaddress_assign'),
|
||||||
url(r'^ip-addresses/(?P<pk>\d+)/$', views.IPAddressView.as_view(), name='ipaddress'),
|
url(r'^ip-addresses/(?P<pk>\d+)/$', views.IPAddressView.as_view(), name='ipaddress'),
|
||||||
url(r'^ip-addresses/(?P<pk>\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'),
|
url(r'^ip-addresses/(?P<pk>\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'),
|
||||||
url(r'^ip-addresses/(?P<pk>\d+)/delete/$', views.IPAddressDeleteView.as_view(), name='ipaddress_delete'),
|
url(r'^ip-addresses/(?P<pk>\d+)/delete/$', views.IPAddressDeleteView.as_view(), name='ipaddress_delete'),
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django_tables2 import RequestConfig
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from django_tables2 import RequestConfig
|
||||||
|
|
||||||
from dcim.models import Device, Interface
|
from dcim.models import Device, Interface
|
||||||
from utilities.paginator import EnhancedPaginator
|
from utilities.paginator import EnhancedPaginator
|
||||||
@ -17,11 +16,8 @@ from utilities.views import (
|
|||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .constants import IPADDRESS_ROLE_ANYCAST
|
from .constants import IPADDRESS_ROLE_ANYCAST, PREFIX_STATUS_ACTIVE, PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED
|
||||||
from .models import (
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
Aggregate, IPAddress, PREFIX_STATUS_ACTIVE, PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED, Prefix, RIR, Role,
|
|
||||||
Service, VLAN, VLANGroup, VRF,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def add_available_prefixes(parent, prefix_list):
|
def add_available_prefixes(parent, prefix_list):
|
||||||
@ -459,9 +455,7 @@ class PrefixView(View):
|
|||||||
aggregate = None
|
aggregate = None
|
||||||
|
|
||||||
# Count child IP addresses
|
# Count child IP addresses
|
||||||
ipaddress_count = IPAddress.objects.filter(
|
ipaddress_count = prefix.get_child_ips().count()
|
||||||
vrf=prefix.vrf, address__net_host_contained=str(prefix.prefix)
|
|
||||||
).count()
|
|
||||||
|
|
||||||
# Parent prefixes table
|
# Parent prefixes table
|
||||||
parent_prefixes = Prefix.objects.filter(
|
parent_prefixes = Prefix.objects.filter(
|
||||||
@ -517,6 +511,7 @@ class PrefixView(View):
|
|||||||
'parent_prefix_table': parent_prefix_table,
|
'parent_prefix_table': parent_prefix_table,
|
||||||
'child_prefix_table': child_prefix_table,
|
'child_prefix_table': child_prefix_table,
|
||||||
'duplicate_prefix_table': duplicate_prefix_table,
|
'duplicate_prefix_table': duplicate_prefix_table,
|
||||||
|
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf or '0', prefix.prefix),
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'return_url': prefix.get_absolute_url(),
|
'return_url': prefix.get_absolute_url(),
|
||||||
})
|
})
|
||||||
@ -529,9 +524,7 @@ class PrefixIPAddressesView(View):
|
|||||||
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
||||||
|
|
||||||
# Find all IPAddresses belonging to this Prefix
|
# Find all IPAddresses belonging to this Prefix
|
||||||
ipaddresses = IPAddress.objects.filter(
|
ipaddresses = prefix.get_child_ips().select_related(
|
||||||
vrf=prefix.vrf, address__net_host_contained=str(prefix.prefix)
|
|
||||||
).select_related(
|
|
||||||
'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
|
'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
|
||||||
)
|
)
|
||||||
ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
|
ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
|
||||||
@ -557,7 +550,7 @@ class PrefixIPAddressesView(View):
|
|||||||
'prefix': prefix,
|
'prefix': prefix,
|
||||||
'ip_table': ip_table,
|
'ip_table': ip_table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf or '0', prefix.prefix),
|
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -693,6 +686,51 @@ class IPAddressEditView(IPAddressCreateView):
|
|||||||
permission_required = 'ipam.change_ipaddress'
|
permission_required = 'ipam.change_ipaddress'
|
||||||
|
|
||||||
|
|
||||||
|
class IPAddressAssignView(PermissionRequiredMixin, View):
|
||||||
|
"""
|
||||||
|
Search for IPAddresses to be assigned to an Interface.
|
||||||
|
"""
|
||||||
|
permission_required = 'ipam.change_ipaddress'
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
# Redirect user if an interface has not been provided
|
||||||
|
if 'interface' not in request.GET:
|
||||||
|
return redirect('ipam:ipaddress_add')
|
||||||
|
|
||||||
|
return super(IPAddressAssignView, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
|
||||||
|
form = forms.IPAddressAssignForm()
|
||||||
|
|
||||||
|
return render(request, 'ipam/ipaddress_assign.html', {
|
||||||
|
'form': form,
|
||||||
|
'return_url': request.GET.get('return_url', ''),
|
||||||
|
})
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
|
||||||
|
form = forms.IPAddressAssignForm(request.POST)
|
||||||
|
table = None
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
|
||||||
|
queryset = IPAddress.objects.select_related(
|
||||||
|
'vrf', 'tenant', 'interface__device', 'interface__virtual_machine'
|
||||||
|
).filter(
|
||||||
|
vrf=form.cleaned_data['vrf'],
|
||||||
|
address__net_host=form.cleaned_data['address'],
|
||||||
|
)
|
||||||
|
table = tables.IPAddressAssignTable(queryset)
|
||||||
|
|
||||||
|
return render(request, 'ipam/ipaddress_assign.html', {
|
||||||
|
'form': form,
|
||||||
|
'table': table,
|
||||||
|
'return_url': request.GET.get('return_url', ''),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||||
permission_required = 'ipam.delete_ipaddress'
|
permission_required = 'ipam.delete_ipaddress'
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
|
@ -4,7 +4,6 @@ from django import forms
|
|||||||
|
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
|
|
||||||
|
|
||||||
OBJ_TYPE_CHOICES = (
|
OBJ_TYPE_CHOICES = (
|
||||||
('', 'All Objects'),
|
('', 'All Objects'),
|
||||||
('Circuits', (
|
('Circuits', (
|
||||||
|
@ -13,7 +13,7 @@ except ImportError:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
VERSION = '2.2.4'
|
VERSION = '2.2.5'
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
@ -148,6 +148,7 @@ MIDDLEWARE = (
|
|||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'utilities.middleware.ExceptionHandlingMiddleware',
|
||||||
'utilities.middleware.LoginRequiredMiddleware',
|
'utilities.middleware.LoginRequiredMiddleware',
|
||||||
'utilities.middleware.APIVersionMiddleware',
|
'utilities.middleware.APIVersionMiddleware',
|
||||||
)
|
)
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from rest_framework_swagger.views import get_swagger_view
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.static import serve
|
from django.views.static import serve
|
||||||
|
from rest_framework_swagger.views import get_swagger_view
|
||||||
|
|
||||||
from netbox.views import APIRootView, handle_500, HomeView, SearchView, trigger_500
|
from netbox.views import APIRootView, HomeView, SearchView
|
||||||
from users.views import LoginView, LogoutView
|
from users.views import LoginView, LogoutView
|
||||||
|
|
||||||
|
|
||||||
handler500 = handle_500
|
|
||||||
swagger_view = get_swagger_view(title='NetBox API')
|
swagger_view = get_swagger_view(title='NetBox API')
|
||||||
|
|
||||||
_patterns = [
|
_patterns = [
|
||||||
@ -48,9 +45,6 @@ _patterns = [
|
|||||||
# Serving static media in Django to pipe it through LoginRequiredMiddleware
|
# Serving static media in Django to pipe it through LoginRequiredMiddleware
|
||||||
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
|
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
|
||||||
|
|
||||||
# Error testing
|
|
||||||
url(r'^500/$', trigger_500),
|
|
||||||
|
|
||||||
# Admin
|
# Admin
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from collections import OrderedDict
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from collections import OrderedDict
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.reverse import reverse
|
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.reverse import reverse
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from circuits.filters import CircuitFilter, ProviderFilter
|
from circuits.filters import CircuitFilter, ProviderFilter
|
||||||
from circuits.models import Circuit, Provider
|
from circuits.models import Circuit, Provider
|
||||||
@ -30,7 +29,6 @@ from virtualization.models import Cluster, VirtualMachine
|
|||||||
from virtualization.tables import ClusterTable, VirtualMachineDetailTable
|
from virtualization.tables import ClusterTable, VirtualMachineDetailTable
|
||||||
from .forms import SearchForm
|
from .forms import SearchForm
|
||||||
|
|
||||||
|
|
||||||
SEARCH_MAX_RESULTS = 15
|
SEARCH_MAX_RESULTS = 15
|
||||||
SEARCH_TYPES = OrderedDict((
|
SEARCH_TYPES = OrderedDict((
|
||||||
# Circuits
|
# Circuits
|
||||||
@ -247,23 +245,3 @@ class APIRootView(APIView):
|
|||||||
('tenancy', reverse('tenancy-api:api-root', request=request, format=format)),
|
('tenancy', reverse('tenancy-api:api-root', request=request, format=format)),
|
||||||
('virtualization', reverse('virtualization-api:api-root', request=request, format=format)),
|
('virtualization', reverse('virtualization-api:api-root', request=request, format=format)),
|
||||||
)))
|
)))
|
||||||
|
|
||||||
|
|
||||||
def handle_500(request):
|
|
||||||
"""
|
|
||||||
Custom server error handler
|
|
||||||
"""
|
|
||||||
type_, error, traceback = sys.exc_info()
|
|
||||||
return render(request, '500.html', {
|
|
||||||
'exception': str(type_),
|
|
||||||
'error': error,
|
|
||||||
}, status=500)
|
|
||||||
|
|
||||||
|
|
||||||
def trigger_500(request):
|
|
||||||
"""
|
|
||||||
Hot-wired method of triggering a server error to test reporting
|
|
||||||
"""
|
|
||||||
raise Exception(
|
|
||||||
"Congratulations, you've triggered an exception! Go tell all your friends what an exceptional person you are."
|
|
||||||
)
|
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
"""
|
|
||||||
WSGI config for do_ipam project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
@ -395,6 +395,9 @@ table.reports td.stats label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
|
.text-nowrap {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.banner-bottom {
|
.banner-bottom {
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
from django.http import HttpResponseBadRequest
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ModelViewSet, ViewSet
|
from rest_framework.viewsets import ModelViewSet, ViewSet
|
||||||
|
|
||||||
from django.http import HttpResponseBadRequest
|
|
||||||
|
|
||||||
from secrets import filters
|
from secrets import filters
|
||||||
from secrets.exceptions import InvalidKey
|
from secrets.exceptions import InvalidKey
|
||||||
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
||||||
from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
|
from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
ERR_USERKEY_MISSING = "No UserKey found for the current user."
|
ERR_USERKEY_MISSING = "No UserKey found for the current user."
|
||||||
ERR_USERKEY_INACTIVE = "UserKey has not been activated for decryption."
|
ERR_USERKEY_INACTIVE = "UserKey has not been activated for decryption."
|
||||||
ERR_PRIVKEY_MISSING = "Private key was not provided."
|
ERR_PRIVKEY_MISSING = "Private key was not provided."
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from .models import Secret, SecretRole
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from utilities.filters import NumericInFilter
|
from utilities.filters import NumericInFilter
|
||||||
|
from .models import Secret, SecretRole
|
||||||
|
|
||||||
|
|
||||||
class SecretRoleFilter(django_filters.FilterSet):
|
class SecretRoleFilter(django_filters.FilterSet):
|
||||||
|
@ -2,7 +2,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from Crypto.Cipher import PKCS1_OAEP
|
from Crypto.Cipher import PKCS1_OAEP
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from Crypto.Cipher import AES, PKCS1_OAEP
|
from Crypto.Cipher import AES, PKCS1_OAEP
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
from Crypto.Util import strxor
|
from Crypto.Util import strxor
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.hashers import make_password, check_password
|
from django.contrib.auth.hashers import make_password, check_password
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
@ -286,6 +286,9 @@ class Secret(CreatedUpdatedModel):
|
|||||||
super(Secret, self).__init__(*args, **kwargs)
|
super(Secret, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
if self.role and self.device and self.name:
|
||||||
|
return '{} for {} ({})'.format(self.role, self.device, self.name)
|
||||||
|
# Return role and device if no name is set
|
||||||
if self.role and self.device:
|
if self.role and self.device:
|
||||||
return '{} for {}'.format(self.role, self.device)
|
return '{} for {}'.format(self.role, self.device)
|
||||||
return 'Secret'
|
return 'Secret'
|
||||||
|
@ -3,10 +3,8 @@ from __future__ import unicode_literals
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from utilities.tables import BaseTable, ToggleColumn
|
from utilities.tables import BaseTable, ToggleColumn
|
||||||
|
|
||||||
from .models import SecretRole, Secret
|
from .models import SecretRole, Secret
|
||||||
|
|
||||||
|
|
||||||
SECRETROLE_ACTIONS = """
|
SECRETROLE_ACTIONS = """
|
||||||
{% if perms.secrets.change_secretrole %}
|
{% if perms.secrets.change_secretrole %}
|
||||||
<a href="{% url 'secrets:secretrole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'secrets:secretrole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import base64
|
|
||||||
|
|
||||||
from rest_framework import status
|
import base64
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
||||||
from users.models import Token
|
from users.models import Token
|
||||||
from utilities.tests import HttpStatusMixin
|
from utilities.tests import HttpStatusMixin
|
||||||
|
|
||||||
|
|
||||||
# Dummy RSA key pair for testing use only
|
# Dummy RSA key pair for testing use only
|
||||||
PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
|
PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEowIBAAKCAQEA97wPWxpq5cClRu8Ssq609ZLfyx6E8ln/v/PdFZ7fxxmA4k+z
|
MIIEowIBAAKCAQEA97wPWxpq5cClRu8Ssq609ZLfyx6E8ln/v/PdFZ7fxxmA4k+z
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from secrets.models import UserKey, Secret, encrypt_master_key, decrypt_master_key, generate_random_key
|
|
||||||
from secrets.hashers import SecretValidationHasher
|
from secrets.hashers import SecretValidationHasher
|
||||||
|
from secrets.models import UserKey, Secret, encrypt_master_key, decrypt_master_key, generate_random_key
|
||||||
|
|
||||||
|
|
||||||
class UserKeyTestCase(TestCase):
|
class UserKeyTestCase(TestCase):
|
||||||
|
@ -4,7 +4,6 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
app_name = 'secrets'
|
app_name = 'secrets'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 col-md-offset-4">
|
<div class="col-md-6 col-md-offset-3">
|
||||||
<div class="panel panel-danger" style="margin-top: 200px">
|
<div class="panel panel-danger" style="margin-top: 200px">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>
|
<strong>
|
||||||
@ -21,13 +21,20 @@
|
|||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p>There was a problem with your request. This error has been logged and administrative staff have
|
{% block message %}
|
||||||
been notified. Please return to the home page and try again.</p>
|
<p>
|
||||||
<p>If you are responsible for this installation, please consider
|
There was a problem with your request. Please contact an administrator.
|
||||||
<a href="https://github.com/digitalocean/netbox/issues">filing a bug report</a>. Additional
|
</p>
|
||||||
information is provided below:</p>
|
{% endblock %}
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
The complete exception is provided below:
|
||||||
|
</p>
|
||||||
<pre><strong>{{ exception }}</strong><br />
|
<pre><strong>{{ exception }}</strong><br />
|
||||||
{{ error }}</pre>
|
{{ error }}</pre>
|
||||||
|
<p>
|
||||||
|
If further assistance is required, please post to the <a href="https://groups.google.com/forum/#!forum/netbox-discuss">NetBox mailing list</a>.
|
||||||
|
</p>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<a href="{% url 'home' %}" class="btn btn-primary">Home Page</a>
|
<a href="{% url 'home' %}" class="btn btn-primary">Home Page</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if circuit.tenant %}
|
{% if circuit.tenant %}
|
||||||
{% if circuit.tenant.group %}
|
{% if circuit.tenant.group %}
|
||||||
<a href="{{ circuit.tenant.group.get_absolute_url }}">{{ circuit.tenant.group.name }}</a>
|
<a href="{{ circuit.tenant.group.get_absolute_url }}">{{ circuit.tenant.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ circuit.tenant.get_absolute_url }}">{{ circuit.tenant }}</a>
|
<a href="{{ circuit.tenant.get_absolute_url }}">{{ circuit.tenant }}</a>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'dcim/inc/device_header.html' with active_tab='info' %}
|
{% include 'dcim/inc/device_header.html' with active_tab='info' %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-5">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Device</strong>
|
<strong>Device</strong>
|
||||||
@ -28,10 +28,10 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if device.rack %}
|
{% if device.rack %}
|
||||||
{% if device.rack.group %}
|
{% if device.rack.group %}
|
||||||
<a href="{{ device.rack.group.get_absolute_url }}">{{ device.rack.group.name }}</a>
|
<a href="{{ device.rack.group.get_absolute_url }}">{{ device.rack.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:rack' pk=device.rack.pk %}">{{ device.rack.name }}</a>{% if device.rack.facility_id %} ({{ device.rack.facility_id }}){% endif %}
|
<a href="{% url 'dcim:rack' pk=device.rack.pk %}">{{ device.rack }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -42,7 +42,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if device.parent_bay %}
|
{% if device.parent_bay %}
|
||||||
{% with device.parent_bay.device as parent %}
|
{% with device.parent_bay.device as parent %}
|
||||||
<a href="{{ parent.get_absolute_url }}">{{ parent }}</a> <i class="fa fa-angle-right"></i> {{ device.parent_bay.name }}
|
<a href="{{ parent.get_absolute_url }}">{{ parent }}</a> <i class="fa fa-angle-right"></i> {{ device.parent_bay }}
|
||||||
{% if parent.position %}
|
{% if parent.position %}
|
||||||
(U{{ parent.position }} / {{ parent.get_face_display }})
|
(U{{ parent.position }} / {{ parent.get_face_display }})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -61,7 +61,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if device.tenant %}
|
{% if device.tenant %}
|
||||||
{% if device.tenant.group %}
|
{% if device.tenant.group %}
|
||||||
<a href="{{ device.tenant.group.get_absolute_url }}">{{ device.tenant.group.name }}</a>
|
<a href="{{ device.tenant.group.get_absolute_url }}">{{ device.tenant.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ device.tenant.get_absolute_url }}">{{ device.tenant }}</a>
|
<a href="{{ device.tenant.get_absolute_url }}">{{ device.tenant }}</a>
|
||||||
@ -172,6 +172,69 @@
|
|||||||
{% with device.get_custom_fields as custom_fields %}
|
{% with device.get_custom_fields as custom_fields %}
|
||||||
{% include 'inc/custom_fields_panel.html' %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Comments</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% if device.comments %}
|
||||||
|
{{ device.comments|gfm }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Console / Power</strong>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover panel-body component-list">
|
||||||
|
{% for cp in console_ports %}
|
||||||
|
{% include 'dcim/inc/consoleport.html' %}
|
||||||
|
{% empty %}
|
||||||
|
{% if device.device_type.console_port_templates.exists %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="alert-warning">
|
||||||
|
<i class="fa fa-fw fa-warning"></i> No console ports defined
|
||||||
|
{% if perms.dcim.add_consoleport %}
|
||||||
|
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% for pp in power_ports %}
|
||||||
|
{% include 'dcim/inc/powerport.html' %}
|
||||||
|
{% empty %}
|
||||||
|
{% if device.device_type.power_port_templates.exists %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="alert-warning">
|
||||||
|
<i class="fa fa-fw fa-warning"></i> No power ports defined
|
||||||
|
{% if perms.dcim.add_powerport %}
|
||||||
|
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
|
||||||
|
<div class="panel-footer text-right">
|
||||||
|
{% if perms.dcim.add_consoleport %}
|
||||||
|
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_powerport %}
|
||||||
|
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
@ -224,67 +287,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<strong>Console / Power</strong>
|
|
||||||
</div>
|
|
||||||
<table class="table table-hover panel-body component-list">
|
|
||||||
{% for cp in console_ports %}
|
|
||||||
{% include 'dcim/inc/consoleport.html' %}
|
|
||||||
{% empty %}
|
|
||||||
{% if device.device_type.console_port_templates.exists %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="alert-warning">
|
|
||||||
<i class="fa fa-fw fa-warning"></i> No console ports defined
|
|
||||||
{% if perms.dcim.add_consoleport %}
|
|
||||||
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% for pp in power_ports %}
|
|
||||||
{% include 'dcim/inc/powerport.html' %}
|
|
||||||
{% empty %}
|
|
||||||
{% if device.device_type.power_port_templates.exists %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="alert-warning">
|
|
||||||
<i class="fa fa-fw fa-warning"></i> No power ports defined
|
|
||||||
{% if perms.dcim.add_powerport %}
|
|
||||||
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% if perms.dcim.add_interface or perms.dcim.add_consoleport or perms.dcim.add_powerport %}
|
|
||||||
<div class="panel-footer text-right">
|
|
||||||
{% if perms.dcim.add_consoleport %}
|
|
||||||
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_powerport %}
|
|
||||||
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<strong>Comments</strong>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{% if device.comments %}
|
|
||||||
{{ device.comments|gfm }}
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">None</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Images</strong>
|
<strong>Images</strong>
|
||||||
@ -326,7 +328,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-7">
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
{% 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 %}
|
{% if perms.dcim.delete_devicebay %}
|
||||||
<form method="post" action="{% url 'dcim:devicebay_bulk_delete' pk=device.pk %}">
|
<form method="post" action="{% url 'dcim:devicebay_bulk_delete' pk=device.pk %}">
|
||||||
@ -350,7 +354,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body component-list">
|
<table class="table table-hover panel-body component-list">
|
||||||
{% for devicebay in device_bays %}
|
{% for devicebay in device_bays %}
|
||||||
{% include 'dcim/inc/devicebay.html' with selectable=True %}
|
{% include 'dcim/inc/devicebay.html' %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No device bays defined</td>
|
<td colspan="4">No device bays defined</td>
|
||||||
@ -405,11 +409,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table id="interfaces_table" class="table table-hover panel-body component-list">
|
<table id="interfaces_table" class="table table-hover panel-body component-list">
|
||||||
|
<tr class="table-headings">
|
||||||
|
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
|
<th></th>
|
||||||
|
{% endif %}
|
||||||
|
<th>Name</th>
|
||||||
|
<th>LAG</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>MTU</th>
|
||||||
|
<th>MAC Address</th>
|
||||||
|
<th colspan="2">Connection</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
{% for iface in interfaces %}
|
{% for iface in interfaces %}
|
||||||
{% include 'dcim/inc/interface.html' with selectable=True %}
|
{% include 'dcim/inc/interface.html' %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No interfaces defined</td>
|
<td colspan="8">No interfaces defined</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
@ -467,8 +483,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body component-list">
|
<table class="table table-hover panel-body component-list">
|
||||||
|
<tr class="table-headings">
|
||||||
|
{% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
|
||||||
|
<th></th>
|
||||||
|
{% endif %}
|
||||||
|
<th>Name</th>
|
||||||
|
<th colspan="2">Connection</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
{% for csp in cs_ports %}
|
{% for csp in cs_ports %}
|
||||||
{% include 'dcim/inc/consoleserverport.html' with selectable=True %}
|
{% include 'dcim/inc/consoleserverport.html' %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No console server ports defined</td>
|
<td colspan="4">No console server ports defined</td>
|
||||||
@ -524,12 +548,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body component-list">
|
<table class="table table-hover panel-body component-list">
|
||||||
|
<tr class="table-headings">
|
||||||
|
{% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
|
||||||
|
<th></th>
|
||||||
|
{% endif %}
|
||||||
|
<th>Name</th>
|
||||||
|
<th colspan="2">Connection</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
{% for po in power_outlets %}
|
{% for po in power_outlets %}
|
||||||
{% include 'dcim/inc/poweroutlet.html' with selectable=True %}
|
{% include 'dcim/inc/poweroutlet.html' %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No power outlets defined</td>
|
<td colspan="4">No power outlets defined</td>
|
||||||
</tr>
|
</tr> text-nowrap
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_poweroutlet or perms.dcim.delete_poweroutlet %}
|
{% if perms.dcim.add_poweroutlet or perms.dcim.delete_poweroutlet %}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-3 control-label required">Bay</label>
|
<label class="col-md-3 control-label required">Bay</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<p class="form-control-static">{{ device_bay.name }}</p>
|
<p class="form-control-static">{{ device_bay }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% render_form form %}
|
{% render_form form %}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<tr class="consoleport{% if cp.cs_port and not cp.connection_status %} info{% endif %}">
|
<tr class="consoleport{% if cp.cs_port and not cp.connection_status %} info{% endif %}">
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-keyboard-o"></i> {{ cp.name }}
|
<i class="fa fa-fw fa-keyboard-o"></i> {{ cp }}
|
||||||
</td>
|
</td>
|
||||||
{% if cp.cs_port %}
|
{% if cp.cs_port %}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device' pk=cp.cs_port.device.pk %}">{{ cp.cs_port.device }}</a>
|
<a href="{% url 'dcim:device' pk=cp.cs_port.device.pk %}">{{ cp.cs_port.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ cp.cs_port.name }}
|
{{ cp.cs_port }}
|
||||||
</td>
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<tr class="consoleserverport{% if csp.connected_console and not csp.connected_console.connection_status %} info{% endif %}">
|
<tr class="consoleserverport{% if csp.connected_console and not csp.connected_console.connection_status %} info{% endif %}">
|
||||||
{% if selectable and perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
|
{% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ csp.pk }}" />
|
<input name="pk" type="checkbox" value="{{ csp.pk }}" />
|
||||||
</td>
|
</td>
|
||||||
@ -7,20 +7,19 @@
|
|||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-keyboard-o"></i> {{ csp }}
|
<i class="fa fa-fw fa-keyboard-o"></i> {{ csp }}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
{% if csp.connected_console %}
|
{% if csp.connected_console %}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device' pk=csp.connected_console.device.pk %}">{{ csp.connected_console.device }}</a>
|
<a href="{% url 'dcim:device' pk=csp.connected_console.device.pk %}">{{ csp.connected_console.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ csp.connected_console.name }}
|
{{ csp.connected_console }}
|
||||||
</td>
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<span class="text-muted">Not connected</span>
|
<span class="text-muted">Not connected</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td colspan="2" class="text-right">
|
<td class="text-right">
|
||||||
{% if perms.dcim.change_consoleserverport %}
|
{% if perms.dcim.change_consoleserverport %}
|
||||||
{% if csp.connected_console %}
|
{% if csp.connected_console %}
|
||||||
{% if csp.connected_console.connection_status %}
|
{% if csp.connected_console.connection_status %}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if device.parent_bay %}
|
{% if device.parent_bay %}
|
||||||
<li><a href="{% url 'dcim:device' pk=device.parent_bay.device.pk %}">{{ device.parent_bay.device }}</a></li>
|
<li><a href="{% url 'dcim:device' pk=device.parent_bay.device.pk %}">{{ device.parent_bay.device }}</a></li>
|
||||||
<li>{{ device.parent_bay.name }}</li>
|
<li>{{ device.parent_bay }}</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ device }}</li>
|
<li>{{ device }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<tr class="devicebay">
|
<tr class="devicebay">
|
||||||
{% if selectable and perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
|
{% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ devicebay.pk }}" />
|
<input name="pk" type="checkbox" value="{{ devicebay.pk }}" />
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% 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 }}
|
||||||
</td>
|
</td>
|
||||||
{% if devicebay.installed_device %}
|
{% if devicebay.installed_device %}
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,21 +1,37 @@
|
|||||||
<tr class="interface{% if not iface.enabled %} danger{% elif iface.connection and iface.connection.connection_status or iface.circuit_termination %} success{% elif iface.connection and not iface.connection.connection_status %} info{% elif iface.is_virtual %} warning{% endif %}">
|
<tr class="interface{% if not iface.enabled %} danger{% elif iface.connection and iface.connection.connection_status or iface.circuit_termination %} success{% elif iface.connection and not iface.connection.connection_status %} info{% elif iface.is_virtual %} warning{% endif %}" id="iface_{{ iface.name }}">
|
||||||
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
|
||||||
|
{# Checkbox #}
|
||||||
|
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Icon and name #}
|
||||||
|
<td>
|
||||||
|
<span title="{{ iface.get_form_factor_display }}">
|
||||||
|
<i class="fa fa-fw fa-{% if iface.mgmt_only %}wrench{% elif iface.is_lag %}align-justify{% elif iface.is_virtual %}circle{% elif iface.is_wireless %}wifi{% else %}exchange{% endif %}"></i>
|
||||||
|
{{ iface }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{# LAG #}
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-{% if iface.mgmt_only %}wrench{% elif iface.is_virtual %}square{% elif iface.is_wireless %}wifi{% else %}exchange{% endif %}"></i>
|
|
||||||
<span title="{{ iface.get_form_factor_display }}">{{ iface.name }}</span>
|
|
||||||
{% if iface.lag %}
|
{% if iface.lag %}
|
||||||
<span class="label label-primary">{{ iface.lag.name }}</span>
|
<a href="#iface_{{ iface.lag }}" class="label label-default">{{ iface.lag }}</a>
|
||||||
{% endif %}
|
|
||||||
{% if iface.description %}
|
|
||||||
<i class="fa fa-fw fa-comment-o" title="{{ iface.description }}"></i>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
{# Description #}
|
||||||
|
<td>{{ iface.description }}</td>
|
||||||
|
|
||||||
|
{# MTU #}
|
||||||
<td>{{ iface.mtu|default:"" }}</td>
|
<td>{{ iface.mtu|default:"" }}</td>
|
||||||
|
|
||||||
|
{# MAC address #}
|
||||||
<td>{{ iface.mac_address|default:"" }}</td>
|
<td>{{ iface.mac_address|default:"" }}</td>
|
||||||
|
|
||||||
|
{# Connection or type #}
|
||||||
{% if iface.is_lag %}
|
{% if iface.is_lag %}
|
||||||
<td colspan="2" class="text-muted">
|
<td colspan="2" class="text-muted">
|
||||||
LAG interface<br />
|
LAG interface<br />
|
||||||
@ -55,7 +71,9 @@
|
|||||||
<span class="text-muted">Not connected</span>
|
<span class="text-muted">Not connected</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td colspan="2" class="text-right">
|
|
||||||
|
{# Buttons #}
|
||||||
|
<td class="text-right text-nowrap">
|
||||||
{% if show_graphs %}
|
{% if show_graphs %}
|
||||||
{% if iface.circuit_termination or iface.connection %}
|
{% if iface.circuit_termination or iface.connection %}
|
||||||
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface-graphs' pk=iface.pk %}" title="Show graphs">
|
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface-graphs' pk=iface.pk %}" title="Show graphs">
|
||||||
@ -96,7 +114,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:interface_edit' pk=iface.pk %}" class="btn btn-info btn-xs" title="Edit interface">
|
<a href="{% if iface.device %}{% url 'dcim:interface_edit' pk=iface.pk %}{% else %}{% url 'virtualization:interface_edit' pk=iface.pk %}{% endif %}" class="btn btn-info btn-xs" title="Edit interface">
|
||||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -106,62 +124,63 @@
|
|||||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'dcim:interface_delete' pk=iface.pk %}" class="btn btn-danger btn-xs" title="Delete interface">
|
<a href="{% if iface.device %}{% url 'dcim:interface_delete' pk=iface.pk %}{% else %}{% url 'virtualization:interface_delete' pk=iface.pk %}{% endif %}" class="btn btn-danger btn-xs" title="Delete interface">
|
||||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% with iface.ip_addresses.all as ipaddresses %}
|
{% for ip in iface.ip_addresses.all %}
|
||||||
{% if ipaddresses %}
|
<tr class="ipaddress">
|
||||||
<tr class="ipaddress">
|
|
||||||
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
{# Placeholder #}
|
||||||
<td></td>
|
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
<td colspan="6" class="subtable">
|
<td></td>
|
||||||
{% else %}
|
{% endif %}
|
||||||
<td colspan="7" class="subtable">
|
|
||||||
|
{# IP address #}
|
||||||
|
<td colspan="2">
|
||||||
|
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{# Primary, status, role #}
|
||||||
|
<td>
|
||||||
|
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
|
||||||
|
<span class="label label-success">Primary</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table class="table table-hover">
|
<span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
|
||||||
{% for ip in ipaddresses %}
|
{% if ip.role %}
|
||||||
<tr>
|
<span class="label label-{{ ip.get_role_class }}">{{ ip.get_role_display }}</span>
|
||||||
<td>
|
{% endif %}
|
||||||
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
|
</td>
|
||||||
{% if ip.description %}
|
|
||||||
<i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i>
|
{# VRF #}
|
||||||
{% endif %}
|
<td colspan="2">
|
||||||
</td>
|
{% if ip.vrf %}
|
||||||
<td>
|
<a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}" title="{{ ip.vrf.rd }}">{{ ip.vrf.name }}</a>
|
||||||
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
|
{% else %}
|
||||||
<span class="label label-success">Primary</span>
|
<span class="text-muted">Global</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
{% if ip.vrf %}
|
{# Description #}
|
||||||
<a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}">{{ ip.vrf }}</a>
|
<td colspan="2">
|
||||||
{% else %}
|
{{ ip.description }}
|
||||||
<span class="text-muted">Global table</span>
|
</td>
|
||||||
{% endif %}
|
|
||||||
</td>
|
{# Buttons #}
|
||||||
<td>
|
<td class="text-right text-nowrap">
|
||||||
<span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
|
{% if perms.ipam.change_ipaddress %}
|
||||||
</td>
|
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
|
||||||
<td class="text-right">
|
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
||||||
{% if perms.ipam.change_ipaddress %}
|
</a>
|
||||||
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
|
{% endif %}
|
||||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
{% if perms.ipam.delete_ipaddress %}
|
||||||
</a>
|
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
|
||||||
{% endif %}
|
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
|
||||||
{% if perms.ipam.delete_ipaddress %}
|
</a>
|
||||||
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
|
{% endif %}
|
||||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
|
</td>
|
||||||
</a>
|
</tr>
|
||||||
{% endif %}
|
{% endfor %}
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="padding-left: {{ indent|add:5 }}px">{{ item.name }}</td>
|
<td style="padding-left: {{ indent|add:5 }}px">{{ item }}</td>
|
||||||
<td>{% if not item.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
<td>{% if not item.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
|
||||||
<td>{{ item.manufacturer|default:"" }}</td>
|
<td>{{ item.manufacturer|default:"" }}</td>
|
||||||
<td>{{ item.part_id }}</td>
|
<td>{{ item.part_id }}</td>
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
<tr class="poweroutlet{% if po.connected_port and not po.connected_port.connection_status %} info{% endif %}">
|
<tr class="poweroutlet{% if po.connected_port and not po.connected_port.connection_status %} info{% endif %}">
|
||||||
{% if selectable and perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
|
{% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ po.pk }}" />
|
<input name="pk" type="checkbox" value="{{ po.pk }}" />
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-bolt"></i> {{ po.name }}
|
<i class="fa fa-fw fa-bolt"></i> {{ po }}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
{% if po.connected_port %}
|
{% if po.connected_port %}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device' pk=po.connected_port.device.pk %}">{{ po.connected_port.device }}</a>
|
<a href="{% url 'dcim:device' pk=po.connected_port.device.pk %}">{{ po.connected_port.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ po.connected_port.name }}
|
{{ po.connected_port }}
|
||||||
</td>
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<span class="text-muted">Not connected</span>
|
<span class="text-muted">Not connected</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td colspan="2" class="text-right">
|
<td class="text-right">
|
||||||
{% if perms.dcim.change_poweroutlet %}
|
{% if perms.dcim.change_poweroutlet %}
|
||||||
{% if po.connected_port %}
|
{% if po.connected_port %}
|
||||||
{% if po.connected_port.connection_status %}
|
{% if po.connected_port.connection_status %}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<tr class="powerport{% if pp.power_outlet and not pp.connection_status %} info{% endif %}">
|
<tr class="powerport{% if pp.power_outlet and not pp.connection_status %} info{% endif %}">
|
||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-bolt"></i> {{ pp.name }}
|
<i class="fa fa-fw fa-bolt"></i> {{ pp }}
|
||||||
</td>
|
</td>
|
||||||
{% if pp.power_outlet %}
|
{% if pp.power_outlet %}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device' pk=pp.power_outlet.device.pk %}">{{ pp.power_outlet.device }}</a>
|
<a href="{% url 'dcim:device' pk=pp.power_outlet.device.pk %}">{{ pp.power_outlet.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ pp.power_outlet.name }}
|
{{ pp.power_outlet }}
|
||||||
</td>
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Rack {{ rack.name }}{% endblock %}</h1>
|
<h1>{% block title %}Rack {{ rack }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=rack %}
|
{% include 'inc/created_updated.html' with obj=rack %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@ -72,7 +72,7 @@
|
|||||||
<td>Group</td>
|
<td>Group</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rack.group %}
|
{% if rack.group %}
|
||||||
<a href="{% url 'dcim:rack_list' %}?site={{ rack.site.slug }}&group={{ rack.group.slug }}">{{ rack.group.name }}</a>
|
<a href="{% url 'dcim:rack_list' %}?site={{ rack.site.slug }}&group={{ rack.group.slug }}">{{ rack.group }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if rack.tenant %}
|
{% if rack.tenant %}
|
||||||
{% if rack.tenant.group %}
|
{% if rack.tenant.group %}
|
||||||
<a href="{{ rack.tenant.group.get_absolute_url }}">{{ rack.tenant.group.name }}</a>
|
<a href="{{ rack.tenant.group.get_absolute_url }}">{{ rack.tenant.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ rack.tenant.get_absolute_url }}">{{ rack.tenant }}</a>
|
<a href="{{ rack.tenant.get_absolute_url }}">{{ rack.tenant }}</a>
|
||||||
@ -173,7 +173,7 @@
|
|||||||
{% for device in nonracked_devices %}
|
{% for device in nonracked_devices %}
|
||||||
<tr{% if device.device_type.u_height %} class="warning"{% endif %}>
|
<tr{% if device.device_type.u_height %} class="warning"{% endif %}>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device' pk=device.pk %}">{{ device.name }}</a>
|
<a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ device.device_role }}</td>
|
<td>{{ device.device_role }}</td>
|
||||||
<td>{{ device.device_type.full_name }}</td>
|
<td>{{ device.device_type.full_name }}</td>
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if site.tenant %}
|
{% if site.tenant %}
|
||||||
{% if site.tenant.group %}
|
{% if site.tenant.group %}
|
||||||
<a href="{{ site.tenant.group.get_absolute_url }}">{{ site.tenant.group.name }}</a>
|
<a href="{{ site.tenant.group.get_absolute_url }}">{{ site.tenant.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ site.tenant.get_absolute_url }}">{{ site.tenant }}</a>
|
<a href="{{ site.tenant.get_absolute_url }}">{{ site.tenant }}</a>
|
||||||
@ -221,7 +221,7 @@
|
|||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for rg in rack_groups %}
|
{% for rg in rack_groups %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><i class="fa fa-fw fa-folder-o"></i> <a href="{{ rg.get_absolute_url }}">{{ rg.name }}</a></td>
|
<td><i class="fa fa-fw fa-folder-o"></i> <a href="{{ rg.get_absolute_url }}">{{ rg }}</a></td>
|
||||||
<td>{{ rg.rack_count }}</td>
|
<td>{{ rg.rack_count }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
18
netbox/templates/exceptions/import_error.html
Normal file
18
netbox/templates/exceptions/import_error.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{% extends '500.html' %}
|
||||||
|
|
||||||
|
{% block message %}
|
||||||
|
<p>
|
||||||
|
A module import error occurred during this request. Common causes include the following:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-warning"></i> <strong>Missing required packages</strong> - This installation of NetBox might be missing one or more required
|
||||||
|
Python packages. These packages are listed in <code>requirements.txt</code> and are normally installed as part
|
||||||
|
of the installation or upgrade process. To verify installed packages, run <code>pip freeze</code> from the
|
||||||
|
console and compare the output to the list of required packages.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-warning"></i> <strong>WSGI service not restarted after upgrade</strong> - If this installation has recently been upgraded,
|
||||||
|
check that the WSGI service (e.g. gunicorn or uWSGI) has been restarted. This ensures that the new code is
|
||||||
|
running.
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
12
netbox/templates/exceptions/permission_error.html
Normal file
12
netbox/templates/exceptions/permission_error.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% extends '500.html' %}
|
||||||
|
|
||||||
|
{% block message %}
|
||||||
|
<p>
|
||||||
|
A file permission error was detected while processing this request. Common causes include the following:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-warning"></i> <strong>Insufficient write permission to the media root</strong> - The configured
|
||||||
|
media root is <code>{{ settings.MEDIA_ROOT }}</code>. Ensure that the user NetBox runs as has access to write
|
||||||
|
files to all locations within this path.
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
17
netbox/templates/exceptions/programming_error.html
Normal file
17
netbox/templates/exceptions/programming_error.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends '500.html' %}
|
||||||
|
|
||||||
|
{% block message %}
|
||||||
|
<p>
|
||||||
|
A database programming error was detected while processing this request. Common causes include the following:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-warning"></i> <strong>Database migrations missing</strong> - When upgrading to a new NetBox release, the upgrade script must
|
||||||
|
be run to apply any new database migrations. You can run migrations manually by executing
|
||||||
|
<code>python3 manage.py migrate</code> from the command line.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-warning"></i> <strong>Unsupported PostgreSQL version</strong> - Ensure that PostgreSQL version 9.4 or higher is in use. You
|
||||||
|
can check this by connecting to the database using NetBox's credentials and issuing a query for
|
||||||
|
<code>SELECT VERSION()</code>.
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
@ -1,4 +1,16 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
|
||||||
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
||||||
<li role="presentation"{% if active_tab == 'add' %} class="active"{% endif %}><a href="{% url 'ipam:ipaddress_add' %}">Individual</a></li>
|
<li role="presentation"{% if active_tab == 'add' %} class="active"{% endif %}>
|
||||||
<li role="presentation"{% if active_tab == 'bulk_add' %} class="active"{% endif %}><a href="{% url 'ipam:ipaddress_bulk_add' %}">Bulk</a></li>
|
<a href="{% url 'ipam:ipaddress_add' %}{% querystring request %}">New IP</a>
|
||||||
|
</li>
|
||||||
|
{% if 'interface' in request.GET %}
|
||||||
|
<li role="presentation"{% if active_tab == 'assign' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'ipam:ipaddress_assign' %}{% querystring request %}">Assign IP</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li role="presentation"{% if active_tab == 'bulk_add' %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'ipam:ipaddress_bulk_add' %}{% querystring request %}">Bulk Create</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
48
netbox/templates/ipam/ipaddress_assign.html
Normal file
48
netbox/templates/ipam/ipaddress_assign.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'utilities/obj_edit.html' %}
|
||||||
|
{% load static from staticfiles %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="{% querystring request %}" method="post" class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form.hidden_fields %}
|
||||||
|
{{ field }}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3">
|
||||||
|
<h3>Assign an IP Address</h3>
|
||||||
|
{% include 'ipam/inc/ipadress_edit_header.html' with active_tab='assign' %}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="panel panel-danger">
|
||||||
|
<div class="panel-heading"><strong>Errors</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><strong>Select IP Address</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% render_field form.vrf %}
|
||||||
|
{% render_field form.address %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3 text-right">
|
||||||
|
<button type="submit" class="btn btn-primary">Search</button>
|
||||||
|
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% if table %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10 col-md-offset-1" style="margin-top: 20px">
|
||||||
|
<h3>Search Results</h3>
|
||||||
|
{% include 'utilities/obj_table.html' with table_template='panel_table.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -30,13 +30,13 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if prefix.tenant %}
|
{% if prefix.tenant %}
|
||||||
{% if prefix.tenant.group %}
|
{% if prefix.tenant.group %}
|
||||||
<a href="{{ prefix.tenant.group.get_absolute_url }}">{{ prefix.tenant.group.name }}</a>
|
<a href="{{ prefix.tenant.group.get_absolute_url }}">{{ prefix.tenant.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ prefix.tenant.get_absolute_url }}">{{ prefix.tenant }}</a>
|
<a href="{{ prefix.tenant.get_absolute_url }}">{{ prefix.tenant }}</a>
|
||||||
{% elif prefix.vrf.tenant %}
|
{% elif prefix.vrf.tenant %}
|
||||||
{% if prefix.vrf.tenant.group %}
|
{% if prefix.vrf.tenant.group %}
|
||||||
<a href="{{ prefix.vrf.tenant.group.get_absolute_url }}">{{ prefix.vrf.tenant.group.name }}</a>
|
<a href="{{ prefix.vrf.tenant.group.get_absolute_url }}">{{ prefix.vrf.tenant.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ prefix.vrf.tenant.get_absolute_url }}">{{ prefix.vrf.tenant }}</a>
|
<a href="{{ prefix.vrf.tenant.get_absolute_url }}">{{ prefix.vrf.tenant }}</a>
|
||||||
@ -75,7 +75,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if prefix.vlan %}
|
{% if prefix.vlan %}
|
||||||
{% if prefix.vlan.group %}
|
{% if prefix.vlan.group %}
|
||||||
<a href="{{ prefix.vlan.group.get_absolute_url }}">{{ prefix.vlan.group.name }}</a>
|
<a href="{{ prefix.vlan.group.get_absolute_url }}">{{ prefix.vlan.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'ipam:vlan' pk=prefix.vlan.pk %}">{{ prefix.vlan.display_name }}</a>
|
<a href="{% url 'ipam:vlan' pk=prefix.vlan.pk %}">{{ prefix.vlan.display_name }}</a>
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<li><a href="{% url 'ipam:vlan_list' %}?site={{ vlan.site.slug }}">{{ vlan.site }}</a></li>
|
<li><a href="{% url 'ipam:vlan_list' %}?site={{ vlan.site.slug }}">{{ vlan.site }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if vlan.group %}
|
{% if vlan.group %}
|
||||||
<li><a href="{% url 'ipam:vlan_list' %}?group={{ vlan.group.slug }}">{{ vlan.group.name }}</a></li>
|
<li><a href="{% url 'ipam:vlan_list' %}?group={{ vlan.group.slug }}">{{ vlan.group }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ vlan.name }} ({{ vlan.vid }})</li>
|
<li>{{ vlan }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@ -68,7 +68,7 @@
|
|||||||
<td>Group</td>
|
<td>Group</td>
|
||||||
<td>
|
<td>
|
||||||
{% if vlan.group %}
|
{% if vlan.group %}
|
||||||
<a href="{{ vlan.group.get_absolute_url }}">{{ vlan.group.name }}</a>
|
<a href="{{ vlan.group.get_absolute_url }}">{{ vlan.group }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -87,7 +87,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if vlan.tenant %}
|
{% if vlan.tenant %}
|
||||||
{% if vlan.tenant.group %}
|
{% if vlan.tenant.group %}
|
||||||
<a href="{{ vlan.tenant.group.get_absolute_url }}">{{ vlan.tenant.group.name }}</a>
|
<a href="{{ vlan.tenant.group.get_absolute_url }}">{{ vlan.tenant.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ vlan.tenant.get_absolute_url }}">{{ vlan.tenant }}</a>
|
<a href="{{ vlan.tenant.get_absolute_url }}">{{ vlan.tenant }}</a>
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
<tr class="interface{% if not iface.enabled %} danger{% endif %}">
|
|
||||||
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
|
||||||
<td class="pk">
|
|
||||||
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
<td>
|
|
||||||
<i class="fa fa-fw fa-square"></i> <span>{{ iface.name }}</span>
|
|
||||||
{% if iface.description %}
|
|
||||||
<i class="fa fa-fw fa-comment-o" title="{{ iface.description }}"></i>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ iface.mtu|default:"" }}</td>
|
|
||||||
<td>{{ iface.mac_address|default:"" }}</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if perms.ipam.add_ipaddress %}
|
|
||||||
<a href="{% url 'ipam:ipaddress_add' %}?interface={{ iface.pk }}&return_url={{ vm.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address">
|
|
||||||
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.change_interface %}
|
|
||||||
<a href="{% url 'virtualization:interface_edit' pk=iface.pk %}" class="btn btn-info btn-xs" title="Edit interface">
|
|
||||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.delete_interface %}
|
|
||||||
<a href="{% url 'virtualization:interface_delete' pk=iface.pk %}" class="btn btn-danger btn-xs" title="Delete interface">
|
|
||||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% for ip in iface.ip_addresses.all %}
|
|
||||||
<tr class="ipaddress">
|
|
||||||
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
|
|
||||||
{% if ip.description %}
|
|
||||||
<i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i>
|
|
||||||
{% endif %}
|
|
||||||
{% if vm.primary_ip4 == ip or vm.primary_ip6 == ip %}
|
|
||||||
<span class="label label-success">Primary</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if ip.vrf %}
|
|
||||||
<a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}">{{ ip.vrf }}</a>
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">Global</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if perms.ipam.change_ipaddress %}
|
|
||||||
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ vm.get_absolute_url }}" class="btn btn-info btn-xs">
|
|
||||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.ipam.delete_ipaddress %}
|
|
||||||
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ vm.get_absolute_url }}" class="btn btn-danger btn-xs">
|
|
||||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
@ -41,7 +41,7 @@
|
|||||||
<h1>{% block title %}{{ vm }}{% endblock %}</h1>
|
<h1>{% block title %}{{ vm }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=vm %}
|
{% include 'inc/created_updated.html' with obj=vm %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-5">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Virtual Machine</strong>
|
<strong>Virtual Machine</strong>
|
||||||
@ -49,7 +49,7 @@
|
|||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ vm.name }}</td>
|
<td>{{ vm }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
@ -82,7 +82,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if vm.tenant %}
|
{% if vm.tenant %}
|
||||||
{% if vm.tenant.group %}
|
{% if vm.tenant.group %}
|
||||||
<a href="{{ vm.tenant.group.get_absolute_url }}">{{ vm.tenant.group.name }}</a>
|
<a href="{{ vm.tenant.group.get_absolute_url }}">{{ vm.tenant.group }}</a>
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ vm.tenant.get_absolute_url }}">{{ vm.tenant }}</a>
|
<a href="{{ vm.tenant.get_absolute_url }}">{{ vm.tenant }}</a>
|
||||||
@ -123,6 +123,21 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% include 'inc/custom_fields_panel.html' with custom_fields=vm.get_custom_fields %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Comments</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% if vm.comments %}
|
||||||
|
{{ vm.comments|gfm }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Cluster</strong>
|
<strong>Cluster</strong>
|
||||||
@ -204,21 +219,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with custom_fields=vm.get_custom_fields %}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<strong>Comments</strong>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{% if vm.comments %}
|
|
||||||
{{ vm.comments|gfm }}
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">None</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-7">
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -244,11 +248,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table id="interfaces_table" class="table table-hover panel-body component-list">
|
<table id="interfaces_table" class="table table-hover panel-body component-list">
|
||||||
|
<tr class="table-headings">
|
||||||
|
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
|
<th></th>
|
||||||
|
{% endif %}
|
||||||
|
<th>Name</th>
|
||||||
|
<th>LAG</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>MTU</th>
|
||||||
|
<th>MAC Address</th>
|
||||||
|
<th colspan="2">Connection</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
{% for iface in interfaces %}
|
{% for iface in interfaces %}
|
||||||
{% include 'virtualization/inc/interface.html' with selectable=True %}
|
{% include 'dcim/inc/interface.html' with device=vm %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">No interfaces defined</td>
|
<td colspan="6">No interfaces defined</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
|
@ -3,10 +3,8 @@ from __future__ import unicode_literals
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from utilities.tables import BaseTable, ToggleColumn
|
from utilities.tables import BaseTable, ToggleColumn
|
||||||
|
|
||||||
from .models import Tenant, TenantGroup
|
from .models import Tenant, TenantGroup
|
||||||
|
|
||||||
|
|
||||||
TENANTGROUP_ACTIONS = """
|
TENANTGROUP_ACTIONS = """
|
||||||
{% if perms.tenancy.change_tenantgroup %}
|
{% if perms.tenancy.change_tenantgroup %}
|
||||||
<a href="{% url 'tenancy:tenantgroup_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'tenancy:tenantgroup_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from users.models import Token
|
from users.models import Token
|
||||||
|
@ -4,7 +4,6 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
app_name = 'tenancy'
|
app_name = 'tenancy'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ from utilities.views import (
|
|||||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .models import Tenant, TenantGroup
|
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
|
from .models import Tenant, TenantGroup
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
|
||||||
|
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
from .models import Token
|
from .models import Token
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.validators import MinLengthValidator
|
from django.core.validators import MinLengthValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
|
@ -4,7 +4,6 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
app_name = 'user'
|
app_name = 'user'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework.permissions import BasePermission
|
from rest_framework.permissions import BasePermission
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import Field, ModelSerializer, ValidationError
|
from rest_framework.serializers import Field, ModelSerializer, ValidationError
|
||||||
from rest_framework.viewsets import ViewSet
|
from rest_framework.viewsets import ViewSet
|
||||||
|
|
||||||
|
|
||||||
WRITE_OPERATIONS = ['create', 'update', 'partial_update', 'delete']
|
WRITE_OPERATIONS = ['create', 'update', 'partial_update', 'delete']
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ from django.db import models
|
|||||||
|
|
||||||
from .forms import ColorSelect
|
from .forms import ColorSelect
|
||||||
|
|
||||||
|
|
||||||
validate_color = RegexValidator('^[0-9a-f]{6}$', 'Enter a valid hexadecimal RGB color code.', 'invalid')
|
validate_color = RegexValidator('^[0-9a-f]{6}$', 'Enter a valid hexadecimal RGB color code.', 'invalid')
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django_filters
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
|
import django_filters
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user