diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 07502c78a..93b3e4af7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -17,7 +17,7 @@ body: What version of NetBox are you currently running? (If you don't have access to the most recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/) before opening a bug report to see if your issue has already been addressed.) - placeholder: v2.11.12 + placeholder: v3.0.0 validations: required: true - type: dropdown @@ -25,7 +25,6 @@ body: label: Python version description: What version of Python are you currently running? options: - - 3.6 - 3.7 - 3.8 - 3.9 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index f7b616907..7a7bcc106 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v2.11.12 + placeholder: v3.0.0 validations: required: true - type: dropdown diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9182457a0..d734ad2f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8] + python-version: [3.7, 3.8, 3.9] + node-version: [14.x] services: redis: image: redis @@ -33,15 +34,30 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies & set up configuration run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pycodestyle coverage ln -s configuration.testing.py netbox/netbox/configuration.py + yarn --cwd netbox/project-static + + - name: Build documentation + run: mkdocs build + + - name: Collect static files + run: python netbox/manage.py collectstatic --no-input - name: Check PEP8 compliance - run: pycodestyle --ignore=W504,E501 netbox/ + run: pycodestyle --ignore=W504,E501 --exclude=node_modules netbox/ + + - name: Check UI ESLint, TypeScript, and Prettier Compliance + run: yarn --cwd netbox/project-static validate - name: Run tests run: coverage run --source="netbox/" netbox/manage.py test netbox/ diff --git a/.gitignore b/.gitignore index 1dea89c21..b594efe4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,12 @@ *.pyc *.swp +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +/netbox/project-static/.cache +/netbox/project-static/docs/* +!/netbox/project-static/docs/.info /netbox/netbox/configuration.py /netbox/netbox/ldap_config.py /netbox/project-static/.cache diff --git a/README.md b/README.md index cb1991447..39981a2b0 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,15 @@ our [contributing guide](CONTRIBUTING.md) prior to beginning any work. ### Screenshots -![Screenshot of main page](docs/media/screenshot1.png "Main page") +![Screenshot of main page (light mode)](docs/media/screenshots/home-light.png "Main page (light mode)") -![Screenshot of rack elevation](docs/media/screenshot2.png "Rack elevation") +![Screenshot of main page (dark mode)](docs/media/screenshots/home-dark.png "Main page (dark mode)") -![Screenshot of prefix hierarchy](docs/media/screenshot3.png "Prefix hierarchy") +![Screenshot of rack elevation](docs/media/screenshots/rack.png "Rack elevation") + +![Screenshot of prefixes hierarchy](docs/media/screenshots/prefixes-list.png "Prefixes hierarchy") + +![Screenshot of cable trace](docs/media/screenshots/cable-trace.png "Cable tracing") ### Related projects diff --git a/base_requirements.txt b/base_requirements.txt index bf03bf71e..acd8d22c8 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -2,10 +2,6 @@ # https://github.com/django/django Django -# Django caching using Redis -# https://github.com/Suor/django-cacheops -django-cacheops - # Django middleware which permits cross-domain API requests # https://github.com/OttoYiu/django-cors-headers django-cors-headers @@ -18,6 +14,10 @@ django-debug-toolbar # https://github.com/carltongibson/django-filter django-filter +# Django debug toolbar extension with support for GraphiQL +# https://github.com/flavors/django-graphiql-debug-toolbar/ +django-graphiql-debug-toolbar + # Modified Preorder Tree Traversal (recursive nesting of objects) # https://github.com/django-mptt/django-mptt django-mptt @@ -30,6 +30,10 @@ django-pglocks # https://github.com/korfuri/django-prometheus django-prometheus +# Django chaching backend using Redis +# https://github.com/jazzband/django-redis +django-redis + # Django integration for RQ (Reqis queuing) # https://github.com/rq/django-rq django-rq @@ -54,6 +58,10 @@ djangorestframework # https://github.com/axnsan12/drf-yasg drf-yasg[validation] +# Django wrapper for Graphene (GraphQL support) +# https://github.com/graphql-python/graphene-django +graphene_django + # WSGI HTTP server # https://gunicorn.org/ gunicorn @@ -66,6 +74,14 @@ Jinja2 # https://github.com/Python-Markdown/markdown Markdown +# File inclusion plugin for Python-Markdown +# https://github.com/cmacmackin/markdown-include +markdown-include + +# MkDocs Material theme (for documentation build) +# https://github.com/squidfunk/mkdocs-material +mkdocs-material + # Library for manipulating IP prefixes and addresses # https://github.com/drkjam/netaddr netaddr diff --git a/contrib/netbox-housekeeping.sh b/contrib/netbox-housekeeping.sh new file mode 100644 index 000000000..5b1c46c5e --- /dev/null +++ b/contrib/netbox-housekeeping.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# This shell script invokes NetBox's housekeeping management command, which +# intended to be run nightly. This script can be copied into your system's +# daily cron directory (e.g. /etc/cron.daily), or referenced directly from +# within the cron configuration file. +# +# If NetBox has been installed into a nonstandard location, update the paths +# below. +/opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py housekeeping diff --git a/contrib/netbox-rq.service b/contrib/netbox-rq.service index 77d70910c..5b03777ed 100644 --- a/contrib/netbox-rq.service +++ b/contrib/netbox-rq.service @@ -11,7 +11,7 @@ User=netbox Group=netbox WorkingDirectory=/opt/netbox -ExecStart=/opt/netbox/venv/bin/python3 /opt/netbox/netbox/manage.py rqworker +ExecStart=/opt/netbox/venv/bin/python3 /opt/netbox/netbox/manage.py rqworker high default low Restart=on-failure RestartSec=30 diff --git a/docs/additional-features/caching.md b/docs/additional-features/caching.md deleted file mode 100644 index ebe91f37d..000000000 --- a/docs/additional-features/caching.md +++ /dev/null @@ -1,28 +0,0 @@ -# Caching - -NetBox supports database query caching using [django-cacheops](https://github.com/Suor/django-cacheops) and Redis. When a query is made, the results are cached in Redis for a short period of time, as defined by the [CACHE_TIMEOUT](../configuration/optional-settings.md#cache_timeout) parameter. Within that time, all recurrences of that specific query will return the pre-fetched results from the cache. - -!!! warning - In NetBox v2.11.10 and later queryset caching is disabled by default, and must be configured. - -If a change is made to any of the objects returned by the query within that time, or if the timeout expires, the results are automatically invalidated and the next request for those results will be sent to the database. - -## Invalidating Cached Data - -Although caching is performed automatically and rarely requires administrative intervention, NetBox provides the `invalidate` management command to force invalidation of cached results. This command can reference a specific object by its type and numeric ID: - -```no-highlight -$ python netbox/manage.py invalidate dcim.Device.34 -``` - -Alternatively, it can also delete all cached results for an object type: - -```no-highlight -$ python netbox/manage.py invalidate dcim.Device -``` - -Finally, calling it with the `all` argument will force invalidation of the entire cache database: - -```no-highlight -$ python netbox/manage.py invalidate all -``` diff --git a/docs/additional-features/napalm.md b/docs/additional-features/napalm.md index 957a5a214..2efaa839e 100644 --- a/docs/additional-features/napalm.md +++ b/docs/additional-features/napalm.md @@ -1,6 +1,6 @@ # NAPALM -NetBox supports integration with the [NAPALM automation](https://napalm-automation.net/) library. NAPALM allows NetBox to serve a proxy for operational data, fetching live data from network devices and returning it to a requester via its REST API. Note that NetBox does not store any NAPALM data locally. +NetBox supports integration with the [NAPALM automation](https://github.com/napalm-automation/napalm) library. NAPALM allows NetBox to serve a proxy for operational data, fetching live data from network devices and returning it to a requester via its REST API. Note that NetBox does not store any NAPALM data locally. The NetBox UI will display tabs for status, LLDP neighbors, and configuration under the device view if the following conditions are met: diff --git a/docs/additional-features/webhooks.md b/docs/additional-features/webhooks.md index 4fce4e037..19133adb1 100644 --- a/docs/additional-features/webhooks.md +++ b/docs/additional-features/webhooks.md @@ -1,6 +1,6 @@ # Webhooks -A webhook is a mechanism for conveying to some external system a change that took place in NetBox. For example, you may want to notify a monitoring system whenever the status of a device is updated in NetBox. This can be done by creating a webhook for the device model in NetBox and identifying the webhook receiver. When NetBox detects a change to a device, an HTTP request containing the details of the change and who made it be sent to the specified receiver. Webhooks are configured in the admin UI under Extras > Webhooks. +A webhook is a mechanism for conveying to some external system a change that took place in NetBox. For example, you may want to notify a monitoring system whenever the status of a device is updated in NetBox. This can be done by creating a webhook for the device model in NetBox and identifying the webhook receiver. When NetBox detects a change to a device, an HTTP request containing the details of the change and who made it be sent to the specified receiver. Webhooks are managed under Logging > Webhooks. !!! warning Webhooks support the inclusion of user-submitted code to generate custom headers and payloads, which may pose security risks under certain conditions. Only grant permission to create or modify webhooks to trusted users. diff --git a/docs/administration/housekeeping.md b/docs/administration/housekeeping.md new file mode 100644 index 000000000..c562613eb --- /dev/null +++ b/docs/administration/housekeeping.md @@ -0,0 +1,10 @@ +# Housekeeping + +NetBox includes a `housekeeping` management command that should be run nightly. This command handles: + +* Clearing expired authentication sessions from the database +* Deleting changelog records older than the configured [retention time](../configuration/optional-settings.md#changelog_retention) + +This command can be invoked directly, or by using the shell script provided at `/opt/netbox/contrib/netbox-housekeeping.sh`. This script can be copied into your cron scheduler's daily jobs directory (e.g. `/etc/cron.daily`) or referenced directly within the cron configuration file. + +The `housekeeping` command can also be run manually at any time: Running the command outside of scheduled execution times will not interfere with its operation. diff --git a/docs/administration/netbox-shell.md b/docs/administration/netbox-shell.md index 2a8d31494..1a10c5c3e 100644 --- a/docs/administration/netbox-shell.md +++ b/docs/administration/netbox-shell.md @@ -11,7 +11,7 @@ This will launch a lightly customized version of [the built-in Django shell](htt ``` $ ./manage.py nbshell ### NetBox interactive shell (localhost) -### Python 3.6.9 | Django 2.2.11 | NetBox 2.7.10 +### Python 3.7.10 | Django 3.2.5 | NetBox 3.0 ### lsmodels() will show available models. Use help() for more info. ``` @@ -194,7 +194,7 @@ To delete multiple objects at once, call `delete()` on a filtered queryset. It's >>> Device.objects.filter(name__icontains='test').count() 27 >>> Device.objects.filter(name__icontains='test').delete() -(35, {'dcim.DeviceBay': 0, 'secrets.Secret': 0, 'dcim.InterfaceConnection': 4, +(35, {'dcim.DeviceBay': 0, 'dcim.InterfaceConnection': 4, 'extras.ImageAttachment': 0, 'dcim.Device': 27, 'dcim.Interface': 4, 'dcim.ConsolePort': 0, 'dcim.PowerPort': 0}) ``` diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index bde911a0e..e324f3d46 100644 --- a/docs/configuration/optional-settings.md +++ b/docs/configuration/optional-settings.md @@ -52,14 +52,6 @@ BASE_PATH = 'netbox/' --- -## CACHE_TIMEOUT - -Default: 0 (disabled) - -The number of seconds that cached database queries will be retained before expiring. - ---- - ## CHANGELOG_RETENTION Default: 90 @@ -96,6 +88,22 @@ CORS_ORIGIN_WHITELIST = [ --- +## CUSTOM_VALIDATORS + +This is a mapping of models to [custom validators](../customization/custom-validation.md) that have been defined locally to enforce custom validation logic. An example is provided below: + +```python +CUSTOM_VALIDATORS = { + 'dcim.site': ( + Validator1, + Validator2, + Validator3 + ) +} +``` + +--- + ## DEBUG Default: False @@ -144,7 +152,7 @@ In order to send email, NetBox needs an email server configured. The following i !!! note The `USE_SSL` and `USE_TLS` parameters are mutually exclusive. -Email is sent from NetBox only for critical events or if configured for [logging](#logging). If you would like to test the email server configuration, Django provides a convenient [send_mail()](https://docs.djangoproject.com/en/stable/topics/email/#send-mail) fuction accessible within the NetBox shell: +Email is sent from NetBox only for critical events or if configured for [logging](#logging). If you would like to test the email server configuration, Django provides a convenient [send_mail()](https://docs.djangoproject.com/en/stable/topics/email/#send-mail) function accessible within the NetBox shell: ```no-highlight # python ./manage.py nbshell @@ -195,6 +203,14 @@ EXEMPT_VIEW_PERMISSIONS = ['*'] --- +## GRAPHQL_ENABLED + +Default: True + +Setting this to False will disable the GraphQL API. + +--- + ## HTTP_PROXIES Default: None @@ -271,7 +287,7 @@ Note that enabling this setting causes NetBox to update a user's session in the Default: False -Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users are permitted to access most data in NetBox (excluding secrets) but not make any changes. +Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users are permitted to access most data in NetBox but not make any changes. --- @@ -327,7 +343,7 @@ Toggle the availability Prometheus-compatible metrics at `/metrics`. See the [Pr ## NAPALM_PASSWORD -NetBox will use these credentials when authenticating to remote devices via the [NAPALM library](https://napalm-automation.net/), if installed. Both parameters are optional. +NetBox will use these credentials when authenticating to remote devices via the supported [NAPALM integration](../additional-features/napalm.md), if installed. Both parameters are optional. !!! note If SSH public key authentication has been set up on the remote device(s) for the system account under which NetBox runs, these parameters are not needed. @@ -482,19 +498,11 @@ When remote user authentication is in use, this is the name of the HTTP header w --- -## RELEASE_CHECK_TIMEOUT - -Default: 86,400 (24 hours) - -The number of seconds to retain the latest version that is fetched from the GitHub API before automatically invalidating it and fetching it from the API again. This must be set to at least one hour (3600 seconds). - ---- - ## RELEASE_CHECK_URL Default: None (disabled) -This parameter defines the URL of the repository that will be checked periodically for new NetBox releases. When a new release is detected, a message will be displayed to administrative users on the home page. This can be set to the official repository (`'https://api.github.com/repos/netbox-community/netbox/releases'`) or a custom fork. Set this to `None` to disable automatic update checks. +This parameter defines the URL of the repository that will be checked for new NetBox releases. When a new release is detected, a message will be displayed to administrative users on the home page. This can be set to the official repository (`'https://api.github.com/repos/netbox-community/netbox/releases'`) or a custom fork. Set this to `None` to disable automatic update checks. !!! note The URL provided **must** be compatible with the [GitHub REST API](https://docs.github.com/en/rest). @@ -505,7 +513,7 @@ This parameter defines the URL of the repository that will be checked periodical Default: `$INSTALL_ROOT/netbox/reports/` -The file path to the location where custom reports will be kept. By default, this is the `netbox/reports/` directory within the base NetBox installation path. +The file path to the location where [custom reports](../customization/reports.md) will be kept. By default, this is the `netbox/reports/` directory within the base NetBox installation path. --- @@ -521,7 +529,7 @@ The maximum execution time of a background task (such as running a custom script Default: `$INSTALL_ROOT/netbox/scripts/` -The file path to the location where custom scripts will be kept. By default, this is the `netbox/scripts/` directory within the base NetBox installation path. +The file path to the location where [custom scripts](../customization/custom-scripts.md) will be kept. By default, this is the `netbox/scripts/` directory within the base NetBox installation path. --- diff --git a/docs/core-functionality/ipam.md b/docs/core-functionality/ipam.md index dd6eee77b..00267dfe0 100644 --- a/docs/core-functionality/ipam.md +++ b/docs/core-functionality/ipam.md @@ -10,6 +10,7 @@ --- +{!docs/models/ipam/iprange.md!} {!docs/models/ipam/ipaddress.md!} --- diff --git a/docs/core-functionality/secrets.md b/docs/core-functionality/secrets.md deleted file mode 100644 index 68771310c..000000000 --- a/docs/core-functionality/secrets.md +++ /dev/null @@ -1,8 +0,0 @@ -# Secrets - -{!docs/models/secrets/secret.md!} -{!docs/models/secrets/secretrole.md!} - ---- - -{!docs/models/secrets/userkey.md!} diff --git a/docs/additional-features/custom-fields.md b/docs/customization/custom-fields.md similarity index 97% rename from docs/additional-features/custom-fields.md rename to docs/customization/custom-fields.md index 649f69256..a9acfb3f7 100644 --- a/docs/additional-features/custom-fields.md +++ b/docs/customization/custom-fields.md @@ -8,7 +8,7 @@ Within the database, custom fields are stored as JSON data directly alongside ea ## Creating Custom Fields -Custom fields must be created through the admin UI under Extras > Custom Fields. NetBox supports six types of custom field: +Custom fields may be created by navigating to Customization > Custom Fields. NetBox supports six types of custom field: * Text: Free-form text (up to 255 characters) * Integer: A whole number (positive or negative) diff --git a/docs/additional-features/custom-links.md b/docs/customization/custom-links.md similarity index 77% rename from docs/additional-features/custom-links.md rename to docs/customization/custom-links.md index 196371ce3..44c8f403f 100644 --- a/docs/additional-features/custom-links.md +++ b/docs/customization/custom-links.md @@ -1,8 +1,8 @@ # Custom Links -Custom links allow users to display arbitrary hyperlinks to external content within NetBox object views. These are helpful for cross-referencing related records in systems outside of NetBox. For example, you might create a custom link on the device view which links to the current device in a network monitoring system. +Custom links allow users to display arbitrary hyperlinks to external content within NetBox object views. These are helpful for cross-referencing related records in systems outside NetBox. For example, you might create a custom link on the device view which links to the current device in a network monitoring system. -Custom links are created under the admin UI. Each link is associated with a particular NetBox object type (site, device, prefix, etc.) and will be displayed on relevant views. Each link is assigned text and a URL, both of which support Jinja2 templating. The text and URL are rendered with the context variable `obj` representing the current object. +Custom links are created by navigating to Customization > Custom Links. Each link is associated with a particular NetBox object type (site, device, prefix, etc.) and will be displayed on relevant views. Each link is assigned text and a URL, both of which support Jinja2 templating. The text and URL are rendered with the context variable `obj` representing the current object. For example, you might define a link like this: @@ -15,7 +15,7 @@ When viewing a device named Router4, this link would render as: View NMS ``` -Custom links appear as buttons at the top right corner of the page. Numeric weighting can be used to influence the ordering of links. +Custom links appear as buttons in the top right corner of the page. Numeric weighting can be used to influence the ordering of links. !!! warning Custom links rely on user-created code to generate arbitrary HTML output, which may be dangerous. Only grant permission to create or modify custom links to trusted users. diff --git a/docs/additional-features/custom-scripts.md b/docs/customization/custom-scripts.md similarity index 96% rename from docs/additional-features/custom-scripts.md rename to docs/customization/custom-scripts.md index 8fe3661ed..a27bcab83 100644 --- a/docs/additional-features/custom-scripts.md +++ b/docs/customization/custom-scripts.md @@ -170,14 +170,9 @@ Similar to `ChoiceVar`, but allows for the selection of multiple choices. A particular object within NetBox. Each ObjectVar must specify a particular model, and allows the user to select one of the available instances. ObjectVar accepts several arguments, listed below. * `model` - The model class -* `display_field` - The name of the REST API object field to display in the selection list (default: `'display'`) * `query_params` - A dictionary of query parameters to use when retrieving available options (optional) * `null_option` - A label representing a "null" or empty choice (optional) -!!! warning - The `display_field` parameter is now deprecated, and will be removed in NetBox v3.0. All ObjectVar instances will - instead use the new standard `display` field for all serializers (introduced in NetBox v2.11). - To limit the selections available within the list, additional query parameters can be passed as the `query_params` dictionary. For example, to show only devices with an "active" status: ```python @@ -288,7 +283,6 @@ class NewBranchScript(Script): switch_model = ObjectVar( description="Access switch model", model=DeviceType, - display_field='model', query_params={ 'manufacturer_id': '$manufacturer' } diff --git a/docs/customization/custom-validation.md b/docs/customization/custom-validation.md new file mode 100644 index 000000000..720e8e487 --- /dev/null +++ b/docs/customization/custom-validation.md @@ -0,0 +1,86 @@ +# Custom Validation + +NetBox validates every object prior to it being written to the database to ensure data integrity. This validation includes things like checking for proper formatting and that references to related objects are valid. However, you may wish to supplement this validation with some rules of your own. For example, perhaps you require that every site's name conforms to a specific pattern. This can be done using NetBox's `CustomValidator` class. + +## CustomValidator + +### Validation Rules + +A custom validator can be instantiated by passing a mapping of attributes to a set of rules to which that attribute must conform. For example: + +```python +from extras.validators import CustomValidator + +CustomValidator({ + 'name': { + 'min_length': 5, + 'max_length': 30, + } +}) +``` + +This defines a custom validator which checks that the length of the `name` attribute for an object is at least five characters long, and no longer than 30 characters. This validation is executed _after_ NetBox has performed its own internal validation. + +The `CustomValidator` class supports several validation types: + +* `min`: Minimum value +* `max`: Maximum value +* `min_length`: Minimum string length +* `max_length`: Maximum string length +* `regex`: Application of a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) +* `required`: A value must be specified +* `prohibited`: A value must _not_ be specified + +The `min` and `max` types should be defined for numeric values, whereas `min_length`, `max_length`, and `regex` are suitable for character strings (text values). The `required` and `prohibited` validators may be used for any field, and should be passed a value of `True`. + +!!! warning + Bear in mind that these validators merely supplement NetBox's own validation: They will not override it. For example, if a certain model field is required by NetBox, setting a validator for it with `{'prohibited': True}` will not work. + +### Custom Validation Logic + +There may be instances where the provided validation types are insufficient. The `CustomValidator` class can be extended to enforce arbitrary validation logic by overriding its `validate()` method, and calling `fail()` when an unsatisfactory condition is detected. + +```python +from extras.validators import CustomValidator + +class MyValidator(CustomValidator): + def validate(self, instance): + if instance.status == 'active' and not instance.description: + self.fail("Active sites must have a description set!", field='status') +``` + +The `fail()` method may optionally specify a field with which to associate the supplied error message. If specified, the error message will appear to the user as associated with this field. If omitted, the error message will not be associated with any field. + +## Assigning Custom Validators + +Custom validators are associated with specific NetBox models under the [CUSTOM_VALIDATORS](../configuration/optional-settings.md#custom_validators) configuration parameter, as such: + +```python +CUSTOM_VALIDATORS = { + 'dcim.site': ( + Validator1, + Validator2, + Validator3 + ) +} +``` + +!!! note + Even if defining only a single validator, it must be passed as an iterable. + +When it is not necessary to define a custom `validate()` method, you may opt to pass a `CustomValidator` instance directly: + +```python +from extras.validators import CustomValidator + +CUSTOM_VALIDATORS = { + 'dcim.site': ( + CustomValidator({ + 'name': { + 'min_length': 5, + 'max_length': 30, + } + }), + ) +} +``` diff --git a/docs/additional-features/export-templates.md b/docs/customization/export-templates.md similarity index 81% rename from docs/additional-features/export-templates.md rename to docs/customization/export-templates.md index 660807444..c6097c552 100644 --- a/docs/additional-features/export-templates.md +++ b/docs/customization/export-templates.md @@ -1,6 +1,6 @@ # Export Templates -NetBox allows users to define custom templates that can be used when exporting objects. To create an export template, navigate to Extras > Export Templates under the admin interface. +NetBox allows users to define custom templates that can be used when exporting objects. To create an export template, navigate to Customization > Export Templates. Each export template is associated with a certain type of object. For instance, if you create an export template for VLANs, your custom template will appear under the "Export" button on the VLANs list. Each export template must have a name, and may optionally designate a specific export [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) and/or file extension. @@ -36,6 +36,16 @@ The `as_attachment` attribute of an export template controls its behavior when r A MIME type and file extension can optionally be defined for each export template. The default MIME type is `text/plain`. +## REST API Integration + +When it is necessary to provide authentication credentials (such as when [`LOGIN_REQUIRED`](../configuration/optional-settings.md#login_required) has been enabled), it is recommended to render export templates via the REST API. This allows the client to specify an authentication token. To render an export template via the REST API, make a `GET` request to the model's list endpoint and append the `export` parameter specifying the export template name. For example: + +``` +GET /api/dcim/sites/?export=MyTemplateName +``` + +Note that the body of the response will contain only the rendered export template content, as opposed to a JSON object or list. + ## Example Here's an example device export template that will generate a simple Nagios configuration from a list of devices. diff --git a/docs/additional-features/reports.md b/docs/customization/reports.md similarity index 100% rename from docs/additional-features/reports.md rename to docs/customization/reports.md diff --git a/docs/development/extending-models.md b/docs/development/extending-models.md index 9a27b68b6..994d2a040 100644 --- a/docs/development/extending-models.md +++ b/docs/development/extending-models.md @@ -32,19 +32,15 @@ class Foo(models.Model): raise ValidationError() ``` -## 3. Add CSV helpers - -Add the name of the new field to `csv_headers` and included a CSV-friendly representation of its data in the model's `to_csv()` method. These will be used when exporting objects in CSV format. - -## 4. Update relevant querysets +## 3. Update relevant querysets If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid extraneous database queries. -## 5. Update API serializer +## 4. Update API serializer Extend the model's API serializer in `.api.serializers` to include the new field. In most cases, it will not be necessary to also extend the nested serializer, which produces a minimal represenation of the model. -## 6. Add field to forms +## 5. Add field to forms Extend any forms to include the new field as appropriate. Common forms include: @@ -53,19 +49,19 @@ Extend any forms to include the new field as appropriate. Common forms include: * **CSV import** - The form used when bulk importing objects in CSV format * **Filter** - Displays the options available for filtering a list of objects (both UI and API) -## 7. Extend object filter set +## 6. Extend object filter set If the new field should be filterable, add it to the `FilterSet` for the model. If the field should be searchable, remember to reference it in the FilterSet's `search()` method. -## 8. Add column to object table +## 7. Add column to object table If the new field will be included in the object list view, add a column to the model's table. For simple fields, adding the field name to `Meta.fields` will be sufficient. More complex fields may require declaring a custom column. -## 9. Update the UI templates +## 8. Update the UI templates Edit the object's view template to display the new field. There may also be a custom add/edit form template that needs to be updated. -## 10. Create/extend test cases +## 9. Create/extend test cases Create or extend the relevant test cases to verify that the new field and any accompanying validation logic perform as expected. This is especially important for relational fields. NetBox incorporates various test suites, including: @@ -77,6 +73,6 @@ Create or extend the relevant test cases to verify that the new field and any ac Be diligent to ensure all of the relevant test suites are adapted or extended as necessary to test any new functionality. -## 11. Update the model's documentation +## 10. Update the model's documentation Each model has a dedicated page in the documentation, at `models//.md`. Update this file to include any relevant information about the new field. diff --git a/docs/development/index.md b/docs/development/index.md index b856e315b..c10c752d5 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -25,7 +25,6 @@ NetBox components are arranged into functional subsections called _apps_ (a carr * `dcim`: Datacenter infrastructure management (sites, racks, and devices) * `extras`: Additional features not considered part of the core data model * `ipam`: IP address management (VRFs, prefixes, IP addresses, and VLANs) -* `secrets`: Encrypted storage of sensitive data (e.g. login credentials) * `tenancy`: Tenants (such as customers) to which NetBox objects may be assigned * `users`: Authentication and user preferences * `utilities`: Resources which are not user-facing (extendable classes, etc.) diff --git a/docs/development/models.md b/docs/development/models.md index 7dec2cb61..93a10fff6 100644 --- a/docs/development/models.md +++ b/docs/development/models.md @@ -10,8 +10,8 @@ The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/ * [Change logging](../additional-features/change-logging.md) - Changes to these objects are automatically recorded in the change log * [Webhooks](../additional-features/webhooks.md) - NetBox is capable of generating outgoing webhooks for these objects -* [Custom fields](../additional-features/custom-fields.md) - These models support the addition of user-defined fields -* [Export templates](../additional-features/export-templates.md) - Users can create custom export templates for these models +* [Custom fields](../customization/custom-fields.md) - These models support the addition of user-defined fields +* [Export templates](../customization/export-templates.md) - Users can create custom export templates for these models * [Tagging](../models/extras/tag.md) - The models can be tagged with user-defined tags * [Journaling](../additional-features/journaling.md) - These models support persistent historical commentary * Nesting - These models can be nested recursively to create a hierarchy @@ -47,7 +47,6 @@ The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/ * [ipam.Service](../models/ipam/service.md) * [ipam.VLAN](../models/ipam/vlan.md) * [ipam.VRF](../models/ipam/vrf.md) -* [secrets.Secret](../models/secrets/secret.md) * [tenancy.Tenant](../models/tenancy/tenant.md) * [virtualization.Cluster](../models/virtualization/cluster.md) * [virtualization.VirtualMachine](../models/virtualization/virtualmachine.md) @@ -62,7 +61,6 @@ The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/ * [ipam.RIR](../models/ipam/rir.md) * [ipam.Role](../models/ipam/role.md) * [ipam.VLANGroup](../models/ipam/vlangroup.md) -* [secrets.SecretRole](../models/secrets/secretrole.md) * [virtualization.ClusterGroup](../models/virtualization/clustergroup.md) * [virtualization.ClusterType](../models/virtualization/clustertype.md) diff --git a/docs/development/release-checklist.md b/docs/development/release-checklist.md index 91d5ab2ab..8fbec84f9 100644 --- a/docs/development/release-checklist.md +++ b/docs/development/release-checklist.md @@ -6,16 +6,6 @@ Check `base_requirements.txt` for any dependencies pinned to a specific version, and upgrade them to their most stable release (where possible). -### Update Static Libraries - -Update the following static libraries to their most recent stable release: - -* Bootstrap 3 -* Material Design Icons -* Select2 -* jQuery -* jQuery UI - ### Link to the Release Notes Page Add the release notes (`/docs/release-notes/X.Y.md`) to the table of contents within `mkdocs.yml`, and point `index.md` to the new file. diff --git a/docs/development/signals.md b/docs/development/signals.md new file mode 100644 index 000000000..8a5d8e43f --- /dev/null +++ b/docs/development/signals.md @@ -0,0 +1,11 @@ +# Signals + +In addition to [Django's built-in signals](https://docs.djangoproject.com/en/stable/topics/signals/), NetBox defines some of its own, listed below. + +## post_clean + +This signal is sent by models which inherit from `CustomValidationMixin` at the end of their `clean()` method. + +### Receivers + +* `extras.signals.run_custom_validators()` diff --git a/docs/development/web-ui.md b/docs/development/web-ui.md new file mode 100644 index 000000000..8afbd96c1 --- /dev/null +++ b/docs/development/web-ui.md @@ -0,0 +1,99 @@ +# Web UI Development + +## Front End Technologies + +The NetBox UI is built on languages and frameworks: + +### Styling & HTML Elements + +#### [Bootstrap](https://getbootstrap.com/) 5 + +The majority of the NetBox UI is made up of stock Bootstrap components, with some styling modifications and custom components added on an as-needed basis. Bootstrap uses [Sass](https://sass-lang.com/), and NetBox extends Bootstrap's core Sass files for theming and customization. + +### Client-side Scripting + +#### [TypeScript](https://www.typescriptlang.org/) + +All client-side scripting is transpiled from TypeScript to JavaScript and served by Django. In development, TypeScript is an _extremely_ effective tool for accurately describing and checking the code, which leads to significantly fewer bugs, a better development experience, and more predictable/readable code. + +As part of the [bundling](#bundling) process, Bootstrap's JavaScript plugins are imported and bundled alongside NetBox's front-end code. + +!!! danger "NetBox is jQuery-free" + Following the Bootstrap team's deprecation of jQuery in Bootstrap 5, NetBox also no longer uses jQuery in front-end code. + +## Guidance + +NetBox generally follows the following guidelines for front-end code: + +- Bootstrap utility classes may be used to solve one-off issues or to implement singular components, as long as the class list does not exceed 4-5 classes. If an element needs more than 5 utility classes, a custom SCSS class should be added that contains the required style properties. +- Custom classes must be commented, explaining the general purpose of the class and where it is used. +- Reuse SCSS variables whenever possible. CSS values should (almost) never be hard-coded. +- All TypeScript functions must have, at a minimum, a basic [JSDoc](https://jsdoc.app/) description of what the function is for and where it is used. If possible, document all function arguments via [`@param` JSDoc block tags](https://jsdoc.app/tags-param.html). +- Expanding on NetBox's [dependency policy](style-guide.md#introducing-new-dependencies), new front-end dependencies should be avoided unless absolutely necessary. Every new front-end dependency adds to the CSS/JavaScript file size that must be loaded by the client and this should be minimized as much as possible. If adding a new dependency is unavoidable, use a tool like [Bundlephobia](https://bundlephobia.com/) to ensure the smallest possible library is used. +- All UI elements must be usable on all common screen sizes, including mobile devices. Be sure to test newly implemented solutions (JavaScript included) on as many screen sizes and device types as possible. +- NetBox aligns with Bootstrap's [supported Browsers and Devices](https://getbootstrap.com/docs/5.1/getting-started/browsers-devices/) list. + +## UI Development + +To contribute to the NetBox UI, you'll need to review the main [Getting Started guide](getting-started.md) in order to set up your base environment. + +### Tools + +Once you have a working NetBox development environment, you'll need to install a few more tools to work with the NetBox UI: + +- [NodeJS](https://nodejs.org/en/download/) (the LTS release should suffice) +- [Yarn](https://yarnpkg.com/getting-started/install) (version 1) + +After Node and Yarn are installed on your system, you'll need to install all the NetBox UI dependencies: + +```console +$ cd netbox/project-static +$ yarn +``` + +!!! warning "Check Your Working Directory" + You need to be in the `netbox/project-static` directory to run the below `yarn` commands. + +### Bundling + +In order for the TypeScript and Sass (SCSS) source files to be usable by a browser, they must first be transpiled (TypeScript → JavaScript, Sass → CSS), bundled, and minified. After making changes to TypeScript or Sass source files, run `yarn bundle`. + +`yarn bundle` is a wrapper around the following subcommands, any of which can be run individually: + +| Command | Action | +| :-------------------- | :---------------------------------------------- | +| `yarn bundle` | Bundle TypeScript and Sass (SCSS) source files. | +| `yarn bundle:styles` | Bundle Sass (SCSS) source files only. | +| `yarn bundle:scripts` | Bundle TypeScript source files only. | + +All output files will be written to `netbox/project-static/dist`, where Django will pick them up when `manage.py collectstatic` is run. + +!!! info "Remember to re-run `manage.py collectstatic`" + If you're running the development web server — `manage.py runserver` — you'll need to run `manage.py collectstatic` to see your changes. + +### Linting, Formatting & Type Checking + +Before committing any changes to TypeScript files, and periodically throughout the development process, you should run `yarn validate` to catch formatting, code quality, or type errors. + +!!! tip "IDE Integrations" + If you're using an IDE, it is strongly recommended to install [ESLint](https://eslint.org/docs/user-guide/integrations), [TypeScript](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support), and [Prettier](https://prettier.io/docs/en/editors.html) integrations, if available. Most of them will automatically check and/or correct issues in the code as you develop, which can significantly increase your productivity as a contributor. + +`yarn validate` is a wrapper around the following subcommands, any of which can be run individually: + +| Command | Action | +| :--------------------------------- | :--------------------------------------------------------------- | +| `yarn validate` | Run all validation. | +| `yarn validate:lint` | Validate TypeScript code via [ESLint](https://eslint.org/) only. | +| `yarn validate:types` | Validate TypeScript code compilation only. | +| `yarn validate:formatting` | Validate code formatting of JavaScript & Sass/SCSS files. | +| `yarn validate:formatting:styles` | Validate code formatting Sass/SCSS only. | +| `yarn validate:formatting:scripts` | Validate code formatting TypeScript only. | + +You can also run the following commands to automatically fix formatting issues: + +| Command | Action | +| :-------------------- | :---------------------------------------------- | +| `yarn format` | Format TypeScript and Sass (SCSS) source files. | +| `yarn format:styles` | Format Sass (SCSS) source files only. | +| `yarn format:scripts` | Format TypeScript source files only. | + diff --git a/docs/extra.css b/docs/extra.css index 6a95f356a..e953fa14c 100644 --- a/docs/extra.css +++ b/docs/extra.css @@ -11,9 +11,19 @@ table { width: 100%; } th { - background-color: #f0f0f0; padding: 6px; + font-weight: bold; } td { padding: 6px; } +/* Remove table header coloring. */ +.md-typeset table:not([class]) th { + color: unset !important; + background-color: unset !important; +} +thead tr { + /* Colorize table headers. */ + background-color: var(--md-code-bg-color); + color: var(--md-code-fg-color); +} diff --git a/docs/graphql-api/overview.md b/docs/graphql-api/overview.md new file mode 100644 index 000000000..f1ce4f455 --- /dev/null +++ b/docs/graphql-api/overview.md @@ -0,0 +1,70 @@ +# GraphQL API Overview + +NetBox provides a read-only [GraphQL](https://graphql.org/) API to complement its REST API. This API is powered by the [Graphene](https://graphene-python.org/) library and [Graphene-Django](https://docs.graphene-python.org/projects/django/en/latest/). + +## Queries + +GraphQL enables the client to specify an arbitrary nested list of fields to include in the response. All queries are made to the root `/graphql` API endpoint. For example, to return the circuit ID and provider name of each circuit with an active status, you can issue a request such as the following: + +``` +curl -H "Authorization: Token $TOKEN" \ +-H "Content-Type: application/json" \ +-H "Accept: application/json" \ +http://netbox/graphql/ \ +--data '{"query": "query {circuits(status:\"active\" {cid provider {name}}}"}' +``` + +The response will include the requested data formatted as JSON: + +```json +{ + "data": { + "circuits": [ + { + "cid": "1002840283", + "provider": { + "name": "CenturyLink" + } + }, + { + "cid": "1002840457", + "provider": { + "name": "CenturyLink" + } + } + ] + } +} +``` + +!!! note + It's recommended to pass the return data through a JSON parser such as `jq` for better readability. + +NetBox provides both a singular and plural query field for each object type: + +* `$OBJECT`: Returns a single object. Must specify the object's unique ID as `(id: 123)`. +* `$OBJECT_list`: Returns a list of objects, optionally filtered by given parameters. + +For example, query `device(id:123)` to fetch a specific device (identified by its unique ID), and query `device_list` (with an optional set of fitlers) to fetch all devices. + +For more detail on constructing GraphQL queries, see the [Graphene documentation](https://docs.graphene-python.org/en/latest/). + +## Filtering + +The GraphQL API employs the same filtering logic as the UI and REST API. Filters can be specified as key-value pairs within parentheses immediately following the query name. For example, the following will return only sites within the North Carolina region with a status of active: + +``` +{"query": "query {sites(region:\"north-carolina\", status:\"active\") {name}}"} +``` + +## Authentication + +NetBox's GraphQL API uses the same API authentication tokens as its REST API. Authentication tokens are included with requests by attaching an `Authorization` HTTP header in the following form: + +``` +Authorization: Token $TOKEN +``` + +## Disabling the GraphQL API + +If not needed, the GraphQL API can be disabled by setting the [`GRAPHQL_ENABLED`](../configuration/optional-settings.md#graphql_enabled) configuration parameter to False and restarting NetBox. diff --git a/docs/index.md b/docs/index.md index 0fc0dc0b7..ad28a708c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -55,7 +55,7 @@ NetBox is built on the [Django](https://djangoproject.com/) Python framework and ## Supported Python Versions -NetBox supports Python 3.6, 3.7, and 3.8 environments currently. (Support for Python 3.5 was removed in NetBox v2.8.) +NetBox supports Python 3.7, 3.8, and 3.9 environments currently. (Support for Python 3.6 was removed in NetBox v3.0.) ## Getting Started diff --git a/docs/installation/1-postgresql.md b/docs/installation/1-postgresql.md index 644b2715c..43b12f0e8 100644 --- a/docs/installation/1-postgresql.md +++ b/docs/installation/1-postgresql.md @@ -40,28 +40,28 @@ sudo systemctl enable postgresql ## Database Creation -At a minimum, we need to create a database for NetBox and assign it a username and password for authentication. This is done with the following commands. +At a minimum, we need to create a database for NetBox and assign it a username and password for authentication. Start by invoking the PostgreSQL shell as the system Postgres user. + +```no-highlight +sudo -u postgres psql +``` + +Within the shell, enter the following commands to create the database and user (role), substituting your own value for the password: + +```postgresql +CREATE DATABASE netbox; +CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K'; +GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox; +``` !!! danger **Do not use the password from the example.** Choose a strong, random password to ensure secure database authentication for your NetBox installation. -```no-highlight -$ sudo -u postgres psql -psql (12.5 (Ubuntu 12.5-0ubuntu0.20.04.1)) -Type "help" for help. - -postgres=# CREATE DATABASE netbox; -CREATE DATABASE -postgres=# CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K'; -CREATE ROLE -postgres=# GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox; -GRANT -postgres=# \q -``` +Once complete, enter `\q` to exit the PostgreSQL shell. ## Verify Service Status -You can verify that authentication works issuing the following command and providing the configured password. (Replace `localhost` with your database server if using a remote database.) +You can verify that authentication works by executing the `psql` command and passing the configured username and password. (Replace `localhost` with your database server if using a remote database.) ```no-highlight $ psql --username netbox --password --host localhost netbox diff --git a/docs/installation/2-redis.md b/docs/installation/2-redis.md index e31873d2b..14dda60f1 100644 --- a/docs/installation/2-redis.md +++ b/docs/installation/2-redis.md @@ -28,6 +28,7 @@ You may wish to modify the Redis configuration at `/etc/redis.conf` or `/etc/red Use the `redis-cli` utility to ensure the Redis service is functional: ```no-highlight -$ redis-cli ping -PONG +redis-cli ping ``` + +If successful, you should receive a `PONG` response from the server. diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index eb717093d..43b23a649 100644 --- a/docs/installation/3-netbox.md +++ b/docs/installation/3-netbox.md @@ -7,7 +7,7 @@ This section of the documentation discusses installing and configuring the NetBo Begin by installing all system packages required by NetBox and its dependencies. !!! note - NetBox v2.8.0 and later require Python 3.6, 3.7, or 3.8. + NetBox v3.0 and later require Python 3.7, 3.8, or 3.9. === "Ubuntu" @@ -36,23 +36,21 @@ This documentation provides two options for installing NetBox: from a downloadab Download the [latest stable release](https://github.com/netbox-community/netbox/releases) from GitHub as a tarball or ZIP archive and extract it to your desired path. In this example, we'll use `/opt/netbox` as the NetBox root. ```no-highlight -$ sudo wget https://github.com/netbox-community/netbox/archive/vX.Y.Z.tar.gz -$ sudo tar -xzf vX.Y.Z.tar.gz -C /opt -$ sudo ln -s /opt/netbox-X.Y.Z/ /opt/netbox -$ ls -l /opt | grep netbox -lrwxrwxrwx 1 root root 13 Jul 20 13:44 netbox -> netbox-2.9.0/ -drwxr-xr-x 2 root root 4096 Jul 20 13:44 netbox-2.9.0 +sudo wget https://github.com/netbox-community/netbox/archive/vX.Y.Z.tar.gz +sudo tar -xzf vX.Y.Z.tar.gz -C /opt +sudo ln -s /opt/netbox-X.Y.Z/ /opt/netbox ``` !!! note - It is recommended to install NetBox in a directory named for its version number. For example, NetBox v2.9.0 would be installed into `/opt/netbox-2.9.0`, and a symlink from `/opt/netbox/` would point to this location. This allows for future releases to be installed in parallel without interrupting the current installation. When changing to the new release, only the symlink needs to be updated. + It is recommended to install NetBox in a directory named for its version number. For example, NetBox v3.0.0 would be installed into `/opt/netbox-3.0.0`, and a symlink from `/opt/netbox/` would point to this location. (You can verify this configuration with the command `ls -l /opt | grep netbox`.) This allows for future releases to be installed in parallel without interrupting the current installation. When changing to the new release, only the symlink needs to be updated. ### Option B: Clone the Git Repository Create the base directory for the NetBox installation. For this guide, we'll use `/opt/netbox`. ```no-highlight -sudo mkdir -p /opt/netbox/ && cd /opt/netbox/ +sudo mkdir -p /opt/netbox/ +cd /opt/netbox/ ``` If `git` is not already installed, install it: @@ -72,10 +70,10 @@ If `git` is not already installed, install it: Next, clone the **master** branch of the NetBox GitHub repository into the current directory. (This branch always holds the current stable release.) ```no-highlight -$ sudo git clone -b master https://github.com/netbox-community/netbox.git . +sudo git clone -b master https://github.com/netbox-community/netbox.git . ``` -The screen below should be the result: +The `git clone` command should generate output similar to the following: ``` Cloning into '.'... @@ -200,7 +198,7 @@ All Python packages required by NetBox are listed in `requirements.txt` and will ### NAPALM -The [NAPALM automation](https://napalm-automation.net/) library allows NetBox to fetch live data from devices and return it to a requester via its REST API. The `NAPALM_USERNAME` and `NAPALM_PASSWORD` configuration parameters define the credentials to be used when connecting to a device. +Integration with the [NAPALM automation](../additional-features/napalm.md) library allows NetBox to fetch live data from devices and return it to a requester via its REST API. The `NAPALM_USERNAME` and `NAPALM_PASSWORD` configuration parameters define the credentials to be used when connecting to a device. ```no-highlight sudo sh -c "echo 'napalm' >> /opt/netbox/local_requirements.txt" @@ -219,14 +217,21 @@ sudo sh -c "echo 'django-storages' >> /opt/netbox/local_requirements.txt" Once NetBox has been configured, we're ready to proceed with the actual installation. We'll run the packaged upgrade script (`upgrade.sh`) to perform the following actions: * Create a Python virtual environment -* Install all required Python packages +* Installs all required Python packages * Run database schema migrations +* Builds the documentation locally (for offline use) * Aggregate static resource files on disk ```no-highlight sudo /opt/netbox/upgrade.sh ``` +Note that **Python 3.7 or later is required** for NetBox v3.0 and later releases. If the default Python installation on your server does not meet this requirement, you'll need to install Python 3.7 or later separately, and pass the path to the support installation as an environment variable named `PYTHON`. (Note that the environment variable must be passed _after_ the `sudo` command.) + +```no-highlight +sudo PYTHON=/usr/bin/python3.7 /opt/netbox/upgrade.sh +``` + !!! note Upon completion, the upgrade script may warn that no existing virtual environment was detected. As this is a new installation, this warning can be safely ignored. @@ -243,30 +248,45 @@ Once the virtual environment has been activated, you should notice the string `( Next, we'll create a superuser account using the `createsuperuser` Django management command (via `manage.py`). Specifying an email address for the user is not required, but be sure to use a very strong password. ```no-highlight -(venv) $ cd /opt/netbox/netbox -(venv) $ python3 manage.py createsuperuser -Username: admin -Email address: admin@example.com -Password: -Password (again): -Superuser created successfully. +cd /opt/netbox/netbox +python3 manage.py createsuperuser ``` +## Schedule the Housekeeping Task + +NetBox includes a `housekeeping` management command that handles some recurring cleanup tasks, such as clearing out old sessions and expired change records. Although this command may be run manually, it is recommended to configure a scheduled job using the system's `cron` daemon or a similar utility. + +A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be copied to your system's daily cron task directory, or included within the crontab directly. (If installing NetBox into a nonstandard path, be sure to update the system paths within this script first.) + +```shell +cp /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/ +``` + +See the [housekeeping documentation](../administration/housekeeping.md) for further details. + ## Test the Application At this point, we should be able to run NetBox's development server for testing. We can check by starting a development instance: ```no-highlight -(venv) $ python3 manage.py runserver 0.0.0.0:8000 --insecure +python3 manage.py runserver 0.0.0.0:8000 --insecure +``` + +If successful, you should see output similar to the following: + +```no-highlight +Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). -November 17, 2020 - 16:08:13 -Django version 3.1.3, using settings 'netbox.settings' -Starting development server at http://0.0.0.0:8000/ +August 30, 2021 - 18:02:23 +Django version 3.2.6, using settings 'netbox.settings' +Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. ``` +Next, connect to the name or IP of the server (as defined in `ALLOWED_HOSTS`) on port 8000; for example, . You should be greeted with the NetBox home page. Try logging in using the username and password specified when creating a superuser. + !!! note By default RHEL based distros will likely block your testing attempts with firewalld. The development server port can be opened with `firewall-cmd` (add `--permanent` if you want the rule to survive server restarts): @@ -274,20 +294,10 @@ Quit the server with CONTROL-C. firewall-cmd --zone=public --add-port=8000/tcp ``` -Next, connect to the name or IP of the server (as defined in `ALLOWED_HOSTS`) on port 8000; for example, . You should be greeted with the NetBox home page. - !!! danger The development server is for development and testing purposes only. It is neither performant nor secure enough for production use. **Do not use it in production.** !!! warning If the test service does not run, or you cannot reach the NetBox home page, something has gone wrong. Do not proceed with the rest of this guide until the installation has been corrected. -Note that the initial user interface will be locked down for non-authenticated users. - -![NetBox UI as seen by a non-authenticated user](../media/installation/netbox_ui_guest.png) - -Try logging in using the superuser account we just created. Once authenticated, you'll be able to access all areas of the UI: - -![NetBox UI as seen by an administrator](../media/installation/netbox_ui_admin.png) - Type `Ctrl+c` to stop the development server. diff --git a/docs/installation/4-gunicorn.md b/docs/installation/4-gunicorn.md index 8168ce98b..7b56754fe 100644 --- a/docs/installation/4-gunicorn.md +++ b/docs/installation/4-gunicorn.md @@ -31,18 +31,23 @@ sudo systemctl enable netbox netbox-rq You can use the command `systemctl status netbox` to verify that the WSGI service is running: ```no-highlight -# systemctl status netbox.service +systemctl status netbox.service +``` + +You should see output similar to the following: + +```no-highlight ● netbox.service - NetBox WSGI Service Loaded: loaded (/etc/systemd/system/netbox.service; enabled; vendor preset: enabled) - Active: active (running) since Tue 2020-11-17 16:18:23 UTC; 3min 35s ago + Active: active (running) since Mon 2021-08-30 04:02:36 UTC; 14h ago Docs: https://netbox.readthedocs.io/en/stable/ - Main PID: 22836 (gunicorn) - Tasks: 6 (limit: 2345) - Memory: 339.3M + Main PID: 1140492 (gunicorn) + Tasks: 19 (limit: 4683) + Memory: 666.2M CGroup: /system.slice/netbox.service - ├─22836 /opt/netbox/venv/bin/python3 /opt/netbox/venv/bin/gunicorn --pid> - ├─22854 /opt/netbox/venv/bin/python3 /opt/netbox/venv/bin/gunicorn --pid> - ├─22855 /opt/netbox/venv/bin/python3 /opt/netbox/venv/bin/gunicorn --pid> + ├─1140492 /opt/netbox/venv/bin/python3 /opt/netbox/venv/bin/gunicorn --pid /va> + ├─1140513 /opt/netbox/venv/bin/python3 /opt/netbox/venv/bin/gunicorn --pid /va> + ├─1140514 /opt/netbox/venv/bin/python3 /opt/netbox/venv/bin/gunicorn --pid /va> ... ``` diff --git a/docs/installation/index.md b/docs/installation/index.md index 1c2fcf567..db1e48620 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -19,13 +19,10 @@ The video below demonstrates the installation of NetBox v2.10.3 on Ubuntu 20.04 | Dependency | Minimum Version | |------------|-----------------| -| Python | 3.6 | +| Python | 3.7 | | PostgreSQL | 9.6 | | Redis | 4.0 | -!!! note - Python 3.7 or later will be required in NetBox v3.0. Users are strongly encouraged to install NetBox using Python 3.7 or later for new deployments. - Below is a simplified overview of the NetBox application stack for reference: ![NetBox UI as seen by a non-authenticated user](../media/installation/netbox_application_stack.png) diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md index e824ad7ab..9854afeb4 100644 --- a/docs/installation/upgrading.md +++ b/docs/installation/upgrading.md @@ -6,11 +6,11 @@ Prior to upgrading your NetBox instance, be sure to carefully review all [releas ## Update Dependencies to Required Versions -NetBox v2.9.0 and later requires the following: +NetBox v3.0 and later requires the following: | Dependency | Minimum Version | |------------|-----------------| -| Python | 3.6 | +| Python | 3.7 | | PostgreSQL | 9.6 | | Redis | 4.0 | @@ -75,16 +75,23 @@ Once the new code is in place, verify that any optional Python packages required sudo ./upgrade.sh ``` +!!! warning + If the default version of Python is not at least 3.7, you'll need to pass the path to a supported Python version as an environment variable when calling the upgrade script. For example: + + ```no-highlight + sudo PYTHON=/usr/bin/python3.7 ./upgrade.sh + ``` + This script performs the following actions: * Destroys and rebuilds the Python virtual environment * Installs all required Python packages (listed in `requirements.txt`) * Installs any additional packages from `local_requirements.txt` * Applies any database migrations that were included in the release +* Builds the documentation locally (for offline use) * Collects all static files to be served by the HTTP service * Deletes stale content types from the database * Deletes all expired user sessions from the database -* Clears all cached data to prevent conflicts with the new release !!! note If the upgrade script prompts a warning about unreflected database migrations, this indicates that some change has @@ -102,5 +109,12 @@ Finally, restart the gunicorn and RQ services: sudo systemctl restart netbox netbox-rq ``` -!!! note - If upgrading from an installation that uses supervisord, please see the instructions for [migrating to systemd](migrating-to-systemd.md). The use of supervisord is no longer supported. +## Verify Housekeeping Scheduling + +If upgrading from a release prior to NetBox v3.0, check that a cron task (or similar scheduled process) has been configured to run NetBox's nightly housekeeping command. A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be copied to your system's daily cron task directory, or included within the crontab directly. (If NetBox has been installed in a nonstandard path, be sure to update the system paths within this script first.) + +```shell +cp /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/ +``` + +See the [housekeeping documentation](../administration/housekeeping.md) for further details. diff --git a/docs/media/installation/netbox_ui_admin.png b/docs/media/installation/netbox_ui_admin.png deleted file mode 100644 index bde4947d5..000000000 Binary files a/docs/media/installation/netbox_ui_admin.png and /dev/null differ diff --git a/docs/media/installation/netbox_ui_guest.png b/docs/media/installation/netbox_ui_guest.png deleted file mode 100644 index a20a5467a..000000000 Binary files a/docs/media/installation/netbox_ui_guest.png and /dev/null differ diff --git a/docs/media/release-notes/netbox30_ui.png b/docs/media/release-notes/netbox30_ui.png new file mode 100644 index 000000000..0f17cbadc Binary files /dev/null and b/docs/media/release-notes/netbox30_ui.png differ diff --git a/docs/media/screenshot1.png b/docs/media/screenshot1.png deleted file mode 100644 index e25a5ed93..000000000 Binary files a/docs/media/screenshot1.png and /dev/null differ diff --git a/docs/media/screenshot2.png b/docs/media/screenshot2.png deleted file mode 100644 index bce0a3f5d..000000000 Binary files a/docs/media/screenshot2.png and /dev/null differ diff --git a/docs/media/screenshot3.png b/docs/media/screenshot3.png deleted file mode 100644 index 6cf67d8f7..000000000 Binary files a/docs/media/screenshot3.png and /dev/null differ diff --git a/docs/media/screenshots/cable-trace.png b/docs/media/screenshots/cable-trace.png new file mode 100644 index 000000000..b35272016 Binary files /dev/null and b/docs/media/screenshots/cable-trace.png differ diff --git a/docs/media/screenshots/home-dark.png b/docs/media/screenshots/home-dark.png new file mode 100644 index 000000000..796637ac5 Binary files /dev/null and b/docs/media/screenshots/home-dark.png differ diff --git a/docs/media/screenshots/home-light.png b/docs/media/screenshots/home-light.png new file mode 100644 index 000000000..78d54a7d2 Binary files /dev/null and b/docs/media/screenshots/home-light.png differ diff --git a/docs/media/screenshots/prefixes-list.png b/docs/media/screenshots/prefixes-list.png new file mode 100644 index 000000000..927a7a04e Binary files /dev/null and b/docs/media/screenshots/prefixes-list.png differ diff --git a/docs/media/screenshots/rack.png b/docs/media/screenshots/rack.png new file mode 100644 index 000000000..dbe9718f7 Binary files /dev/null and b/docs/media/screenshots/rack.png differ diff --git a/docs/models/dcim/platform.md b/docs/models/dcim/platform.md index a860904b5..65188fa6e 100644 --- a/docs/models/dcim/platform.md +++ b/docs/models/dcim/platform.md @@ -4,6 +4,6 @@ A platform defines the type of software running on a device or virtual machine. Platforms may optionally be limited by manufacturer: If a platform is assigned to a particular manufacturer, it can only be assigned to devices with a type belonging to that manufacturer. -The platform model is also used to indicate which [NAPALM](https://napalm-automation.net/) driver and any associated arguments NetBox should use when connecting to a remote device. The name of the driver along with optional parameters are stored with the platform. +The platform model is also used to indicate which [NAPALM](../../additional-features/napalm.md) driver and any associated arguments NetBox should use when connecting to a remote device. The name of the driver along with optional parameters are stored with the platform. The assignment of platforms to devices is an optional feature, and may be disregarded if not desired. diff --git a/docs/models/dcim/virtualchassis.md b/docs/models/dcim/virtualchassis.md index b2a7d3bc9..3b6fb9d17 100644 --- a/docs/models/dcim/virtualchassis.md +++ b/docs/models/dcim/virtualchassis.md @@ -2,7 +2,7 @@ A virtual chassis represents a set of devices which share a common control plane. A common example of this is a stack of switches which are connected and configured to operate as a single device. A virtual chassis must be assigned a name and may be assigned a domain. -Each device in the virtual chassis is referred to as a VC member, and assigned a position and (optionally) a priority. VC member devices commonly reside within the same rack, though this is not a requirement. One of the devices may be designated as the VC master: This device will typically be assigned a name, secrets, services, and other attributes related to managing the VC. +Each device in the virtual chassis is referred to as a VC member, and assigned a position and (optionally) a priority. VC member devices commonly reside within the same rack, though this is not a requirement. One of the devices may be designated as the VC master: This device will typically be assigned a name, services, and other attributes related to managing the VC. !!! note It's important to recognize the distinction between a virtual chassis and a chassis-based device. A virtual chassis is **not** suitable for modeling a chassis-based switch with removable line cards (such as the Juniper EX9208), as its line cards are _not_ physically autonomous devices. diff --git a/docs/models/ipam/iprange.md b/docs/models/ipam/iprange.md new file mode 100644 index 000000000..ab712e5b2 --- /dev/null +++ b/docs/models/ipam/iprange.md @@ -0,0 +1,11 @@ +# IP Ranges + +This model represents an arbitrary range of individual IPv4 or IPv6 addresses, inclusive of its starting and ending addresses. For instance, the range 192.0.2.10 to 192.0.2.20 has eleven members. (The total member count is available as the `size` property on an IPRange instance.) Like prefixes and IP addresses, each IP range may optionally be assigned to a VRF and/or tenant. + +IP also ranges share the same functional roles as prefixes and VLANs, although the assignment of a role is optional. Each IP range must be assigned an operational status, which is one of the following: + +* Active - Provisioned and in use +* Reserved - Designated for future use +* Deprecated - No longer in use + +The status of a range does _not_ have any impact on its member IP addresses, which may have their statuses modified separately. diff --git a/docs/models/secrets/secret.md b/docs/models/secrets/secret.md deleted file mode 100644 index 4c3482624..000000000 --- a/docs/models/secrets/secret.md +++ /dev/null @@ -1,5 +0,0 @@ -# Secrets - -A secret represents a single credential or other sensitive string of characters which must be stored securely. Each secret is assigned to a device within NetBox. The plaintext value of a secret is encrypted to a ciphertext immediately prior to storage within the database using a 256-bit AES master key. A SHA256 hash of the plaintext is also stored along with each ciphertext to validate the decrypted plaintext. - -Each secret can also store an optional name parameter, which is not encrypted. This may be useful for storing user names. diff --git a/docs/models/secrets/secretrole.md b/docs/models/secrets/secretrole.md deleted file mode 100644 index 23f68912b..000000000 --- a/docs/models/secrets/secretrole.md +++ /dev/null @@ -1,9 +0,0 @@ -# Secret Roles - -Each secret is assigned a functional role which indicates what it is used for. Secret roles are customizable. Typical roles might include: - -* Login credentials -* SNMP community strings -* RADIUS/TACACS+ keys -* IKE key strings -* Routing protocol shared secrets diff --git a/docs/models/secrets/userkey.md b/docs/models/secrets/userkey.md deleted file mode 100644 index fd66c7bdb..000000000 --- a/docs/models/secrets/userkey.md +++ /dev/null @@ -1,35 +0,0 @@ -# User Keys - -Each user within NetBox can associate his or her account with an RSA public key. If activated by an administrator, this user key will contain a unique, encrypted copy of the AES master key needed to retrieve secret data. - -User keys may be created by users individually, however they are of no use until they have been activated by a user who already possesses an active user key. - -## Supported Key Format - -Public key formats supported - -- PKCS#1 RSAPublicKey* (PEM header: BEGIN RSA PUBLIC KEY) -- X.509 SubjectPublicKeyInfo** (PEM header: BEGIN PUBLIC KEY) -- **OpenSSH line format is not supported.** - -Private key formats supported (unencrypted) - -- PKCS#1 RSAPrivateKey** (PEM header: BEGIN RSA PRIVATE KEY) -- PKCS#8 PrivateKeyInfo* (PEM header: BEGIN PRIVATE KEY) - - -## Creating the First User Key - -When NetBox is first installed, it contains no encryption keys. Before it can store secrets, a user (typically the superuser) must create a user key. This can be done by navigating to Profile > User Key. - -To create a user key, you can either generate a new RSA key pair, or upload the public key belonging to a pair you already have. If generating a new key pair, **you must save the private key** locally before saving your new user key. Once your user key has been created, its public key will be displayed under your profile. - -When the first user key is created in NetBox, a random master encryption key is generated automatically. This key is then encrypted using the public key provided and stored as part of your user key. **The master key cannot be recovered** without your private key. - -Once a user key has been assigned an encrypted copy of the master key, it is considered activated and can now be used to encrypt and decrypt secrets. - -## Creating Additional User Keys - -Any user can create his or her user key by generating or uploading a public RSA key. However, a user key cannot be used to encrypt or decrypt secrets until it has been activated with an encrypted copy of the master key. - -Only an administrator with an active user key can activate other user keys. To do so, access the NetBox admin UI and navigate to Secrets > User Keys. Select the user key(s) to be activated, and select "activate selected user keys" from the actions dropdown. You will need to provide your private key in order to decrypt the master key. A copy of the master key is then encrypted using the public key associated with the user key being activated. diff --git a/docs/plugins/development.md b/docs/plugins/development.md index f008da2fb..007d8434f 100644 --- a/docs/plugins/development.md +++ b/docs/plugins/development.md @@ -48,7 +48,7 @@ The plugin source directory contains all of the actual Python code and other res ### Create setup.py -`setup.py` is the [setup script](https://docs.python.org/3.6/distutils/setupscript.html) we'll use to install our plugin once it's finished. The primary function of this script is to call the setuptools library's `setup()` function to create a Python distribution package. We can pass a number of keyword arguments to inform the package creation as well as to provide metadata about the plugin. An example `setup.py` is below: +`setup.py` is the [setup script](https://docs.python.org/3.7/distutils/setupscript.html) we'll use to install our plugin once it's finished. The primary function of this script is to call the setuptools library's `setup()` function to create a Python distribution package. We can pass a number of keyword arguments to inform the package creation as well as to provide metadata about the plugin. An example `setup.py` is below: ```python from setuptools import find_packages, setup @@ -113,7 +113,6 @@ NetBox looks for the `config` variable within a plugin's `__init__.py` to load i | `min_version` | Minimum version of NetBox with which the plugin is compatible | | `max_version` | Maximum version of NetBox with which the plugin is compatible | | `middleware` | A list of middleware classes to append after NetBox's build-in middleware | -| `caching_config` | Plugin-specific cache configuration | `template_extensions` | The dotted path to the list of template extension classes (default: `template_content.template_extensions`) | | `menu_items` | The dotted path to the list of menu items provided by the plugin (default: `navigation.menu_items`) | @@ -386,30 +385,30 @@ class SiteAnimalCount(PluginTemplateExtension): template_extensions = [SiteAnimalCount] ``` -## Caching Configuration +## Background Tasks -By default, all query operations within a plugin are cached. To change this, define a caching configuration under the PluginConfig class' `caching_config` attribute. All configuration keys will be applied within the context of the plugin; there is no need to include the plugin name. An example configuration is below: +By default, Netbox provides 3 differents [RQ](https://python-rq.org/) queues to run background jobs : *high*, *default* and *low*. +These 3 core queues can be used out-of-the-box by plugins to define background tasks. + +Plugins can also define dedicated queues. These queues can be configured under the PluginConfig class `queues` attribute. An example configuration +is below: ```python class MyPluginConfig(PluginConfig): + name = 'myplugin' ... - caching_config = { - 'foo': { - 'ops': 'get', - 'timeout': 60 * 15, - }, - '*': { - 'ops': 'all', - } - } + queues = [ + 'queue1', + 'queue2', + 'queue-whatever-the-name' + ] ``` -To disable caching for your plugin entirely, set: +The PluginConfig above creates 3 queues with the following names: *myplugin.queue1*, *myplugin.queue2*, *myplugin.queue-whatever-the-name*. +As you can see, the queue's name is always preprended with the plugin's name, to avoid any name clashes between different plugins. + +In case you create dedicated queues for your plugin, it is strongly advised to also create a dedicated RQ worker instance. This instance should only listen to the queues defined in your plugin - to avoid impact between your background tasks and netbox internal tasks. -```python -caching_config = { - '*': None -} ``` - -See the [django-cacheops](https://github.com/Suor/django-cacheops) documentation for more detail on configuring caching. +python manage.py rqworker myplugin.queue1 myplugin.queue2 myplugin.queue-whatever-the-name +``` diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index f7f6f36e9..247ba3e1d 120000 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -1 +1 @@ -version-2.11.md \ No newline at end of file +version-3.0.md \ No newline at end of file diff --git a/docs/release-notes/version-2.1.md b/docs/release-notes/version-2.1.md index e5fa41d82..d4804661f 100644 --- a/docs/release-notes/version-2.1.md +++ b/docs/release-notes/version-2.1.md @@ -121,7 +121,7 @@ A new API endpoint has been added at `/api/ipam/prefixes//available-ips/`. A #### NAPALM Integration ([#1348](https://github.com/netbox-community/netbox/issues/1348)) -The [NAPALM automation](https://napalm-automation.net/) library provides an abstracted interface for pulling live data (e.g. uptime, software version, running config, LLDP neighbors, etc.) from network devices. The NetBox API has been extended to support executing read-only NAPALM methods on devices defined in NetBox. To enable this functionality, ensure that NAPALM has been installed (`pip install napalm`) and the `NETBOX_USERNAME` and `NETBOX_PASSWORD` [configuration parameters](https://netbox.readthedocs.io/en/stable/configuration/optional-settings/#netbox_username) have been set in configuration.py. +The [NAPALM automation](https://github.com/napalm-automation/napalm) library provides an abstracted interface for pulling live data (e.g. uptime, software version, running config, LLDP neighbors, etc.) from network devices. The NetBox API has been extended to support executing read-only NAPALM methods on devices defined in NetBox. To enable this functionality, ensure that NAPALM has been installed (`pip install napalm`) and the `NETBOX_USERNAME` and `NETBOX_PASSWORD` [configuration parameters](https://netbox.readthedocs.io/en/stable/configuration/optional-settings/#netbox_username) have been set in configuration.py. ### Enhancements diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index e8228920d..7e9e8fea3 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -218,7 +218,7 @@ #### Custom Scripts ([#3415](https://github.com/netbox-community/netbox/issues/3415)) -Custom scripts allow for the execution of arbitrary code via the NetBox UI. They can be used to automatically create, manipulate, or clean up objects or perform other tasks within NetBox. Scripts are defined as Python files which contain one or more subclasses of `extras.scripts.Script`. Variable fields can be defined within scripts, which render as form fields within the web UI to prompt the user for input data. Scripts are executed and information is logged via the web UI. Please see [the docs](https://netbox.readthedocs.io/en/stable/additional-features/custom-scripts/) for more detail. +Custom scripts allow for the execution of arbitrary code via the NetBox UI. They can be used to automatically create, manipulate, or clean up objects or perform other tasks within NetBox. Scripts are defined as Python files which contain one or more subclasses of `extras.scripts.Script`. Variable fields can be defined within scripts, which render as form fields within the web UI to prompt the user for input data. Scripts are executed and information is logged via the web UI. Please see [the docs](https://netbox.readthedocs.io/en/stable/customization/custom-scripts/) for more detail. Note: There are currently no API endpoints for this feature. These are planned for the upcoming v2.7 release. diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md new file mode 100644 index 000000000..93b8ae565 --- /dev/null +++ b/docs/release-notes/version-3.0.md @@ -0,0 +1,250 @@ +# NetBox v3.0 + +## v3.0.0 (2021-08-30) + +!!! warning "Existing Deployments Must Upgrade from v2.11" + Upgrading an existing NetBox deployment to version 3.0 **must** be done from version 2.11.0 or later. If attempting to upgrade a deployment of NetBox v2.10 or earlier, first upgrade to a NetBox v2.11 release, and then upgrade from v2.11 to v3.0. This will avoid any problems with the database migration optimizations implemented in version 3.0. (This is not necessary for _new_ installations.) + +### Breaking Changes + +* Python 3.6 is no longer supported. NetBox v3.0 supports Python 3.7, 3.8, and 3.9. +* The secrets functionality present in prior releases of NetBox has been removed. The NetBox maintainers strongly recommend the adoption of [Hashicorp Vault](https://github.com/hashicorp/vault) in place of this feature. Development of a NetBox plugin to replace the legacy secrets functionality is also underway. +* The default CSV export format for all objects now includes all available data from the object list. Additionally, the CSV headers now use human-friendly titles rather than raw field names. If backward compatibility with the old format is desired, export templates can be written to reproduce it. +* The `invalidate` management command (which clears cached database queries) is no longer needed and has been removed (see [#6639](https://github.com/netbox-community/netbox/issues/6639)). +* Support for queryset caching configuration (`caching_config`) has been removed from the plugins API (see [#6639](https://github.com/netbox-community/netbox/issues/6639)). +* The `cacheops_*` metrics have been removed from the Prometheus exporter (see [#6639](https://github.com/netbox-community/netbox/issues/6639)). +* The `display_field` keyword argument has been removed from custom script ObjectVar and MultiObjectVar fields. These widgets will use the `display` value provided by the REST API. +* The deprecated `display_name` field has been removed from all REST API serializers. (API clients should reference the `display` field instead.) +* The redundant REST API endpoints for console, power, and interface connections have been removed. The same data can be retrieved by querying the respective model endpoints with the `?connected=True` filter applied. + +### New Features + +#### Updated User Interface ([#5893](https://github.com/netbox-community/netbox/issues/5893)) + +The NetBox user interface has been completely overhauled with a fresh new look! Beyond the cosmetic improvements, this initiative has allowed us to modernize the entire front end, upgrading from Bootstrap 3 to Bootstrap 5, and eliminating dependencies on outdated libraries such as jQuery and jQuery-UI. The new user interface also features a dark mode option. + +![NetBox v3.0 user interface](../media/release-notes/netbox30_ui.png) + +A huge thank you to NetBox maintainer [Matt Love](https://github.com/thatmattlove) for his tremendous work on this! + +#### GraphQL API ([#2007](https://github.com/netbox-community/netbox/issues/2007)) + +A new [GraphQL API](https://graphql.org/) has been added to complement NetBox's REST API. GraphQL allows the client to specify which fields of the available data to return in each request. NetBox's implementation, which employs [Graphene](https://graphene-python.org/), also includes a user-friendly query interface known as GraphiQL. + +Here's an example GraphQL request: + +``` +{ + circuit_list { + cid + provider { + name + } + termination_a { + id + } + termination_z { + id + } + } +} +``` + +And the response: + +``` +{ + "data": { + "circuit_list": [ + { + "cid": "1002840283", + "provider": { + "name": "CenturyLink" + }, + "termination_a": null, + "termination_z": { + "id": "23" + } + }, +... +``` + +All GraphQL requests are made at the `/graphql` URL (which also serves the GraphiQL UI). The API is currently read-only, however users who wish to disable it until needed can do so by setting the `GRAPHQL_ENABLED` configuration parameter to False. For more detail on NetBox's GraphQL implementation, see [the GraphQL API documentation](../graphql-api/overview.md). + +#### IP Ranges ([#834](https://github.com/netbox-community/netbox/issues/834)) + +NetBox now supports modeling arbitrary IP ranges, which are defined by specifying a starting and ending IP address (e.g. to denote DHCP pools). Similar to prefixes, each IP range may optionally be assigned to a VRF and/or tenant, and can be assigned a functional role. An IP range must be assigned a status of active, reserved, or deprecated. The REST API implementation for this model also includes an "available IPs" endpoint which functions similarly to the endpoint for prefixes. + +More information about IP ranges is available [in the documentation](../models/ipam/iprange.md). + +#### Custom Model Validation ([#5963](https://github.com/netbox-community/netbox/issues/5963)) + +This release introduces the [`CUSTOM_VALIDATORS`](../configuration/optional-settings.md#custom_validators) configuration parameter, which allows administrators to map NetBox models to custom validator classes to enforce custom validation logic. For example, the following configuration requires every site to have a name of at least ten characters and a description: + +```python +from extras.validators import CustomValidator + +CUSTOM_VALIDATORS = { + 'dcim.site': ( + CustomValidator({ + 'name': { + 'min_length': 10, + }, + 'description': { + 'required': True, + } + }), + ) +} +``` + +CustomValidator can also be subclassed to enforce more complex logic by overriding its `validate()` method. See the [custom validation](../customization/custom-validation.md) documentation for more details. + +#### SVG Cable Traces ([#6000](https://github.com/netbox-community/netbox/issues/6000)) + +Cable trace diagrams are now rendered as atomic SVG images, similar to rack elevations. These images are embedded in the UI and can be easily downloaded for use outside NetBox. SVG images can also be generated directly through the REST API, by specifying SVG as the render format for the `trace` endpoint on a cable termination: + +```no-highlight +GET /api/dcim/interfaces/>/trace/?render=svg +``` + +The width of the rendered image in pixels may optionally be specified by appending the `&width=` parameter to the request. The default width is 400px. + +#### New Views for Models Previously Under the Admin UI ([#6466](https://github.com/netbox-community/netbox/issues/6466)) + +New UI views have been introduced to manage the following models: + +* Custom fields +* Custom links +* Export templates +* Webhooks + +These models were previously managed under the admin section of the UI. Moving them to dedicated views ensures a more consistent and convenient user experience. + +#### REST API Token Provisioning ([#5264](https://github.com/netbox-community/netbox/issues/5264)) + +The new REST API endpoint `/api/users/tokens/` has been added, which includes a child endpoint for provisioning new REST API tokens using a username and password. This allows a user to gain REST API access without needing to first create a token via the web UI. + +``` +$ curl -X POST \ +-H "Content-Type: application/json" \ +-H "Accept: application/json; indent=4" \ +https://netbox/api/users/tokens/provision/ \ +--data '{ + "username": "hankhill", + "password: "I<3C3H8", +}' +``` + +If the supplied credentials are valid, NetBox will create and return a new token for the user. + +#### New Housekeeping Command ([#6590](https://github.com/netbox-community/netbox/issues/6590)) + +A new management command has been added: `manage.py housekeeping`. This command is intended to be run nightly via a system cron job. It performs the following tasks: + +* Clear expired authentication sessions from the database +* Delete change log records which have surpassed the configured retention period (if configured) +* Check for new NetBox releases (if enabled) + +A convenience script for calling this command via an automated scheduler has been included at `/contrib/netbox-housekeeping.sh`. Please see the [housekeeping documentation](../administration/housekeeping.md) for further details. + +#### Custom Queue Support for Plugins ([#6651](https://github.com/netbox-community/netbox/issues/6651)) + +NetBox uses Redis and Django-RQ for background task queuing. Whereas previous releases employed only a single default queue, NetBox now provides a high-, medium- (default), and low-priority queue for use by plugins. (These will also likely be used internally as new functionality is added in future releases.) + +Plugins can also now create their own custom queues by defining a `queues` list within their PluginConfig class: + +```python +class MyPluginConfig(PluginConfig): + name = 'myplugin' + ... + queues = [ + 'queue1', + 'queue2', + 'queue-whatever-the-name' + ] +``` + +Note that NetBox's `rqworker` process will _not_ service custom queues by default, since it has no way to infer the priority of each queue. Plugin authors should be diligent in including instructions for proper worker configuration in their plugin's documentation. + +### Enhancements + +* [#2434](https://github.com/netbox-community/netbox/issues/2434) - Add option to assign IP address upon creating a new interface +* [#3665](https://github.com/netbox-community/netbox/issues/3665) - Enable rendering export templates via REST API +* [#3682](https://github.com/netbox-community/netbox/issues/3682) - Add `color` field to front and rear ports +* [#4609](https://github.com/netbox-community/netbox/issues/4609) - Allow marking prefixes as fully utilized +* [#5203](https://github.com/netbox-community/netbox/issues/5203) - Remember user preference when toggling display of device images in rack elevations +* [#5806](https://github.com/netbox-community/netbox/issues/5806) - Add kilometer and mile as choices for cable length unit +* [#6154](https://github.com/netbox-community/netbox/issues/6154) - Allow decimal values for cable lengths +* [#6328](https://github.com/netbox-community/netbox/issues/6328) - Build and serve documentation locally + +### Bug Fixes (from v3.2-beta2) + +* [#6977](https://github.com/netbox-community/netbox/issues/6977) - Truncate global search dropdown on small screens +* [#6979](https://github.com/netbox-community/netbox/issues/6979) - Hide "create & add another" button for circuit terminations +* [#6982](https://github.com/netbox-community/netbox/issues/6982) - Fix styling of empty dropdown list under dark mode +* [#6996](https://github.com/netbox-community/netbox/issues/6996) - Global search bar should be full width on mobile +* [#7001](https://github.com/netbox-community/netbox/issues/7001) - Fix page focus on load +* [#7034](https://github.com/netbox-community/netbox/issues/7034) - Fix toggling of VLAN group scope selector fields +* [#7045](https://github.com/netbox-community/netbox/issues/7045) - Fix navigation menu rendering under Chrome + +### Other Changes + +* [#5223](https://github.com/netbox-community/netbox/issues/5223) - Remove the console/power/interface connections REST API endpoints +* [#5278](https://github.com/netbox-community/netbox/issues/5278) - Remove the secrets functionality from NetBox core +* [#5532](https://github.com/netbox-community/netbox/issues/5532) - Drop support for Python 3.6 +* [#5994](https://github.com/netbox-community/netbox/issues/5994) - Drop support for `display_field` argument on ObjectVar +* [#6068](https://github.com/netbox-community/netbox/issues/6068) - Drop support for legacy static CSV export +* [#6338](https://github.com/netbox-community/netbox/issues/6338) - Decimal fields are no longer coerced to strings in REST API +* [#6471](https://github.com/netbox-community/netbox/issues/6471) - Optimize database migrations +* [#6639](https://github.com/netbox-community/netbox/issues/6639) - Drop support for queryset caching (django-cacheops) +* [#6713](https://github.com/netbox-community/netbox/issues/6713) - Checking for new releases is now done as part of the housekeeping routine +* [#6767](https://github.com/netbox-community/netbox/issues/6767) - Add support for Python 3.9 + +### Configuration Changes + +* The `CACHE_TIMEOUT` configuration parameter has been removed. +* The `RELEASE_CHECK_TIMEOUT` configuration parameter has been removed. + +### REST API Changes + +* Removed all endpoints related to the secrets functionality: + * `/api/secrets/generate-rsa-key-pair/` + * `/api/secrets/get-session-key/` + * `/api/secrets/secrets/` + * `/api/secrets/secret-roles/` +* Removed the following "connections" endpoints: + * `/api/dcim/console-connections/` + * `/api/dcim/power-connections/` + * `/api/dcim/interface-connections/` +* Added the `/api/ipam/ip-ranges/` endpoint +* Added the `/api/users/tokens/` endpoint + * The `provision/` child endpoint can be used to provision new REST API tokens by supplying a valid username and password +* dcim.Cable + * `length` is now a decimal value +* dcim.Device + * Removed the `display_name` attribute (use `display` instead) +* dcim.DeviceType + * Removed the `display_name` attribute (use `display` instead) +* dcim.FrontPort + * Added `color` field +* dcim.FrontPortTemplate + * Added `color` field +* dcim.Rack + * Removed the `display_name` attribute (use `display` instead) +* dcim.RearPort + * Added `color` field +* dcim.RearPortTemplate + * Added `color` field +* dcim.Site + * `latitude` and `longitude` are now decimal fields rather than strings +* extras.ContentType + * Removed the `display_name` attribute (use `display` instead) +* ipam.Prefix + * Added the `mark_utilized` boolean field +* ipam.VLAN + * Removed the `display_name` attribute (use `display` instead) +* ipam.VRF + * Removed the `display_name` attribute (use `display` instead) +* virtualization.VirtualMachine + * `vcpus` is now a decimal field rather than a string diff --git a/docs/requirements.txt b/docs/requirements.txt index 18b55d37b..c8726f8e6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,7 @@ +# File inclusion plugin for Python-Markdown +# https://github.com/cmacmackin/markdown-include +markdown-include + +# MkDocs Material theme (for documentation build) +# https://github.com/squidfunk/mkdocs-material mkdocs-material -git+https://github.com/cmacmackin/markdown-include.git diff --git a/docs/rest-api/authentication.md b/docs/rest-api/authentication.md index 7fb789e0f..93c1ce303 100644 --- a/docs/rest-api/authentication.md +++ b/docs/rest-api/authentication.md @@ -11,7 +11,7 @@ An authentication token is attached to a request by setting the `Authorization` ``` $ curl -H "Authorization: Token $TOKEN" \ -H "Accept: application/json; indent=4" \ -http://netbox/api/dcim/sites/ +https://netbox/api/dcim/sites/ { "count": 10, "next": null, @@ -23,8 +23,46 @@ http://netbox/api/dcim/sites/ A token is not required for read-only operations which have been exempted from permissions enforcement (using the [`EXEMPT_VIEW_PERMISSIONS`](../configuration/optional-settings.md#exempt_view_permissions) configuration parameter). However, if a token _is_ required but not present in a request, the API will return a 403 (Forbidden) response: ``` -$ curl http://netbox/api/dcim/sites/ +$ curl https://netbox/api/dcim/sites/ { "detail": "Authentication credentials were not provided." } ``` + +## Initial Token Provisioning + +Ideally, each user should provision his or her own REST API token(s) via the web UI. However, you may encounter where a token must be created by a user via the REST API itself. NetBox provides a special endpoint to provision tokens using a valid username and password combination. + +To provision a token via the REST API, make a `POST` request to the `/api/users/tokens/provision/` endpoint: + +``` +$ curl -X POST \ +-H "Content-Type: application/json" \ +-H "Accept: application/json; indent=4" \ +https://netbox/api/users/tokens/provision/ \ +--data '{ + "username": "hankhill", + "password: "I<3C3H8", +}' +``` + +Note that we are _not_ passing an existing REST API token with this request. If the supplied credentials are valid, a new REST API token will be automatically created for the user. Note that the key will be automatically generated, and write ability will be enabled. + +```json +{ + "id": 6, + "url": "https://netbox/api/users/tokens/6/", + "display": "3c9cb9 (hankhill)", + "user": { + "id": 2, + "url": "https://netbox/api/users/users/2/", + "display": "hankhill", + "username": "hankhill" + }, + "created": "2021-06-11T20:09:13.339367Z", + "expires": null, + "key": "9fc9b897abec9ada2da6aec9dbc34596293c9cb9", + "write_enabled": true, + "description": "" +} +``` diff --git a/docs/rest-api/overview.md b/docs/rest-api/overview.md index 088286e22..6a9235438 100644 --- a/docs/rest-api/overview.md +++ b/docs/rest-api/overview.md @@ -67,7 +67,7 @@ Comprehensive, interactive documentation of all REST API endpoints is available ## Endpoint Hierarchy -NetBox's entire REST API is housed under the API root at `https:///api/`. The URL structure is divided at the root level by application: circuits, DCIM, extras, IPAM, plugins, secrets, tenancy, users, and virtualization. Within each application exists a separate path for each model. For example, the provider and circuit objects are located under the "circuits" application: +NetBox's entire REST API is housed under the API root at `https:///api/`. The URL structure is divided at the root level by application: circuits, DCIM, extras, IPAM, plugins, tenancy, users, and virtualization. Within each application exists a separate path for each model. For example, the provider and circuit objects are located under the "circuits" application: * `/api/circuits/providers/` * `/api/circuits/circuits/` diff --git a/docs/rest-api/working-with-secrets.md b/docs/rest-api/working-with-secrets.md deleted file mode 100644 index 5fbbf7355..000000000 --- a/docs/rest-api/working-with-secrets.md +++ /dev/null @@ -1,172 +0,0 @@ -# Working with Secrets - -As with most other objects, the REST API can be used to view, create, modify, and delete secrets. However, additional steps are needed to encrypt or decrypt secret data. - -## Generating a Session Key - -In order to encrypt or decrypt secret data, a session key must be attached to the API request. To generate a session key, send an authenticated request to the `/api/secrets/get-session-key/` endpoint with the private RSA key which matches your [UserKey](../core-functionality/secrets.md#user-keys). The private key must be POSTed with the name `private_key`. - -```no-highlight -$ curl -X POST http://netbox/api/secrets/get-session-key/ \ --H "Authorization: Token $TOKEN" \ --H "Accept: application/json; indent=4" \ ---data-urlencode "private_key@" -``` - -```json -{ - "session_key": "dyEnxlc9lnGzaOAV1dV/xqYPV63njIbdZYOgnAlGPHk=" -} -``` - -!!! note - To read the private key from a file, use the convention above. Alternatively, the private key can be read from an environment variable using `--data-urlencode "private_key=$PRIVATE_KEY"`. - -The request uses the provided private key to unlock your stored copy of the master key and generate a temporary session key, which can be attached in the `X-Session-Key` header of future API requests. - -## Retrieving Secrets - -A session key is not needed to retrieve unencrypted secrets: The secret is returned like any normal object with its `plaintext` field set to null. - -```no-highlight -$ curl http://netbox/api/secrets/secrets/2587/ \ --H "Authorization: Token $TOKEN" \ --H "Accept: application/json; indent=4" -``` - -```json -{ - "id": 2587, - "url": "http://netbox/api/secrets/secrets/2587/", - "device": { - "id": 1827, - "url": "http://netbox/api/dcim/devices/1827/", - "name": "MyTestDevice", - "display_name": "MyTestDevice" - }, - "role": { - "id": 1, - "url": "http://netbox/api/secrets/secret-roles/1/", - "name": "Login Credentials", - "slug": "login-creds" - }, - "name": "admin", - "plaintext": null, - "hash": "pbkdf2_sha256$1000$G6mMFe4FetZQ$f+0itZbAoUqW5pd8+NH8W5rdp/2QNLIBb+LGdt4OSKA=", - "tags": [], - "custom_fields": {}, - "created": "2017-03-21", - "last_updated": "2017-03-21T19:28:44.265582Z" -} -``` - -To decrypt a secret, we must include our session key in the `X-Session-Key` header when sending the `GET` request: - -```no-highlight -$ curl http://netbox/api/secrets/secrets/2587/ \ --H "Authorization: Token $TOKEN" \ --H "Accept: application/json; indent=4" \ --H "X-Session-Key: dyEnxlc9lnGzaOAV1dV/xqYPV63njIbdZYOgnAlGPHk=" -``` - -```json -{ - "id": 2587, - "url": "http://netbox/api/secrets/secrets/2587/", - "device": { - "id": 1827, - "url": "http://netbox/api/dcim/devices/1827/", - "name": "MyTestDevice", - "display_name": "MyTestDevice" - }, - "role": { - "id": 1, - "url": "http://netbox/api/secrets/secret-roles/1/", - "name": "Login Credentials", - "slug": "login-creds" - }, - "name": "admin", - "plaintext": "foobar", - "hash": "pbkdf2_sha256$1000$G6mMFe4FetZQ$f+0itZbAoUqW5pd8+NH8W5rdp/2QNLIBb+LGdt4OSKA=", - "tags": [], - "custom_fields": {}, - "created": "2017-03-21", - "last_updated": "2017-03-21T19:28:44.265582Z" -} -``` - -Multiple secrets within a list can be decrypted in this manner as well: - -```no-highlight -$ curl http://netbox/api/secrets/secrets/?limit=3 \ --H "Authorization: Token $TOKEN" \ --H "Accept: application/json; indent=4" \ --H "X-Session-Key: dyEnxlc9lnGzaOAV1dV/xqYPV63njIbdZYOgnAlGPHk=" -``` - -```json -{ - "count": 3482, - "next": "http://netbox/api/secrets/secrets/?limit=3&offset=3", - "previous": null, - "results": [ - { - "id": 2587, - "plaintext": "foobar", - ... - }, - { - "id": 2588, - "plaintext": "MyP@ssw0rd!", - ... - }, - { - "id": 2589, - "plaintext": "AnotherSecret!", - ... - }, - ] -} -``` - -## Creating and Updating Secrets - -Session keys are required when creating or modifying secrets. The secret's `plaintext` attribute is set to its non-encrypted value, and NetBox uses the session key to compute and store the encrypted value. - -```no-highlight -$ curl -X POST http://netbox/api/secrets/secrets/ \ --H "Content-Type: application/json" \ --H "Authorization: Token $TOKEN" \ --H "Accept: application/json; indent=4" \ --H "X-Session-Key: dyEnxlc9lnGzaOAV1dV/xqYPV63njIbdZYOgnAlGPHk=" \ ---data '{"device": 1827, "role": 1, "name": "backup", "plaintext": "Drowssap1"}' -``` - -```json -{ - "id": 6194, - "url": "http://netbox/api/secrets/secrets/9194/", - "device": { - "id": 1827, - "url": "http://netbox/api/dcim/devices/1827/", - "name": "device43", - "display_name": "device43" - }, - "role": { - "id": 1, - "url": "http://netbox/api/secrets/secret-roles/1/", - "name": "Login Credentials", - "slug": "login-creds" - }, - "name": "backup", - "plaintext": "Drowssap1", - "hash": "pbkdf2_sha256$1000$J9db8sI5vBrd$IK6nFXnFl+K+nR5/KY8RSDxU1skYL8G69T5N3jZxM7c=", - "tags": [], - "custom_fields": {}, - "created": "2020-08-05", - "last_updated": "2020-08-05T16:51:14.990506Z" -} -``` - -!!! note - Don't forget to include the `Content-Type: application/json` header when making a POST or PATCH request. diff --git a/mkdocs.yml b/mkdocs.yml index 16b345b96..f4c0cb137 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,5 @@ site_name: NetBox Documentation +site_dir: netbox/project-static/docs site_url: https://netbox.readthedocs.io/ repo_name: netbox-community/netbox repo_url: https://github.com/netbox-community/netbox @@ -6,9 +7,24 @@ python: install: - requirements: docs/requirements.txt theme: - name: material - icon: - repo: fontawesome/brands/github + name: material + icon: + repo: fontawesome/brands/github + palette: + - scheme: default + toggle: + icon: material/lightbulb-outline + name: Switch to Dark Mode + - scheme: slate + toggle: + icon: material/lightbulb + name: Switch to Light Mode +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/netbox-community/netbox + - icon: fontawesome/brands/slack + link: https://slack.netbox.dev extra_css: - extra.css markdown_extensions: @@ -47,20 +63,20 @@ nav: - Service Mapping: 'core-functionality/services.md' - Circuits: 'core-functionality/circuits.md' - Power Tracking: 'core-functionality/power.md' - - Secrets: 'core-functionality/secrets.md' - Tenancy: 'core-functionality/tenancy.md' + - Customization: + - Custom Fields: 'customization/custom-fields.md' + - Custom Validation: 'customization/custom-validation.md' + - Custom Links: 'customization/custom-links.md' + - Export Templates: 'customization/export-templates.md' + - Custom Scripts: 'customization/custom-scripts.md' + - Reports: 'customization/reports.md' - Additional Features: - - Caching: 'additional-features/caching.md' - Change Logging: 'additional-features/change-logging.md' - Context Data: 'models/extras/configcontext.md' - - Custom Fields: 'additional-features/custom-fields.md' - - Custom Links: 'additional-features/custom-links.md' - - Custom Scripts: 'additional-features/custom-scripts.md' - - Export Templates: 'additional-features/export-templates.md' - Journaling: 'additional-features/journaling.md' - NAPALM: 'additional-features/napalm.md' - Prometheus Metrics: 'additional-features/prometheus-metrics.md' - - Reports: 'additional-features/reports.md' - Tags: 'models/extras/tag.md' - Webhooks: 'additional-features/webhooks.md' - Plugins: @@ -68,13 +84,15 @@ nav: - Developing Plugins: 'plugins/development.md' - Administration: - Permissions: 'administration/permissions.md' + - Housekeeping: 'administration/housekeeping.md' - Replicating NetBox: 'administration/replicating-netbox.md' - NetBox Shell: 'administration/netbox-shell.md' - REST API: - Overview: 'rest-api/overview.md' - Filtering: 'rest-api/filtering.md' - Authentication: 'rest-api/authentication.md' - - Working with Secrets: 'rest-api/working-with-secrets.md' + - GraphQL API: + - Overview: 'graphql-api/overview.md' - Development: - Introduction: 'development/index.md' - Getting Started: 'development/getting-started.md' @@ -82,10 +100,13 @@ nav: - Models: 'development/models.md' - Adding Models: 'development/adding-models.md' - Extending Models: 'development/extending-models.md' + - Signals: 'development/signals.md' - Application Registry: 'development/application-registry.md' - User Preferences: 'development/user-preferences.md' + - Web UI: 'development/web-ui.md' - Release Checklist: 'development/release-checklist.md' - Release Notes: + - Version 3.0: 'release-notes/version-3.0.md' - Version 2.11: 'release-notes/version-2.11.md' - Version 2.10: 'release-notes/version-2.10.md' - Version 2.9: 'release-notes/version-2.9.md' diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 1b3eb3242..56cd46d4a 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext as _ from dcim.models import Region, Site, SiteGroup from extras.forms import ( - AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldModelForm, CustomFieldModelCSVForm, + AddRemoveTagsForm, CustomFieldModelBulkEditForm, CustomFieldModelFilterForm, CustomFieldModelForm, CustomFieldModelCSVForm, ) from extras.models import Tag from tenancy.forms import TenancyFilterForm, TenancyForm @@ -11,7 +11,7 @@ from tenancy.models import Tenant from utilities.forms import ( add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SelectSpeedWidget, SmallTextarea, SlugField, - StaticSelect2, StaticSelect2Multiple, TagFilterField, + StaticSelect, StaticSelectMultiple, TagFilterField, ) from .choices import CircuitStatusChoices from .models import * @@ -60,10 +60,12 @@ class ProviderCSVForm(CustomFieldModelCSVForm): class Meta: model = Provider - fields = Provider.csv_headers + fields = ( + 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', + ) -class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput @@ -102,24 +104,39 @@ class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdi ] -class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm): +class ProviderFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Provider + field_groups = [ + ['q', 'tag'], + ['region_id', 'site_group_id', 'site_id'], + ['asn'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' + ) + site_group_id = DynamicModelMultipleChoiceField( + queryset=SiteGroup.objects.all(), + required=False, + label=_('Site group'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), required=False, query_params={ - 'region_id': '$region_id' + 'region_id': '$region_id', + 'site_group_id': '$site_group_id', }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) asn = forms.IntegerField( required=False, @@ -166,7 +183,7 @@ class ProviderNetworkCSVForm(CustomFieldModelCSVForm): ] -class ProviderNetworkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class ProviderNetworkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=ProviderNetwork.objects.all(), widget=forms.MultipleHiddenInput @@ -190,17 +207,22 @@ class ProviderNetworkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField ] -class ProviderNetworkFilterForm(BootstrapMixin, CustomFieldFilterForm): +class ProviderNetworkFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = ProviderNetwork - field_order = ['q', 'provider_id'] + field_groups = ( + ('q', 'tag'), + ('provider_id',), + ) q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) provider_id = DynamicModelMultipleChoiceField( queryset=Provider.objects.all(), required=False, - label=_('Provider') + label=_('Provider'), + fetch_trigger='open' ) tag = TagFilterField(model) @@ -219,7 +241,7 @@ class CircuitTypeForm(BootstrapMixin, CustomFieldModelForm): ] -class CircuitTypeBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): +class CircuitTypeBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=CircuitType.objects.all(), widget=forms.MultipleHiddenInput @@ -238,7 +260,7 @@ class CircuitTypeCSVForm(CustomFieldModelCSVForm): class Meta: model = CircuitType - fields = CircuitType.csv_headers + fields = ('name', 'slug', 'description') help_texts = { 'name': 'Name of circuit type', } @@ -276,7 +298,7 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): 'commit_rate': "Committed rate", } widgets = { - 'status': StaticSelect2(), + 'status': StaticSelect(), 'install_date': DatePicker(), 'commit_rate': SelectSpeedWidget(), } @@ -312,7 +334,7 @@ class CircuitCSVForm(CustomFieldModelCSVForm): ] -class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput @@ -329,7 +351,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit choices=add_blank_choice(CircuitStatusChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) tenant = DynamicModelChoiceField( queryset=Tenant.objects.all(), @@ -354,25 +376,31 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit ] -class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): +class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Circuit - field_order = [ - 'q', 'type_id', 'provider_id', 'provider_network_id', 'status', 'region_id', 'site_id', 'tenant_group_id', 'tenant_id', - 'commit_rate', + field_groups = [ + ['q', 'tag'], + ['provider_id', 'provider_network_id'], + ['type_id', 'status', 'commit_rate'], + ['region_id', 'site_group_id', 'site_id'], + ['tenant_group_id', 'tenant_id'], ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) type_id = DynamicModelMultipleChoiceField( queryset=CircuitType.objects.all(), required=False, - label=_('Type') + label=_('Type'), + fetch_trigger='open' ) provider_id = DynamicModelMultipleChoiceField( queryset=Provider.objects.all(), required=False, - label=_('Provider') + label=_('Provider'), + fetch_trigger='open' ) provider_network_id = DynamicModelMultipleChoiceField( queryset=ProviderNetwork.objects.all(), @@ -380,25 +408,35 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm query_params={ 'provider_id': '$provider_id' }, - label=_('Provider network') + label=_('Provider network'), + fetch_trigger='open' ) status = forms.MultipleChoiceField( choices=CircuitStatusChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' + ) + site_group_id = DynamicModelMultipleChoiceField( + queryset=SiteGroup.objects.all(), + required=False, + label=_('Site group'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), required=False, query_params={ - 'region_id': '$region_id' + 'region_id': '$region_id', + 'site_group_id': '$site_group_id', }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) commit_rate = forms.IntegerField( required=False, diff --git a/netbox/secrets/api/__init__.py b/netbox/circuits/graphql/__init__.py similarity index 100% rename from netbox/secrets/api/__init__.py rename to netbox/circuits/graphql/__init__.py diff --git a/netbox/circuits/graphql/schema.py b/netbox/circuits/graphql/schema.py new file mode 100644 index 000000000..f65874239 --- /dev/null +++ b/netbox/circuits/graphql/schema.py @@ -0,0 +1,21 @@ +import graphene + +from netbox.graphql.fields import ObjectField, ObjectListField +from .types import * + + +class CircuitsQuery(graphene.ObjectType): + circuit = ObjectField(CircuitType) + circuit_list = ObjectListField(CircuitType) + + circuit_termination = ObjectField(CircuitTerminationType) + circuit_termination_list = ObjectListField(CircuitTerminationType) + + circuit_type = ObjectField(CircuitTypeType) + circuit_type_list = ObjectListField(CircuitTypeType) + + provider = ObjectField(ProviderType) + provider_list = ObjectListField(ProviderType) + + provider_network = ObjectField(ProviderNetworkType) + provider_network_list = ObjectListField(ProviderNetworkType) diff --git a/netbox/circuits/graphql/types.py b/netbox/circuits/graphql/types.py new file mode 100644 index 000000000..a6c28c4cd --- /dev/null +++ b/netbox/circuits/graphql/types.py @@ -0,0 +1,50 @@ +from circuits import filtersets, models +from netbox.graphql.types import ObjectType, OrganizationalObjectType, PrimaryObjectType + +__all__ = ( + 'CircuitTerminationType', + 'CircuitType', + 'CircuitTypeType', + 'ProviderType', + 'ProviderNetworkType', +) + + +class CircuitTerminationType(ObjectType): + + class Meta: + model = models.CircuitTermination + fields = '__all__' + filterset_class = filtersets.CircuitTerminationFilterSet + + +class CircuitType(PrimaryObjectType): + + class Meta: + model = models.Circuit + fields = '__all__' + filterset_class = filtersets.CircuitFilterSet + + +class CircuitTypeType(OrganizationalObjectType): + + class Meta: + model = models.CircuitType + fields = '__all__' + filterset_class = filtersets.CircuitTypeFilterSet + + +class ProviderType(PrimaryObjectType): + + class Meta: + model = models.Provider + fields = '__all__' + filterset_class = filtersets.ProviderFilterSet + + +class ProviderNetworkType(PrimaryObjectType): + + class Meta: + model = models.ProviderNetwork + fields = '__all__' + filterset_class = filtersets.ProviderNetworkFilterSet diff --git a/netbox/circuits/migrations/0001_initial.py b/netbox/circuits/migrations/0001_initial.py deleted file mode 100644 index dd4dc612b..000000000 --- a/netbox/circuits/migrations/0001_initial.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-06-22 18:21 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Circuit', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('cid', models.CharField(max_length=50, verbose_name=b'Circuit ID')), - ('install_date', models.DateField(blank=True, null=True, verbose_name=b'Date installed')), - ('port_speed', models.PositiveIntegerField(verbose_name=b'Port speed (Kbps)')), - ('commit_rate', models.PositiveIntegerField(blank=True, null=True, verbose_name=b'Commit rate (Kbps)')), - ('xconnect_id', models.CharField(blank=True, max_length=50, verbose_name=b'Cross-connect ID')), - ('pp_info', models.CharField(blank=True, max_length=100, verbose_name=b'Patch panel/port(s)')), - ('comments', models.TextField(blank=True)), - ], - options={ - 'ordering': ['provider', 'cid'], - }, - ), - migrations.CreateModel( - name='CircuitType', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, unique=True)), - ('slug', models.SlugField(unique=True)), - ], - options={ - 'ordering': ['name'], - }, - ), - migrations.CreateModel( - name='Provider', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('name', models.CharField(max_length=50, unique=True)), - ('slug', models.SlugField(unique=True)), - ('asn', models.PositiveIntegerField(blank=True, null=True, verbose_name=b'ASN')), - ('account', models.CharField(blank=True, max_length=30, verbose_name=b'Account number')), - ('portal_url', models.URLField(blank=True, verbose_name=b'Portal')), - ('noc_contact', models.TextField(blank=True, verbose_name=b'NOC contact')), - ('admin_contact', models.TextField(blank=True, verbose_name=b'Admin contact')), - ('comments', models.TextField(blank=True)), - ], - options={ - 'ordering': ['name'], - }, - ), - ] diff --git a/netbox/circuits/migrations/0001_squashed.py b/netbox/circuits/migrations/0001_squashed.py new file mode 100644 index 000000000..851f40a22 --- /dev/null +++ b/netbox/circuits/migrations/0001_squashed.py @@ -0,0 +1,107 @@ +import dcim.fields +import django.core.serializers.json +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + replaces = [ + ('circuits', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Circuit', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('cid', models.CharField(max_length=100)), + ('status', models.CharField(default='active', max_length=50)), + ('install_date', models.DateField(blank=True, null=True)), + ('commit_rate', models.PositiveIntegerField(blank=True, null=True)), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ], + options={ + 'ordering': ['provider', 'cid'], + }, + ), + migrations.CreateModel( + name='CircuitTermination', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('term_side', models.CharField(max_length=1)), + ('port_speed', models.PositiveIntegerField(blank=True, null=True)), + ('upstream_speed', models.PositiveIntegerField(blank=True, null=True)), + ('xconnect_id', models.CharField(blank=True, max_length=50)), + ('pp_info', models.CharField(blank=True, max_length=100)), + ('description', models.CharField(blank=True, max_length=200)), + ], + options={ + 'ordering': ['circuit', 'term_side'], + }, + ), + migrations.CreateModel( + name='CircuitType', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('description', models.CharField(blank=True, max_length=200)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='Provider', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('asn', dcim.fields.ASNField(blank=True, null=True)), + ('account', models.CharField(blank=True, max_length=30)), + ('portal_url', models.URLField(blank=True)), + ('noc_contact', models.TextField(blank=True)), + ('admin_contact', models.TextField(blank=True)), + ('comments', models.TextField(blank=True)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='ProviderNetwork', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='networks', to='circuits.provider')), + ], + options={ + 'ordering': ('provider', 'name'), + }, + ), + ] diff --git a/netbox/circuits/migrations/0002_auto_20160622_1821.py b/netbox/circuits/migrations/0002_auto_20160622_1821.py deleted file mode 100644 index 2d350b5f3..000000000 --- a/netbox/circuits/migrations/0002_auto_20160622_1821.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-06-22 18:21 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('dcim', '0001_initial'), - ('circuits', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='circuit', - name='interface', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='circuit', to='dcim.Interface'), - ), - migrations.AddField( - model_name='circuit', - name='provider', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.Provider'), - ), - migrations.AddField( - model_name='circuit', - name='site', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='dcim.Site'), - ), - migrations.AddField( - model_name='circuit', - name='type', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.CircuitType'), - ), - migrations.AlterUniqueTogether( - name='circuit', - unique_together=set([('provider', 'cid')]), - ), - ] diff --git a/netbox/circuits/migrations/0002_squashed_0029.py b/netbox/circuits/migrations/0002_squashed_0029.py new file mode 100644 index 000000000..11fcbd6e6 --- /dev/null +++ b/netbox/circuits/migrations/0002_squashed_0029.py @@ -0,0 +1,129 @@ +from django.db import migrations, models +import django.db.models.deletion +import taggit.managers + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0001_initial'), + ('contenttypes', '0002_remove_content_type_name'), + ('circuits', '0001_initial'), + ('extras', '0001_initial'), + ('tenancy', '0001_initial'), + ] + + replaces = [ + ('circuits', '0002_auto_20160622_1821'), + ('circuits', '0003_provider_32bit_asn_support'), + ('circuits', '0004_circuit_add_tenant'), + ('circuits', '0005_circuit_add_upstream_speed'), + ('circuits', '0006_terminations'), + ('circuits', '0007_circuit_add_description'), + ('circuits', '0008_circuittermination_interface_protect_on_delete'), + ('circuits', '0009_unicode_literals'), + ('circuits', '0010_circuit_status'), + ('circuits', '0011_tags'), + ('circuits', '0012_change_logging'), + ('circuits', '0013_cables'), + ('circuits', '0014_circuittermination_description'), + ('circuits', '0015_custom_tag_models'), + ('circuits', '0016_3569_circuit_fields'), + ('circuits', '0017_circuittype_description'), + ('circuits', '0018_standardize_description'), + ('circuits', '0019_nullbooleanfield_to_booleanfield'), + ('circuits', '0020_custom_field_data'), + ('circuits', '0021_cache_cable_peer'), + ('circuits', '0022_cablepath'), + ('circuits', '0023_circuittermination_port_speed_optional'), + ('circuits', '0024_standardize_name_length'), + ('circuits', '0025_standardize_models'), + ('circuits', '0026_mark_connected'), + ('circuits', '0027_providernetwork'), + ('circuits', '0028_cache_circuit_terminations'), + ('circuits', '0029_circuit_tracing'), + ] + + operations = [ + migrations.AddField( + model_name='providernetwork', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='provider', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='circuittermination', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='circuittermination', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='circuittermination', + name='circuit', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='circuits.circuit'), + ), + migrations.AddField( + model_name='circuittermination', + name='provider_network', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='circuits.providernetwork'), + ), + migrations.AddField( + model_name='circuittermination', + name='site', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='dcim.site'), + ), + migrations.AddField( + model_name='circuit', + name='provider', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.provider'), + ), + migrations.AddField( + model_name='circuit', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='circuit', + name='tenant', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='tenancy.tenant'), + ), + migrations.AddField( + model_name='circuit', + name='termination_a', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'), + ), + migrations.AddField( + model_name='circuit', + name='termination_z', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'), + ), + migrations.AddField( + model_name='circuit', + name='type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.circuittype'), + ), + migrations.AddConstraint( + model_name='providernetwork', + constraint=models.UniqueConstraint(fields=('provider', 'name'), name='circuits_providernetwork_provider_name'), + ), + migrations.AlterUniqueTogether( + name='providernetwork', + unique_together={('provider', 'name')}, + ), + migrations.AlterUniqueTogether( + name='circuittermination', + unique_together={('circuit', 'term_side')}, + ), + migrations.AlterUniqueTogether( + name='circuit', + unique_together={('provider', 'cid')}, + ), + ] diff --git a/netbox/circuits/migrations/0003_provider_32bit_asn_support.py b/netbox/circuits/migrations/0003_provider_32bit_asn_support.py deleted file mode 100644 index e1e9adab9..000000000 --- a/netbox/circuits/migrations/0003_provider_32bit_asn_support.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-13 19:24 -import dcim.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0002_auto_20160622_1821'), - ] - - operations = [ - migrations.AlterField( - model_name='provider', - name='asn', - field=dcim.fields.ASNField(blank=True, null=True, verbose_name=b'ASN'), - ), - ] diff --git a/netbox/circuits/migrations/0004_circuit_add_tenant.py b/netbox/circuits/migrations/0004_circuit_add_tenant.py deleted file mode 100644 index de81f21eb..000000000 --- a/netbox/circuits/migrations/0004_circuit_add_tenant.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-07-26 21:59 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('tenancy', '0001_initial'), - ('circuits', '0003_provider_32bit_asn_support'), - ] - - operations = [ - migrations.AddField( - model_name='circuit', - name='tenant', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='tenancy.Tenant'), - ), - ] diff --git a/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py b/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py deleted file mode 100644 index 51b09ad4c..000000000 --- a/netbox/circuits/migrations/0005_circuit_add_upstream_speed.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-08-08 20:24 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0004_circuit_add_tenant'), - ] - - operations = [ - migrations.AddField( - model_name='circuit', - name='upstream_speed', - field=models.PositiveIntegerField(blank=True, help_text=b'Upstream speed, if different from port speed', null=True, verbose_name=b'Upstream speed (Kbps)'), - ), - ] diff --git a/netbox/circuits/migrations/0006_terminations.py b/netbox/circuits/migrations/0006_terminations.py deleted file mode 100644 index 1a083c3da..000000000 --- a/netbox/circuits/migrations/0006_terminations.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-12-13 16:30 -from django.db import migrations, models -import django.db.models.deletion - - -def circuits_to_terms(apps, schema_editor): - Circuit = apps.get_model('circuits', 'Circuit') - CircuitTermination = apps.get_model('circuits', 'CircuitTermination') - for c in Circuit.objects.all(): - CircuitTermination( - circuit=c, - term_side=b'A', - site=c.site, - interface=c.interface, - port_speed=c.port_speed, - upstream_speed=c.upstream_speed, - xconnect_id=c.xconnect_id, - pp_info=c.pp_info, - ).save() - - -def terms_to_circuits(apps, schema_editor): - CircuitTermination = apps.get_model('circuits', 'CircuitTermination') - for ct in CircuitTermination.objects.filter(term_side='A'): - c = ct.circuit - c.site = ct.site - c.interface = ct.interface - c.port_speed = ct.port_speed - c.upstream_speed = ct.upstream_speed - c.xconnect_id = ct.xconnect_id - c.pp_info = ct.pp_info - c.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0022_color_names_to_rgb'), - ('circuits', '0005_circuit_add_upstream_speed'), - ] - - operations = [ - migrations.CreateModel( - name='CircuitTermination', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('term_side', models.CharField(choices=[(b'A', b'A'), (b'Z', b'Z')], max_length=1, - verbose_name='Termination')), - ('port_speed', models.PositiveIntegerField(verbose_name=b'Port speed (Kbps)')), - ('upstream_speed', - models.PositiveIntegerField(blank=True, help_text=b'Upstream speed, if different from port speed', - null=True, verbose_name=b'Upstream speed (Kbps)')), - ('xconnect_id', models.CharField(blank=True, max_length=50, verbose_name=b'Cross-connect ID')), - ('pp_info', models.CharField(blank=True, max_length=100, verbose_name=b'Patch panel/port(s)')), - ('circuit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', - to='circuits.Circuit')), - ('interface', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - related_name='circuit_termination', to='dcim.Interface')), - ('site', - models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', - to='dcim.Site')), - ], - options={ - 'ordering': ['circuit', 'term_side'], - }, - ), - migrations.AlterUniqueTogether( - name='circuittermination', - unique_together=set([('circuit', 'term_side')]), - ), - migrations.RunPython(circuits_to_terms, terms_to_circuits), - migrations.RemoveField( - model_name='circuit', - name='interface', - ), - migrations.RemoveField( - model_name='circuit', - name='port_speed', - ), - migrations.RemoveField( - model_name='circuit', - name='pp_info', - ), - migrations.RemoveField( - model_name='circuit', - name='site', - ), - migrations.RemoveField( - model_name='circuit', - name='upstream_speed', - ), - migrations.RemoveField( - model_name='circuit', - name='xconnect_id', - ), - ] diff --git a/netbox/circuits/migrations/0007_circuit_add_description.py b/netbox/circuits/migrations/0007_circuit_add_description.py deleted file mode 100644 index 238cb07dd..000000000 --- a/netbox/circuits/migrations/0007_circuit_add_description.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-01-17 20:08 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0006_terminations'), - ] - - operations = [ - migrations.AddField( - model_name='circuit', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - ] diff --git a/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py b/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py deleted file mode 100644 index b7ccafd26..000000000 --- a/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-04-19 17:17 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0007_circuit_add_description'), - ] - - operations = [ - migrations.AlterField( - model_name='circuittermination', - name='interface', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_termination', to='dcim.Interface'), - ), - ] diff --git a/netbox/circuits/migrations/0009_unicode_literals.py b/netbox/circuits/migrations/0009_unicode_literals.py deleted file mode 100644 index 0cc58fea9..000000000 --- a/netbox/circuits/migrations/0009_unicode_literals.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-05-24 15:34 -import dcim.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0008_circuittermination_interface_protect_on_delete'), - ] - - operations = [ - migrations.AlterField( - model_name='circuit', - name='cid', - field=models.CharField(max_length=50, verbose_name='Circuit ID'), - ), - migrations.AlterField( - model_name='circuit', - name='commit_rate', - field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)'), - ), - migrations.AlterField( - model_name='circuit', - name='install_date', - field=models.DateField(blank=True, null=True, verbose_name='Date installed'), - ), - migrations.AlterField( - model_name='circuittermination', - name='port_speed', - field=models.PositiveIntegerField(verbose_name='Port speed (Kbps)'), - ), - migrations.AlterField( - model_name='circuittermination', - name='pp_info', - field=models.CharField(blank=True, max_length=100, verbose_name='Patch panel/port(s)'), - ), - migrations.AlterField( - model_name='circuittermination', - name='term_side', - field=models.CharField(choices=[('A', 'A'), ('Z', 'Z')], max_length=1, verbose_name='Termination'), - ), - migrations.AlterField( - model_name='circuittermination', - name='upstream_speed', - field=models.PositiveIntegerField(blank=True, help_text='Upstream speed, if different from port speed', null=True, verbose_name='Upstream speed (Kbps)'), - ), - migrations.AlterField( - model_name='circuittermination', - name='xconnect_id', - field=models.CharField(blank=True, max_length=50, verbose_name='Cross-connect ID'), - ), - migrations.AlterField( - model_name='provider', - name='account', - field=models.CharField(blank=True, max_length=30, verbose_name='Account number'), - ), - migrations.AlterField( - model_name='provider', - name='admin_contact', - field=models.TextField(blank=True, verbose_name='Admin contact'), - ), - migrations.AlterField( - model_name='provider', - name='asn', - field=dcim.fields.ASNField(blank=True, null=True, verbose_name='ASN'), - ), - migrations.AlterField( - model_name='provider', - name='noc_contact', - field=models.TextField(blank=True, verbose_name='NOC contact'), - ), - migrations.AlterField( - model_name='provider', - name='portal_url', - field=models.URLField(blank=True, verbose_name='Portal'), - ), - ] diff --git a/netbox/circuits/migrations/0010_circuit_status.py b/netbox/circuits/migrations/0010_circuit_status.py deleted file mode 100644 index 675a0c1fb..000000000 --- a/netbox/circuits/migrations/0010_circuit_status.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.9 on 2018-02-06 18:48 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0009_unicode_literals'), - ] - - operations = [ - migrations.AddField( - model_name='circuit', - name='status', - field=models.PositiveSmallIntegerField(choices=[[2, 'Planned'], [3, 'Provisioning'], [1, 'Active'], [4, 'Offline'], [0, 'Deprovisioning'], [5, 'Decommissioned']], default=1), - ), - ] diff --git a/netbox/circuits/migrations/0011_tags.py b/netbox/circuits/migrations/0011_tags.py deleted file mode 100644 index 112436223..000000000 --- a/netbox/circuits/migrations/0011_tags.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.12 on 2018-05-22 19:04 -from django.db import migrations -import taggit.managers - - -class Migration(migrations.Migration): - - dependencies = [ - ('taggit', '0002_auto_20150616_2121'), - ('circuits', '0010_circuit_status'), - ] - - operations = [ - migrations.AddField( - model_name='circuit', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='provider', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - ] diff --git a/netbox/circuits/migrations/0012_change_logging.py b/netbox/circuits/migrations/0012_change_logging.py deleted file mode 100644 index c9a3ee41d..000000000 --- a/netbox/circuits/migrations/0012_change_logging.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.12 on 2018-06-13 17:14 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0011_tags'), - ] - - operations = [ - migrations.AddField( - model_name='circuittype', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='circuittype', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AlterField( - model_name='circuit', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AlterField( - model_name='circuit', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AlterField( - model_name='provider', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AlterField( - model_name='provider', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - ] diff --git a/netbox/circuits/migrations/0013_cables.py b/netbox/circuits/migrations/0013_cables.py deleted file mode 100644 index ec0284be0..000000000 --- a/netbox/circuits/migrations/0013_cables.py +++ /dev/null @@ -1,89 +0,0 @@ -import sys - -from django.db import migrations, models -import django.db.models.deletion - -CONNECTION_STATUS_CONNECTED = True - - -def circuit_terminations_to_cables(apps, schema_editor): - """ - Copy all existing CircuitTermination Interface associations as Cables - """ - ContentType = apps.get_model('contenttypes', 'ContentType') - CircuitTermination = apps.get_model('circuits', 'CircuitTermination') - Interface = apps.get_model('dcim', 'Interface') - Cable = apps.get_model('dcim', 'Cable') - - # Load content types - circuittermination_type = ContentType.objects.get_for_model(CircuitTermination) - interface_type = ContentType.objects.get_for_model(Interface) - - # Create a new Cable instance from each console connection - if 'test' not in sys.argv: - print("\n Adding circuit terminations... ", end='', flush=True) - for circuittermination in CircuitTermination.objects.filter(interface__isnull=False): - - # Create the new Cable - cable = Cable.objects.create( - termination_a_type=circuittermination_type, - termination_a_id=circuittermination.id, - termination_b_type=interface_type, - termination_b_id=circuittermination.interface_id, - status=CONNECTION_STATUS_CONNECTED - ) - - # Cache the Cable on its two termination points - CircuitTermination.objects.filter(pk=circuittermination.pk).update( - cable=cable, - connected_endpoint=circuittermination.interface, - connection_status=CONNECTION_STATUS_CONNECTED - ) - # Cache the connected Cable on the Interface - Interface.objects.filter(pk=circuittermination.interface_id).update( - cable=cable, - _connected_circuittermination=circuittermination, - connection_status=CONNECTION_STATUS_CONNECTED - ) - - cable_count = Cable.objects.filter(termination_a_type=circuittermination_type).count() - if 'test' not in sys.argv: - print("{} cables created".format(cable_count)) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('circuits', '0012_change_logging'), - ('dcim', '0066_cables'), - ] - - operations = [ - - # Add new CircuitTermination fields - migrations.AddField( - model_name='circuittermination', - name='connected_endpoint', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Interface'), - ), - migrations.AddField( - model_name='circuittermination', - name='connection_status', - field=models.NullBooleanField(), - ), - migrations.AddField( - model_name='circuittermination', - name='cable', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'), - ), - - # Copy CircuitTermination connections to Interfaces as Cables - migrations.RunPython(circuit_terminations_to_cables), - - # Remove interface field from CircuitTermination - migrations.RemoveField( - model_name='circuittermination', - name='interface', - ), - ] diff --git a/netbox/circuits/migrations/0014_circuittermination_description.py b/netbox/circuits/migrations/0014_circuittermination_description.py deleted file mode 100644 index 2b3070427..000000000 --- a/netbox/circuits/migrations/0014_circuittermination_description.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.3 on 2018-11-05 18:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0013_cables'), - ] - - operations = [ - migrations.AddField( - model_name='circuittermination', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - ] diff --git a/netbox/circuits/migrations/0015_custom_tag_models.py b/netbox/circuits/migrations/0015_custom_tag_models.py deleted file mode 100644 index 11bde72ff..000000000 --- a/netbox/circuits/migrations/0015_custom_tag_models.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.1.4 on 2019-02-20 06:56 - -from django.db import migrations -import taggit.managers - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0014_circuittermination_description'), - ('extras', '0019_tag_taggeditem'), - ] - - operations = [ - migrations.AlterField( - model_name='circuit', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='provider', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - ] diff --git a/netbox/circuits/migrations/0016_3569_circuit_fields.py b/netbox/circuits/migrations/0016_3569_circuit_fields.py deleted file mode 100644 index a65f72d61..000000000 --- a/netbox/circuits/migrations/0016_3569_circuit_fields.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.db import migrations, models - - -CIRCUIT_STATUS_CHOICES = ( - (0, 'deprovisioning'), - (1, 'active'), - (2, 'planned'), - (3, 'provisioning'), - (4, 'offline'), - (5, 'decommissioned') -) - - -def circuit_status_to_slug(apps, schema_editor): - Circuit = apps.get_model('circuits', 'Circuit') - for id, slug in CIRCUIT_STATUS_CHOICES: - Circuit.objects.filter(status=str(id)).update(status=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('circuits', '0015_custom_tag_models'), - ] - - operations = [ - - # Circuit.status - migrations.AlterField( - model_name='circuit', - name='status', - field=models.CharField(default='active', max_length=50), - ), - migrations.RunPython( - code=circuit_status_to_slug - ), - - ] diff --git a/netbox/circuits/migrations/0017_circuittype_description.py b/netbox/circuits/migrations/0017_circuittype_description.py deleted file mode 100644 index 4cb5591dd..000000000 --- a/netbox/circuits/migrations/0017_circuittype_description.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.6 on 2019-12-10 18:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0016_3569_circuit_fields'), - ] - - operations = [ - migrations.AddField( - model_name='circuittype', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - ] diff --git a/netbox/circuits/migrations/0018_standardize_description.py b/netbox/circuits/migrations/0018_standardize_description.py deleted file mode 100644 index a0a213e17..000000000 --- a/netbox/circuits/migrations/0018_standardize_description.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.0.3 on 2020-03-13 20:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0017_circuittype_description'), - ] - - operations = [ - migrations.AlterField( - model_name='circuit', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='circuittermination', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='circuittype', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - ] diff --git a/netbox/circuits/migrations/0019_nullbooleanfield_to_booleanfield.py b/netbox/circuits/migrations/0019_nullbooleanfield_to_booleanfield.py deleted file mode 100644 index c8e844284..000000000 --- a/netbox/circuits/migrations/0019_nullbooleanfield_to_booleanfield.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1b1 on 2020-07-16 15:55 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0018_standardize_description'), - ] - - operations = [ - migrations.AlterField( - model_name='circuittermination', - name='connection_status', - field=models.BooleanField(blank=True, null=True), - ), - ] diff --git a/netbox/circuits/migrations/0020_custom_field_data.py b/netbox/circuits/migrations/0020_custom_field_data.py deleted file mode 100644 index 97da9962c..000000000 --- a/netbox/circuits/migrations/0020_custom_field_data.py +++ /dev/null @@ -1,22 +0,0 @@ -import django.core.serializers.json -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0019_nullbooleanfield_to_booleanfield'), - ] - - operations = [ - migrations.AddField( - model_name='circuit', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='provider', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - ] diff --git a/netbox/circuits/migrations/0021_cache_cable_peer.py b/netbox/circuits/migrations/0021_cache_cable_peer.py deleted file mode 100644 index 630c3b4ec..000000000 --- a/netbox/circuits/migrations/0021_cache_cable_peer.py +++ /dev/null @@ -1,49 +0,0 @@ -import sys - -from django.db import migrations, models -import django.db.models.deletion - - -def cache_cable_peers(apps, schema_editor): - ContentType = apps.get_model('contenttypes', 'ContentType') - Cable = apps.get_model('dcim', 'Cable') - CircuitTermination = apps.get_model('circuits', 'CircuitTermination') - - if 'test' not in sys.argv: - print(f"\n Updating circuit termination cable peers...", flush=True) - ct = ContentType.objects.get_for_model(CircuitTermination) - for cable in Cable.objects.filter(termination_a_type=ct): - CircuitTermination.objects.filter(pk=cable.termination_a_id).update( - _cable_peer_type_id=cable.termination_b_type_id, - _cable_peer_id=cable.termination_b_id - ) - for cable in Cable.objects.filter(termination_b_type=ct): - CircuitTermination.objects.filter(pk=cable.termination_b_id).update( - _cable_peer_type_id=cable.termination_a_type_id, - _cable_peer_id=cable.termination_a_id - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('circuits', '0020_custom_field_data'), - ] - - operations = [ - migrations.AddField( - model_name='circuittermination', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='circuittermination', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.RunPython( - code=cache_cable_peers, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/circuits/migrations/0022_cablepath.py b/netbox/circuits/migrations/0022_cablepath.py deleted file mode 100644 index 4a5b26efa..000000000 --- a/netbox/circuits/migrations/0022_cablepath.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0121_cablepath'), - ('circuits', '0021_cache_cable_peer'), - ] - - operations = [ - migrations.AddField( - model_name='circuittermination', - name='_path', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), - ), - migrations.RemoveField( - model_name='circuittermination', - name='connected_endpoint', - ), - migrations.RemoveField( - model_name='circuittermination', - name='connection_status', - ), - ] diff --git a/netbox/circuits/migrations/0023_circuittermination_port_speed_optional.py b/netbox/circuits/migrations/0023_circuittermination_port_speed_optional.py deleted file mode 100644 index ea9190623..000000000 --- a/netbox/circuits/migrations/0023_circuittermination_port_speed_optional.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1 on 2020-10-09 17:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0022_cablepath'), - ] - - operations = [ - migrations.AlterField( - model_name='circuittermination', - name='port_speed', - field=models.PositiveIntegerField(blank=True, null=True), - ), - ] diff --git a/netbox/circuits/migrations/0024_standardize_name_length.py b/netbox/circuits/migrations/0024_standardize_name_length.py deleted file mode 100644 index 8d0ae48e3..000000000 --- a/netbox/circuits/migrations/0024_standardize_name_length.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 3.1 on 2020-10-15 19:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0023_circuittermination_port_speed_optional'), - ] - - operations = [ - migrations.AlterField( - model_name='circuit', - name='cid', - field=models.CharField(max_length=100), - ), - migrations.AlterField( - model_name='circuittype', - name='name', - field=models.CharField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='circuittype', - name='slug', - field=models.SlugField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='provider', - name='name', - field=models.CharField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='provider', - name='slug', - field=models.SlugField(max_length=100, unique=True), - ), - ] diff --git a/netbox/circuits/migrations/0025_standardize_models.py b/netbox/circuits/migrations/0025_standardize_models.py deleted file mode 100644 index 42745f35b..000000000 --- a/netbox/circuits/migrations/0025_standardize_models.py +++ /dev/null @@ -1,47 +0,0 @@ -import django.core.serializers.json -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0024_standardize_name_length'), - ] - - operations = [ - migrations.AddField( - model_name='circuittype', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AlterField( - model_name='circuit', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='circuittermination', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='circuittype', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='provider', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AddField( - model_name='circuittermination', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='circuittermination', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - ] diff --git a/netbox/circuits/migrations/0027_providernetwork.py b/netbox/circuits/migrations/0027_providernetwork.py deleted file mode 100644 index e8fbdb8d4..000000000 --- a/netbox/circuits/migrations/0027_providernetwork.py +++ /dev/null @@ -1,65 +0,0 @@ -import django.core.serializers.json -from django.db import migrations, models -import django.db.models.deletion -import taggit.managers - - -class Migration(migrations.Migration): - - dependencies = [ - ('extras', '0058_journalentry'), - ('circuits', '0026_mark_connected'), - ] - - operations = [ - # Create the new ProviderNetwork model - migrations.CreateModel( - name='ProviderNetwork', - fields=[ - ('created', models.DateField(auto_now_add=True, null=True)), - ('last_updated', models.DateTimeField(auto_now=True, null=True)), - ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=100)), - ('description', models.CharField(blank=True, max_length=200)), - ('comments', models.TextField(blank=True)), - ('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='networks', to='circuits.provider')), - ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), - ], - options={ - 'ordering': ('provider', 'name'), - }, - ), - migrations.AddConstraint( - model_name='providernetwork', - constraint=models.UniqueConstraint(fields=('provider', 'name'), name='circuits_providernetwork_provider_name'), - ), - migrations.AlterUniqueTogether( - name='providernetwork', - unique_together={('provider', 'name')}, - ), - - # Add ProviderNetwork FK to CircuitTermination - migrations.AddField( - model_name='circuittermination', - name='provider_network', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='circuits.providernetwork'), - ), - migrations.AlterField( - model_name='circuittermination', - name='site', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='dcim.site'), - ), - - # Add FKs to CircuitTermination on Circuit - migrations.AddField( - model_name='circuit', - name='termination_a', - field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'), - ), - migrations.AddField( - model_name='circuit', - name='termination_z', - field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'), - ), - ] diff --git a/netbox/circuits/migrations/0028_cache_circuit_terminations.py b/netbox/circuits/migrations/0028_cache_circuit_terminations.py deleted file mode 100644 index 23734348e..000000000 --- a/netbox/circuits/migrations/0028_cache_circuit_terminations.py +++ /dev/null @@ -1,37 +0,0 @@ -import sys - -from django.db import migrations - - -def cache_circuit_terminations(apps, schema_editor): - Circuit = apps.get_model('circuits', 'Circuit') - CircuitTermination = apps.get_model('circuits', 'CircuitTermination') - - if 'test' not in sys.argv: - print(f"\n Caching circuit terminations...", flush=True) - - a_terminations = { - ct.circuit_id: ct.pk for ct in CircuitTermination.objects.filter(term_side='A') - } - z_terminations = { - ct.circuit_id: ct.pk for ct in CircuitTermination.objects.filter(term_side='Z') - } - for circuit in Circuit.objects.all(): - Circuit.objects.filter(pk=circuit.pk).update( - termination_a_id=a_terminations.get(circuit.pk), - termination_z_id=z_terminations.get(circuit.pk), - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0027_providernetwork'), - ] - - operations = [ - migrations.RunPython( - code=cache_circuit_terminations, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/circuits/migrations/0029_circuit_tracing.py b/netbox/circuits/migrations/0029_circuit_tracing.py deleted file mode 100644 index bddb38bb6..000000000 --- a/netbox/circuits/migrations/0029_circuit_tracing.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.db import migrations -from django.db.models import Q - - -def delete_obsolete_cablepaths(apps, schema_editor): - """ - Delete all CablePath instances which originate or terminate at a CircuitTermination. - """ - ContentType = apps.get_model('contenttypes', 'ContentType') - CircuitTermination = apps.get_model('circuits', 'CircuitTermination') - CablePath = apps.get_model('dcim', 'CablePath') - - ct = ContentType.objects.get_for_model(CircuitTermination) - CablePath.objects.filter(Q(origin_type=ct) | Q(destination_type=ct)).delete() - - -class Migration(migrations.Migration): - - dependencies = [ - ('circuits', '0028_cache_circuit_terminations'), - ] - - operations = [ - migrations.RemoveField( - model_name='circuittermination', - name='_path', - ), - migrations.RunPython( - code=delete_obsolete_cablepaths, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 699ded7b0..39f38d0b0 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -63,9 +63,6 @@ class Provider(PrimaryModel): objects = RestrictedQuerySet.as_manager() - csv_headers = [ - 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', - ] clone_fields = [ 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', ] @@ -79,18 +76,6 @@ class Provider(PrimaryModel): def get_absolute_url(self): return reverse('circuits:provider', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.asn, - self.account, - self.portal_url, - self.noc_contact, - self.admin_contact, - self.comments, - ) - # # Provider networks @@ -118,10 +103,6 @@ class ProviderNetwork(PrimaryModel): blank=True ) - csv_headers = [ - 'provider', 'name', 'description', 'comments', - ] - objects = RestrictedQuerySet.as_manager() class Meta: @@ -140,14 +121,6 @@ class ProviderNetwork(PrimaryModel): def get_absolute_url(self): return reverse('circuits:providernetwork', args=[self.pk]) - def to_csv(self): - return ( - self.provider.name, - self.name, - self.description, - self.comments, - ) - @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class CircuitType(OrganizationalModel): @@ -170,8 +143,6 @@ class CircuitType(OrganizationalModel): objects = RestrictedQuerySet.as_manager() - csv_headers = ['name', 'slug', 'description'] - class Meta: ordering = ['name'] @@ -181,13 +152,6 @@ class CircuitType(OrganizationalModel): def get_absolute_url(self): return reverse('circuits:circuittype', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.description, - ) - @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks') class Circuit(PrimaryModel): @@ -259,9 +223,6 @@ class Circuit(PrimaryModel): objects = RestrictedQuerySet.as_manager() - csv_headers = [ - 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', - ] clone_fields = [ 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', ] @@ -276,19 +237,6 @@ class Circuit(PrimaryModel): def get_absolute_url(self): return reverse('circuits:circuit', args=[self.pk]) - def to_csv(self): - return ( - self.cid, - self.provider.name, - self.type.name, - self.get_status_display(), - self.tenant.name if self.tenant else None, - self.install_date, - self.commit_rate, - self.description, - self.comments, - ) - def get_status_class(self): return CircuitStatusChoices.CSS_CLASSES.get(self.status) diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index e7bb889e0..dfbfe68a4 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -264,7 +264,7 @@ class CircuitSwapTerminations(generic.ObjectEditView): 'termination_a': circuit.termination_a, 'termination_z': circuit.termination_z, 'form': form, - 'panel_class': 'default', + 'panel_class': 'light', 'button_class': 'primary', 'return_url': circuit.get_absolute_url(), }) diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py index 80e003efc..67ae9b046 100644 --- a/netbox/dcim/api/nested_serializers.py +++ b/netbox/dcim/api/nested_serializers.py @@ -101,7 +101,7 @@ class NestedRackSerializer(WritableNestedSerializer): class Meta: model = models.Rack - fields = ['id', 'url', 'display', 'name', 'display_name', 'device_count'] + fields = ['id', 'url', 'display', 'name', 'device_count'] class NestedRackReservationSerializer(WritableNestedSerializer): @@ -136,7 +136,7 @@ class NestedDeviceTypeSerializer(WritableNestedSerializer): class Meta: model = models.DeviceType - fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'device_count'] + fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'device_count'] class NestedConsolePortTemplateSerializer(WritableNestedSerializer): @@ -232,7 +232,7 @@ class NestedDeviceSerializer(WritableNestedSerializer): class Meta: model = models.Device - fields = ['id', 'url', 'display', 'name', 'display_name'] + fields = ['id', 'url', 'display', 'name'] class NestedConsoleServerPortSerializer(WritableNestedSerializer): diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index c9d69fd00..8e2fa15af 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -177,10 +177,9 @@ class RackSerializer(PrimaryModelSerializer): class Meta: model = Rack fields = [ - 'id', 'url', 'display', 'name', 'facility_id', 'display_name', 'site', 'location', 'tenant', 'status', - 'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', - 'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', - 'powerfeed_count', + 'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial', + 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', + 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count', ] # Omit the UniqueTogetherValidator that would be automatically added to validate (location, facility_id). This # prevents facility_id from being interpreted as a required field. @@ -293,9 +292,9 @@ class DeviceTypeSerializer(PrimaryModelSerializer): class Meta: model = DeviceType fields = [ - 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'part_number', 'u_height', - 'is_full_depth', 'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', - 'created', 'last_updated', 'device_count', + 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', + 'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created', + 'last_updated', 'device_count', ] @@ -394,8 +393,8 @@ class RearPortTemplateSerializer(ValidatedModelSerializer): class Meta: model = RearPortTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'positions', 'description', 'created', - 'last_updated', + 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'color', 'positions', 'description', + 'created', 'last_updated', ] @@ -408,7 +407,7 @@ class FrontPortTemplateSerializer(ValidatedModelSerializer): class Meta: model = FrontPortTemplate fields = [ - 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position', + 'id', 'url', 'display', 'device_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'created', 'last_updated', ] @@ -474,10 +473,10 @@ class DeviceSerializer(PrimaryModelSerializer): class Meta: model = Device fields = [ - 'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', - 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', - 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', - 'comments', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', + 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', + 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', + 'tags', 'custom_fields', 'created', 'last_updated', ] validators = [] @@ -510,10 +509,10 @@ class DeviceWithConfigContextSerializer(DeviceSerializer): class Meta(DeviceSerializer.Meta): fields = [ - 'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', - 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', - 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', - 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', + 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', + 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', + 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', + 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', ] @swagger_serializer_method(serializer_or_field=serializers.DictField) @@ -675,8 +674,9 @@ class RearPortSerializer(PrimaryModelSerializer, CableTerminationSerializer): class Meta: model = RearPort fields = [ - 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', - 'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', + 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'color', 'positions', 'description', + 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created', + 'last_updated', '_occupied', ] @@ -701,9 +701,9 @@ class FrontPortSerializer(PrimaryModelSerializer, CableTerminationSerializer): class Meta: model = FrontPort fields = [ - 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', - 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created', - 'last_updated', '_occupied', + 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', + 'description', 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', + 'created', 'last_updated', '_occupied', ] @@ -845,31 +845,6 @@ class CablePathSerializer(serializers.ModelSerializer): return ret -# -# Interface connections -# - -class InterfaceConnectionSerializer(ValidatedModelSerializer): - interface_a = serializers.SerializerMethodField() - interface_b = NestedInterfaceSerializer(source='_path.destination') - connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True) - - class Meta: - model = Interface - fields = ['interface_a', 'interface_b', 'connected_endpoint_reachable'] - - @swagger_serializer_method(serializer_or_field=NestedInterfaceSerializer) - def get_interface_a(self, obj): - context = {'request': self.context['request']} - return NestedInterfaceSerializer(instance=obj, context=context).data - - @swagger_serializer_method(serializer_or_field=serializers.BooleanField) - def get_connected_endpoint_reachable(self, obj): - if obj._path is not None: - return obj._path.is_active - return None - - # # Virtual chassis # diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 43f956cb2..491f4e7f2 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -46,11 +46,6 @@ router.register('rear-ports', views.RearPortViewSet) router.register('device-bays', views.DeviceBayViewSet) router.register('inventory-items', views.InventoryItemViewSet) -# Connections -router.register('console-connections', views.ConsoleConnectionViewSet, basename='consoleconnections') -router.register('power-connections', views.PowerConnectionViewSet, basename='powerconnections') -router.register('interface-connections', views.InterfaceConnectionViewSet, basename='interfaceconnections') - # Cables router.register('cables', views.CableViewSet) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 744d16e0a..3ee225335 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -2,18 +2,15 @@ import socket from collections import OrderedDict from django.conf import settings -from django.contrib.contenttypes.models import ContentType -from django.db.models import F from django.http import HttpResponseForbidden, HttpResponse from django.shortcuts import get_object_or_404 from drf_yasg import openapi from drf_yasg.openapi import Parameter from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action -from rest_framework.mixins import ListModelMixin from rest_framework.response import Response from rest_framework.routers import APIRootView -from rest_framework.viewsets import GenericViewSet, ViewSet +from rest_framework.viewsets import ViewSet from circuits.models import Circuit from dcim import filtersets @@ -53,6 +50,18 @@ class PathEndpointMixin(object): # Initialize the path array path = [] + if request.GET.get('render', None) == 'svg': + # Render SVG + try: + width = min(int(request.GET.get('width')), 1600) + except (ValueError, TypeError): + width = None + drawing = obj.get_trace_svg( + base_url=request.build_absolute_uri('/'), + width=width + ) + return HttpResponse(drawing.tostring(), content_type='image/svg+xml') + for near_end, cable, far_end in obj.trace(): if near_end is None: # Split paths @@ -570,36 +579,6 @@ class InventoryItemViewSet(ModelViewSet): brief_prefetch_fields = ['device'] -# -# Connections -# - -class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet): - queryset = ConsolePort.objects.prefetch_related('device', '_path').filter( - _path__destination_id__isnull=False - ) - serializer_class = serializers.ConsolePortSerializer - filterset_class = filtersets.ConsoleConnectionFilterSet - - -class PowerConnectionViewSet(ListModelMixin, GenericViewSet): - queryset = PowerPort.objects.prefetch_related('device', '_path').filter( - _path__destination_id__isnull=False - ) - serializer_class = serializers.PowerPortSerializer - filterset_class = filtersets.PowerConnectionFilterSet - - -class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet): - queryset = Interface.objects.prefetch_related('device', '_path').filter( - _path__destination_type__app_label='dcim', - _path__destination_type__model='interface', - _path__destination_id__isnull=False - ) - serializer_class = serializers.InterfaceConnectionSerializer - filterset_class = filtersets.InterfaceConnectionFilterSet - - # # Cables # diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 63054c2ce..d20db1aa9 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -169,7 +169,7 @@ class DeviceStatusChoices(ChoiceSet): STATUS_PLANNED: 'info', STATUS_STAGED: 'primary', STATUS_FAILED: 'danger', - STATUS_INVENTORY: 'default', + STATUS_INVENTORY: 'secondary', STATUS_DECOMMISSIONING: 'warning', } @@ -1090,14 +1090,21 @@ class CableStatusChoices(ChoiceSet): class CableLengthUnitChoices(ChoiceSet): + # Metric + UNIT_KILOMETER = 'km' UNIT_METER = 'm' UNIT_CENTIMETER = 'cm' + + # Imperial + UNIT_MILE = 'mi' UNIT_FOOT = 'ft' UNIT_INCH = 'in' CHOICES = ( + (UNIT_KILOMETER, 'Kilometers'), (UNIT_METER, 'Meters'), (UNIT_CENTIMETER, 'Centimeters'), + (UNIT_MILE, 'Miles'), (UNIT_FOOT, 'Feet'), (UNIT_INCH, 'Inches'), ) diff --git a/netbox/dcim/elevations.py b/netbox/dcim/elevations.py deleted file mode 100644 index 93c44f087..000000000 --- a/netbox/dcim/elevations.py +++ /dev/null @@ -1,232 +0,0 @@ -import svgwrite - -from django.conf import settings -from django.urls import reverse -from django.utils.http import urlencode - -from utilities.utils import foreground_color -from .choices import DeviceFaceChoices -from .constants import RACK_ELEVATION_BORDER_WIDTH - - -class RackElevationSVG: - """ - Use this class to render a rack elevation as an SVG image. - - :param rack: A NetBox Rack instance - :param user: User instance. If specified, only devices viewable by this user will be fully displayed. - :param include_images: If true, the SVG document will embed front/rear device face images, where available - :param base_url: Base URL for links within the SVG document. If none, links will be relative. - """ - def __init__(self, rack, user=None, include_images=True, base_url=None): - self.rack = rack - self.include_images = include_images - if base_url is not None: - self.base_url = base_url.rstrip('/') - else: - self.base_url = '' - - # Determine the subset of devices within this rack that are viewable by the user, if any - permitted_devices = self.rack.devices - if user is not None: - permitted_devices = permitted_devices.restrict(user, 'view') - self.permitted_device_ids = permitted_devices.values_list('pk', flat=True) - - @staticmethod - def _get_device_description(device): - return '{} ({}) — {} ({}U) {} {}'.format( - device.name, - device.device_role, - device.device_type.display_name, - device.device_type.u_height, - device.asset_tag or '', - device.serial or '' - ) - - @staticmethod - def _add_gradient(drawing, id_, color): - gradient = drawing.linearGradient( - start=(0, 0), - end=(0, 25), - spreadMethod='repeat', - id_=id_, - gradientTransform='rotate(45, 0, 0)', - gradientUnits='userSpaceOnUse' - ) - gradient.add_stop_color(offset='0%', color='#f7f7f7') - gradient.add_stop_color(offset='50%', color='#f7f7f7') - gradient.add_stop_color(offset='50%', color=color) - gradient.add_stop_color(offset='100%', color=color) - drawing.defs.add(gradient) - - @staticmethod - def _setup_drawing(width, height): - drawing = svgwrite.Drawing(size=(width, height)) - - # add the stylesheet - with open('{}/css/rack_elevation.css'.format(settings.STATICFILES_DIRS[0])) as css_file: - drawing.defs.add(drawing.style(css_file.read())) - - # add gradients - RackElevationSVG._add_gradient(drawing, 'reserved', '#c7c7ff') - RackElevationSVG._add_gradient(drawing, 'occupied', '#d7d7d7') - RackElevationSVG._add_gradient(drawing, 'blocked', '#ffc0c0') - - return drawing - - def _draw_device_front(self, drawing, device, start, end, text): - name = str(device) - if device.devicebay_count: - name += ' ({}/{})'.format(device.get_children().count(), device.devicebay_count) - - color = device.device_role.color - link = drawing.add( - drawing.a( - href='{}{}'.format(self.base_url, reverse('dcim:device', kwargs={'pk': device.pk})), - target='_top', - fill='black' - ) - ) - link.set_desc(self._get_device_description(device)) - link.add(drawing.rect(start, end, style='fill: #{}'.format(color), class_='slot')) - hex_color = '#{}'.format(foreground_color(color)) - link.add(drawing.text(str(name), insert=text, fill=hex_color)) - - # Embed front device type image if one exists - if self.include_images and device.device_type.front_image: - image = drawing.image( - href=device.device_type.front_image.url, - insert=start, - size=end, - class_='device-image' - ) - image.fit(scale='slice') - link.add(image) - - def _draw_device_rear(self, drawing, device, start, end, text): - rect = drawing.rect(start, end, class_="slot blocked") - rect.set_desc(self._get_device_description(device)) - drawing.add(rect) - drawing.add(drawing.text(str(device), insert=text)) - - # Embed rear device type image if one exists - if self.include_images and device.device_type.rear_image: - image = drawing.image( - href=device.device_type.rear_image.url, - insert=start, - size=end, - class_='device-image' - ) - image.fit(scale='slice') - drawing.add(image) - - @staticmethod - def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation): - link = drawing.add( - drawing.a( - href='{}?{}'.format( - reverse('dcim:device_add'), - urlencode({'rack': rack.pk, 'site': rack.site.pk, 'face': face_id, 'position': id_}) - ), - target='_top' - ) - ) - if reservation: - link.set_desc('{} — {} · {}'.format( - reservation.description, reservation.user, reservation.created - )) - link.add(drawing.rect(start, end, class_=class_)) - link.add(drawing.text("add device", insert=text, class_='add-device')) - - def merge_elevations(self, face): - elevation = self.rack.get_rack_units(face=face, expand_devices=False) - if face == DeviceFaceChoices.FACE_REAR: - other_face = DeviceFaceChoices.FACE_FRONT - else: - other_face = DeviceFaceChoices.FACE_REAR - other = self.rack.get_rack_units(face=other_face) - - unit_cursor = 0 - for u in elevation: - o = other[unit_cursor] - if not u['device'] and o['device'] and o['device'].device_type.is_full_depth: - u['device'] = o['device'] - u['height'] = 1 - unit_cursor += u.get('height', 1) - - return elevation - - def render(self, face, unit_width, unit_height, legend_width): - """ - Return an SVG document representing a rack elevation. - """ - drawing = self._setup_drawing( - unit_width + legend_width + RACK_ELEVATION_BORDER_WIDTH * 2, - unit_height * self.rack.u_height + RACK_ELEVATION_BORDER_WIDTH * 2 - ) - reserved_units = self.rack.get_reserved_units() - - unit_cursor = 0 - for ru in range(0, self.rack.u_height): - start_y = ru * unit_height - position_coordinates = (legend_width / 2, start_y + unit_height / 2 + RACK_ELEVATION_BORDER_WIDTH) - unit = ru + 1 if self.rack.desc_units else self.rack.u_height - ru - drawing.add( - drawing.text(str(unit), position_coordinates, class_="unit") - ) - - for unit in self.merge_elevations(face): - - # Loop through all units in the elevation - device = unit['device'] - height = unit.get('height', 1) - - # Setup drawing coordinates - x_offset = legend_width + RACK_ELEVATION_BORDER_WIDTH - y_offset = unit_cursor * unit_height + RACK_ELEVATION_BORDER_WIDTH - end_y = unit_height * height - start_cordinates = (x_offset, y_offset) - end_cordinates = (unit_width, end_y) - text_cordinates = (x_offset + (unit_width / 2), y_offset + end_y / 2) - - # Draw the device - if device and device.face == face and device.pk in self.permitted_device_ids: - self._draw_device_front(drawing, device, start_cordinates, end_cordinates, text_cordinates) - elif device and device.device_type.is_full_depth and device.pk in self.permitted_device_ids: - self._draw_device_rear(drawing, device, start_cordinates, end_cordinates, text_cordinates) - elif device: - # Devices which the user does not have permission to view are rendered only as unavailable space - drawing.add(drawing.rect(start_cordinates, end_cordinates, class_='blocked')) - else: - # Draw shallow devices, reservations, or empty units - class_ = 'slot' - reservation = reserved_units.get(unit["id"]) - if device: - class_ += ' occupied' - if reservation: - class_ += ' reserved' - self._draw_empty( - drawing, - self.rack, - start_cordinates, - end_cordinates, - text_cordinates, - unit["id"], - face, - class_, - reservation - ) - - unit_cursor += height - - # Wrap the drawing with a border - border_width = RACK_ELEVATION_BORDER_WIDTH - border_offset = RACK_ELEVATION_BORDER_WIDTH / 2 - frame = drawing.rect( - insert=(legend_width + border_offset, border_offset), - size=(unit_width + border_width, self.rack.u_height * unit_height + border_width), - class_='rack' - ) - drawing.add(frame) - - return drawing diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index b04c14ba9..02749ba1c 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -538,7 +538,7 @@ class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponent class Meta: model = FrontPortTemplate - fields = ['id', 'name', 'type'] + fields = ['id', 'name', 'type', 'color'] class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): @@ -549,7 +549,7 @@ class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentF class Meta: model = RearPortTemplate - fields = ['id', 'name', 'type', 'positions'] + fields = ['id', 'name', 'type', 'color', 'positions'] class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): @@ -831,6 +831,17 @@ class DeviceComponentFilterSet(django_filters.FilterSet): to_field_name='slug', label='Site name (slug)', ) + location_id = django_filters.ModelMultipleChoiceFilter( + field_name='device__location', + queryset=Location.objects.all(), + label='Location (ID)', + ) + location = django_filters.ModelMultipleChoiceFilter( + field_name='device__location__slug', + queryset=Location.objects.all(), + to_field_name='slug', + label='Location (slug)', + ) device_id = django_filters.ModelMultipleChoiceFilter( queryset=Device.objects.all(), label='Device (ID)', @@ -1027,7 +1038,7 @@ class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT class Meta: model = FrontPort - fields = ['id', 'name', 'label', 'type', 'description'] + fields = ['id', 'name', 'label', 'type', 'color', 'description'] class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): @@ -1038,7 +1049,7 @@ class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTe class Meta: model = RearPort - fields = ['id', 'name', 'label', 'type', 'positions', 'description'] + fields = ['id', 'name', 'label', 'type', 'color', 'positions', 'description'] class DeviceBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet): @@ -1053,39 +1064,6 @@ class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet): method='search', label='Search', ) - region_id = TreeNodeMultipleChoiceFilter( - queryset=Region.objects.all(), - field_name='device__site__region', - lookup_expr='in', - label='Region (ID)', - ) - region = TreeNodeMultipleChoiceFilter( - queryset=Region.objects.all(), - field_name='device__site__region', - lookup_expr='in', - to_field_name='slug', - label='Region (slug)', - ) - site_id = django_filters.ModelMultipleChoiceFilter( - field_name='device__site', - queryset=Site.objects.all(), - label='Site (ID)', - ) - site = django_filters.ModelMultipleChoiceFilter( - field_name='device__site__slug', - queryset=Site.objects.all(), - to_field_name='slug', - label='Site name (slug)', - ) - device_id = django_filters.ModelChoiceFilter( - queryset=Device.objects.all(), - label='Device (ID)', - ) - device = django_filters.ModelChoiceFilter( - queryset=Device.objects.all(), - to_field_name='name', - label='Device (name)', - ) parent_id = django_filters.ModelMultipleChoiceFilter( queryset=InventoryItem.objects.all(), label='Parent inventory item (ID)', diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 2eca6da08..b69944cf6 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -13,8 +13,8 @@ from timezone_field import TimeZoneFormField from circuits.models import Circuit, CircuitTermination, Provider from extras.forms import ( - AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldModelCSVForm, CustomFieldFilterForm, - CustomFieldModelForm, LocalConfigContextFilterForm, + AddRemoveTagsForm, CustomFieldModelBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelFilterForm, CustomFieldModelForm, + CustomFieldsMixin, LocalConfigContextFilterForm, ) from extras.models import Tag from ipam.constants import BGP_ASN_MAX, BGP_ASN_MIN @@ -23,9 +23,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, - ColorSelect, CommentField, CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, + ColorField, CommentField, CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, - NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, + NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect, StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import Cluster, ClusterGroup @@ -54,12 +54,13 @@ def get_device_by_name_or_pk(name): return device -class DeviceComponentFilterForm(BootstrapMixin, CustomFieldFilterForm): +class DeviceComponentFilterForm(BootstrapMixin, CustomFieldModelFilterForm): field_order = [ 'q', 'name', 'label', 'region_id', 'site_group_id', 'site_id', ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) name = forms.CharField( @@ -71,28 +72,43 @@ class DeviceComponentFilterForm(BootstrapMixin, CustomFieldFilterForm): region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_group_id = DynamicModelMultipleChoiceField( queryset=SiteGroup.objects.all(), required=False, - label=_('Site group') + label=_('Site group'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), required=False, query_params={ - 'region_id': '$region_id' + 'region_id': '$region_id', + 'group_id': '$site_group_id', }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' + ) + location_id = DynamicModelMultipleChoiceField( + queryset=Location.objects.all(), + required=False, + query_params={ + 'site_id': '$site_id', + }, + label=_('Location'), + fetch_trigger='open' ) device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, query_params={ - 'site_id': '$site_id' + 'site_id': '$site_id', + 'location_id': '$location_id', }, - label=_('Device') + label=_('Device'), + fetch_trigger='open' ) @@ -215,10 +231,10 @@ class RegionCSVForm(CustomFieldModelCSVForm): class Meta: model = Region - fields = Region.csv_headers + fields = ('name', 'slug', 'parent', 'description') -class RegionBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): +class RegionBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Region.objects.all(), widget=forms.MultipleHiddenInput @@ -236,12 +252,23 @@ class RegionBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['parent', 'description'] -class RegionFilterForm(BootstrapMixin, CustomFieldFilterForm): - model = Site +class RegionFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = Region + field_groups = [ + ['q'], + ['parent_id'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) + parent_id = DynamicModelMultipleChoiceField( + queryset=Region.objects.all(), + required=False, + label=_('Parent region'), + fetch_trigger='open' + ) # @@ -272,10 +299,10 @@ class SiteGroupCSVForm(CustomFieldModelCSVForm): class Meta: model = SiteGroup - fields = SiteGroup.csv_headers + fields = ('name', 'slug', 'parent', 'description') -class SiteGroupBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): +class SiteGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=SiteGroup.objects.all(), widget=forms.MultipleHiddenInput @@ -293,12 +320,23 @@ class SiteGroupBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['parent', 'description'] -class SiteGroupFilterForm(BootstrapMixin, CustomFieldFilterForm): +class SiteGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = SiteGroup + field_groups = [ + ['q'], + ['parent_id'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) + parent_id = DynamicModelMultipleChoiceField( + queryset=SiteGroup.objects.all(), + required=False, + label=_('Parent group'), + fetch_trigger='open' + ) # @@ -318,7 +356,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): time_zone = TimeZoneFormField( choices=add_blank_choice(TimeZoneFormField().choices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) comments = CommentField() tags = DynamicModelMultipleChoiceField( @@ -354,8 +392,8 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): 'rows': 3, } ), - 'status': StaticSelect2(), - 'time_zone': StaticSelect2(), + 'status': StaticSelect(), + 'time_zone': StaticSelect(), } help_texts = { 'name': "Full name of the site", @@ -397,7 +435,11 @@ class SiteCSVForm(CustomFieldModelCSVForm): class Meta: model = Site - fields = Site.csv_headers + fields = ( + 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'time_zone', 'description', + 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', + 'contact_email', 'comments', + ) help_texts = { 'time_zone': mark_safe( 'Time zone (available options)' @@ -405,7 +447,7 @@ class SiteCSVForm(CustomFieldModelCSVForm): } -class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Site.objects.all(), widget=forms.MultipleHiddenInput @@ -414,7 +456,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor choices=add_blank_choice(SiteStatusChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) region = DynamicModelChoiceField( queryset=Region.objects.all(), @@ -441,7 +483,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor time_zone = TimeZoneFormField( choices=add_blank_choice(TimeZoneFormField().choices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) class Meta: @@ -450,27 +492,35 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor ] -class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): +class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Site field_order = ['q', 'status', 'region_id', 'tenant_group_id', 'tenant_id'] + field_groups = [ + ['q', 'tag'], + ['status', 'region_id', 'group_id'], + ['tenant_group_id', 'tenant_id'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) status = forms.MultipleChoiceField( choices=SiteStatusChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple(), ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) group_id = DynamicModelMultipleChoiceField( queryset=SiteGroup.objects.all(), required=False, - label=_('Group') + label=_('Site group'), + fetch_trigger='open' ) tag = TagFilterField(model) @@ -535,10 +585,10 @@ class LocationCSVForm(CustomFieldModelCSVForm): class Meta: model = Location - fields = Location.csv_headers + fields = ('site', 'parent', 'name', 'slug', 'description') -class LocationBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): +class LocationBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Location.objects.all(), widget=forms.MultipleHiddenInput @@ -563,24 +613,34 @@ class LocationBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['parent', 'description'] -class LocationFilterForm(BootstrapMixin, CustomFieldFilterForm): +class LocationFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Location q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' + ) + site_group_id = DynamicModelMultipleChoiceField( + queryset=SiteGroup.objects.all(), + required=False, + label=_('Site group'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), required=False, query_params={ - 'region_id': '$region_id' + 'region_id': '$region_id', + 'group_id': '$site_group_id', }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) parent_id = DynamicModelMultipleChoiceField( queryset=Location.objects.all(), @@ -589,7 +649,8 @@ class LocationFilterForm(BootstrapMixin, CustomFieldFilterForm): 'region_id': '$region_id', 'site_id': '$site_id', }, - label=_('Parent') + label=_('Parent'), + fetch_trigger='open' ) @@ -612,21 +673,19 @@ class RackRoleCSVForm(CustomFieldModelCSVForm): class Meta: model = RackRole - fields = RackRole.csv_headers + fields = ('name', 'slug', 'color', 'description') help_texts = { 'color': mark_safe('RGB color in hexadecimal (e.g. 00ff00)'), } -class RackRoleBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): +class RackRoleBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=RackRole.objects.all(), widget=forms.MultipleHiddenInput ) - color = forms.CharField( - max_length=6, # RGB color code - required=False, - widget=ColorSelect() + color = ColorField( + required=False ) description = forms.CharField( max_length=200, @@ -694,10 +753,10 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): 'u_height': "Height in rack units", } widgets = { - 'status': StaticSelect2(), - 'type': StaticSelect2(), - 'width': StaticSelect2(), - 'outer_unit': StaticSelect2(), + 'status': StaticSelect(), + 'type': StaticSelect(), + 'width': StaticSelect(), + 'outer_unit': StaticSelect(), } @@ -745,7 +804,10 @@ class RackCSVForm(CustomFieldModelCSVForm): class Meta: model = Rack - fields = Rack.csv_headers + fields = ( + 'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', + 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments', + ) def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) @@ -757,7 +819,7 @@ class RackCSVForm(CustomFieldModelCSVForm): self.fields['location'].queryset = self.fields['location'].queryset.filter(**params) -class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput @@ -799,7 +861,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor choices=add_blank_choice(RackStatusChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) role = DynamicModelChoiceField( queryset=RackRole.objects.all(), @@ -817,12 +879,12 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor type = forms.ChoiceField( choices=add_blank_choice(RackTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) width = forms.ChoiceField( choices=add_blank_choice(RackWidthChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) u_height = forms.IntegerField( required=False, @@ -844,7 +906,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor outer_unit = forms.ChoiceField( choices=add_blank_choice(RackDimensionUnitChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) comments = CommentField( widget=SmallTextarea, @@ -857,17 +919,26 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor ] -class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): +class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Rack field_order = ['q', 'region_id', 'site_id', 'location_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id'] + field_groups = [ + ['q', 'tag'], + ['region_id', 'site_id', 'location_id'], + ['status', 'role_id'], + ['type', 'width', 'asset_tag'], + ['tenant_group_id', 'tenant_id'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -875,7 +946,8 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): query_params={ 'region_id': '$region_id' }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) location_id = DynamicModelMultipleChoiceField( queryset=Location.objects.all(), @@ -884,28 +956,30 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): query_params={ 'site_id': '$site_id' }, - label=_('Location') + label=_('Location'), + fetch_trigger='open' ) status = forms.MultipleChoiceField( choices=RackStatusChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) type = forms.MultipleChoiceField( choices=RackTypeChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) width = forms.MultipleChoiceField( choices=RackWidthChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) role_id = DynamicModelMultipleChoiceField( queryset=RackRole.objects.all(), required=False, null_option='None', - label=_('Role') + label=_('Role'), + fetch_trigger='open' ) asset_tag = forms.CharField( required=False @@ -919,7 +993,8 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): class RackElevationFilterForm(RackFilterForm): field_order = [ - 'q', 'region_id', 'site_id', 'location_id', 'id', 'status', 'role_id', 'tenant_group_id', 'tenant_id', + 'q', 'region_id', 'site_id', 'location_id', 'id', 'status', 'role_id', 'tenant_group_id', + 'tenant_id', ] id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), @@ -928,7 +1003,8 @@ class RackElevationFilterForm(RackFilterForm): query_params={ 'site_id': '$site_id', 'location_id': '$location_id', - } + }, + fetch_trigger='open' ) @@ -942,14 +1018,16 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): required=False, initial_params={ 'sites': '$site' - } + }, + fetch_trigger='open' ) site_group = DynamicModelChoiceField( queryset=SiteGroup.objects.all(), required=False, initial_params={ 'sites': '$site' - } + }, + fetch_trigger='open' ) site = DynamicModelChoiceField( queryset=Site.objects.all(), @@ -957,21 +1035,24 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): query_params={ 'region_id': '$region', 'group_id': '$site_group', - } + }, + fetch_trigger='open' ) location = DynamicModelChoiceField( queryset=Location.objects.all(), required=False, query_params={ 'site_id': '$site' - } + }, + fetch_trigger='open' ) rack = DynamicModelChoiceField( queryset=Rack.objects.all(), query_params={ 'site_id': '$site', 'location_id': '$location', - } + }, + fetch_trigger='open' ) units = NumericArrayField( base_field=forms.IntegerField(), @@ -981,11 +1062,12 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): queryset=User.objects.order_by( 'username' ), - widget=StaticSelect2() + widget=StaticSelect() ) tags = DynamicModelMultipleChoiceField( queryset=Tag.objects.all(), - required=False + required=False, + fetch_trigger='open' ) class Meta: @@ -1050,7 +1132,7 @@ class RackReservationCSVForm(CustomFieldModelCSVForm): self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params) -class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=RackReservation.objects.all(), widget=forms.MultipleHiddenInput() @@ -1060,7 +1142,7 @@ class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField 'username' ), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) tenant = DynamicModelChoiceField( queryset=Tenant.objects.all(), @@ -1075,17 +1157,25 @@ class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField nullable_fields = [] -class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): +class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = RackReservation field_order = ['q', 'region_id', 'site_id', 'location_id', 'user_id', 'tenant_group_id', 'tenant_id'] + field_groups = [ + ['q', 'tag'], + ['user_id'], + ['region_id', 'site_id', 'location_id'], + ['tenant_group_id', 'tenant_id'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -1093,13 +1183,15 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFi query_params={ 'region_id': '$region_id' }, - label=_('Region') + label=_('Site'), + fetch_trigger='open' ) location_id = DynamicModelMultipleChoiceField( queryset=Location.objects.prefetch_related('site'), required=False, label=_('Location'), - null_option='None' + null_option='None', + fetch_trigger='open' ) user_id = DynamicModelMultipleChoiceField( queryset=User.objects.all(), @@ -1107,7 +1199,8 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFi label=_('User'), widget=APISelectMultiple( api_url='/api/users/users/', - ) + ), + fetch_trigger='open' ) tag = TagFilterField(model) @@ -1130,10 +1223,10 @@ class ManufacturerCSVForm(CustomFieldModelCSVForm): class Meta: model = Manufacturer - fields = Manufacturer.csv_headers + fields = ('name', 'slug', 'description') -class ManufacturerBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): +class ManufacturerBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Manufacturer.objects.all(), widget=forms.MultipleHiddenInput @@ -1177,7 +1270,7 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldModelForm): ('Images', ('front_image', 'rear_image')), ) widgets = { - 'subdevice_role': StaticSelect2(), + 'subdevice_role': StaticSelect(), 'front_image': forms.ClearableFileInput(attrs={ 'accept': DEVICETYPE_IMAGE_FORMATS }), @@ -1201,7 +1294,7 @@ class DeviceTypeImportForm(BootstrapMixin, forms.ModelForm): ] -class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput() @@ -1224,61 +1317,68 @@ class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE nullable_fields = [] -class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): +class DeviceTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = DeviceType + field_groups = [ + ['q', 'tag'], + ['manufacturer_id', 'subdevice_role'], + ['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, - label=_('Manufacturer') + label=_('Manufacturer'), + fetch_trigger='open' ) subdevice_role = forms.MultipleChoiceField( choices=add_blank_choice(SubdeviceRoleChoices), required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) console_ports = forms.NullBooleanField( required=False, label='Has console ports', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) console_server_ports = forms.NullBooleanField( required=False, label='Has console server ports', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) power_ports = forms.NullBooleanField( required=False, label='Has power ports', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) power_outlets = forms.NullBooleanField( required=False, label='Has power outlets', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) interfaces = forms.NullBooleanField( required=False, label='Has interfaces', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) pass_through_ports = forms.NullBooleanField( required=False, label='Has pass-through ports', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) @@ -1326,7 +1426,7 @@ class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm): class ConsolePortTemplateCreateForm(ComponentTemplateCreateForm): type = forms.ChoiceField( choices=add_blank_choice(ConsolePortTypeChoices), - widget=StaticSelect2() + widget=StaticSelect() ) field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'description') @@ -1343,7 +1443,7 @@ class ConsolePortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): type = forms.ChoiceField( choices=add_blank_choice(ConsolePortTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) class Meta: @@ -1365,7 +1465,7 @@ class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm): class ConsoleServerPortTemplateCreateForm(ComponentTemplateCreateForm): type = forms.ChoiceField( choices=add_blank_choice(ConsolePortTypeChoices), - widget=StaticSelect2() + widget=StaticSelect() ) field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'description') @@ -1382,7 +1482,7 @@ class ConsoleServerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): type = forms.ChoiceField( choices=add_blank_choice(ConsolePortTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) description = forms.CharField( required=False @@ -1437,7 +1537,7 @@ class PowerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): type = forms.ChoiceField( choices=add_blank_choice(PowerPortTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) maximum_draw = forms.IntegerField( min_value=1, @@ -1491,7 +1591,7 @@ class PowerOutletTemplateCreateForm(ComponentTemplateCreateForm): feed_leg = forms.ChoiceField( choices=add_blank_choice(PowerOutletFeedLegChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) field_order = ( 'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'power_port', 'feed_leg', @@ -1528,7 +1628,7 @@ class PowerOutletTemplateBulkEditForm(BootstrapMixin, BulkEditForm): type = forms.ChoiceField( choices=add_blank_choice(PowerOutletTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) power_port = forms.ModelChoiceField( queryset=PowerPortTemplate.objects.all(), @@ -1537,7 +1637,7 @@ class PowerOutletTemplateBulkEditForm(BootstrapMixin, BulkEditForm): feed_leg = forms.ChoiceField( choices=add_blank_choice(PowerOutletFeedLegChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) description = forms.CharField( required=False @@ -1567,14 +1667,14 @@ class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm): ] widgets = { 'device_type': forms.HiddenInput(), - 'type': StaticSelect2(), + 'type': StaticSelect(), } class InterfaceTemplateCreateForm(ComponentTemplateCreateForm): type = forms.ChoiceField( choices=InterfaceTypeChoices, - widget=StaticSelect2() + widget=StaticSelect() ) mgmt_only = forms.BooleanField( required=False, @@ -1595,7 +1695,7 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm): type = forms.ChoiceField( choices=add_blank_choice(InterfaceTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) mgmt_only = forms.NullBooleanField( required=False, @@ -1615,11 +1715,11 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm): class Meta: model = FrontPortTemplate fields = [ - 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', + 'device_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', ] widgets = { 'device_type': forms.HiddenInput(), - 'rear_port': StaticSelect2(), + 'rear_port': StaticSelect(), } def __init__(self, *args, **kwargs): @@ -1636,7 +1736,10 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm): class FrontPortTemplateCreateForm(ComponentTemplateCreateForm): type = forms.ChoiceField( choices=PortTypeChoices, - widget=StaticSelect2() + widget=StaticSelect() + ) + color = ColorField( + required=False ) rear_port_set = forms.MultipleChoiceField( choices=[], @@ -1644,7 +1747,7 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm): help_text='Select one rear port assignment for each front port being created.', ) field_order = ( - 'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'rear_port_set', 'description', + 'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'color', 'rear_port_set', 'description', ) def __init__(self, *args, **kwargs): @@ -1706,7 +1809,10 @@ class FrontPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): type = forms.ChoiceField( choices=add_blank_choice(PortTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() + ) + color = ColorField( + required=False ) description = forms.CharField( required=False @@ -1721,18 +1827,21 @@ class RearPortTemplateForm(BootstrapMixin, forms.ModelForm): class Meta: model = RearPortTemplate fields = [ - 'device_type', 'name', 'label', 'type', 'positions', 'description', + 'device_type', 'name', 'label', 'type', 'color', 'positions', 'description', ] widgets = { 'device_type': forms.HiddenInput(), - 'type': StaticSelect2(), + 'type': StaticSelect(), } class RearPortTemplateCreateForm(ComponentTemplateCreateForm): type = forms.ChoiceField( choices=PortTypeChoices, - widget=StaticSelect2(), + widget=StaticSelect(), + ) + color = ColorField( + required=False ) positions = forms.IntegerField( min_value=REARPORT_POSITIONS_MIN, @@ -1740,7 +1849,9 @@ class RearPortTemplateCreateForm(ComponentTemplateCreateForm): initial=1, help_text='The number of front ports which may be mapped to each rear port' ) - field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'positions', 'description') + field_order = ( + 'manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'type', 'color', 'positions', 'description', + ) class RearPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): @@ -1755,7 +1866,10 @@ class RearPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): type = forms.ChoiceField( choices=add_blank_choice(PortTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() + ) + color = ColorField( + required=False ) description = forms.CharField( required=False @@ -1934,21 +2048,19 @@ class DeviceRoleCSVForm(CustomFieldModelCSVForm): class Meta: model = DeviceRole - fields = DeviceRole.csv_headers + fields = ('name', 'slug', 'color', 'vm_role', 'description') help_texts = { 'color': mark_safe('RGB color in hexadecimal (e.g. 00ff00)'), } -class DeviceRoleBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): +class DeviceRoleBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=DeviceRole.objects.all(), widget=forms.MultipleHiddenInput ) - color = forms.CharField( - max_length=6, # RGB color code - required=False, - widget=ColorSelect() + color = ColorField( + required=False ) vm_role = forms.NullBooleanField( required=False, @@ -1998,10 +2110,10 @@ class PlatformCSVForm(CustomFieldModelCSVForm): class Meta: model = Platform - fields = Platform.csv_headers + fields = ('name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description') -class PlatformBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): +class PlatformBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Platform.objects.all(), widget=forms.MultipleHiddenInput @@ -2024,6 +2136,21 @@ class PlatformBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['manufacturer', 'napalm_driver', 'description'] +class PlatformFilterForm(BootstrapMixin, CustomFieldModelFilterForm): + model = Platform + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) + manufacturer_id = DynamicModelMultipleChoiceField( + queryset=Manufacturer.objects.all(), + required=False, + label=_('Manufacturer'), + fetch_trigger='open' + ) + + # # Devices # @@ -2076,6 +2203,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): attrs={ 'disabled-indicator': 'device', 'data-query-param-face': "[\"$face\"]", + # The UI will not sort this element's options. + 'pre-sorted': '' } ) ) @@ -2141,10 +2270,10 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): "config context", } widgets = { - 'face': StaticSelect2(), - 'status': StaticSelect2(), - 'primary_ip4': StaticSelect2(), - 'primary_ip6': StaticSelect2(), + 'face': StaticSelect(), + 'status': StaticSelect(), + 'primary_ip4': StaticSelect(), + 'primary_ip6': StaticSelect(), } def __init__(self, *args, **kwargs): @@ -2362,7 +2491,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm): self.instance.rack = parent.rack -class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Device.objects.all(), widget=forms.MultipleHiddenInput() @@ -2404,7 +2533,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF status = forms.ChoiceField( choices=add_blank_choice(DeviceStatusChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) serial = forms.CharField( max_length=50, @@ -2418,25 +2547,39 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF ] -class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldFilterForm): +class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm): model = Device field_order = [ 'q', 'region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id', 'manufacturer_id', 'device_type_id', 'asset_tag', 'mac_address', 'has_primary_ip', ] + field_groups = [ + ['q', 'tag'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id'], + ['status', 'role_id', 'asset_tag', 'mac_address'], + ['manufacturer_id', 'device_type_id', 'platform_id'], + ['tenant_group_id', 'tenant_id'], + [ + 'has_primary_ip', 'virtual_chassis_member', 'console_ports', 'console_server_ports', 'power_ports', + 'power_outlets', 'interfaces', 'pass_through_ports', 'local_context_data', + ], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_group_id = DynamicModelMultipleChoiceField( queryset=SiteGroup.objects.all(), required=False, - label=_('Site group') + label=_('Site group'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -2445,7 +2588,8 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt 'region_id': '$region_id', 'group_id': '$site_group_id', }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) location_id = DynamicModelMultipleChoiceField( queryset=Location.objects.all(), @@ -2454,7 +2598,8 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt query_params={ 'site_id': '$site_id' }, - label=_('Location') + label=_('Location'), + fetch_trigger='open' ) rack_id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), @@ -2464,17 +2609,20 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt 'site_id': '$site_id', 'location_id': '$location_id', }, - label=_('Rack') + label=_('Rack'), + fetch_trigger='open' ) role_id = DynamicModelMultipleChoiceField( queryset=DeviceRole.objects.all(), required=False, - label=_('Role') + label=_('Role'), + fetch_trigger='open' ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, - label=_('Manufacturer') + label=_('Manufacturer'), + fetch_trigger='open' ) device_type_id = DynamicModelMultipleChoiceField( queryset=DeviceType.objects.all(), @@ -2482,18 +2630,20 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt query_params={ 'manufacturer_id': '$manufacturer_id' }, - label=_('Model') + label=_('Model'), + fetch_trigger='open' ) platform_id = DynamicModelMultipleChoiceField( queryset=Platform.objects.all(), required=False, null_option='None', - label=_('Platform') + label=_('Platform'), + fetch_trigger='open' ) status = forms.MultipleChoiceField( choices=DeviceStatusChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) asset_tag = forms.CharField( required=False @@ -2505,56 +2655,56 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt has_primary_ip = forms.NullBooleanField( required=False, label='Has a primary IP', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) virtual_chassis_member = forms.NullBooleanField( required=False, label='Virtual chassis member', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) console_ports = forms.NullBooleanField( required=False, label='Has console ports', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) console_server_ports = forms.NullBooleanField( required=False, label='Has console server ports', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) power_ports = forms.NullBooleanField( required=False, label='Has power ports', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) power_outlets = forms.NullBooleanField( required=False, label='Has power outlets', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) interfaces = forms.NullBooleanField( required=False, label='Has interfaces', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) pass_through_ports = forms.NullBooleanField( required=False, label='Has pass-through ports', - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) @@ -2565,7 +2715,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt # Device components # -class ComponentCreateForm(BootstrapMixin, CustomFieldForm, ComponentForm): +class ComponentCreateForm(BootstrapMixin, CustomFieldsMixin, ComponentForm): """ Base form for the creation of device components (models subclassed from ComponentModel). """ @@ -2582,7 +2732,7 @@ class ComponentCreateForm(BootstrapMixin, CustomFieldForm, ComponentForm): ) -class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldForm, ComponentForm): +class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentForm): pk = forms.ModelMultipleChoiceField( queryset=Device.objects.all(), widget=forms.MultipleHiddenInput() @@ -2604,15 +2754,20 @@ class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldForm, ComponentForm) class ConsolePortFilterForm(DeviceComponentFilterForm): model = ConsolePort + field_groups = [ + ['q', 'tag'], + ['name', 'label', 'type', 'speed'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] type = forms.MultipleChoiceField( choices=ConsolePortTypeChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) speed = forms.MultipleChoiceField( choices=ConsolePortSpeedChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) tag = TagFilterField(model) @@ -2638,12 +2793,12 @@ class ConsolePortCreateForm(ComponentCreateForm): type = forms.ChoiceField( choices=add_blank_choice(ConsolePortTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) speed = forms.ChoiceField( choices=add_blank_choice(ConsolePortSpeedChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'speed', 'mark_connected', 'description', 'tags') @@ -2660,7 +2815,7 @@ class ConsolePortBulkEditForm( form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=ConsolePort.objects.all(), @@ -2695,7 +2850,7 @@ class ConsolePortCSVForm(CustomFieldModelCSVForm): class Meta: model = ConsolePort - fields = ConsolePort.csv_headers + fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description') # @@ -2705,15 +2860,20 @@ class ConsolePortCSVForm(CustomFieldModelCSVForm): class ConsoleServerPortFilterForm(DeviceComponentFilterForm): model = ConsoleServerPort + field_groups = [ + ['q', 'tag'], + ['name', 'label', 'type', 'speed'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] type = forms.MultipleChoiceField( choices=ConsolePortTypeChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) speed = forms.MultipleChoiceField( choices=ConsolePortSpeedChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) tag = TagFilterField(model) @@ -2739,12 +2899,12 @@ class ConsoleServerPortCreateForm(ComponentCreateForm): type = forms.ChoiceField( choices=add_blank_choice(ConsolePortTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) speed = forms.ChoiceField( choices=add_blank_choice(ConsolePortSpeedChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'speed', 'mark_connected', 'description', 'tags') @@ -2761,7 +2921,7 @@ class ConsoleServerPortBulkEditForm( form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description']), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=ConsoleServerPort.objects.all(), @@ -2796,7 +2956,7 @@ class ConsoleServerPortCSVForm(CustomFieldModelCSVForm): class Meta: model = ConsoleServerPort - fields = ConsoleServerPort.csv_headers + fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description') # @@ -2806,10 +2966,15 @@ class ConsoleServerPortCSVForm(CustomFieldModelCSVForm): class PowerPortFilterForm(DeviceComponentFilterForm): model = PowerPort + field_groups = [ + ['q', 'tag'], + ['name', 'label', 'type'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] type = forms.MultipleChoiceField( choices=PowerPortTypeChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) tag = TagFilterField(model) @@ -2836,7 +3001,7 @@ class PowerPortCreateForm(ComponentCreateForm): type = forms.ChoiceField( choices=add_blank_choice(PowerPortTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) maximum_draw = forms.IntegerField( min_value=1, @@ -2866,7 +3031,7 @@ class PowerPortBulkEditForm( form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description']), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=PowerPort.objects.all(), @@ -2894,7 +3059,9 @@ class PowerPortCSVForm(CustomFieldModelCSVForm): class Meta: model = PowerPort - fields = PowerPort.csv_headers + fields = ( + 'device', 'name', 'label', 'type', 'mark_connected', 'maximum_draw', 'allocated_draw', 'description', + ) # @@ -2904,10 +3071,15 @@ class PowerPortCSVForm(CustomFieldModelCSVForm): class PowerOutletFilterForm(DeviceComponentFilterForm): model = PowerOutlet + field_groups = [ + ['q', 'tag'], + ['name', 'label', 'type'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] type = forms.MultipleChoiceField( choices=PowerOutletTypeChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) tag = TagFilterField(model) @@ -2946,7 +3118,7 @@ class PowerOutletCreateForm(ComponentCreateForm): type = forms.ChoiceField( choices=add_blank_choice(PowerOutletTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) power_port = forms.ModelChoiceField( queryset=PowerPort.objects.all(), @@ -2983,7 +3155,7 @@ class PowerOutletBulkEditForm( form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=PowerOutlet.objects.all(), @@ -3039,7 +3211,7 @@ class PowerOutletCSVForm(CustomFieldModelCSVForm): class Meta: model = PowerOutlet - fields = PowerOutlet.csv_headers + fields = ('device', 'name', 'label', 'type', 'mark_connected', 'power_port', 'feed_leg', 'description') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -3071,20 +3243,25 @@ class PowerOutletCSVForm(CustomFieldModelCSVForm): class InterfaceFilterForm(DeviceComponentFilterForm): model = Interface + field_groups = [ + ['q', 'tag'], + ['name', 'label', 'type', 'enabled', 'mgmt_only', 'mac_address'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] type = forms.MultipleChoiceField( choices=InterfaceTypeChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) enabled = forms.NullBooleanField( required=False, - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) mgmt_only = forms.NullBooleanField( required=False, - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) @@ -3143,8 +3320,8 @@ class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm): ] widgets = { 'device': forms.HiddenInput(), - 'type': StaticSelect2(), - 'mode': StaticSelect2(), + 'type': StaticSelect(), + 'mode': StaticSelect(), } labels = { 'mode': '802.1Q Mode', @@ -3175,7 +3352,7 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm): model = Interface type = forms.ChoiceField( choices=InterfaceTypeChoices, - widget=StaticSelect2(), + widget=StaticSelect(), ) enabled = forms.BooleanField( required=False, @@ -3208,7 +3385,7 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm): mode = forms.ChoiceField( choices=add_blank_choice(InterfaceModeChoices), required=False, - widget=StaticSelect2(), + widget=StaticSelect(), ) untagged_vlan = DynamicModelChoiceField( queryset=VLAN.objects.all(), @@ -3248,7 +3425,7 @@ class InterfaceBulkEditForm( ]), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=Interface.objects.all(), @@ -3378,7 +3555,10 @@ class InterfaceCSVForm(CustomFieldModelCSVForm): class Meta: model = Interface - fields = Interface.csv_headers + fields = ( + 'device', 'name', 'label', 'parent', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', 'mtu', + 'mgmt_only', 'description', 'mode', + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -3421,11 +3601,19 @@ class InterfaceCSVForm(CustomFieldModelCSVForm): # class FrontPortFilterForm(DeviceComponentFilterForm): + field_groups = [ + ['q', 'tag'], + ['name', 'label', 'type', 'color'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] model = FrontPort type = forms.MultipleChoiceField( choices=PortTypeChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() + ) + color = ColorField( + required=False ) tag = TagFilterField(model) @@ -3439,13 +3627,13 @@ class FrontPortForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = FrontPort fields = [ - 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'mark_connected', 'description', - 'tags', + 'device', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected', + 'description', 'tags', ] widgets = { 'device': forms.HiddenInput(), - 'type': StaticSelect2(), - 'rear_port': StaticSelect2(), + 'type': StaticSelect(), + 'rear_port': StaticSelect(), } def __init__(self, *args, **kwargs): @@ -3463,7 +3651,10 @@ class FrontPortCreateForm(ComponentCreateForm): model = FrontPort type = forms.ChoiceField( choices=PortTypeChoices, - widget=StaticSelect2(), + widget=StaticSelect(), + ) + color = ColorField( + required=False ) rear_port_set = forms.MultipleChoiceField( choices=[], @@ -3471,7 +3662,8 @@ class FrontPortCreateForm(ComponentCreateForm): help_text='Select one rear port assignment for each front port being created.', ) field_order = ( - 'device', 'name_pattern', 'label_pattern', 'type', 'rear_port_set', 'mark_connected', 'description', 'tags', + 'device', 'name_pattern', 'label_pattern', 'type', 'color', 'rear_port_set', 'mark_connected', 'description', + 'tags', ) def __init__(self, *args, **kwargs): @@ -3530,10 +3722,10 @@ class FrontPortCreateForm(ComponentCreateForm): class FrontPortBulkEditForm( - form_from_model(FrontPort, ['label', 'type', 'mark_connected', 'description']), + form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=FrontPort.objects.all(), @@ -3561,7 +3753,10 @@ class FrontPortCSVForm(CustomFieldModelCSVForm): class Meta: model = FrontPort - fields = FrontPort.csv_headers + fields = ( + 'device', 'name', 'label', 'type', 'color', 'mark_connected', 'rear_port', 'rear_port_position', + 'description', + ) help_texts = { 'rear_port_position': 'Mapped position on corresponding rear port', } @@ -3595,10 +3790,18 @@ class FrontPortCSVForm(CustomFieldModelCSVForm): class RearPortFilterForm(DeviceComponentFilterForm): model = RearPort + field_groups = [ + ['q', 'tag'], + ['name', 'label', 'type', 'color'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] type = forms.MultipleChoiceField( choices=PortTypeChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() + ) + color = ColorField( + required=False ) tag = TagFilterField(model) @@ -3612,11 +3815,11 @@ class RearPortForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = RearPort fields = [ - 'device', 'name', 'label', 'type', 'positions', 'mark_connected', 'description', 'tags', + 'device', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags', ] widgets = { 'device': forms.HiddenInput(), - 'type': StaticSelect2(), + 'type': StaticSelect(), } @@ -3624,7 +3827,10 @@ class RearPortCreateForm(ComponentCreateForm): model = RearPort type = forms.ChoiceField( choices=PortTypeChoices, - widget=StaticSelect2(), + widget=StaticSelect(), + ) + color = ColorField( + required=False ) positions = forms.IntegerField( min_value=REARPORT_POSITIONS_MIN, @@ -3633,12 +3839,13 @@ class RearPortCreateForm(ComponentCreateForm): help_text='The number of front ports which may be mapped to each rear port' ) field_order = ( - 'device', 'name_pattern', 'label_pattern', 'type', 'positions', 'mark_connected', 'description', 'tags', + 'device', 'name_pattern', 'label_pattern', 'type', 'color', 'positions', 'mark_connected', 'description', + 'tags', ) class RearPortBulkCreateForm( - form_from_model(RearPort, ['type', 'positions', 'mark_connected']), + form_from_model(RearPort, ['type', 'color', 'positions', 'mark_connected']), DeviceBulkAddComponentForm ): model = RearPort @@ -3646,10 +3853,10 @@ class RearPortBulkCreateForm( class RearPortBulkEditForm( - form_from_model(RearPort, ['label', 'type', 'mark_connected', 'description']), + form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=RearPort.objects.all(), @@ -3672,7 +3879,7 @@ class RearPortCSVForm(CustomFieldModelCSVForm): class Meta: model = RearPort - fields = RearPort.csv_headers + fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description') help_texts = { 'positions': 'Number of front ports which may be mapped' } @@ -3684,6 +3891,11 @@ class RearPortCSVForm(CustomFieldModelCSVForm): class DeviceBayFilterForm(DeviceComponentFilterForm): model = DeviceBay + field_groups = [ + ['q', 'tag'], + ['name', 'label'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] tag = TagFilterField(model) @@ -3713,7 +3925,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form): queryset=Device.objects.all(), label='Child Device', help_text="Child devices must first be created and assigned to the site/rack of the parent device.", - widget=StaticSelect2(), + widget=StaticSelect(), ) def __init__(self, device_bay, *args, **kwargs): @@ -3738,7 +3950,7 @@ class DeviceBayBulkEditForm( form_from_model(DeviceBay, ['label', 'description']), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=DeviceBay.objects.all(), @@ -3766,7 +3978,7 @@ class DeviceBayCSVForm(CustomFieldModelCSVForm): class Meta: model = DeviceBay - fields = DeviceBay.csv_headers + fields = ('device', 'name', 'label', 'installed_device', 'description') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -3878,7 +4090,9 @@ class InventoryItemCSVForm(CustomFieldModelCSVForm): class Meta: model = InventoryItem - fields = InventoryItem.csv_headers + fields = ( + 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -3911,7 +4125,7 @@ class InventoryItemBulkEditForm( form_from_model(InventoryItem, ['label', 'manufacturer', 'part_id', 'description']), BootstrapMixin, AddRemoveTagsForm, - CustomFieldBulkEditForm + CustomFieldModelBulkEditForm ): pk = forms.ModelMultipleChoiceField( queryset=InventoryItem.objects.all(), @@ -3928,10 +4142,16 @@ class InventoryItemBulkEditForm( class InventoryItemFilterForm(DeviceComponentFilterForm): model = InventoryItem + field_groups = [ + ['q', 'tag'], + ['name', 'label', 'manufacturer_id', 'serial', 'asset_tag', 'discovered'], + ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], + ] manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, - label=_('Manufacturer') + label=_('Manufacturer'), + fetch_trigger='open' ) serial = forms.CharField( required=False @@ -3941,7 +4161,7 @@ class InventoryItemFilterForm(DeviceComponentFilterForm): ) discovered = forms.NullBooleanField( required=False, - widget=StaticSelect2( + widget=StaticSelect( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) @@ -4016,9 +4236,9 @@ class ConnectCableToDeviceForm(BootstrapMixin, CustomFieldModelForm): 'termination_b_id', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags', ] widgets = { - 'status': StaticSelect2, - 'type': StaticSelect2, - 'length_unit': StaticSelect2, + 'status': StaticSelect, + 'type': StaticSelect, + 'length_unit': StaticSelect, } def clean_termination_b_id(self): @@ -4236,9 +4456,9 @@ class CableForm(BootstrapMixin, CustomFieldModelForm): 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags', ] widgets = { - 'status': StaticSelect2, - 'type': StaticSelect2, - 'length_unit': StaticSelect2, + 'status': StaticSelect, + 'type': StaticSelect, + 'length_unit': StaticSelect, } error_messages = { 'length': { @@ -4342,7 +4562,7 @@ class CableCSVForm(CustomFieldModelCSVForm): return length_unit if length_unit is not None else '' -class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Cable.objects.all(), widget=forms.MultipleHiddenInput @@ -4351,22 +4571,20 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFo choices=add_blank_choice(CableTypeChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) status = forms.ChoiceField( choices=add_blank_choice(CableStatusChoices), required=False, - widget=StaticSelect2(), + widget=StaticSelect(), initial='' ) label = forms.CharField( max_length=100, required=False ) - color = forms.CharField( - max_length=6, # RGB color code - required=False, - widget=ColorSelect() + color = ColorField( + required=False ) length = forms.IntegerField( min_value=1, @@ -4376,7 +4594,7 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFo choices=add_blank_choice(CableLengthUnitChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) class Meta: @@ -4396,16 +4614,24 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFo }) -class CableFilterForm(BootstrapMixin, CustomFieldFilterForm): +class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Cable + field_groups = [ + ['q', 'tag'], + ['site_id', 'rack_id', 'device_id'], + ['type', 'status', 'color'], + ['tenant_id'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -4413,12 +4639,14 @@ class CableFilterForm(BootstrapMixin, CustomFieldFilterForm): query_params={ 'region_id': '$region_id' }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) tenant_id = DynamicModelMultipleChoiceField( queryset=Tenant.objects.all(), required=False, - label=_('Tenant') + label=_('Tenant'), + fetch_trigger='open' ) rack_id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), @@ -4427,22 +4655,21 @@ class CableFilterForm(BootstrapMixin, CustomFieldFilterForm): null_option='None', query_params={ 'site_id': '$site_id' - } + }, + fetch_trigger='open' ) type = forms.MultipleChoiceField( choices=add_blank_choice(CableTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) status = forms.ChoiceField( required=False, choices=add_blank_choice(CableStatusChoices), - widget=StaticSelect2() + widget=StaticSelect() ) - color = forms.CharField( - max_length=6, # RGB color code - required=False, - widget=ColorSelect() + color = ColorField( + required=False ) device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), @@ -4452,7 +4679,8 @@ class CableFilterForm(BootstrapMixin, CustomFieldFilterForm): 'tenant_id': '$tenant_id', 'rack_id': '$rack_id', }, - label=_('Device') + label=_('Device'), + fetch_trigger='open' ) tag = TagFilterField(model) @@ -4465,7 +4693,8 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form): region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -4473,7 +4702,8 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form): query_params={ 'region_id': '$region_id' }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), @@ -4481,7 +4711,8 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form): query_params={ 'site_id': '$site_id' }, - label=_('Device') + label=_('Device'), + fetch_trigger='open' ) @@ -4489,7 +4720,8 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form): region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -4497,7 +4729,8 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form): query_params={ 'region_id': '$region_id' }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), @@ -4505,7 +4738,8 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form): query_params={ 'site_id': '$site_id' }, - label=_('Device') + label=_('Device'), + fetch_trigger='open' ) @@ -4513,7 +4747,8 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form): region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -4521,7 +4756,8 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form): query_params={ 'region_id': '$region_id' }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), @@ -4529,7 +4765,8 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form): query_params={ 'site_id': '$site_id' }, - label=_('Device') + label=_('Device'), + fetch_trigger='open' ) @@ -4672,6 +4909,10 @@ class DeviceVCMembershipForm(forms.ModelForm): # Require VC position (only required when the Device is a VirtualChassis member) self.fields['vc_position'].required = True + # Add bootstrap classes to form elements. + self.fields['vc_position'].widget.attrs = {'class': 'form-control'} + self.fields['vc_priority'].widget.attrs = {'class': 'form-control'} + # Validation of vc_position is optional. This is only required when adding a new member to an existing # VirtualChassis. Otherwise, vc_position validation is handled by BaseVCMemberFormSet. self.validate_vc_position = validate_vc_position @@ -4741,7 +4982,7 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form): return device -class VirtualChassisBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class VirtualChassisBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=VirtualChassis.objects.all(), widget=forms.MultipleHiddenInput() @@ -4765,33 +5006,43 @@ class VirtualChassisCSVForm(CustomFieldModelCSVForm): class Meta: model = VirtualChassis - fields = VirtualChassis.csv_headers + fields = ('name', 'domain', 'master') -class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): +class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = VirtualChassis field_order = ['q', 'region_id', 'site_group_id', 'site_id', 'tenant_group_id', 'tenant_id'] + field_groups = [ + ['q', 'tag'], + ['region_id', 'site_group_id', 'site_id'], + ['tenant_group_id', 'tenant_id'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_group_id = DynamicModelMultipleChoiceField( queryset=SiteGroup.objects.all(), required=False, - label=_('Site group') + label=_('Site group'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), required=False, query_params={ - 'region_id': '$region_id' + 'region_id': '$region_id', + 'group_id': '$site_group_id', }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) tag = TagFilterField(model) @@ -4858,7 +5109,7 @@ class PowerPanelCSVForm(CustomFieldModelCSVForm): class Meta: model = PowerPanel - fields = PowerPanel.csv_headers + fields = ('site', 'location', 'name') def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) @@ -4870,7 +5121,7 @@ class PowerPanelCSVForm(CustomFieldModelCSVForm): self.fields['location'].queryset = self.fields['location'].queryset.filter(**params) -class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=PowerPanel.objects.all(), widget=forms.MultipleHiddenInput @@ -4909,29 +5160,38 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE nullable_fields = ['location'] -class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm): +class PowerPanelFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = PowerPanel + field_groups = ( + ('q', 'tag'), + ('region_id', 'site_group_id', 'site_id', 'location_id') + ) q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_group_id = DynamicModelMultipleChoiceField( queryset=SiteGroup.objects.all(), required=False, - label=_('Site group') + label=_('Site group'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), required=False, query_params={ - 'region_id': '$region_id' + 'region_id': '$region_id', + 'group_id': '$site_group_id', }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) location_id = DynamicModelMultipleChoiceField( queryset=Location.objects.all(), @@ -4940,7 +5200,8 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm): query_params={ 'site_id': '$site_id' }, - label=_('Location') + label=_('Location'), + fetch_trigger='open' ) tag = TagFilterField(model) @@ -5006,10 +5267,10 @@ class PowerFeedForm(BootstrapMixin, CustomFieldModelForm): ('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')), ) widgets = { - 'status': StaticSelect2(), - 'type': StaticSelect2(), - 'supply': StaticSelect2(), - 'phase': StaticSelect2(), + 'status': StaticSelect(), + 'type': StaticSelect(), + 'supply': StaticSelect(), + 'phase': StaticSelect(), } @@ -5059,7 +5320,10 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm): class Meta: model = PowerFeed - fields = PowerFeed.csv_headers + fields = ( + 'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', + 'voltage', 'amperage', 'max_utilization', 'comments', + ) def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) @@ -5082,7 +5346,7 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm): self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params) -class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=PowerFeed.objects.all(), widget=forms.MultipleHiddenInput @@ -5099,25 +5363,25 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd choices=add_blank_choice(PowerFeedStatusChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) type = forms.ChoiceField( choices=add_blank_choice(PowerFeedTypeChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) supply = forms.ChoiceField( choices=add_blank_choice(PowerFeedSupplyChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) phase = forms.ChoiceField( choices=add_blank_choice(PowerFeedPhaseChoices), required=False, initial='', - widget=StaticSelect2() + widget=StaticSelect() ) voltage = forms.IntegerField( required=False @@ -5143,21 +5407,30 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd ] -class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm): +class PowerFeedFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = PowerFeed + field_groups = [ + ['q', 'tag'], + ['region_id', 'site_group_id', 'site_id'], + ['power_panel_id', 'rack_id'], + ['status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization'], + ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, - label=_('Region') + label=_('Region'), + fetch_trigger='open' ) site_group_id = DynamicModelMultipleChoiceField( queryset=SiteGroup.objects.all(), required=False, - label=_('Site group') + label=_('Site group'), + fetch_trigger='open' ) site_id = DynamicModelMultipleChoiceField( queryset=Site.objects.all(), @@ -5165,7 +5438,8 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm): query_params={ 'region_id': '$region_id' }, - label=_('Site') + label=_('Site'), + fetch_trigger='open' ) power_panel_id = DynamicModelMultipleChoiceField( queryset=PowerPanel.objects.all(), @@ -5174,7 +5448,8 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm): query_params={ 'site_id': '$site_id' }, - label=_('Power panel') + label=_('Power panel'), + fetch_trigger='open' ) rack_id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), @@ -5183,27 +5458,28 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm): query_params={ 'site_id': '$site_id' }, - label=_('Rack') + label=_('Rack'), + fetch_trigger='open' ) status = forms.MultipleChoiceField( choices=PowerFeedStatusChoices, required=False, - widget=StaticSelect2Multiple() + widget=StaticSelectMultiple() ) type = forms.ChoiceField( choices=add_blank_choice(PowerFeedTypeChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) supply = forms.ChoiceField( choices=add_blank_choice(PowerFeedSupplyChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) phase = forms.ChoiceField( choices=add_blank_choice(PowerFeedPhaseChoices), required=False, - widget=StaticSelect2() + widget=StaticSelect() ) voltage = forms.IntegerField( required=False diff --git a/netbox/secrets/migrations/__init__.py b/netbox/dcim/graphql/__init__.py similarity index 100% rename from netbox/secrets/migrations/__init__.py rename to netbox/dcim/graphql/__init__.py diff --git a/netbox/dcim/graphql/schema.py b/netbox/dcim/graphql/schema.py new file mode 100644 index 000000000..13e0c20ec --- /dev/null +++ b/netbox/dcim/graphql/schema.py @@ -0,0 +1,105 @@ +import graphene + +from netbox.graphql.fields import ObjectField, ObjectListField +from .types import * + + +class DCIMQuery(graphene.ObjectType): + cable = ObjectField(CableType) + cable_list = ObjectListField(CableType) + + console_port = ObjectField(ConsolePortType) + console_port_list = ObjectListField(ConsolePortType) + + console_port_template = ObjectField(ConsolePortTemplateType) + console_port_template_list = ObjectListField(ConsolePortTemplateType) + + console_server_port = ObjectField(ConsoleServerPortType) + console_server_port_list = ObjectListField(ConsoleServerPortType) + + console_server_port_template = ObjectField(ConsoleServerPortTemplateType) + console_server_port_template_list = ObjectListField(ConsoleServerPortTemplateType) + + device = ObjectField(DeviceType) + device_list = ObjectListField(DeviceType) + + device_bay = ObjectField(DeviceBayType) + device_bay_list = ObjectListField(DeviceBayType) + + device_bay_template = ObjectField(DeviceBayTemplateType) + device_bay_template_list = ObjectListField(DeviceBayTemplateType) + + device_role = ObjectField(DeviceRoleType) + device_role_list = ObjectListField(DeviceRoleType) + + device_type = ObjectField(DeviceTypeType) + device_type_list = ObjectListField(DeviceTypeType) + + front_port = ObjectField(FrontPortType) + front_port_list = ObjectListField(FrontPortType) + + front_port_template = ObjectField(FrontPortTemplateType) + front_port_template_list = ObjectListField(FrontPortTemplateType) + + interface = ObjectField(InterfaceType) + interface_list = ObjectListField(InterfaceType) + + interface_template = ObjectField(InterfaceTemplateType) + interface_template_list = ObjectListField(InterfaceTemplateType) + + inventory_item = ObjectField(InventoryItemType) + inventory_item_list = ObjectListField(InventoryItemType) + + location = ObjectField(LocationType) + location_list = ObjectListField(LocationType) + + manufacturer = ObjectField(ManufacturerType) + manufacturer_list = ObjectListField(ManufacturerType) + + platform = ObjectField(PlatformType) + platform_list = ObjectListField(PlatformType) + + power_feed = ObjectField(PowerFeedType) + power_feed_list = ObjectListField(PowerFeedType) + + power_outlet = ObjectField(PowerOutletType) + power_outlet_list = ObjectListField(PowerOutletType) + + power_outlet_template = ObjectField(PowerOutletTemplateType) + power_outlet_template_list = ObjectListField(PowerOutletTemplateType) + + power_panel = ObjectField(PowerPanelType) + power_panel_list = ObjectListField(PowerPanelType) + + power_port = ObjectField(PowerPortType) + power_port_list = ObjectListField(PowerPortType) + + power_port_template = ObjectField(PowerPortTemplateType) + power_port_template_list = ObjectListField(PowerPortTemplateType) + + rack = ObjectField(RackType) + rack_list = ObjectListField(RackType) + + rack_reservation = ObjectField(RackReservationType) + rack_reservation_list = ObjectListField(RackReservationType) + + rack_role = ObjectField(RackRoleType) + rack_role_list = ObjectListField(RackRoleType) + + rear_port = ObjectField(RearPortType) + rear_port_list = ObjectListField(RearPortType) + + rear_port_template = ObjectField(RearPortTemplateType) + rear_port_template_list = ObjectListField(RearPortTemplateType) + + region = ObjectField(RegionType) + region_list = ObjectListField(RegionType) + + site = ObjectField(SiteType) + site_list = ObjectListField(SiteType) + + site_group = ObjectField(SiteGroupType) + site_group_list = ObjectListField(SiteGroupType) + + virtual_chassis = ObjectField(VirtualChassisType) + virtual_chassis_list = ObjectListField(VirtualChassisType) diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py new file mode 100644 index 000000000..be10556be --- /dev/null +++ b/netbox/dcim/graphql/types.py @@ -0,0 +1,391 @@ +from dcim import filtersets, models +from extras.graphql.mixins import ( + ChangelogMixin, ConfigContextMixin, CustomFieldsMixin, ImageAttachmentsMixin, TagsMixin, +) +from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin +from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, PrimaryObjectType + +__all__ = ( + 'CableType', + 'ComponentObjectType', + 'ConsolePortType', + 'ConsolePortTemplateType', + 'ConsoleServerPortType', + 'ConsoleServerPortTemplateType', + 'DeviceType', + 'DeviceBayType', + 'DeviceBayTemplateType', + 'DeviceRoleType', + 'DeviceTypeType', + 'FrontPortType', + 'FrontPortTemplateType', + 'InterfaceType', + 'InterfaceTemplateType', + 'InventoryItemType', + 'LocationType', + 'ManufacturerType', + 'PlatformType', + 'PowerFeedType', + 'PowerOutletType', + 'PowerOutletTemplateType', + 'PowerPanelType', + 'PowerPortType', + 'PowerPortTemplateType', + 'RackType', + 'RackReservationType', + 'RackRoleType', + 'RearPortType', + 'RearPortTemplateType', + 'RegionType', + 'SiteType', + 'SiteGroupType', + 'VirtualChassisType', +) + + +# +# Base types +# + + +class ComponentObjectType( + ChangelogMixin, + CustomFieldsMixin, + TagsMixin, + BaseObjectType +): + """ + Base type for device/VM components + """ + class Meta: + abstract = True + + +class ComponentTemplateObjectType( + ChangelogMixin, + BaseObjectType +): + """ + Base type for device/VM components + """ + class Meta: + abstract = True + + +# +# Model types +# + +class CableType(PrimaryObjectType): + + class Meta: + model = models.Cable + fields = '__all__' + filterset_class = filtersets.CableFilterSet + + def resolve_type(self, info): + return self.type or None + + def resolve_length_unit(self, info): + return self.length_unit or None + + +class ConsolePortType(ComponentObjectType): + + class Meta: + model = models.ConsolePort + exclude = ('_path',) + filterset_class = filtersets.ConsolePortFilterSet + + def resolve_type(self, info): + return self.type or None + + +class ConsolePortTemplateType(ComponentTemplateObjectType): + + class Meta: + model = models.ConsolePortTemplate + fields = '__all__' + filterset_class = filtersets.ConsolePortTemplateFilterSet + + def resolve_type(self, info): + return self.type or None + + +class ConsoleServerPortType(ComponentObjectType): + + class Meta: + model = models.ConsoleServerPort + exclude = ('_path',) + filterset_class = filtersets.ConsoleServerPortFilterSet + + def resolve_type(self, info): + return self.type or None + + +class ConsoleServerPortTemplateType(ComponentTemplateObjectType): + + class Meta: + model = models.ConsoleServerPortTemplate + fields = '__all__' + filterset_class = filtersets.ConsoleServerPortTemplateFilterSet + + def resolve_type(self, info): + return self.type or None + + +class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, PrimaryObjectType): + + class Meta: + model = models.Device + fields = '__all__' + filterset_class = filtersets.DeviceFilterSet + + def resolve_face(self, info): + return self.face or None + + +class DeviceBayType(ComponentObjectType): + + class Meta: + model = models.DeviceBay + fields = '__all__' + filterset_class = filtersets.DeviceBayFilterSet + + +class DeviceBayTemplateType(ComponentTemplateObjectType): + + class Meta: + model = models.DeviceBayTemplate + fields = '__all__' + filterset_class = filtersets.DeviceBayTemplateFilterSet + + +class DeviceRoleType(OrganizationalObjectType): + + class Meta: + model = models.DeviceRole + fields = '__all__' + filterset_class = filtersets.DeviceRoleFilterSet + + +class DeviceTypeType(PrimaryObjectType): + + class Meta: + model = models.DeviceType + fields = '__all__' + filterset_class = filtersets.DeviceTypeFilterSet + + def resolve_subdevice_role(self, info): + return self.subdevice_role or None + + +class FrontPortType(ComponentObjectType): + + class Meta: + model = models.FrontPort + fields = '__all__' + filterset_class = filtersets.FrontPortFilterSet + + +class FrontPortTemplateType(ComponentTemplateObjectType): + + class Meta: + model = models.FrontPortTemplate + fields = '__all__' + filterset_class = filtersets.FrontPortTemplateFilterSet + + +class InterfaceType(IPAddressesMixin, ComponentObjectType): + + class Meta: + model = models.Interface + exclude = ('_path',) + filterset_class = filtersets.InterfaceFilterSet + + def resolve_mode(self, info): + return self.mode or None + + +class InterfaceTemplateType(ComponentTemplateObjectType): + + class Meta: + model = models.InterfaceTemplate + fields = '__all__' + filterset_class = filtersets.InterfaceTemplateFilterSet + + +class InventoryItemType(ComponentObjectType): + + class Meta: + model = models.InventoryItem + fields = '__all__' + filterset_class = filtersets.InventoryItemFilterSet + + +class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, OrganizationalObjectType): + + class Meta: + model = models.Location + fields = '__all__' + filterset_class = filtersets.LocationFilterSet + + +class ManufacturerType(OrganizationalObjectType): + + class Meta: + model = models.Manufacturer + fields = '__all__' + filterset_class = filtersets.ManufacturerFilterSet + + +class PlatformType(OrganizationalObjectType): + + class Meta: + model = models.Platform + fields = '__all__' + filterset_class = filtersets.PlatformFilterSet + + +class PowerFeedType(PrimaryObjectType): + + class Meta: + model = models.PowerFeed + exclude = ('_path',) + filterset_class = filtersets.PowerFeedFilterSet + + +class PowerOutletType(ComponentObjectType): + + class Meta: + model = models.PowerOutlet + exclude = ('_path',) + filterset_class = filtersets.PowerOutletFilterSet + + def resolve_feed_leg(self, info): + return self.feed_leg or None + + def resolve_type(self, info): + return self.type or None + + +class PowerOutletTemplateType(ComponentTemplateObjectType): + + class Meta: + model = models.PowerOutletTemplate + fields = '__all__' + filterset_class = filtersets.PowerOutletTemplateFilterSet + + def resolve_feed_leg(self, info): + return self.feed_leg or None + + def resolve_type(self, info): + return self.type or None + + +class PowerPanelType(PrimaryObjectType): + + class Meta: + model = models.PowerPanel + fields = '__all__' + filterset_class = filtersets.PowerPanelFilterSet + + +class PowerPortType(ComponentObjectType): + + class Meta: + model = models.PowerPort + exclude = ('_path',) + filterset_class = filtersets.PowerPortFilterSet + + def resolve_type(self, info): + return self.type or None + + +class PowerPortTemplateType(ComponentTemplateObjectType): + + class Meta: + model = models.PowerPortTemplate + fields = '__all__' + filterset_class = filtersets.PowerPortTemplateFilterSet + + def resolve_type(self, info): + return self.type or None + + +class RackType(VLANGroupsMixin, ImageAttachmentsMixin, PrimaryObjectType): + + class Meta: + model = models.Rack + fields = '__all__' + filterset_class = filtersets.RackFilterSet + + def resolve_type(self, info): + return self.type or None + + def resolve_outer_unit(self, info): + return self.outer_unit or None + + +class RackReservationType(PrimaryObjectType): + + class Meta: + model = models.RackReservation + fields = '__all__' + filterset_class = filtersets.RackReservationFilterSet + + +class RackRoleType(OrganizationalObjectType): + + class Meta: + model = models.RackRole + fields = '__all__' + filterset_class = filtersets.RackRoleFilterSet + + +class RearPortType(ComponentObjectType): + + class Meta: + model = models.RearPort + fields = '__all__' + filterset_class = filtersets.RearPortFilterSet + + +class RearPortTemplateType(ComponentTemplateObjectType): + + class Meta: + model = models.RearPortTemplate + fields = '__all__' + filterset_class = filtersets.RearPortTemplateFilterSet + + +class RegionType(VLANGroupsMixin, OrganizationalObjectType): + + class Meta: + model = models.Region + fields = '__all__' + filterset_class = filtersets.RegionFilterSet + + +class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, PrimaryObjectType): + + class Meta: + model = models.Site + fields = '__all__' + filterset_class = filtersets.SiteFilterSet + + +class SiteGroupType(VLANGroupsMixin, OrganizationalObjectType): + + class Meta: + model = models.SiteGroup + fields = '__all__' + filterset_class = filtersets.SiteGroupFilterSet + + +class VirtualChassisType(PrimaryObjectType): + + class Meta: + model = models.VirtualChassis + fields = '__all__' + filterset_class = filtersets.VirtualChassisFilterSet diff --git a/netbox/dcim/migrations/0001_initial.py b/netbox/dcim/migrations/0001_initial.py deleted file mode 100644 index db5f3faf2..000000000 --- a/netbox/dcim/migrations/0001_initial.py +++ /dev/null @@ -1,296 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-06-22 18:21 -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import utilities.fields - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='ConsolePort', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ('connection_status', models.NullBooleanField(choices=[[False, b'Planned'], [True, b'Connected']], default=True)), - ], - options={ - 'ordering': ['device', 'name'], - }, - ), - migrations.CreateModel( - name='ConsolePortTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ], - options={ - 'ordering': ['device_type', 'name'], - }, - ), - migrations.CreateModel( - name='ConsoleServerPort', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ], - ), - migrations.CreateModel( - name='ConsoleServerPortTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ], - options={ - 'ordering': ['device_type', 'name'], - }, - ), - migrations.CreateModel( - name='Device', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('name', utilities.fields.NullableCharField(blank=True, max_length=50, null=True, unique=True)), - ('serial', models.CharField(blank=True, max_length=50, verbose_name=b'Serial number')), - ('position', models.PositiveSmallIntegerField(blank=True, help_text=b'Number of the lowest U position occupied by the device', null=True, validators=[django.core.validators.MinValueValidator(1)], verbose_name=b'Position (U)')), - ('face', models.PositiveSmallIntegerField(blank=True, choices=[[0, b'Front'], [1, b'Rear']], null=True, verbose_name=b'Rack face')), - ('status', models.BooleanField(choices=[[True, b'Active'], [False, b'Offline']], default=True, verbose_name=b'Status')), - ('comments', models.TextField(blank=True)), - ], - options={ - 'ordering': ['name'], - }, - ), - migrations.CreateModel( - name='DeviceRole', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, unique=True)), - ('slug', models.SlugField(unique=True)), - ('color', models.CharField(choices=[[b'teal', b'Teal'], [b'green', b'Green'], [b'blue', b'Blue'], [b'purple', b'Purple'], [b'yellow', b'Yellow'], [b'orange', b'Orange'], [b'red', b'Red'], [b'light_gray', b'Light Gray'], [b'medium_gray', b'Medium Gray'], [b'dark_gray', b'Dark Gray']], max_length=30)), - ], - options={ - 'ordering': ['name'], - }, - ), - migrations.CreateModel( - name='DeviceType', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('model', models.CharField(max_length=50)), - ('slug', models.SlugField()), - ('u_height', models.PositiveSmallIntegerField(default=1, verbose_name=b'Height (U)')), - ('is_full_depth', models.BooleanField(default=True, help_text=b'Device consumes both front and rear rack faces', verbose_name=b'Is full depth')), - ('is_console_server', models.BooleanField(default=False, help_text=b'This type of device has console server ports', verbose_name=b'Is a console server')), - ('is_pdu', models.BooleanField(default=False, help_text=b'This type of device has power outlets', verbose_name=b'Is a PDU')), - ('is_network_device', models.BooleanField(default=True, help_text=b'This type of device has network interfaces', verbose_name=b'Is a network device')), - ], - options={ - 'ordering': ['manufacturer', 'model'], - }, - ), - migrations.CreateModel( - name='Interface', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ('form_factor', models.PositiveSmallIntegerField(choices=[[0, b'Virtual'], [800, b'10/100M (Copper)'], [1000, b'1GE (Copper)'], [1100, b'1GE (SFP)'], [1200, b'10GE (SFP+)'], [1300, b'10GE (XFP)'], [1400, b'40GE (QSFP+)']], default=1200)), - ('mgmt_only', models.BooleanField(default=False, help_text=b'This interface is used only for out-of-band management', verbose_name=b'OOB Management')), - ('description', models.CharField(blank=True, max_length=100)), - ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.Device')), - ], - options={ - 'ordering': ['device', 'name'], - }, - ), - migrations.CreateModel( - name='InterfaceConnection', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('connection_status', models.BooleanField(choices=[[False, b'Planned'], [True, b'Connected']], default=True, verbose_name=b'Status')), - ('interface_a', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='connected_as_a', to='dcim.Interface')), - ('interface_b', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='connected_as_b', to='dcim.Interface')), - ], - ), - migrations.CreateModel( - name='InterfaceTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ('form_factor', models.PositiveSmallIntegerField(choices=[[0, b'Virtual'], [800, b'10/100M (Copper)'], [1000, b'1GE (Copper)'], [1100, b'1GE (SFP)'], [1200, b'10GE (SFP+)'], [1300, b'10GE (XFP)'], [1400, b'40GE (QSFP+)']], default=1200)), - ('mgmt_only', models.BooleanField(default=False, verbose_name=b'Management only')), - ('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interface_templates', to='dcim.DeviceType')), - ], - options={ - 'ordering': ['device_type', 'name'], - }, - ), - migrations.CreateModel( - name='Manufacturer', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, unique=True)), - ('slug', models.SlugField(unique=True)), - ], - options={ - 'ordering': ['name'], - }, - ), - migrations.CreateModel( - name='Module', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, verbose_name=b'Name')), - ('part_id', models.CharField(blank=True, max_length=50, verbose_name=b'Part ID')), - ('serial', models.CharField(blank=True, max_length=50, verbose_name=b'Serial number')), - ('discovered', models.BooleanField(default=False, verbose_name=b'Discovered')), - ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='dcim.Device')), - ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='submodules', to='dcim.Module')), - ], - options={ - 'ordering': ['device__id', 'parent__id', 'name'], - }, - ), - migrations.CreateModel( - name='Platform', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, unique=True)), - ('slug', models.SlugField(unique=True)), - ('rpc_client', models.CharField(blank=True, choices=[[b'juniper-junos', b'Juniper Junos (NETCONF)'], [b'cisco-ios', b'Cisco IOS (SSH)'], [b'opengear', b'Opengear (SSH)']], max_length=30, verbose_name=b'RPC client')), - ], - options={ - 'ordering': ['name'], - }, - ), - migrations.CreateModel( - name='PowerOutlet', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='power_outlets', to='dcim.Device')), - ], - ), - migrations.CreateModel( - name='PowerOutletTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='power_outlet_templates', to='dcim.DeviceType')), - ], - options={ - 'ordering': ['device_type', 'name'], - }, - ), - migrations.CreateModel( - name='PowerPort', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ('connection_status', models.NullBooleanField(choices=[[False, b'Planned'], [True, b'Connected']], default=True)), - ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='power_ports', to='dcim.Device')), - ('power_outlet', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connected_port', to='dcim.PowerOutlet')), - ], - options={ - 'ordering': ['device', 'name'], - }, - ), - migrations.CreateModel( - name='PowerPortTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='power_port_templates', to='dcim.DeviceType')), - ], - options={ - 'ordering': ['device_type', 'name'], - }, - ), - migrations.CreateModel( - name='Rack', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('name', models.CharField(max_length=50)), - ('facility_id', utilities.fields.NullableCharField(blank=True, max_length=30, null=True, verbose_name=b'Facility ID')), - ('u_height', models.PositiveSmallIntegerField(default=42, verbose_name=b'Height (U)')), - ('comments', models.TextField(blank=True)), - ], - options={ - 'ordering': ['site', 'name'], - }, - ), - migrations.CreateModel( - name='RackGroup', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50)), - ('slug', models.SlugField()), - ], - options={ - 'ordering': ['site', 'name'], - }, - ), - migrations.CreateModel( - name='Site', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('name', models.CharField(max_length=50, unique=True)), - ('slug', models.SlugField(unique=True)), - ('facility', models.CharField(blank=True, max_length=50)), - ('asn', models.PositiveIntegerField(blank=True, null=True, verbose_name=b'ASN')), - ('physical_address', models.CharField(blank=True, max_length=200)), - ('shipping_address', models.CharField(blank=True, max_length=200)), - ('comments', models.TextField(blank=True)), - ], - options={ - 'ordering': ['name'], - }, - ), - migrations.AddField( - model_name='rackgroup', - name='site', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rack_groups', to='dcim.Site'), - ), - migrations.AddField( - model_name='rack', - name='group', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='racks', to='dcim.RackGroup'), - ), - migrations.AddField( - model_name='rack', - name='site', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.Site'), - ), - migrations.AddField( - model_name='devicetype', - name='manufacturer', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='device_types', to='dcim.Manufacturer'), - ), - migrations.AddField( - model_name='device', - name='device_role', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.DeviceRole'), - ), - migrations.AddField( - model_name='device', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='instances', to='dcim.DeviceType'), - ), - migrations.AddField( - model_name='device', - name='platform', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='dcim.Platform'), - ), - ] diff --git a/netbox/dcim/migrations/0001_squashed.py b/netbox/dcim/migrations/0001_squashed.py new file mode 100644 index 000000000..bb99d199f --- /dev/null +++ b/netbox/dcim/migrations/0001_squashed.py @@ -0,0 +1,663 @@ +import dcim.fields +import django.contrib.postgres.fields +import django.core.serializers.json +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import timezone_field.fields +import utilities.fields +import utilities.ordering +import utilities.query_functions +import utilities.validators + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + replaces = [ + ('dcim', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Cable', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('termination_a_id', models.PositiveIntegerField()), + ('termination_b_id', models.PositiveIntegerField()), + ('type', models.CharField(blank=True, max_length=50)), + ('status', models.CharField(default='connected', max_length=50)), + ('label', models.CharField(blank=True, max_length=100)), + ('color', utilities.fields.ColorField(blank=True, max_length=6)), + ('length', models.PositiveSmallIntegerField(blank=True, null=True)), + ('length_unit', models.CharField(blank=True, max_length=50)), + ('_abs_length', models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True)), + ], + options={ + 'ordering': ['pk'], + }, + ), + migrations.CreateModel( + name='CablePath', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('origin_id', models.PositiveIntegerField()), + ('destination_id', models.PositiveIntegerField(blank=True, null=True)), + ('path', dcim.fields.PathField(base_field=models.CharField(max_length=40), size=None)), + ('is_active', models.BooleanField(default=False)), + ('is_split', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='ConsolePort', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('type', models.CharField(blank=True, max_length=50)), + ('speed', models.PositiveSmallIntegerField(blank=True, null=True)), + ], + options={ + 'ordering': ('device', '_name'), + }, + ), + migrations.CreateModel( + name='ConsolePortTemplate', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('type', models.CharField(blank=True, max_length=50)), + ], + options={ + 'ordering': ('device_type', '_name'), + }, + ), + migrations.CreateModel( + name='ConsoleServerPort', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('type', models.CharField(blank=True, max_length=50)), + ('speed', models.PositiveSmallIntegerField(blank=True, null=True)), + ], + options={ + 'ordering': ('device', '_name'), + }, + ), + migrations.CreateModel( + name='ConsoleServerPortTemplate', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('type', models.CharField(blank=True, max_length=50)), + ], + options={ + 'ordering': ('device_type', '_name'), + }, + ), + migrations.CreateModel( + name='Device', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('local_context_data', models.JSONField(blank=True, null=True)), + ('name', models.CharField(blank=True, max_length=64, null=True)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize, null=True)), + ('serial', models.CharField(blank=True, max_length=50)), + ('asset_tag', models.CharField(blank=True, max_length=50, null=True, unique=True)), + ('position', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])), + ('face', models.CharField(blank=True, max_length=50)), + ('status', models.CharField(default='active', max_length=50)), + ('vc_position', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)])), + ('vc_priority', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)])), + ('comments', models.TextField(blank=True)), + ], + options={ + 'ordering': ('_name', 'pk'), + }, + ), + migrations.CreateModel( + name='DeviceBay', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ], + options={ + 'ordering': ('device', '_name'), + }, + ), + migrations.CreateModel( + name='DeviceBayTemplate', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ], + options={ + 'ordering': ('device_type', '_name'), + }, + ), + migrations.CreateModel( + name='DeviceRole', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('color', utilities.fields.ColorField(default='9e9e9e', max_length=6)), + ('vm_role', models.BooleanField(default=True)), + ('description', models.CharField(blank=True, max_length=200)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='DeviceType', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('model', models.CharField(max_length=100)), + ('slug', models.SlugField(max_length=100)), + ('part_number', models.CharField(blank=True, max_length=50)), + ('u_height', models.PositiveSmallIntegerField(default=1)), + ('is_full_depth', models.BooleanField(default=True)), + ('subdevice_role', models.CharField(blank=True, max_length=50)), + ('front_image', models.ImageField(blank=True, upload_to='devicetype-images')), + ('rear_image', models.ImageField(blank=True, upload_to='devicetype-images')), + ('comments', models.TextField(blank=True)), + ], + options={ + 'ordering': ['manufacturer', 'model'], + }, + ), + migrations.CreateModel( + name='FrontPort', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('type', models.CharField(max_length=50)), + ('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)])), + ], + options={ + 'ordering': ('device', '_name'), + }, + ), + migrations.CreateModel( + name='FrontPortTemplate', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('type', models.CharField(max_length=50)), + ('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)])), + ], + options={ + 'ordering': ('device_type', '_name'), + }, + ), + migrations.CreateModel( + name='Interface', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('enabled', models.BooleanField(default=True)), + ('mac_address', dcim.fields.MACAddressField(blank=True, null=True)), + ('mtu', models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65536)])), + ('mode', models.CharField(blank=True, max_length=50)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface)), + ('type', models.CharField(max_length=50)), + ('mgmt_only', models.BooleanField(default=False)), + ], + options={ + 'ordering': ('device', utilities.query_functions.CollateAsChar('_name')), + }, + ), + migrations.CreateModel( + name='InterfaceTemplate', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface)), + ('type', models.CharField(max_length=50)), + ('mgmt_only', models.BooleanField(default=False)), + ], + options={ + 'ordering': ('device_type', '_name'), + }, + ), + migrations.CreateModel( + name='InventoryItem', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('part_id', models.CharField(blank=True, max_length=50)), + ('serial', models.CharField(blank=True, max_length=50)), + ('asset_tag', models.CharField(blank=True, max_length=50, null=True, unique=True)), + ('discovered', models.BooleanField(default=False)), + ('lft', models.PositiveIntegerField(editable=False)), + ('rght', models.PositiveIntegerField(editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(editable=False)), + ], + options={ + 'ordering': ('device__id', 'parent__id', '_name'), + }, + ), + migrations.CreateModel( + name='Location', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('slug', models.SlugField(max_length=100)), + ('description', models.CharField(blank=True, max_length=200)), + ('lft', models.PositiveIntegerField(editable=False)), + ('rght', models.PositiveIntegerField(editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(editable=False)), + ], + options={ + 'ordering': ['site', 'name'], + }, + ), + migrations.CreateModel( + name='Manufacturer', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('description', models.CharField(blank=True, max_length=200)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='Platform', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('napalm_driver', models.CharField(blank=True, max_length=50)), + ('napalm_args', models.JSONField(blank=True, null=True)), + ('description', models.CharField(blank=True, max_length=200)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='PowerFeed', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('name', models.CharField(max_length=100)), + ('status', models.CharField(default='active', max_length=50)), + ('type', models.CharField(default='primary', max_length=50)), + ('supply', models.CharField(default='ac', max_length=50)), + ('phase', models.CharField(default='single-phase', max_length=50)), + ('voltage', models.SmallIntegerField(default=120, validators=[utilities.validators.ExclusionValidator([0])])), + ('amperage', models.PositiveSmallIntegerField(default=20, validators=[django.core.validators.MinValueValidator(1)])), + ('max_utilization', models.PositiveSmallIntegerField(default=80, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])), + ('available_power', models.PositiveIntegerField(default=0, editable=False)), + ('comments', models.TextField(blank=True)), + ], + options={ + 'ordering': ['power_panel', 'name'], + }, + ), + migrations.CreateModel( + name='PowerOutlet', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('type', models.CharField(blank=True, max_length=50)), + ('feed_leg', models.CharField(blank=True, max_length=50)), + ], + options={ + 'ordering': ('device', '_name'), + }, + ), + migrations.CreateModel( + name='PowerOutletTemplate', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('type', models.CharField(blank=True, max_length=50)), + ('feed_leg', models.CharField(blank=True, max_length=50)), + ], + options={ + 'ordering': ('device_type', '_name'), + }, + ), + migrations.CreateModel( + name='PowerPanel', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ], + options={ + 'ordering': ['site', 'name'], + }, + ), + migrations.CreateModel( + name='PowerPort', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('type', models.CharField(blank=True, max_length=50)), + ('maximum_draw', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])), + ('allocated_draw', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])), + ], + options={ + 'ordering': ('device', '_name'), + }, + ), + migrations.CreateModel( + name='PowerPortTemplate', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('type', models.CharField(blank=True, max_length=50)), + ('maximum_draw', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])), + ('allocated_draw', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])), + ], + options={ + 'ordering': ('device_type', '_name'), + }, + ), + migrations.CreateModel( + name='Rack', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('facility_id', models.CharField(blank=True, max_length=50, null=True)), + ('status', models.CharField(default='active', max_length=50)), + ('serial', models.CharField(blank=True, max_length=50)), + ('asset_tag', models.CharField(blank=True, max_length=50, null=True, unique=True)), + ('type', models.CharField(blank=True, max_length=50)), + ('width', models.PositiveSmallIntegerField(default=19)), + ('u_height', models.PositiveSmallIntegerField(default=42, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])), + ('desc_units', models.BooleanField(default=False)), + ('outer_width', models.PositiveSmallIntegerField(blank=True, null=True)), + ('outer_depth', models.PositiveSmallIntegerField(blank=True, null=True)), + ('outer_unit', models.CharField(blank=True, max_length=50)), + ('comments', models.TextField(blank=True)), + ], + options={ + 'ordering': ('site', 'location', '_name', 'pk'), + }, + ), + migrations.CreateModel( + name='RackReservation', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('units', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(), size=None)), + ('description', models.CharField(max_length=200)), + ], + options={ + 'ordering': ['created', 'pk'], + }, + ), + migrations.CreateModel( + name='RackRole', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('color', utilities.fields.ColorField(default='9e9e9e', max_length=6)), + ('description', models.CharField(blank=True, max_length=200)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='RearPort', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)), + ('mark_connected', models.BooleanField(default=False)), + ('type', models.CharField(max_length=50)), + ('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)])), + ], + options={ + 'ordering': ('device', '_name'), + }, + ), + migrations.CreateModel( + name='RearPortTemplate', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('label', models.CharField(blank=True, max_length=64)), + ('description', models.CharField(blank=True, max_length=200)), + ('type', models.CharField(max_length=50)), + ('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)])), + ], + options={ + 'ordering': ('device_type', '_name'), + }, + ), + migrations.CreateModel( + name='Region', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('description', models.CharField(blank=True, max_length=200)), + ('lft', models.PositiveIntegerField(editable=False)), + ('rght', models.PositiveIntegerField(editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(editable=False)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Site', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('status', models.CharField(default='active', max_length=50)), + ('facility', models.CharField(blank=True, max_length=50)), + ('asn', dcim.fields.ASNField(blank=True, null=True)), + ('time_zone', timezone_field.fields.TimeZoneField(blank=True)), + ('description', models.CharField(blank=True, max_length=200)), + ('physical_address', models.CharField(blank=True, max_length=200)), + ('shipping_address', models.CharField(blank=True, max_length=200)), + ('latitude', models.DecimalField(blank=True, decimal_places=6, max_digits=8, null=True)), + ('longitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)), + ('contact_name', models.CharField(blank=True, max_length=50)), + ('contact_phone', models.CharField(blank=True, max_length=20)), + ('contact_email', models.EmailField(blank=True, max_length=254)), + ('comments', models.TextField(blank=True)), + ], + options={ + 'ordering': ('_name',), + }, + ), + migrations.CreateModel( + name='SiteGroup', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100, unique=True)), + ('slug', models.SlugField(max_length=100, unique=True)), + ('description', models.CharField(blank=True, max_length=200)), + ('lft', models.PositiveIntegerField(editable=False)), + ('rght', models.PositiveIntegerField(editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(editable=False)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='VirtualChassis', + fields=[ + ('created', models.DateField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('domain', models.CharField(blank=True, max_length=30)), + ('master', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vc_master_for', to='dcim.device')), + ], + options={ + 'verbose_name_plural': 'virtual chassis', + 'ordering': ['name'], + }, + ), + ] diff --git a/netbox/dcim/migrations/0002_auto_20160622_1821.py b/netbox/dcim/migrations/0002_auto_20160622_1821.py deleted file mode 100644 index 1e3aa4d2a..000000000 --- a/netbox/dcim/migrations/0002_auto_20160622_1821.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-06-22 18:21 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('dcim', '0001_initial'), - ('ipam', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='primary_ip', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_for', to='ipam.IPAddress', verbose_name=b'Primary IP'), - ), - migrations.AddField( - model_name='device', - name='rack', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Rack'), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cs_port_templates', to='dcim.DeviceType'), - ), - migrations.AddField( - model_name='consoleserverport', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cs_ports', to='dcim.Device'), - ), - migrations.AddField( - model_name='consoleporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='console_port_templates', to='dcim.DeviceType'), - ), - migrations.AddField( - model_name='consoleport', - name='cs_port', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connected_console', to='dcim.ConsoleServerPort', verbose_name=b'Console server port'), - ), - migrations.AddField( - model_name='consoleport', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='console_ports', to='dcim.Device'), - ), - migrations.AlterUniqueTogether( - name='rackgroup', - unique_together=set([('site', 'name'), ('site', 'slug')]), - ), - migrations.AlterUniqueTogether( - name='rack', - unique_together=set([('site', 'facility_id'), ('site', 'name')]), - ), - migrations.AlterUniqueTogether( - name='powerporttemplate', - unique_together=set([('device_type', 'name')]), - ), - migrations.AlterUniqueTogether( - name='powerport', - unique_together=set([('device', 'name')]), - ), - migrations.AlterUniqueTogether( - name='poweroutlettemplate', - unique_together=set([('device_type', 'name')]), - ), - migrations.AlterUniqueTogether( - name='poweroutlet', - unique_together=set([('device', 'name')]), - ), - migrations.AlterUniqueTogether( - name='module', - unique_together=set([('device', 'parent', 'name')]), - ), - migrations.AlterUniqueTogether( - name='interfacetemplate', - unique_together=set([('device_type', 'name')]), - ), - migrations.AlterUniqueTogether( - name='interface', - unique_together=set([('device', 'name')]), - ), - migrations.AlterUniqueTogether( - name='devicetype', - unique_together=set([('manufacturer', 'slug'), ('manufacturer', 'model')]), - ), - migrations.AlterUniqueTogether( - name='device', - unique_together=set([('rack', 'position', 'face')]), - ), - migrations.AlterUniqueTogether( - name='consoleserverporttemplate', - unique_together=set([('device_type', 'name')]), - ), - migrations.AlterUniqueTogether( - name='consoleserverport', - unique_together=set([('device', 'name')]), - ), - migrations.AlterUniqueTogether( - name='consoleporttemplate', - unique_together=set([('device_type', 'name')]), - ), - migrations.AlterUniqueTogether( - name='consoleport', - unique_together=set([('device', 'name')]), - ), - ] diff --git a/netbox/dcim/migrations/0002_squashed.py b/netbox/dcim/migrations/0002_squashed.py new file mode 100644 index 000000000..a1b6db30a --- /dev/null +++ b/netbox/dcim/migrations/0002_squashed.py @@ -0,0 +1,313 @@ +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import mptt.fields +import taggit.managers + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0002_remove_content_type_name'), + ('extras', '0001_initial'), + ('tenancy', '0001_initial'), + ] + + replaces = [ + ('dcim', '0002_auto_20160622_1821'), + ] + + operations = [ + migrations.AddField( + model_name='virtualchassis', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='sitegroup', + name='parent', + field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.sitegroup'), + ), + migrations.AddField( + model_name='site', + name='group', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='dcim.sitegroup'), + ), + migrations.AddField( + model_name='site', + name='region', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='dcim.region'), + ), + migrations.AddField( + model_name='site', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='site', + name='tenant', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sites', to='tenancy.tenant'), + ), + migrations.AddField( + model_name='region', + name='parent', + field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.region'), + ), + migrations.AddField( + model_name='rearporttemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearporttemplates', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='rearport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='rearport', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='rearport', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearports', to='dcim.device'), + ), + migrations.AddField( + model_name='rearport', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='rackreservation', + name='rack', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='dcim.rack'), + ), + migrations.AddField( + model_name='rackreservation', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='rackreservation', + name='tenant', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='rackreservations', to='tenancy.tenant'), + ), + migrations.AddField( + model_name='rackreservation', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='rack', + name='location', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='racks', to='dcim.location'), + ), + migrations.AddField( + model_name='rack', + name='role', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.rackrole'), + ), + migrations.AddField( + model_name='rack', + name='site', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.site'), + ), + migrations.AddField( + model_name='rack', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='rack', + name='tenant', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='tenancy.tenant'), + ), + migrations.AddField( + model_name='powerporttemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='powerporttemplates', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='powerport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='powerport', + name='_path', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), + ), + migrations.AddField( + model_name='powerport', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='powerport', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='powerports', to='dcim.device'), + ), + migrations.AddField( + model_name='powerport', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='powerpanel', + name='location', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.location'), + ), + migrations.AddField( + model_name='powerpanel', + name='site', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dcim.site'), + ), + migrations.AddField( + model_name='powerpanel', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='poweroutlettemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlettemplates', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='poweroutlettemplate', + name='power_port', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlet_templates', to='dcim.powerporttemplate'), + ), + migrations.AddField( + model_name='poweroutlet', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='poweroutlet', + name='_path', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), + ), + migrations.AddField( + model_name='poweroutlet', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='poweroutlet', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlets', to='dcim.device'), + ), + migrations.AddField( + model_name='poweroutlet', + name='power_port', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlets', to='dcim.powerport'), + ), + migrations.AddField( + model_name='poweroutlet', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='powerfeed', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='powerfeed', + name='_path', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), + ), + migrations.AddField( + model_name='powerfeed', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='powerfeed', + name='power_panel', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='powerfeeds', to='dcim.powerpanel'), + ), + migrations.AddField( + model_name='powerfeed', + name='rack', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.rack'), + ), + migrations.AddField( + model_name='powerfeed', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='platform', + name='manufacturer', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='platforms', to='dcim.manufacturer'), + ), + migrations.AddField( + model_name='location', + name='parent', + field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.location'), + ), + migrations.AddField( + model_name='location', + name='site', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='dcim.site'), + ), + migrations.AddField( + model_name='inventoryitem', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventoryitems', to='dcim.device'), + ), + migrations.AddField( + model_name='inventoryitem', + name='manufacturer', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_items', to='dcim.manufacturer'), + ), + migrations.AddField( + model_name='inventoryitem', + name='parent', + field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.inventoryitem'), + ), + migrations.AddField( + model_name='inventoryitem', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='interfacetemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfacetemplates', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='interface', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='interface', + name='_path', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), + ), + migrations.AddField( + model_name='interface', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='interface', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.device'), + ), + migrations.AddField( + model_name='interface', + name='lag', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='member_interfaces', to='dcim.interface'), + ), + migrations.AddField( + model_name='interface', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='child_interfaces', to='dcim.interface'), + ), + ] diff --git a/netbox/dcim/migrations/0003_auto_20160628_1721.py b/netbox/dcim/migrations/0003_auto_20160628_1721.py deleted file mode 100644 index 312d0456c..000000000 --- a/netbox/dcim/migrations/0003_auto_20160628_1721.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-06-28 17:21 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0002_auto_20160622_1821'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[0, b'Virtual'], [800, b'10/100M (100BASE-TX)'], [1000, b'1GE (1000BASE-T)'], [1100, b'1GE (SFP)'], [1150, b'10GE (10GBASE-T)'], [1200, b'10GE (SFP+)'], [1300, b'10GE (XFP)'], [1400, b'40GE (QSFP+)']], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[0, b'Virtual'], [800, b'10/100M (100BASE-TX)'], [1000, b'1GE (1000BASE-T)'], [1100, b'1GE (SFP)'], [1150, b'10GE (10GBASE-T)'], [1200, b'10GE (SFP+)'], [1300, b'10GE (XFP)'], [1400, b'40GE (QSFP+)']], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0003_squashed_0130.py b/netbox/dcim/migrations/0003_squashed_0130.py new file mode 100644 index 000000000..48ea238d9 --- /dev/null +++ b/netbox/dcim/migrations/0003_squashed_0130.py @@ -0,0 +1,485 @@ +from django.db import migrations, models +import django.db.models.deletion +import taggit.managers + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0002_auto_20160622_1821'), + ('virtualization', '0001_virtualization'), + ('contenttypes', '0002_remove_content_type_name'), + ('ipam', '0001_initial'), + ('tenancy', '0001_initial'), + ('extras', '0002_custom_fields'), + ] + + replaces = [ + ('dcim', '0003_auto_20160628_1721'), + ('dcim', '0004_auto_20160701_2049'), + ('dcim', '0005_auto_20160706_1722'), + ('dcim', '0006_add_device_primary_ip4_ip6'), + ('dcim', '0007_device_copy_primary_ip'), + ('dcim', '0008_device_remove_primary_ip'), + ('dcim', '0009_site_32bit_asn_support'), + ('dcim', '0010_devicebay_installed_device_set_null'), + ('dcim', '0011_devicetype_part_number'), + ('dcim', '0012_site_rack_device_add_tenant'), + ('dcim', '0013_add_interface_form_factors'), + ('dcim', '0014_rack_add_type_width'), + ('dcim', '0015_rack_add_u_height_validator'), + ('dcim', '0016_module_add_manufacturer'), + ('dcim', '0017_rack_add_role'), + ('dcim', '0018_device_add_asset_tag'), + ('dcim', '0019_new_iface_form_factors'), + ('dcim', '0020_rack_desc_units'), + ('dcim', '0021_add_ff_flexstack'), + ('dcim', '0022_color_names_to_rgb'), + ('dcim', '0023_devicetype_comments'), + ('dcim', '0024_site_add_contact_fields'), + ('dcim', '0025_devicetype_add_interface_ordering'), + ('dcim', '0026_add_rack_reservations'), + ('dcim', '0027_device_add_site'), + ('dcim', '0028_device_copy_rack_to_site'), + ('dcim', '0029_allow_rackless_devices'), + ('dcim', '0030_interface_add_lag'), + ('dcim', '0031_regions'), + ('dcim', '0032_device_increase_name_length'), + ('dcim', '0033_rackreservation_rack_editable'), + ('dcim', '0034_rename_module_to_inventoryitem'), + ('dcim', '0035_device_expand_status_choices'), + ('dcim', '0036_add_ff_juniper_vcp'), + ('dcim', '0037_unicode_literals'), + ('dcim', '0038_wireless_interfaces'), + ('dcim', '0039_interface_add_enabled_mtu'), + ('dcim', '0040_inventoryitem_add_asset_tag_description'), + ('dcim', '0041_napalm_integration'), + ('dcim', '0042_interface_ff_10ge_cx4'), + ('dcim', '0043_device_component_name_lengths'), + ('dcim', '0044_virtualization'), + ('dcim', '0045_devicerole_vm_role'), + ('dcim', '0046_rack_lengthen_facility_id'), + ('dcim', '0047_more_100ge_form_factors'), + ('dcim', '0048_rack_serial'), + ('dcim', '0049_rackreservation_change_user'), + ('dcim', '0050_interface_vlan_tagging'), + ('dcim', '0051_rackreservation_tenant'), + ('dcim', '0052_virtual_chassis'), + ('dcim', '0053_platform_manufacturer'), + ('dcim', '0054_site_status_timezone_description'), + ('dcim', '0055_virtualchassis_ordering'), + ('dcim', '0056_django2'), + ('dcim', '0057_tags'), + ('dcim', '0058_relax_rack_naming_constraints'), + ('dcim', '0059_site_latitude_longitude'), + ('dcim', '0060_change_logging'), + ('dcim', '0061_platform_napalm_args'), + ('dcim', '0062_interface_mtu'), + ('dcim', '0063_device_local_context_data'), + ('dcim', '0064_remove_platform_rpc_client'), + ('dcim', '0065_front_rear_ports'), + ('dcim', '0066_cables'), + ('dcim', '0067_device_type_remove_qualifiers'), + ('dcim', '0068_rack_new_fields'), + ('dcim', '0069_deprecate_nullablecharfield'), + ('dcim', '0070_custom_tag_models'), + ('dcim', '0071_device_components_add_description'), + ('dcim', '0072_powerfeeds'), + ('dcim', '0073_interface_form_factor_to_type'), + ('dcim', '0074_increase_field_length_platform_name_slug'), + ('dcim', '0075_cable_devices'), + ('dcim', '0076_console_port_types'), + ('dcim', '0077_power_types'), + ('dcim', '0078_3569_site_fields'), + ('dcim', '0079_3569_rack_fields'), + ('dcim', '0080_3569_devicetype_fields'), + ('dcim', '0081_3569_device_fields'), + ('dcim', '0082_3569_interface_fields'), + ('dcim', '0082_3569_port_fields'), + ('dcim', '0083_3569_cable_fields'), + ('dcim', '0084_3569_powerfeed_fields'), + ('dcim', '0085_3569_poweroutlet_fields'), + ('dcim', '0086_device_name_nonunique'), + ('dcim', '0087_role_descriptions'), + ('dcim', '0088_powerfeed_available_power'), + ('dcim', '0089_deterministic_ordering'), + ('dcim', '0090_cable_termination_models'), + ('dcim', '0091_interface_type_other'), + ('dcim', '0092_fix_rack_outer_unit'), + ('dcim', '0093_device_component_ordering'), + ('dcim', '0094_device_component_template_ordering'), + ('dcim', '0095_primary_model_ordering'), + ('dcim', '0096_interface_ordering'), + ('dcim', '0097_interfacetemplate_type_other'), + ('dcim', '0098_devicetype_images'), + ('dcim', '0099_powerfeed_negative_voltage'), + ('dcim', '0100_mptt_remove_indexes'), + ('dcim', '0101_nested_rackgroups'), + ('dcim', '0102_nested_rackgroups_rebuild'), + ('dcim', '0103_standardize_description'), + ('dcim', '0104_correct_infiniband_types'), + ('dcim', '0105_interface_name_collation'), + ('dcim', '0106_role_default_color'), + ('dcim', '0107_component_labels'), + ('dcim', '0108_add_tags'), + ('dcim', '0109_interface_remove_vm'), + ('dcim', '0110_virtualchassis_name'), + ('dcim', '0111_component_template_description'), + ('dcim', '0112_standardize_components'), + ('dcim', '0113_nullbooleanfield_to_booleanfield'), + ('dcim', '0114_update_jsonfield'), + ('dcim', '0115_rackreservation_order'), + ('dcim', '0116_rearport_max_positions'), + ('dcim', '0117_custom_field_data'), + ('dcim', '0118_inventoryitem_mptt'), + ('dcim', '0119_inventoryitem_mptt_rebuild'), + ('dcim', '0120_cache_cable_peer'), + ('dcim', '0121_cablepath'), + ('dcim', '0122_standardize_name_length'), + ('dcim', '0123_standardize_models'), + ('dcim', '0124_mark_connected'), + ('dcim', '0125_console_port_speed'), + ('dcim', '0126_rename_rackgroup_location'), + ('dcim', '0127_device_location'), + ('dcim', '0128_device_location_populate'), + ('dcim', '0129_interface_parent'), + ('dcim', '0130_sitegroup'), + ] + + operations = [ + migrations.AddField( + model_name='interface', + name='tagged_vlans', + field=models.ManyToManyField(blank=True, related_name='interfaces_as_tagged', to='ipam.VLAN'), + ), + migrations.AddField( + model_name='interface', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='interface', + name='untagged_vlan', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='interfaces_as_untagged', to='ipam.vlan'), + ), + migrations.AddField( + model_name='frontporttemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontporttemplates', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='frontporttemplate', + name='rear_port', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontport_templates', to='dcim.rearporttemplate'), + ), + migrations.AddField( + model_name='frontport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='frontport', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='frontport', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.device'), + ), + migrations.AddField( + model_name='frontport', + name='rear_port', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.rearport'), + ), + migrations.AddField( + model_name='frontport', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='devicetype', + name='manufacturer', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='device_types', to='dcim.manufacturer'), + ), + migrations.AddField( + model_name='devicetype', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='devicebaytemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devicebaytemplates', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='devicebay', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devicebays', to='dcim.device'), + ), + migrations.AddField( + model_name='devicebay', + name='installed_device', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parent_bay', to='dcim.device'), + ), + migrations.AddField( + model_name='devicebay', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='device', + name='cluster', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='virtualization.cluster'), + ), + migrations.AddField( + model_name='device', + name='device_role', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.devicerole'), + ), + migrations.AddField( + model_name='device', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='instances', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='device', + name='location', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.location'), + ), + migrations.AddField( + model_name='device', + name='platform', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='dcim.platform'), + ), + migrations.AddField( + model_name='device', + name='primary_ip4', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.ipaddress'), + ), + migrations.AddField( + model_name='device', + name='primary_ip6', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.ipaddress'), + ), + migrations.AddField( + model_name='device', + name='rack', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.rack'), + ), + migrations.AddField( + model_name='device', + name='site', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.site'), + ), + migrations.AddField( + model_name='device', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='device', + name='tenant', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='tenancy.tenant'), + ), + migrations.AddField( + model_name='device', + name='virtual_chassis', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='dcim.virtualchassis'), + ), + migrations.AddField( + model_name='consoleserverporttemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverporttemplates', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='consoleserverport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='consoleserverport', + name='_path', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), + ), + migrations.AddField( + model_name='consoleserverport', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='consoleserverport', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverports', to='dcim.device'), + ), + migrations.AddField( + model_name='consoleserverport', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='consoleporttemplate', + name='device_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleporttemplates', to='dcim.devicetype'), + ), + migrations.AddField( + model_name='consoleport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='consoleport', + name='_path', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), + ), + migrations.AddField( + model_name='consoleport', + name='cable', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'), + ), + migrations.AddField( + model_name='consoleport', + name='device', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleports', to='dcim.device'), + ), + migrations.AddField( + model_name='consoleport', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='cablepath', + name='destination_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='cablepath', + name='origin_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='cable', + name='_termination_a_device', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.device'), + ), + migrations.AddField( + model_name='cable', + name='_termination_b_device', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.device'), + ), + migrations.AddField( + model_name='cable', + name='tags', + field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), + ), + migrations.AddField( + model_name='cable', + name='termination_a_type', + field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='cable', + name='termination_b_type', + field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AlterUniqueTogether( + name='rearporttemplate', + unique_together={('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='rearport', + unique_together={('device', 'name')}, + ), + migrations.AlterUniqueTogether( + name='rack', + unique_together={('location', 'facility_id'), ('location', 'name')}, + ), + migrations.AlterUniqueTogether( + name='powerporttemplate', + unique_together={('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='powerport', + unique_together={('device', 'name')}, + ), + migrations.AlterUniqueTogether( + name='powerpanel', + unique_together={('site', 'name')}, + ), + migrations.AlterUniqueTogether( + name='poweroutlettemplate', + unique_together={('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='poweroutlet', + unique_together={('device', 'name')}, + ), + migrations.AlterUniqueTogether( + name='powerfeed', + unique_together={('power_panel', 'name')}, + ), + migrations.AlterUniqueTogether( + name='location', + unique_together={('site', 'name'), ('site', 'slug')}, + ), + migrations.AlterUniqueTogether( + name='inventoryitem', + unique_together={('device', 'parent', 'name')}, + ), + migrations.AlterUniqueTogether( + name='interfacetemplate', + unique_together={('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='interface', + unique_together={('device', 'name')}, + ), + migrations.AlterUniqueTogether( + name='frontporttemplate', + unique_together={('rear_port', 'rear_port_position'), ('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='frontport', + unique_together={('device', 'name'), ('rear_port', 'rear_port_position')}, + ), + migrations.AlterUniqueTogether( + name='devicetype', + unique_together={('manufacturer', 'model'), ('manufacturer', 'slug')}, + ), + migrations.AlterUniqueTogether( + name='devicebaytemplate', + unique_together={('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='devicebay', + unique_together={('device', 'name')}, + ), + migrations.AlterUniqueTogether( + name='device', + unique_together={('rack', 'position', 'face'), ('virtual_chassis', 'vc_position'), ('site', 'tenant', 'name')}, + ), + migrations.AlterUniqueTogether( + name='consoleserverporttemplate', + unique_together={('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='consoleserverport', + unique_together={('device', 'name')}, + ), + migrations.AlterUniqueTogether( + name='consoleporttemplate', + unique_together={('device_type', 'name')}, + ), + migrations.AlterUniqueTogether( + name='consoleport', + unique_together={('device', 'name')}, + ), + migrations.AlterUniqueTogether( + name='cablepath', + unique_together={('origin_type', 'origin_id')}, + ), + migrations.AlterUniqueTogether( + name='cable', + unique_together={('termination_b_type', 'termination_b_id'), ('termination_a_type', 'termination_a_id')}, + ), + ] diff --git a/netbox/dcim/migrations/0004_auto_20160701_2049.py b/netbox/dcim/migrations/0004_auto_20160701_2049.py deleted file mode 100644 index 0806acb82..000000000 --- a/netbox/dcim/migrations/0004_auto_20160701_2049.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-01 20:49 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0003_auto_20160628_1721'), - ] - - operations = [ - migrations.CreateModel( - name='DeviceBay', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, verbose_name=b'Name')), - ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_bays', to='dcim.Device')), - ('installed_device', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_bay', to='dcim.Device')), - ], - options={ - 'ordering': ['device', 'name'], - }, - ), - migrations.CreateModel( - name='DeviceBayTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30)), - ], - options={ - 'ordering': ['device_type', 'name'], - }, - ), - migrations.AddField( - model_name='devicetype', - name='subdevice_role', - field=models.NullBooleanField(choices=[(None, b'N/A'), (True, b'Parent'), (False, b'Child')], default=None, help_text=b'Parent devices house child devices in device bays. Select "None" if this device type is neither a parent nor a child.', verbose_name=b'Parent/child status'), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_bay_templates', to='dcim.DeviceType'), - ), - migrations.AlterUniqueTogether( - name='devicebaytemplate', - unique_together=set([('device_type', 'name')]), - ), - migrations.AlterUniqueTogether( - name='devicebay', - unique_together=set([('device', 'name')]), - ), - ] diff --git a/netbox/dcim/migrations/0005_auto_20160706_1722.py b/netbox/dcim/migrations/0005_auto_20160706_1722.py deleted file mode 100644 index a286d6ff3..000000000 --- a/netbox/dcim/migrations/0005_auto_20160706_1722.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-06 17:22 -import dcim.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0004_auto_20160701_2049'), - ] - - operations = [ - migrations.AddField( - model_name='interface', - name='mac_address', - field=dcim.fields.MACAddressField(blank=True, null=True, verbose_name=b'MAC Address'), - ), - migrations.AlterField( - model_name='devicetype', - name='subdevice_role', - field=models.NullBooleanField(choices=[(None, b'None'), (True, b'Parent'), (False, b'Child')], default=None, help_text=b'Parent devices house child devices in device bays. Select "None" if this device type is neither a parent nor a child.', verbose_name=b'Parent/child status'), - ), - ] diff --git a/netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py b/netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py deleted file mode 100644 index 6038cc027..000000000 --- a/netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-11 18:40 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('ipam', '0001_initial'), - ('dcim', '0005_auto_20160706_1722'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='primary_ip4', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.IPAddress', verbose_name=b'Primary IPv4'), - ), - migrations.AddField( - model_name='device', - name='primary_ip6', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.IPAddress', verbose_name=b'Primary IPv6'), - ), - ] diff --git a/netbox/dcim/migrations/0007_device_copy_primary_ip.py b/netbox/dcim/migrations/0007_device_copy_primary_ip.py deleted file mode 100644 index 0d53337f7..000000000 --- a/netbox/dcim/migrations/0007_device_copy_primary_ip.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-11 18:40 -from django.db import migrations - - -def copy_primary_ip(apps, schema_editor): - Device = apps.get_model('dcim', 'Device') - for d in Device.objects.select_related('primary_ip'): - if not d.primary_ip: - continue - if d.primary_ip.family == 4: - d.primary_ip4 = d.primary_ip - elif d.primary_ip.family == 6: - d.primary_ip6 = d.primary_ip - d.save() - - -def restore_primary_ip(apps, schema_editor): - Device = apps.get_model('dcim', 'Device') - for d in Device.objects.select_related('primary_ip4', 'primary_ip6'): - if d.primary_ip: - continue - # Prefer IPv6 over IPv4 - if d.primary_ip6: - d.primary_ip = d.primary_ip6 - elif d.primary_ip4: - d.primary_ip = d.primary_ip4 - d.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0006_add_device_primary_ip4_ip6'), - ] - - operations = [ - migrations.RunPython(copy_primary_ip, restore_primary_ip), - ] diff --git a/netbox/dcim/migrations/0008_device_remove_primary_ip.py b/netbox/dcim/migrations/0008_device_remove_primary_ip.py deleted file mode 100644 index f43452de2..000000000 --- a/netbox/dcim/migrations/0008_device_remove_primary_ip.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-11 19:01 -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0007_device_copy_primary_ip'), - ] - - operations = [ - migrations.RemoveField( - model_name='device', - name='primary_ip', - ), - ] diff --git a/netbox/dcim/migrations/0009_site_32bit_asn_support.py b/netbox/dcim/migrations/0009_site_32bit_asn_support.py deleted file mode 100644 index 0a72a6cf4..000000000 --- a/netbox/dcim/migrations/0009_site_32bit_asn_support.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-13 19:24 -import dcim.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0008_device_remove_primary_ip'), - ] - - operations = [ - migrations.AlterField( - model_name='site', - name='asn', - field=dcim.fields.ASNField(blank=True, null=True, verbose_name=b'ASN'), - ), - ] diff --git a/netbox/dcim/migrations/0010_devicebay_installed_device_set_null.py b/netbox/dcim/migrations/0010_devicebay_installed_device_set_null.py deleted file mode 100644 index 769a6f678..000000000 --- a/netbox/dcim/migrations/0010_devicebay_installed_device_set_null.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-14 21:38 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0009_site_32bit_asn_support'), - ] - - operations = [ - migrations.AlterField( - model_name='devicebay', - name='installed_device', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parent_bay', to='dcim.Device'), - ), - ] diff --git a/netbox/dcim/migrations/0011_devicetype_part_number.py b/netbox/dcim/migrations/0011_devicetype_part_number.py deleted file mode 100644 index eb77ea500..000000000 --- a/netbox/dcim/migrations/0011_devicetype_part_number.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-07-26 15:05 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0010_devicebay_installed_device_set_null'), - ] - - operations = [ - migrations.AddField( - model_name='devicetype', - name='part_number', - field=models.CharField(blank=True, help_text=b'Discrete part number (optional)', max_length=50), - ), - ] diff --git a/netbox/dcim/migrations/0012_site_rack_device_add_tenant.py b/netbox/dcim/migrations/0012_site_rack_device_add_tenant.py deleted file mode 100644 index b01f507c3..000000000 --- a/netbox/dcim/migrations/0012_site_rack_device_add_tenant.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-07-26 21:59 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('tenancy', '0001_initial'), - ('dcim', '0011_devicetype_part_number'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='tenant', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='tenancy.Tenant'), - ), - migrations.AddField( - model_name='rack', - name='tenant', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='tenancy.Tenant'), - ), - migrations.AddField( - model_name='site', - name='tenant', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sites', to='tenancy.Tenant'), - ), - ] diff --git a/netbox/dcim/migrations/0013_add_interface_form_factors.py b/netbox/dcim/migrations/0013_add_interface_form_factors.py deleted file mode 100644 index 478cb59ff..000000000 --- a/netbox/dcim/migrations/0013_add_interface_form_factors.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-08-06 20:24 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0012_site_rack_device_add_tenant'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet', [[800, b'100BASE-TX (10/100M)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Modular', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1300, b'XFP (10GE)'], [1200, b'SFP+ (10GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet', [[800, b'100BASE-TX (10/100M)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Modular', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1300, b'XFP (10GE)'], [1200, b'SFP+ (10GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0014_rack_add_type_width.py b/netbox/dcim/migrations/0014_rack_add_type_width.py deleted file mode 100644 index a3922c8cd..000000000 --- a/netbox/dcim/migrations/0014_rack_add_type_width.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-08-08 21:11 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0013_add_interface_form_factors'), - ] - - operations = [ - migrations.AddField( - model_name='rack', - name='type', - field=models.PositiveSmallIntegerField(blank=True, choices=[(100, b'2-post frame'), (200, b'4-post frame'), (300, b'4-post cabinet'), (1000, b'Wall-mounted frame'), (1100, b'Wall-mounted cabinet')], null=True, verbose_name=b'Type'), - ), - migrations.AddField( - model_name='rack', - name='width', - field=models.PositiveSmallIntegerField(choices=[(19, b'19 inches'), (23, b'23 inches')], default=19, help_text=b'Rail-to-rail width', verbose_name=b'Width'), - ), - ] diff --git a/netbox/dcim/migrations/0015_rack_add_u_height_validator.py b/netbox/dcim/migrations/0015_rack_add_u_height_validator.py deleted file mode 100644 index 167dd8f54..000000000 --- a/netbox/dcim/migrations/0015_rack_add_u_height_validator.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-08-09 21:18 -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0014_rack_add_type_width'), - ] - - operations = [ - migrations.AlterField( - model_name='rack', - name='u_height', - field=models.PositiveSmallIntegerField(default=42, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name=b'Height (U)'), - ), - ] diff --git a/netbox/dcim/migrations/0016_module_add_manufacturer.py b/netbox/dcim/migrations/0016_module_add_manufacturer.py deleted file mode 100644 index 7204e6626..000000000 --- a/netbox/dcim/migrations/0016_module_add_manufacturer.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-08-10 13:45 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0015_rack_add_u_height_validator'), - ] - - operations = [ - migrations.AddField( - model_name='module', - name='manufacturer', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='modules', to='dcim.Manufacturer'), - ), - ] diff --git a/netbox/dcim/migrations/0017_rack_add_role.py b/netbox/dcim/migrations/0017_rack_add_role.py deleted file mode 100644 index 48500f4b4..000000000 --- a/netbox/dcim/migrations/0017_rack_add_role.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.8 on 2016-08-10 14:58 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0016_module_add_manufacturer'), - ] - - operations = [ - migrations.CreateModel( - name='RackRole', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, unique=True)), - ('slug', models.SlugField(unique=True)), - ('color', models.CharField(choices=[[b'teal', b'Teal'], [b'green', b'Green'], [b'blue', b'Blue'], [b'purple', b'Purple'], [b'yellow', b'Yellow'], [b'orange', b'Orange'], [b'red', b'Red'], [b'light_gray', b'Light Gray'], [b'medium_gray', b'Medium Gray'], [b'dark_gray', b'Dark Gray']], max_length=30)), - ], - options={ - 'ordering': ['name'], - }, - ), - migrations.AddField( - model_name='rack', - name='role', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.RackRole'), - ), - ] diff --git a/netbox/dcim/migrations/0018_device_add_asset_tag.py b/netbox/dcim/migrations/0018_device_add_asset_tag.py deleted file mode 100644 index 84d1cef35..000000000 --- a/netbox/dcim/migrations/0018_device_add_asset_tag.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-08-11 15:42 -from django.db import migrations -import utilities.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0017_rack_add_role'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='asset_tag', - field=utilities.fields.NullableCharField(blank=True, help_text=b'A unique tag used to identify this device', max_length=50, null=True, unique=True, verbose_name=b'Asset tag'), - ), - ] diff --git a/netbox/dcim/migrations/0019_new_iface_form_factors.py b/netbox/dcim/migrations/0019_new_iface_form_factors.py deleted file mode 100644 index b2d8be533..000000000 --- a/netbox/dcim/migrations/0019_new_iface_form_factors.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-09-13 15:20 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0018_device_add_asset_tag'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]], [b'Other', [[32767, b'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]], [b'Other', [[32767, b'Other']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0020_rack_desc_units.py b/netbox/dcim/migrations/0020_rack_desc_units.py deleted file mode 100644 index 7408c82ef..000000000 --- a/netbox/dcim/migrations/0020_rack_desc_units.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-10-28 15:01 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0019_new_iface_form_factors'), - ] - - operations = [ - migrations.AddField( - model_name='rack', - name='desc_units', - field=models.BooleanField(default=False, help_text=b'Units are numbered top-to-bottom', verbose_name=b'Descending units'), - ), - ] diff --git a/netbox/dcim/migrations/0021_add_ff_flexstack.py b/netbox/dcim/migrations/0021_add_ff_flexstack.py deleted file mode 100644 index bb4c4f4be..000000000 --- a/netbox/dcim/migrations/0021_add_ff_flexstack.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-10-31 18:47 -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0020_rack_desc_units'), - ] - - operations = [ - migrations.AlterField( - model_name='device', - name='position', - field=models.PositiveSmallIntegerField(blank=True, help_text=b'The lowest-numbered unit occupied by the device', null=True, validators=[django.core.validators.MinValueValidator(1)], verbose_name=b'Position (U)'), - ), - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0022_color_names_to_rgb.py b/netbox/dcim/migrations/0022_color_names_to_rgb.py deleted file mode 100644 index 87fba4787..000000000 --- a/netbox/dcim/migrations/0022_color_names_to_rgb.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-12-06 16:35 -from django.db import migrations -import utilities.fields - - -COLOR_CONVERSION = { - 'teal': '009688', - 'green': '4caf50', - 'blue': '2196f3', - 'purple': '9c27b0', - 'yellow': 'ffeb3b', - 'orange': 'ff9800', - 'red': 'f44336', - 'light_gray': 'c0c0c0', - 'medium_gray': '9e9e9e', - 'dark_gray': '607d8b', -} - - -def color_names_to_rgb(apps, schema_editor): - RackRole = apps.get_model('dcim', 'RackRole') - DeviceRole = apps.get_model('dcim', 'DeviceRole') - for color_name, color_rgb in COLOR_CONVERSION.items(): - RackRole.objects.filter(color=color_name).update(color=color_rgb) - DeviceRole.objects.filter(color=color_name).update(color=color_rgb) - - -def color_rgb_to_name(apps, schema_editor): - RackRole = apps.get_model('dcim', 'RackRole') - DeviceRole = apps.get_model('dcim', 'DeviceRole') - for color_name, color_rgb in COLOR_CONVERSION.items(): - RackRole.objects.filter(color=color_rgb).update(color=color_name) - DeviceRole.objects.filter(color=color_rgb).update(color=color_name) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0021_add_ff_flexstack'), - ] - - operations = [ - migrations.RunPython(color_names_to_rgb, color_rgb_to_name), - migrations.AlterField( - model_name='devicerole', - name='color', - field=utilities.fields.ColorField(max_length=6), - ), - migrations.AlterField( - model_name='rackrole', - name='color', - field=utilities.fields.ColorField(max_length=6), - ), - ] diff --git a/netbox/dcim/migrations/0023_devicetype_comments.py b/netbox/dcim/migrations/0023_devicetype_comments.py deleted file mode 100644 index 5f70e8076..000000000 --- a/netbox/dcim/migrations/0023_devicetype_comments.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-12-16 16:08 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0022_color_names_to_rgb'), - ] - - operations = [ - migrations.AddField( - model_name='devicetype', - name='comments', - field=models.TextField(blank=True), - ), - ] diff --git a/netbox/dcim/migrations/0024_site_add_contact_fields.py b/netbox/dcim/migrations/0024_site_add_contact_fields.py deleted file mode 100644 index 218107ba2..000000000 --- a/netbox/dcim/migrations/0024_site_add_contact_fields.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2016-12-29 16:23 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0023_devicetype_comments'), - ] - - operations = [ - migrations.AddField( - model_name='site', - name='contact_email', - field=models.EmailField(blank=True, max_length=254, verbose_name=b'Contact E-mail'), - ), - migrations.AddField( - model_name='site', - name='contact_name', - field=models.CharField(blank=True, max_length=50), - ), - migrations.AddField( - model_name='site', - name='contact_phone', - field=models.CharField(blank=True, max_length=20), - ), - ] diff --git a/netbox/dcim/migrations/0025_devicetype_add_interface_ordering.py b/netbox/dcim/migrations/0025_devicetype_add_interface_ordering.py deleted file mode 100644 index 56db88f1c..000000000 --- a/netbox/dcim/migrations/0025_devicetype_add_interface_ordering.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-01-06 16:56 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0024_site_add_contact_fields'), - ] - - operations = [ - migrations.AddField( - model_name='devicetype', - name='interface_ordering', - field=models.PositiveSmallIntegerField(choices=[[1, b'Slot/position'], [2, b'Name (alphabetically)']], default=1), - ), - ] diff --git a/netbox/dcim/migrations/0026_add_rack_reservations.py b/netbox/dcim/migrations/0026_add_rack_reservations.py deleted file mode 100644 index ba66feea5..000000000 --- a/netbox/dcim/migrations/0026_add_rack_reservations.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-02-16 18:43 -from django.conf import settings -import django.contrib.postgres.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dcim', '0025_devicetype_add_interface_ordering'), - ] - - operations = [ - migrations.CreateModel( - name='RackReservation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('units', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(), size=None)), - ('created', models.DateTimeField(auto_now_add=True)), - ('description', models.CharField(max_length=100)), - ('rack', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='dcim.Rack')), - ('user', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ['created'], - }, - ), - ] diff --git a/netbox/dcim/migrations/0027_device_add_site.py b/netbox/dcim/migrations/0027_device_add_site.py deleted file mode 100644 index bef85a822..000000000 --- a/netbox/dcim/migrations/0027_device_add_site.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-02-16 21:21 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0026_add_rack_reservations'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='site', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Site'), - ), - ] diff --git a/netbox/dcim/migrations/0028_device_copy_rack_to_site.py b/netbox/dcim/migrations/0028_device_copy_rack_to_site.py deleted file mode 100644 index a67f34b38..000000000 --- a/netbox/dcim/migrations/0028_device_copy_rack_to_site.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-02-16 21:23 -from django.db import migrations - - -def copy_site_from_rack(apps, schema_editor): - Device = apps.get_model('dcim', 'Device') - for device in Device.objects.all(): - device.site = device.rack.site - device.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0027_device_add_site'), - ] - - operations = [ - migrations.RunPython(copy_site_from_rack), - ] diff --git a/netbox/dcim/migrations/0029_allow_rackless_devices.py b/netbox/dcim/migrations/0029_allow_rackless_devices.py deleted file mode 100644 index dd9f30bf2..000000000 --- a/netbox/dcim/migrations/0029_allow_rackless_devices.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-02-16 21:25 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0028_device_copy_rack_to_site'), - ] - - operations = [ - migrations.AlterField( - model_name='device', - name='rack', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Rack'), - ), - migrations.AlterField( - model_name='device', - name='site', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Site'), - ), - ] diff --git a/netbox/dcim/migrations/0030_interface_add_lag.py b/netbox/dcim/migrations/0030_interface_add_lag.py deleted file mode 100644 index 1ffd74f04..000000000 --- a/netbox/dcim/migrations/0030_interface_add_lag.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-02-27 19:55 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0029_allow_rackless_devices'), - ] - - operations = [ - migrations.AddField( - model_name='interface', - name='lag', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='member_interfaces', to='dcim.Interface', verbose_name=b'Parent LAG'), - ), - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0031_regions.py b/netbox/dcim/migrations/0031_regions.py deleted file mode 100644 index 73bb77b3f..000000000 --- a/netbox/dcim/migrations/0031_regions.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-02-28 17:14 -from django.db import migrations, models -import django.db.models.deletion -import mptt.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0030_interface_add_lag'), - ] - - operations = [ - migrations.CreateModel( - name='Region', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50, unique=True)), - ('slug', models.SlugField(unique=True)), - ('lft', models.PositiveIntegerField(db_index=True, editable=False)), - ('rght', models.PositiveIntegerField(db_index=True, editable=False)), - ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), - ('level', models.PositiveIntegerField(db_index=True, editable=False)), - ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.Region')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='site', - name='region', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='dcim.Region'), - ), - ] diff --git a/netbox/dcim/migrations/0032_device_increase_name_length.py b/netbox/dcim/migrations/0032_device_increase_name_length.py deleted file mode 100644 index ff0cd137f..000000000 --- a/netbox/dcim/migrations/0032_device_increase_name_length.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.4 on 2017-03-02 15:09 -from django.db import migrations -import utilities.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0031_regions'), - ] - - operations = [ - migrations.AlterField( - model_name='device', - name='name', - field=utilities.fields.NullableCharField(blank=True, max_length=64, null=True, unique=True), - ), - ] diff --git a/netbox/dcim/migrations/0033_rackreservation_rack_editable.py b/netbox/dcim/migrations/0033_rackreservation_rack_editable.py deleted file mode 100644 index 567de4345..000000000 --- a/netbox/dcim/migrations/0033_rackreservation_rack_editable.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.6 on 2017-03-17 18:39 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0032_device_increase_name_length'), - ] - - operations = [ - migrations.AlterField( - model_name='rackreservation', - name='rack', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='dcim.Rack'), - ), - ] diff --git a/netbox/dcim/migrations/0034_rename_module_to_inventoryitem.py b/netbox/dcim/migrations/0034_rename_module_to_inventoryitem.py deleted file mode 100644 index db2f0577a..000000000 --- a/netbox/dcim/migrations/0034_rename_module_to_inventoryitem.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.6 on 2017-03-21 14:55 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0033_rackreservation_rack_editable'), - ] - - operations = [ - migrations.RenameModel( - old_name='Module', - new_name='InventoryItem', - ), - migrations.AlterField( - model_name='inventoryitem', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventory_items', to='dcim.Device'), - ), - migrations.AlterField( - model_name='inventoryitem', - name='parent', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.InventoryItem'), - ), - migrations.AlterField( - model_name='inventoryitem', - name='manufacturer', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_items', to='dcim.Manufacturer'), - ), - ] diff --git a/netbox/dcim/migrations/0035_device_expand_status_choices.py b/netbox/dcim/migrations/0035_device_expand_status_choices.py deleted file mode 100644 index a6f7aa563..000000000 --- a/netbox/dcim/migrations/0035_device_expand_status_choices.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10.7 on 2017-05-08 15:57 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0034_rename_module_to_inventoryitem'), - ] - - # We convert the BooleanField to an IntegerField first as PostgreSQL does not provide a direct cast for boolean to - # smallint (attempting to convert directly yields the error "cannot cast type boolean to smallint"). - operations = [ - migrations.AlterField( - model_name='device', - name='status', - field=models.PositiveIntegerField(choices=[[1, b'Active'], [0, b'Offline'], [2, b'Planned'], [3, b'Staged'], [4, b'Failed'], [5, b'Inventory']], default=1, verbose_name=b'Status'), - ), - migrations.AlterField( - model_name='device', - name='status', - field=models.PositiveSmallIntegerField(choices=[[1, b'Active'], [0, b'Offline'], [2, b'Planned'], [3, b'Staged'], [4, b'Failed'], [5, b'Inventory']], default=1, verbose_name=b'Status'), - ), - ] diff --git a/netbox/dcim/migrations/0036_add_ff_juniper_vcp.py b/netbox/dcim/migrations/0036_add_ff_juniper_vcp.py deleted file mode 100644 index ceed22638..000000000 --- a/netbox/dcim/migrations/0036_add_ff_juniper_vcp.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-05-09 16:00 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0035_device_expand_status_choices'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus'], [5200, b'Juniper VCP']]], [b'Other', [[32767, b'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus'], [5200, b'Juniper VCP']]], [b'Other', [[32767, b'Other']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0037_unicode_literals.py b/netbox/dcim/migrations/0037_unicode_literals.py deleted file mode 100644 index 57ad7a744..000000000 --- a/netbox/dcim/migrations/0037_unicode_literals.py +++ /dev/null @@ -1,207 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-05-24 15:34 -import dcim.fields -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import utilities.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0036_add_ff_juniper_vcp'), - ] - - operations = [ - migrations.AlterField( - model_name='consoleport', - name='connection_status', - field=models.NullBooleanField(choices=[[False, 'Planned'], [True, 'Connected']], default=True), - ), - migrations.AlterField( - model_name='consoleport', - name='cs_port', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connected_console', to='dcim.ConsoleServerPort', verbose_name='Console server port'), - ), - migrations.AlterField( - model_name='device', - name='asset_tag', - field=utilities.fields.NullableCharField(blank=True, help_text='A unique tag used to identify this device', max_length=50, null=True, unique=True, verbose_name='Asset tag'), - ), - migrations.AlterField( - model_name='device', - name='face', - field=models.PositiveSmallIntegerField(blank=True, choices=[[0, 'Front'], [1, 'Rear']], null=True, verbose_name='Rack face'), - ), - migrations.AlterField( - model_name='device', - name='position', - field=models.PositiveSmallIntegerField(blank=True, help_text='The lowest-numbered unit occupied by the device', null=True, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Position (U)'), - ), - migrations.AlterField( - model_name='device', - name='primary_ip4', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.IPAddress', verbose_name='Primary IPv4'), - ), - migrations.AlterField( - model_name='device', - name='primary_ip6', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.IPAddress', verbose_name='Primary IPv6'), - ), - migrations.AlterField( - model_name='device', - name='serial', - field=models.CharField(blank=True, max_length=50, verbose_name='Serial number'), - ), - migrations.AlterField( - model_name='device', - name='status', - field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [0, 'Offline'], [2, 'Planned'], [3, 'Staged'], [4, 'Failed'], [5, 'Inventory']], default=1, verbose_name='Status'), - ), - migrations.AlterField( - model_name='devicebay', - name='name', - field=models.CharField(max_length=50, verbose_name='Name'), - ), - migrations.AlterField( - model_name='devicetype', - name='interface_ordering', - field=models.PositiveSmallIntegerField(choices=[[1, 'Slot/position'], [2, 'Name (alphabetically)']], default=1), - ), - migrations.AlterField( - model_name='devicetype', - name='is_console_server', - field=models.BooleanField(default=False, help_text='This type of device has console server ports', verbose_name='Is a console server'), - ), - migrations.AlterField( - model_name='devicetype', - name='is_full_depth', - field=models.BooleanField(default=True, help_text='Device consumes both front and rear rack faces', verbose_name='Is full depth'), - ), - migrations.AlterField( - model_name='devicetype', - name='is_network_device', - field=models.BooleanField(default=True, help_text='This type of device has network interfaces', verbose_name='Is a network device'), - ), - migrations.AlterField( - model_name='devicetype', - name='is_pdu', - field=models.BooleanField(default=False, help_text='This type of device has power outlets', verbose_name='Is a PDU'), - ), - migrations.AlterField( - model_name='devicetype', - name='part_number', - field=models.CharField(blank=True, help_text='Discrete part number (optional)', max_length=50), - ), - migrations.AlterField( - model_name='devicetype', - name='subdevice_role', - field=models.NullBooleanField(choices=[(None, 'None'), (True, 'Parent'), (False, 'Child')], default=None, help_text='Parent devices house child devices in device bays. Select "None" if this device type is neither a parent nor a child.', verbose_name='Parent/child status'), - ), - migrations.AlterField( - model_name='devicetype', - name='u_height', - field=models.PositiveSmallIntegerField(default=1, verbose_name='Height (U)'), - ), - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interface', - name='lag', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='member_interfaces', to='dcim.Interface', verbose_name='Parent LAG'), - ), - migrations.AlterField( - model_name='interface', - name='mac_address', - field=dcim.fields.MACAddressField(blank=True, null=True, verbose_name='MAC Address'), - ), - migrations.AlterField( - model_name='interface', - name='mgmt_only', - field=models.BooleanField(default=False, help_text='This interface is used only for out-of-band management', verbose_name='OOB Management'), - ), - migrations.AlterField( - model_name='interfaceconnection', - name='connection_status', - field=models.BooleanField(choices=[[False, 'Planned'], [True, 'Connected']], default=True, verbose_name='Status'), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='mgmt_only', - field=models.BooleanField(default=False, verbose_name='Management only'), - ), - migrations.AlterField( - model_name='inventoryitem', - name='discovered', - field=models.BooleanField(default=False, verbose_name='Discovered'), - ), - migrations.AlterField( - model_name='inventoryitem', - name='name', - field=models.CharField(max_length=50, verbose_name='Name'), - ), - migrations.AlterField( - model_name='inventoryitem', - name='part_id', - field=models.CharField(blank=True, max_length=50, verbose_name='Part ID'), - ), - migrations.AlterField( - model_name='inventoryitem', - name='serial', - field=models.CharField(blank=True, max_length=50, verbose_name='Serial number'), - ), - migrations.AlterField( - model_name='platform', - name='rpc_client', - field=models.CharField(blank=True, choices=[['juniper-junos', 'Juniper Junos (NETCONF)'], ['cisco-ios', 'Cisco IOS (SSH)'], ['opengear', 'Opengear (SSH)']], max_length=30, verbose_name='RPC client'), - ), - migrations.AlterField( - model_name='powerport', - name='connection_status', - field=models.NullBooleanField(choices=[[False, 'Planned'], [True, 'Connected']], default=True), - ), - migrations.AlterField( - model_name='rack', - name='desc_units', - field=models.BooleanField(default=False, help_text='Units are numbered top-to-bottom', verbose_name='Descending units'), - ), - migrations.AlterField( - model_name='rack', - name='facility_id', - field=utilities.fields.NullableCharField(blank=True, max_length=30, null=True, verbose_name='Facility ID'), - ), - migrations.AlterField( - model_name='rack', - name='type', - field=models.PositiveSmallIntegerField(blank=True, choices=[(100, '2-post frame'), (200, '4-post frame'), (300, '4-post cabinet'), (1000, 'Wall-mounted frame'), (1100, 'Wall-mounted cabinet')], null=True, verbose_name='Type'), - ), - migrations.AlterField( - model_name='rack', - name='u_height', - field=models.PositiveSmallIntegerField(default=42, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Height (U)'), - ), - migrations.AlterField( - model_name='rack', - name='width', - field=models.PositiveSmallIntegerField(choices=[(19, '19 inches'), (23, '23 inches')], default=19, help_text='Rail-to-rail width', verbose_name='Width'), - ), - migrations.AlterField( - model_name='site', - name='asn', - field=dcim.fields.ASNField(blank=True, null=True, verbose_name='ASN'), - ), - migrations.AlterField( - model_name='site', - name='contact_email', - field=models.EmailField(blank=True, max_length=254, verbose_name='Contact E-mail'), - ), - ] diff --git a/netbox/dcim/migrations/0038_wireless_interfaces.py b/netbox/dcim/migrations/0038_wireless_interfaces.py deleted file mode 100644 index 78ea103e5..000000000 --- a/netbox/dcim/migrations/0038_wireless_interfaces.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-06-16 21:38 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0037_unicode_literals'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0039_interface_add_enabled_mtu.py b/netbox/dcim/migrations/0039_interface_add_enabled_mtu.py deleted file mode 100644 index c5f8dc83d..000000000 --- a/netbox/dcim/migrations/0039_interface_add_enabled_mtu.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.1 on 2017-06-23 17:05 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0038_wireless_interfaces'), - ] - - operations = [ - migrations.AddField( - model_name='interface', - name='enabled', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='interface', - name='mtu', - field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='MTU'), - ), - ] diff --git a/netbox/dcim/migrations/0040_inventoryitem_add_asset_tag_description.py b/netbox/dcim/migrations/0040_inventoryitem_add_asset_tag_description.py deleted file mode 100644 index aaca23ea8..000000000 --- a/netbox/dcim/migrations/0040_inventoryitem_add_asset_tag_description.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2017-06-23 20:44 -from django.db import migrations, models -import utilities.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0039_interface_add_enabled_mtu'), - ] - - operations = [ - migrations.AddField( - model_name='inventoryitem', - name='asset_tag', - field=utilities.fields.NullableCharField(blank=True, help_text='A unique tag used to identify this item', max_length=50, null=True, unique=True, verbose_name='Asset tag'), - ), - migrations.AddField( - model_name='inventoryitem', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - ] diff --git a/netbox/dcim/migrations/0041_napalm_integration.py b/netbox/dcim/migrations/0041_napalm_integration.py deleted file mode 100644 index 3acad9f0b..000000000 --- a/netbox/dcim/migrations/0041_napalm_integration.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-14 17:26 -from django.db import migrations, models - - -def rpc_client_to_napalm_driver(apps, schema_editor): - """ - Migrate legacy RPC clients to their respective NAPALM drivers - """ - Platform = apps.get_model('dcim', 'Platform') - - Platform.objects.filter(rpc_client='juniper-junos').update(napalm_driver='junos') - Platform.objects.filter(rpc_client='cisco-ios').update(napalm_driver='ios') - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0040_inventoryitem_add_asset_tag_description'), - ] - - operations = [ - migrations.AlterModelOptions( - name='device', - options={'ordering': ['name']}, - ), - migrations.AddField( - model_name='platform', - name='napalm_driver', - field=models.CharField(blank=True, help_text='The name of the NAPALM driver to use when interacting with devices.', max_length=50, verbose_name='NAPALM driver'), - ), - migrations.AlterField( - model_name='platform', - name='rpc_client', - field=models.CharField(blank=True, choices=[['juniper-junos', 'Juniper Junos (NETCONF)'], ['cisco-ios', 'Cisco IOS (SSH)'], ['opengear', 'Opengear (SSH)']], max_length=30, verbose_name='Legacy RPC client'), - ), - migrations.RunPython(rpc_client_to_napalm_driver), - ] diff --git a/netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py b/netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py deleted file mode 100644 index e667d9451..000000000 --- a/netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-08-29 21:00 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0041_napalm_integration'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0043_device_component_name_lengths.py b/netbox/dcim/migrations/0043_device_component_name_lengths.py deleted file mode 100644 index 9f0ba2243..000000000 --- a/netbox/dcim/migrations/0043_device_component_name_lengths.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-08-29 21:26 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0042_interface_ff_10ge_cx4'), - ] - - operations = [ - migrations.AlterField( - model_name='consoleport', - name='name', - field=models.CharField(max_length=50), - ), - migrations.AlterField( - model_name='consoleporttemplate', - name='name', - field=models.CharField(max_length=50), - ), - migrations.AlterField( - model_name='consoleserverport', - name='name', - field=models.CharField(max_length=50), - ), - migrations.AlterField( - model_name='consoleserverporttemplate', - name='name', - field=models.CharField(max_length=50), - ), - migrations.AlterField( - model_name='devicebaytemplate', - name='name', - field=models.CharField(max_length=50), - ), - migrations.AlterField( - model_name='interface', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='poweroutlet', - name='name', - field=models.CharField(max_length=50), - ), - migrations.AlterField( - model_name='poweroutlettemplate', - name='name', - field=models.CharField(max_length=50), - ), - migrations.AlterField( - model_name='powerport', - name='name', - field=models.CharField(max_length=50), - ), - migrations.AlterField( - model_name='powerporttemplate', - name='name', - field=models.CharField(max_length=50), - ), - ] diff --git a/netbox/dcim/migrations/0044_virtualization.py b/netbox/dcim/migrations/0044_virtualization.py deleted file mode 100644 index 362979aef..000000000 --- a/netbox/dcim/migrations/0044_virtualization.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-08-31 14:15 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('virtualization', '0001_virtualization'), - ('dcim', '0043_device_component_name_lengths'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='cluster', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='virtualization.Cluster'), - ), - migrations.AddField( - model_name='interface', - name='virtual_machine', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='virtualization.VirtualMachine'), - ), - migrations.AlterField( - model_name='interface', - name='device', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.Device'), - ), - ] diff --git a/netbox/dcim/migrations/0045_devicerole_vm_role.py b/netbox/dcim/migrations/0045_devicerole_vm_role.py deleted file mode 100644 index 306a5a806..000000000 --- a/netbox/dcim/migrations/0045_devicerole_vm_role.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-09-29 16:09 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0044_virtualization'), - ] - - operations = [ - migrations.AddField( - model_name='devicerole', - name='vm_role', - field=models.BooleanField(default=True, help_text='Virtual machines may be assigned to this role', verbose_name='VM Role'), - ), - ] diff --git a/netbox/dcim/migrations/0046_rack_lengthen_facility_id.py b/netbox/dcim/migrations/0046_rack_lengthen_facility_id.py deleted file mode 100644 index f6e93a43d..000000000 --- a/netbox/dcim/migrations/0046_rack_lengthen_facility_id.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-10-09 17:43 -from django.db import migrations -import utilities.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0045_devicerole_vm_role'), - ] - - operations = [ - migrations.AlterField( - model_name='rack', - name='facility_id', - field=utilities.fields.NullableCharField(blank=True, max_length=50, null=True, verbose_name='Facility ID'), - ), - ] diff --git a/netbox/dcim/migrations/0047_more_100ge_form_factors.py b/netbox/dcim/migrations/0047_more_100ge_form_factors.py deleted file mode 100644 index a76ef6c8d..000000000 --- a/netbox/dcim/migrations/0047_more_100ge_form_factors.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-10-09 18:43 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0046_rack_lengthen_facility_id'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0048_rack_serial.py b/netbox/dcim/migrations/0048_rack_serial.py deleted file mode 100644 index 3fb7c0d2e..000000000 --- a/netbox/dcim/migrations/0048_rack_serial.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-10-09 18:50 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0047_more_100ge_form_factors'), - ] - - operations = [ - migrations.AddField( - model_name='rack', - name='serial', - field=models.CharField(blank=True, max_length=50, verbose_name='Serial number'), - ), - ] diff --git a/netbox/dcim/migrations/0049_rackreservation_change_user.py b/netbox/dcim/migrations/0049_rackreservation_change_user.py deleted file mode 100644 index 2d03db587..000000000 --- a/netbox/dcim/migrations/0049_rackreservation_change_user.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-10-31 17:32 -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0048_rack_serial'), - ] - - operations = [ - migrations.AlterField( - model_name='rackreservation', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/netbox/dcim/migrations/0050_interface_vlan_tagging.py b/netbox/dcim/migrations/0050_interface_vlan_tagging.py deleted file mode 100644 index 8acaf4eec..000000000 --- a/netbox/dcim/migrations/0050_interface_vlan_tagging.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-11-10 20:10 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('ipam', '0020_ipaddress_add_role_carp'), - ('dcim', '0049_rackreservation_change_user'), - ] - - operations = [ - migrations.AddField( - model_name='interface', - name='mode', - field=models.PositiveSmallIntegerField(blank=True, choices=[[100, 'Access'], [200, 'Tagged'], [300, 'Tagged All']], null=True), - ), - migrations.AddField( - model_name='interface', - name='tagged_vlans', - field=models.ManyToManyField(blank=True, related_name='interfaces_as_tagged', to='ipam.VLAN', verbose_name='Tagged VLANs'), - ), - migrations.AddField( - model_name='interface', - name='untagged_vlan', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces_as_untagged', to='ipam.VLAN', verbose_name='Untagged VLAN'), - ), - ] diff --git a/netbox/dcim/migrations/0051_rackreservation_tenant.py b/netbox/dcim/migrations/0051_rackreservation_tenant.py deleted file mode 100644 index ca0513ab0..000000000 --- a/netbox/dcim/migrations/0051_rackreservation_tenant.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-11-15 18:56 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('tenancy', '0003_unicode_literals'), - ('dcim', '0050_interface_vlan_tagging'), - ] - - operations = [ - migrations.AddField( - model_name='rackreservation', - name='tenant', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='rackreservations', to='tenancy.Tenant'), - ), - ] diff --git a/netbox/dcim/migrations/0052_virtual_chassis.py b/netbox/dcim/migrations/0052_virtual_chassis.py deleted file mode 100644 index 56777744c..000000000 --- a/netbox/dcim/migrations/0052_virtual_chassis.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-11-27 17:27 -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0051_rackreservation_tenant'), - ] - - operations = [ - migrations.CreateModel( - name='VirtualChassis', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('domain', models.CharField(blank=True, max_length=30)), - ('master', models.OneToOneField(default=1, on_delete=django.db.models.deletion.PROTECT, related_name='vc_master_for', to='dcim.Device')), - ], - ), - migrations.AddField( - model_name='device', - name='virtual_chassis', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='dcim.VirtualChassis'), - ), - migrations.AddField( - model_name='device', - name='vc_position', - field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)]), - ), - migrations.AddField( - model_name='device', - name='vc_priority', - field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)]), - ), - migrations.AlterUniqueTogether( - name='device', - unique_together=set([('virtual_chassis', 'vc_position'), ('rack', 'position', 'face')]), - ), - ] diff --git a/netbox/dcim/migrations/0053_platform_manufacturer.py b/netbox/dcim/migrations/0053_platform_manufacturer.py deleted file mode 100644 index bb5f24c91..000000000 --- a/netbox/dcim/migrations/0053_platform_manufacturer.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-12-19 20:56 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0052_virtual_chassis'), - ] - - operations = [ - migrations.AddField( - model_name='platform', - name='manufacturer', - field=models.ForeignKey(blank=True, help_text='Optionally limit this platform to devices of a certain manufacturer', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='platforms', to='dcim.Manufacturer'), - ), - migrations.AlterField( - model_name='platform', - name='napalm_driver', - field=models.CharField(blank=True, help_text='The name of the NAPALM driver to use when interacting with devices', max_length=50, verbose_name='NAPALM driver'), - ), - ] diff --git a/netbox/dcim/migrations/0054_site_status_timezone_description.py b/netbox/dcim/migrations/0054_site_status_timezone_description.py deleted file mode 100644 index 554bf554c..000000000 --- a/netbox/dcim/migrations/0054_site_status_timezone_description.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2018-01-25 18:21 -from django.db import migrations, models -import timezone_field.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0053_platform_manufacturer'), - ] - - operations = [ - migrations.AddField( - model_name='site', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - migrations.AddField( - model_name='site', - name='status', - field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [2, 'Planned'], [4, 'Retired']], default=1), - ), - migrations.AddField( - model_name='site', - name='time_zone', - field=timezone_field.fields.TimeZoneField(blank=True), - ), - ] diff --git a/netbox/dcim/migrations/0055_virtualchassis_ordering.py b/netbox/dcim/migrations/0055_virtualchassis_ordering.py deleted file mode 100644 index ab23f403f..000000000 --- a/netbox/dcim/migrations/0055_virtualchassis_ordering.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.9 on 2018-02-21 14:41 -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0054_site_status_timezone_description'), - ] - - operations = [ - migrations.AlterModelOptions( - name='virtualchassis', - options={'ordering': ['master'], 'verbose_name_plural': 'virtual chassis'}, - ), - migrations.AlterField( - model_name='virtualchassis', - name='master', - field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='vc_master_for', to='dcim.Device'), - ), - ] diff --git a/netbox/dcim/migrations/0056_django2.py b/netbox/dcim/migrations/0056_django2.py deleted file mode 100644 index bb7af920e..000000000 --- a/netbox/dcim/migrations/0056_django2.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.0.3 on 2018-03-30 14:18 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0055_virtualchassis_ordering'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='untagged_vlan', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='interfaces_as_untagged', to='ipam.VLAN', verbose_name='Untagged VLAN'), - ), - migrations.AlterField( - model_name='platform', - name='manufacturer', - field=models.ForeignKey(blank=True, help_text='Optionally limit this platform to devices of a certain manufacturer', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='platforms', to='dcim.Manufacturer'), - ), - ] diff --git a/netbox/dcim/migrations/0057_tags.py b/netbox/dcim/migrations/0057_tags.py deleted file mode 100644 index 44ed09497..000000000 --- a/netbox/dcim/migrations/0057_tags.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.12 on 2018-05-22 19:04 -from django.db import migrations -import taggit.managers - - -class Migration(migrations.Migration): - - dependencies = [ - ('taggit', '0002_auto_20150616_2121'), - ('dcim', '0056_django2'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='devicetype', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='rack', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='site', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='consoleport', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='consoleserverport', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='devicebay', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='interface', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='inventoryitem', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='poweroutlet', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='powerport', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddField( - model_name='virtualchassis', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - ] diff --git a/netbox/dcim/migrations/0058_relax_rack_naming_constraints.py b/netbox/dcim/migrations/0058_relax_rack_naming_constraints.py deleted file mode 100644 index 9676e973d..000000000 --- a/netbox/dcim/migrations/0058_relax_rack_naming_constraints.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.12 on 2018-05-22 19:27 -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0057_tags'), - ] - - operations = [ - migrations.AlterModelOptions( - name='rack', - options={'ordering': ['site', 'group', 'name']}, - ), - migrations.AlterUniqueTogether( - name='rack', - unique_together=set([('group', 'name'), ('group', 'facility_id')]), - ), - ] diff --git a/netbox/dcim/migrations/0059_site_latitude_longitude.py b/netbox/dcim/migrations/0059_site_latitude_longitude.py deleted file mode 100644 index 7c019ed5d..000000000 --- a/netbox/dcim/migrations/0059_site_latitude_longitude.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.12 on 2018-06-21 18:45 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0058_relax_rack_naming_constraints'), - ] - - operations = [ - migrations.AddField( - model_name='site', - name='latitude', - field=models.DecimalField(blank=True, decimal_places=6, max_digits=8, null=True), - ), - migrations.AddField( - model_name='site', - name='longitude', - field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), - ), - ] diff --git a/netbox/dcim/migrations/0060_change_logging.py b/netbox/dcim/migrations/0060_change_logging.py deleted file mode 100644 index 12a9f95ad..000000000 --- a/netbox/dcim/migrations/0060_change_logging.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.12 on 2018-06-13 17:14 -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0059_site_latitude_longitude'), - ] - - operations = [ - migrations.AddField( - model_name='devicerole', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='devicerole', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='devicetype', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='devicetype', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='manufacturer', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='manufacturer', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='platform', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='platform', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='rackgroup', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='rackgroup', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='rackreservation', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='rackrole', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='rackrole', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='region', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='region', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='virtualchassis', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='virtualchassis', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AlterField( - model_name='device', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AlterField( - model_name='device', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AlterField( - model_name='rack', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AlterField( - model_name='rack', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AlterField( - model_name='rackreservation', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AlterField( - model_name='site', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AlterField( - model_name='site', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - ] diff --git a/netbox/dcim/migrations/0061_platform_napalm_args.py b/netbox/dcim/migrations/0061_platform_napalm_args.py deleted file mode 100644 index 6da863aec..000000000 --- a/netbox/dcim/migrations/0061_platform_napalm_args.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.0.6 on 2018-06-29 15:02 - -import django.contrib.postgres.fields.jsonb -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0060_change_logging'), - ] - - operations = [ - migrations.AddField( - model_name='platform', - name='napalm_args', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Additional arguments to pass when initiating the NAPALM driver (JSON format)', null=True, verbose_name='NAPALM arguments'), - ), - ] diff --git a/netbox/dcim/migrations/0062_interface_mtu.py b/netbox/dcim/migrations/0062_interface_mtu.py deleted file mode 100644 index d1ae92520..000000000 --- a/netbox/dcim/migrations/0062_interface_mtu.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.0.8 on 2018-08-22 14:23 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0061_platform_napalm_args'), - ] - - operations = [ - migrations.AlterField( - model_name='interface', - name='mtu', - field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65536)], verbose_name='MTU'), - ), - migrations.AlterField( - model_name='interface', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['SONET', [[6100, 'OC-3/STM-1'], [6200, 'OC-12/STM-4'], [6300, 'OC-48/STM-16'], [6400, 'OC-192/STM-64'], [6500, 'OC-768/STM-256'], [6600, 'OC-1920/STM-640'], [6700, 'OC-3840/STM-1234']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)'], [3320, 'SFP28 (32GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP'], [5300, 'Extreme SummitStack'], [5310, 'Extreme SummitStack-128'], [5320, 'Extreme SummitStack-256'], [5330, 'Extreme SummitStack-512']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='form_factor', - field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['SONET', [[6100, 'OC-3/STM-1'], [6200, 'OC-12/STM-4'], [6300, 'OC-48/STM-16'], [6400, 'OC-192/STM-64'], [6500, 'OC-768/STM-256'], [6600, 'OC-1920/STM-640'], [6700, 'OC-3840/STM-1234']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)'], [3320, 'SFP28 (32GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP'], [5300, 'Extreme SummitStack'], [5310, 'Extreme SummitStack-128'], [5320, 'Extreme SummitStack-256'], [5330, 'Extreme SummitStack-512']]], ['Other', [[32767, 'Other']]]], default=1200), - ), - ] diff --git a/netbox/dcim/migrations/0063_device_local_context_data.py b/netbox/dcim/migrations/0063_device_local_context_data.py deleted file mode 100644 index 73c568887..000000000 --- a/netbox/dcim/migrations/0063_device_local_context_data.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.0.8 on 2018-09-16 02:01 - -import django.contrib.postgres.fields.jsonb -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0062_interface_mtu'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='local_context_data', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), - ), - ] diff --git a/netbox/dcim/migrations/0064_remove_platform_rpc_client.py b/netbox/dcim/migrations/0064_remove_platform_rpc_client.py deleted file mode 100644 index 4926c4b32..000000000 --- a/netbox/dcim/migrations/0064_remove_platform_rpc_client.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.0.8 on 2018-08-22 16:09 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0063_device_local_context_data'), - ] - - operations = [ - migrations.RemoveField( - model_name='platform', - name='rpc_client', - ), - ] diff --git a/netbox/dcim/migrations/0065_front_rear_ports.py b/netbox/dcim/migrations/0065_front_rear_ports.py deleted file mode 100644 index a7fe9eab9..000000000 --- a/netbox/dcim/migrations/0065_front_rear_ports.py +++ /dev/null @@ -1,131 +0,0 @@ -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import taggit.managers - - -class Migration(migrations.Migration): - - dependencies = [ - ('taggit', '0002_auto_20150616_2121'), - ('dcim', '0064_remove_platform_rpc_client'), - ] - - operations = [ - migrations.CreateModel( - name='FrontPort', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=64)), - ('type', models.PositiveSmallIntegerField()), - ('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])), - ('description', models.CharField(blank=True, max_length=100)), - ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.Device')), - ], - options={ - 'ordering': ['device', 'name'], - }, - ), - migrations.CreateModel( - name='FrontPortTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=64)), - ('type', models.PositiveSmallIntegerField()), - ('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])), - ], - options={ - 'ordering': ['device_type', 'name'], - }, - ), - migrations.CreateModel( - name='RearPort', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=64)), - ('type', models.PositiveSmallIntegerField()), - ('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])), - ('description', models.CharField(blank=True, max_length=100)), - ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearports', to='dcim.Device')), - ('tags', taggit.managers.TaggableManager(through='taggit.TaggedItem', to='taggit.Tag')), - ], - options={ - 'ordering': ['device', 'name'], - }, - ), - migrations.CreateModel( - name='RearPortTemplate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=64)), - ('type', models.PositiveSmallIntegerField()), - ('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])), - ], - options={ - 'ordering': ['device_type', 'name'], - }, - ), - migrations.AddField( - model_name='rearporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearport_templates', to='dcim.DeviceType'), - ), - migrations.AddField( - model_name='frontporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontport_templates', to='dcim.DeviceType'), - ), - migrations.AddField( - model_name='frontporttemplate', - name='rear_port', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontport_templates', to='dcim.RearPortTemplate'), - ), - migrations.AddField( - model_name='frontport', - name='rear_port', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.RearPort'), - ), - migrations.AddField( - model_name='frontport', - name='tags', - field=taggit.managers.TaggableManager(through='taggit.TaggedItem', to='taggit.Tag'), - ), - migrations.AlterUniqueTogether( - name='rearporttemplate', - unique_together={('device_type', 'name')}, - ), - migrations.AlterUniqueTogether( - name='rearport', - unique_together={('device', 'name')}, - ), - migrations.AlterUniqueTogether( - name='frontporttemplate', - unique_together={('rear_port', 'rear_port_position'), ('device_type', 'name')}, - ), - migrations.AlterUniqueTogether( - name='frontport', - unique_together={('device', 'name'), ('rear_port', 'rear_port_position')}, - ), - - # Rename reverse relationships of component templates to DeviceType - migrations.AlterField( - model_name='consoleporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleport_templates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='consoleserverporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverport_templates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='poweroutlettemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlet_templates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='powerporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='powerport_templates', to='dcim.DeviceType'), - ), - ] diff --git a/netbox/dcim/migrations/0066_cables.py b/netbox/dcim/migrations/0066_cables.py deleted file mode 100644 index b30a2a8fa..000000000 --- a/netbox/dcim/migrations/0066_cables.py +++ /dev/null @@ -1,322 +0,0 @@ -import sys - -from django.db import migrations, models -import django.db.models.deletion - -import utilities.fields - - -def console_connections_to_cables(apps, schema_editor): - """ - Copy all existing console connections as Cables - """ - ContentType = apps.get_model('contenttypes', 'ContentType') - ConsolePort = apps.get_model('dcim', 'ConsolePort') - ConsoleServerPort = apps.get_model('dcim', 'ConsoleServerPort') - Cable = apps.get_model('dcim', 'Cable') - - # Load content types - consoleport_type = ContentType.objects.get_for_model(ConsolePort) - consoleserverport_type = ContentType.objects.get_for_model(ConsoleServerPort) - - # Create a new Cable instance from each console connection - if 'test' not in sys.argv: - print("\n Adding console connections... ", end='', flush=True) - for consoleport in ConsolePort.objects.filter(connected_endpoint__isnull=False): - - # Create the new Cable - cable = Cable.objects.create( - termination_a_type=consoleport_type, - termination_a_id=consoleport.id, - termination_b_type=consoleserverport_type, - termination_b_id=consoleport.connected_endpoint_id, - status=consoleport.connection_status - ) - - # Cache the Cable on its two termination points - ConsolePort.objects.filter(pk=consoleport.id).update( - cable=cable - ) - ConsoleServerPort.objects.filter(pk=consoleport.connected_endpoint_id).update( - connection_status=consoleport.connection_status, - cable=cable - ) - - cable_count = Cable.objects.filter(termination_a_type=consoleport_type).count() - if 'test' not in sys.argv: - print("{} cables created".format(cable_count)) - - # Normalize connection_status for all non-connected ConsolePorts - ConsolePort.objects.filter(connected_endpoint__isnull=True).update(connection_status=None) - - -def power_connections_to_cables(apps, schema_editor): - """ - Copy all existing power connections as Cables - """ - ContentType = apps.get_model('contenttypes', 'ContentType') - PowerPort = apps.get_model('dcim', 'PowerPort') - PowerOutlet = apps.get_model('dcim', 'PowerOutlet') - Cable = apps.get_model('dcim', 'Cable') - - # Load content types - powerport_type = ContentType.objects.get_for_model(PowerPort) - poweroutlet_type = ContentType.objects.get_for_model(PowerOutlet) - - # Create a new Cable instance from each power connection - if 'test' not in sys.argv: - print(" Adding power connections... ", end='', flush=True) - for powerport in PowerPort.objects.filter(connected_endpoint__isnull=False): - - # Create the new Cable - cable = Cable.objects.create( - termination_a_type=powerport_type, - termination_a_id=powerport.id, - termination_b_type=poweroutlet_type, - termination_b_id=powerport.connected_endpoint_id, - status=powerport.connection_status - ) - - # Cache the Cable on its two termination points - PowerPort.objects.filter(pk=powerport.id).update( - cable=cable - ) - PowerOutlet.objects.filter(pk=powerport.connected_endpoint_id).update( - connection_status=powerport.connection_status, - cable=cable - ) - - cable_count = Cable.objects.filter(termination_a_type=powerport_type).count() - if 'test' not in sys.argv: - print("{} cables created".format(cable_count)) - - # Normalize connection_status for all non-connected PowerPorts - PowerPort.objects.filter(connected_endpoint__isnull=True).update(connection_status=None) - - -def interface_connections_to_cables(apps, schema_editor): - """ - Copy all InterfaceConnections as Cables - """ - ContentType = apps.get_model('contenttypes', 'ContentType') - Interface = apps.get_model('dcim', 'Interface') - InterfaceConnection = apps.get_model('dcim', 'InterfaceConnection') - Cable = apps.get_model('dcim', 'Cable') - - # Load content types - interface_type = ContentType.objects.get_for_model(Interface) - - # Create a new Cable instance from each InterfaceConnection - if 'test' not in sys.argv: - print(" Adding interface connections... ", end='', flush=True) - for conn in InterfaceConnection.objects.all(): - - # Create the new Cable - cable = Cable.objects.create( - termination_a_type=interface_type, - termination_a_id=conn.interface_a_id, - termination_b_type=interface_type, - termination_b_id=conn.interface_b_id, - status=conn.connection_status - ) - - # Cache the connected Cable on each Interface - Interface.objects.filter(pk=conn.interface_a_id).update( - _connected_interface=conn.interface_b, - connection_status=conn.connection_status, - cable=cable - ) - Interface.objects.filter(pk=conn.interface_b_id).update( - _connected_interface=conn.interface_a, - connection_status=conn.connection_status, - cable=cable - ) - - cable_count = Cable.objects.filter(termination_a_type=interface_type).count() - if 'test' not in sys.argv: - print("{} cables created".format(cable_count)) - - -def delete_interfaceconnection_content_type(apps, schema_editor): - """ - Delete the ContentType for the InterfaceConnection model. (This is not done automatically upon model deletion.) - """ - ContentType = apps.get_model('contenttypes', 'ContentType') - InterfaceConnection = apps.get_model('dcim', 'InterfaceConnection') - ContentType.objects.get_for_model(InterfaceConnection).delete() - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('circuits', '0006_terminations'), - ('dcim', '0065_front_rear_ports'), - ] - - operations = [ - - # Create the Cable model - migrations.CreateModel( - name='Cable', - options={'ordering': ['pk']}, - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), - ('created', models.DateField(auto_now_add=True, null=True)), - ('last_updated', models.DateTimeField(auto_now=True, null=True)), - ('termination_a_id', models.PositiveIntegerField()), - ('termination_b_id', models.PositiveIntegerField()), - ('type', models.PositiveSmallIntegerField(blank=True, null=True)), - ('status', models.BooleanField(default=True)), - ('label', models.CharField(blank=True, max_length=100)), - ('color', utilities.fields.ColorField(blank=True, max_length=6)), - ('length', models.PositiveSmallIntegerField(blank=True, null=True)), - ('length_unit', models.PositiveSmallIntegerField(blank=True, null=True)), - ('_abs_length', models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True)), - ('termination_a_type', models.ForeignKey(limit_choices_to={'model__in': ['consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport', 'circuittermination', 'powerfeed']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), - ('termination_b_type', models.ForeignKey(limit_choices_to={'model__in': ['consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport', 'circuittermination', 'powerfeed']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')), - ], - ), - migrations.AlterUniqueTogether( - name='cable', - unique_together={('termination_b_type', 'termination_b_id'), ('termination_a_type', 'termination_a_id')}, - ), - - # Alter console port models - migrations.RenameField( - model_name='consoleport', - old_name='cs_port', - new_name='connected_endpoint' - ), - migrations.AlterField( - model_name='consoleport', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleports', to='dcim.Device'), - ), - migrations.AlterField( - model_name='consoleport', - name='connected_endpoint', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connected_endpoint', to='dcim.ConsoleServerPort'), - ), - migrations.AlterField( - model_name='consoleport', - name='connection_status', - field=models.NullBooleanField(), - ), - migrations.AddField( - model_name='consoleport', - name='cable', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'), - ), - migrations.AlterField( - model_name='consoleserverport', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverports', to='dcim.Device'), - ), - migrations.AddField( - model_name='consoleserverport', - name='cable', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'), - ), - migrations.AddField( - model_name='consoleserverport', - name='connection_status', - field=models.NullBooleanField(), - ), - - # Alter power port models - migrations.RenameField( - model_name='powerport', - old_name='power_outlet', - new_name='connected_endpoint' - ), - migrations.AlterField( - model_name='powerport', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='powerports', to='dcim.Device'), - ), - migrations.AlterField( - model_name='powerport', - name='connected_endpoint', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connected_endpoint', to='dcim.PowerOutlet'), - ), - migrations.AlterField( - model_name='powerport', - name='connection_status', - field=models.NullBooleanField(), - ), - migrations.AddField( - model_name='powerport', - name='cable', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'), - ), - migrations.AlterField( - model_name='poweroutlet', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlets', to='dcim.Device'), - ), - migrations.AddField( - model_name='poweroutlet', - name='cable', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'), - ), - migrations.AddField( - model_name='poweroutlet', - name='connection_status', - field=models.NullBooleanField(), - ), - - # Alter the Interface model - migrations.AddField( - model_name='interface', - name='_connected_circuittermination', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.CircuitTermination'), - ), - migrations.AddField( - model_name='interface', - name='_connected_interface', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Interface'), - ), - migrations.AddField( - model_name='interface', - name='connection_status', - field=models.NullBooleanField(), - ), - migrations.AddField( - model_name='interface', - name='cable', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'), - ), - - # Alter front/rear port models - migrations.AddField( - model_name='frontport', - name='cable', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'), - ), - migrations.AddField( - model_name='rearport', - name='cable', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'), - ), - - # Copy console/power/interface connections as Cables - migrations.RunPython(console_connections_to_cables), - migrations.RunPython(power_connections_to_cables), - migrations.RunPython(interface_connections_to_cables), - - # Delete the InterfaceConnection model and its ContentType - migrations.RunPython(delete_interfaceconnection_content_type), - migrations.RemoveField( - model_name='interfaceconnection', - name='interface_a', - ), - migrations.RemoveField( - model_name='interfaceconnection', - name='interface_b', - ), - migrations.DeleteModel( - name='InterfaceConnection', - ), - ] diff --git a/netbox/dcim/migrations/0067_device_type_remove_qualifiers.py b/netbox/dcim/migrations/0067_device_type_remove_qualifiers.py deleted file mode 100644 index e78ccd8b6..000000000 --- a/netbox/dcim/migrations/0067_device_type_remove_qualifiers.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-26 17:49 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0066_cables'), - ] - - operations = [ - migrations.RemoveField( - model_name='devicetype', - name='is_console_server', - ), - migrations.RemoveField( - model_name='devicetype', - name='is_network_device', - ), - migrations.RemoveField( - model_name='devicetype', - name='is_pdu', - ), - migrations.RemoveField( - model_name='devicetype', - name='interface_ordering', - ), - ] diff --git a/netbox/dcim/migrations/0068_rack_new_fields.py b/netbox/dcim/migrations/0068_rack_new_fields.py deleted file mode 100644 index 5ad4703e4..000000000 --- a/netbox/dcim/migrations/0068_rack_new_fields.py +++ /dev/null @@ -1,38 +0,0 @@ -from django.db import migrations, models - -import utilities.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0067_device_type_remove_qualifiers'), - ] - - operations = [ - migrations.AddField( - model_name='rack', - name='status', - field=models.PositiveSmallIntegerField(default=3), - ), - migrations.AddField( - model_name='rack', - name='asset_tag', - field=utilities.fields.NullableCharField(blank=True, max_length=50, null=True, unique=True), - ), - migrations.AddField( - model_name='rack', - name='outer_depth', - field=models.PositiveSmallIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='rack', - name='outer_unit', - field=models.PositiveSmallIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='rack', - name='outer_width', - field=models.PositiveSmallIntegerField(blank=True, null=True), - ), - ] diff --git a/netbox/dcim/migrations/0069_deprecate_nullablecharfield.py b/netbox/dcim/migrations/0069_deprecate_nullablecharfield.py deleted file mode 100644 index 77cee8517..000000000 --- a/netbox/dcim/migrations/0069_deprecate_nullablecharfield.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.1.5 on 2019-02-14 14:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0068_rack_new_fields'), - ] - - operations = [ - migrations.AlterField( - model_name='device', - name='asset_tag', - field=models.CharField(blank=True, max_length=50, null=True, unique=True), - ), - migrations.AlterField( - model_name='device', - name='name', - field=models.CharField(blank=True, max_length=64, null=True, unique=True), - ), - migrations.AlterField( - model_name='inventoryitem', - name='asset_tag', - field=models.CharField(blank=True, max_length=50, null=True, unique=True), - ), - migrations.AlterField( - model_name='rack', - name='asset_tag', - field=models.CharField(blank=True, max_length=50, null=True, unique=True), - ), - migrations.AlterField( - model_name='rack', - name='facility_id', - field=models.CharField(blank=True, max_length=50, null=True), - ), - ] diff --git a/netbox/dcim/migrations/0070_custom_tag_models.py b/netbox/dcim/migrations/0070_custom_tag_models.py deleted file mode 100644 index ee78bed02..000000000 --- a/netbox/dcim/migrations/0070_custom_tag_models.py +++ /dev/null @@ -1,85 +0,0 @@ -# Generated by Django 2.1.4 on 2019-02-20 06:56 - -from django.db import migrations -import taggit.managers - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0069_deprecate_nullablecharfield'), - ('extras', '0019_tag_taggeditem'), - ] - - operations = [ - migrations.AlterField( - model_name='consoleport', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='consoleserverport', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='device', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='devicebay', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='devicetype', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='frontport', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='interface', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='inventoryitem', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='poweroutlet', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='powerport', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='rack', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='rearport', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='site', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AlterField( - model_name='virtualchassis', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - ] diff --git a/netbox/dcim/migrations/0071_device_components_add_description.py b/netbox/dcim/migrations/0071_device_components_add_description.py deleted file mode 100644 index d38f8a88b..000000000 --- a/netbox/dcim/migrations/0071_device_components_add_description.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.1.7 on 2019-02-20 18:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0070_custom_tag_models'), - ] - - operations = [ - migrations.AddField( - model_name='consoleport', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - migrations.AddField( - model_name='consoleserverport', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - migrations.AddField( - model_name='devicebay', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - migrations.AddField( - model_name='poweroutlet', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - migrations.AddField( - model_name='powerport', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - ] diff --git a/netbox/dcim/migrations/0072_powerfeeds.py b/netbox/dcim/migrations/0072_powerfeeds.py deleted file mode 100644 index 63662684d..000000000 --- a/netbox/dcim/migrations/0072_powerfeeds.py +++ /dev/null @@ -1,134 +0,0 @@ -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import taggit.managers - - -class Migration(migrations.Migration): - - dependencies = [ - ('extras', '0021_add_color_comments_changelog_to_tag'), - ('dcim', '0071_device_components_add_description'), - ] - - operations = [ - migrations.CreateModel( - name='PowerFeed', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), - ('created', models.DateField(auto_now_add=True, null=True)), - ('last_updated', models.DateTimeField(auto_now=True, null=True)), - ('name', models.CharField(max_length=50)), - ('status', models.PositiveSmallIntegerField(default=1)), - ('type', models.PositiveSmallIntegerField(default=1)), - ('supply', models.PositiveSmallIntegerField(default=1)), - ('phase', models.PositiveSmallIntegerField(default=1)), - ('voltage', models.PositiveSmallIntegerField(default=120, validators=[django.core.validators.MinValueValidator(1)])), - ('amperage', models.PositiveSmallIntegerField(default=20, validators=[django.core.validators.MinValueValidator(1)])), - ('max_utilization', models.PositiveSmallIntegerField(default=80, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])), - ('available_power', models.PositiveSmallIntegerField(default=0, editable=False)), - ('comments', models.TextField(blank=True)), - ('cable', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable')), - ], - options={ - 'ordering': ['power_panel', 'name'], - }, - ), - migrations.CreateModel( - name='PowerPanel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), - ('created', models.DateField(auto_now_add=True, null=True)), - ('last_updated', models.DateTimeField(auto_now=True, null=True)), - ('name', models.CharField(max_length=50)), - ('rack_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.RackGroup')), - ('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dcim.Site')), - ], - options={ - 'ordering': ['site', 'name'], - }, - ), - migrations.AddField( - model_name='powerfeed', - name='power_panel', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='powerfeeds', to='dcim.PowerPanel'), - ), - migrations.AddField( - model_name='powerfeed', - name='rack', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.Rack'), - ), - migrations.AddField( - model_name='powerfeed', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AddField( - model_name='powerfeed', - name='connected_endpoint', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.PowerPort'), - ), - migrations.AddField( - model_name='powerfeed', - name='connection_status', - field=models.NullBooleanField(), - ), - migrations.RenameField( - model_name='powerport', - old_name='connected_endpoint', - new_name='_connected_poweroutlet', - ), - migrations.AddField( - model_name='powerport', - name='_connected_powerfeed', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.PowerFeed'), - ), - migrations.AddField( - model_name='powerport', - name='allocated_draw', - field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]), - ), - migrations.AddField( - model_name='powerport', - name='maximum_draw', - field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]), - ), - migrations.AddField( - model_name='powerporttemplate', - name='allocated_draw', - field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]), - ), - migrations.AddField( - model_name='powerporttemplate', - name='maximum_draw', - field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]), - ), - migrations.AlterUniqueTogether( - name='powerpanel', - unique_together={('site', 'name')}, - ), - migrations.AlterUniqueTogether( - name='powerfeed', - unique_together={('power_panel', 'name')}, - ), - migrations.AddField( - model_name='poweroutlet', - name='feed_leg', - field=models.PositiveSmallIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='poweroutlet', - name='power_port', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlets', to='dcim.PowerPort'), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='feed_leg', - field=models.PositiveSmallIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='power_port', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlet_templates', to='dcim.PowerPortTemplate'), - ), - ] diff --git a/netbox/dcim/migrations/0073_interface_form_factor_to_type.py b/netbox/dcim/migrations/0073_interface_form_factor_to_type.py deleted file mode 100644 index 38016ea00..000000000 --- a/netbox/dcim/migrations/0073_interface_form_factor_to_type.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.1.7 on 2019-04-12 17:27 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0072_powerfeeds'), - ] - - operations = [ - migrations.RenameField( - model_name='interface', - old_name='form_factor', - new_name='type', - ), - migrations.RenameField( - model_name='interfacetemplate', - old_name='form_factor', - new_name='type', - ), - ] diff --git a/netbox/dcim/migrations/0074_increase_field_length_platform_name_slug.py b/netbox/dcim/migrations/0074_increase_field_length_platform_name_slug.py deleted file mode 100644 index 2c8a2255c..000000000 --- a/netbox/dcim/migrations/0074_increase_field_length_platform_name_slug.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2 on 2019-07-17 20:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0073_interface_form_factor_to_type'), - ] - - operations = [ - migrations.AlterField( - model_name='platform', - name='name', - field=models.CharField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='platform', - name='slug', - field=models.SlugField(max_length=100, unique=True), - ), - ] diff --git a/netbox/dcim/migrations/0075_cable_devices.py b/netbox/dcim/migrations/0075_cable_devices.py deleted file mode 100644 index f022fe111..000000000 --- a/netbox/dcim/migrations/0075_cable_devices.py +++ /dev/null @@ -1,60 +0,0 @@ -import sys - -from django.db import migrations, models -import django.db.models.deletion - - -def cache_cable_devices(apps, schema_editor): - Cable = apps.get_model('dcim', 'Cable') - - if 'test' not in sys.argv: - print("\nUpdating cable device terminations...") - cable_count = Cable.objects.count() - - # Cache A/B termination devices on all existing Cables. Note that the custom save() method on Cable is not - # available during a migration, so we replicate its logic here. - for i, cable in enumerate(Cable.objects.all(), start=1): - - if not i % 1000 and 'test' not in sys.argv: - print("[{}/{}]".format(i, cable_count)) - - termination_a_model = apps.get_model(cable.termination_a_type.app_label, cable.termination_a_type.model) - termination_a_device = None - if hasattr(termination_a_model, 'device'): - termination_a = termination_a_model.objects.get(pk=cable.termination_a_id) - termination_a_device = termination_a.device - - termination_b_model = apps.get_model(cable.termination_b_type.app_label, cable.termination_b_type.model) - termination_b_device = None - if hasattr(termination_b_model, 'device'): - termination_b = termination_b_model.objects.get(pk=cable.termination_b_id) - termination_b_device = termination_b.device - - Cable.objects.filter(pk=cable.pk).update( - _termination_a_device=termination_a_device, - _termination_b_device=termination_b_device - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0074_increase_field_length_platform_name_slug'), - ] - - operations = [ - migrations.AddField( - model_name='cable', - name='_termination_a_device', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.Device'), - ), - migrations.AddField( - model_name='cable', - name='_termination_b_device', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.Device'), - ), - migrations.RunPython( - code=cache_cable_devices, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0076_console_port_types.py b/netbox/dcim/migrations/0076_console_port_types.py deleted file mode 100644 index 844b32283..000000000 --- a/netbox/dcim/migrations/0076_console_port_types.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.2.6 on 2019-10-30 17:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0075_cable_devices'), - ] - - operations = [ - migrations.AddField( - model_name='consoleport', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - migrations.AddField( - model_name='consoleporttemplate', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - migrations.AddField( - model_name='consoleserverport', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - ] diff --git a/netbox/dcim/migrations/0077_power_types.py b/netbox/dcim/migrations/0077_power_types.py deleted file mode 100644 index 702bd837b..000000000 --- a/netbox/dcim/migrations/0077_power_types.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.2.6 on 2019-11-06 19:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0076_console_port_types'), - ] - - operations = [ - migrations.AddField( - model_name='poweroutlet', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - migrations.AddField( - model_name='powerport', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - migrations.AddField( - model_name='powerporttemplate', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - ] diff --git a/netbox/dcim/migrations/0078_3569_site_fields.py b/netbox/dcim/migrations/0078_3569_site_fields.py deleted file mode 100644 index 8775abe5e..000000000 --- a/netbox/dcim/migrations/0078_3569_site_fields.py +++ /dev/null @@ -1,35 +0,0 @@ -from django.db import migrations, models - -SITE_STATUS_CHOICES = ( - (1, 'active'), - (2, 'planned'), - (4, 'retired'), -) - - -def site_status_to_slug(apps, schema_editor): - Site = apps.get_model('dcim', 'Site') - for id, slug in SITE_STATUS_CHOICES: - Site.objects.filter(status=str(id)).update(status=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0077_power_types'), - ] - - operations = [ - - # Site.status - migrations.AlterField( - model_name='site', - name='status', - field=models.CharField(default='active', max_length=50), - ), - migrations.RunPython( - code=site_status_to_slug - ), - - ] diff --git a/netbox/dcim/migrations/0079_3569_rack_fields.py b/netbox/dcim/migrations/0079_3569_rack_fields.py deleted file mode 100644 index da544bb7a..000000000 --- a/netbox/dcim/migrations/0079_3569_rack_fields.py +++ /dev/null @@ -1,92 +0,0 @@ -from django.db import migrations, models - -RACK_TYPE_CHOICES = ( - (100, '2-post-frame'), - (200, '4-post-frame'), - (300, '4-post-cabinet'), - (1000, 'wall-frame'), - (1100, 'wall-cabinet'), -) - -RACK_STATUS_CHOICES = ( - (0, 'reserved'), - (1, 'available'), - (2, 'planned'), - (3, 'active'), - (4, 'deprecated'), -) - -RACK_DIMENSION_CHOICES = ( - (1000, 'mm'), - (2000, 'in'), -) - - -def rack_type_to_slug(apps, schema_editor): - Rack = apps.get_model('dcim', 'Rack') - for id, slug in RACK_TYPE_CHOICES: - Rack.objects.filter(type=str(id)).update(type=slug) - - -def rack_status_to_slug(apps, schema_editor): - Rack = apps.get_model('dcim', 'Rack') - for id, slug in RACK_STATUS_CHOICES: - Rack.objects.filter(status=str(id)).update(status=slug) - - -def rack_outer_unit_to_slug(apps, schema_editor): - Rack = apps.get_model('dcim', 'Rack') - for id, slug in RACK_DIMENSION_CHOICES: - Rack.objects.filter(outer_unit=str(id)).update(outer_unit=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0078_3569_site_fields'), - ] - - operations = [ - - # Rack.type - migrations.AlterField( - model_name='rack', - name='type', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=rack_type_to_slug - ), - migrations.AlterField( - model_name='rack', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - - # Rack.status - migrations.AlterField( - model_name='rack', - name='status', - field=models.CharField(default='active', max_length=50), - ), - migrations.RunPython( - code=rack_status_to_slug - ), - - # Rack.outer_unit - migrations.AlterField( - model_name='rack', - name='outer_unit', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=rack_outer_unit_to_slug - ), - migrations.AlterField( - model_name='rack', - name='outer_unit', - field=models.CharField(blank=True, max_length=50), - ), - - ] diff --git a/netbox/dcim/migrations/0080_3569_devicetype_fields.py b/netbox/dcim/migrations/0080_3569_devicetype_fields.py deleted file mode 100644 index e729eaa55..000000000 --- a/netbox/dcim/migrations/0080_3569_devicetype_fields.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.db import migrations, models - -SUBDEVICE_ROLE_CHOICES = ( - ('true', 'parent'), - ('false', 'child'), -) - - -def devicetype_subdevicerole_to_slug(apps, schema_editor): - DeviceType = apps.get_model('dcim', 'DeviceType') - for boolean, slug in SUBDEVICE_ROLE_CHOICES: - DeviceType.objects.filter(subdevice_role=boolean).update(subdevice_role=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0079_3569_rack_fields'), - ] - - operations = [ - - # DeviceType.subdevice_role - migrations.AlterField( - model_name='devicetype', - name='subdevice_role', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=devicetype_subdevicerole_to_slug - ), - migrations.AlterField( - model_name='devicetype', - name='subdevice_role', - field=models.CharField(blank=True, max_length=50), - ), - - ] diff --git a/netbox/dcim/migrations/0081_3569_device_fields.py b/netbox/dcim/migrations/0081_3569_device_fields.py deleted file mode 100644 index f1f0bdb2b..000000000 --- a/netbox/dcim/migrations/0081_3569_device_fields.py +++ /dev/null @@ -1,65 +0,0 @@ -from django.db import migrations, models - -DEVICE_FACE_CHOICES = ( - (0, 'front'), - (1, 'rear'), -) - -DEVICE_STATUS_CHOICES = ( - (0, 'offline'), - (1, 'active'), - (2, 'planned'), - (3, 'staged'), - (4, 'failed'), - (5, 'inventory'), - (6, 'decommissioning'), -) - - -def device_face_to_slug(apps, schema_editor): - Device = apps.get_model('dcim', 'Device') - for id, slug in DEVICE_FACE_CHOICES: - Device.objects.filter(face=str(id)).update(face=slug) - - -def device_status_to_slug(apps, schema_editor): - Device = apps.get_model('dcim', 'Device') - for id, slug in DEVICE_STATUS_CHOICES: - Device.objects.filter(status=str(id)).update(status=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0080_3569_devicetype_fields'), - ] - - operations = [ - - # Device.face - migrations.AlterField( - model_name='device', - name='face', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=device_face_to_slug - ), - migrations.AlterField( - model_name='device', - name='face', - field=models.CharField(blank=True, max_length=50), - ), - - # Device.status - migrations.AlterField( - model_name='device', - name='status', - field=models.CharField(default='active', max_length=50), - ), - migrations.RunPython( - code=device_status_to_slug - ), - - ] diff --git a/netbox/dcim/migrations/0082_3569_interface_fields.py b/netbox/dcim/migrations/0082_3569_interface_fields.py deleted file mode 100644 index 57701ce0a..000000000 --- a/netbox/dcim/migrations/0082_3569_interface_fields.py +++ /dev/null @@ -1,147 +0,0 @@ -from django.db import migrations, models - - -INTERFACE_TYPE_CHOICES = ( - (0, 'virtual'), - (200, 'lag'), - (800, '100base-tx'), - (1000, '1000base-t'), - (1050, '1000base-x-gbic'), - (1100, '1000base-x-sfp'), - (1120, '2.5gbase-t'), - (1130, '5gbase-t'), - (1150, '10gbase-t'), - (1170, '10gbase-cx4'), - (1200, '10gbase-x-sfpp'), - (1300, '10gbase-x-xfp'), - (1310, '10gbase-x-xenpak'), - (1320, '10gbase-x-x2'), - (1350, '25gbase-x-sfp28'), - (1400, '40gbase-x-qsfpp'), - (1420, '50gbase-x-sfp28'), - (1500, '100gbase-x-cfp'), - (1510, '100gbase-x-cfp2'), - (1520, '100gbase-x-cfp4'), - (1550, '100gbase-x-cpak'), - (1600, '100gbase-x-qsfp28'), - (1650, '200gbase-x-cfp2'), - (1700, '200gbase-x-qsfp56'), - (1750, '400gbase-x-qsfpdd'), - (1800, '400gbase-x-osfp'), - (2600, 'ieee802.11a'), - (2610, 'ieee802.11g'), - (2620, 'ieee802.11n'), - (2630, 'ieee802.11ac'), - (2640, 'ieee802.11ad'), - (2810, 'gsm'), - (2820, 'cdma'), - (2830, 'lte'), - (6100, 'sonet-oc3'), - (6200, 'sonet-oc12'), - (6300, 'sonet-oc48'), - (6400, 'sonet-oc192'), - (6500, 'sonet-oc768'), - (6600, 'sonet-oc1920'), - (6700, 'sonet-oc3840'), - (3010, '1gfc-sfp'), - (3020, '2gfc-sfp'), - (3040, '4gfc-sfp'), - (3080, '8gfc-sfpp'), - (3160, '16gfc-sfpp'), - (3320, '32gfc-sfp28'), - (3400, '128gfc-sfp28'), - (7010, 'inifiband-sdr'), - (7020, 'inifiband-ddr'), - (7030, 'inifiband-qdr'), - (7040, 'inifiband-fdr10'), - (7050, 'inifiband-fdr'), - (7060, 'inifiband-edr'), - (7070, 'inifiband-hdr'), - (7080, 'inifiband-ndr'), - (7090, 'inifiband-xdr'), - (4000, 't1'), - (4010, 'e1'), - (4040, 't3'), - (4050, 'e3'), - (5000, 'cisco-stackwise'), - (5050, 'cisco-stackwise-plus'), - (5100, 'cisco-flexstack'), - (5150, 'cisco-flexstack-plus'), - (5200, 'juniper-vcp'), - (5300, 'extreme-summitstack'), - (5310, 'extreme-summitstack-128'), - (5320, 'extreme-summitstack-256'), - (5330, 'extreme-summitstack-512'), -) - - -INTERFACE_MODE_CHOICES = ( - (100, 'access'), - (200, 'tagged'), - (300, 'tagged-all'), -) - - -def interfacetemplate_type_to_slug(apps, schema_editor): - InterfaceTemplate = apps.get_model('dcim', 'InterfaceTemplate') - for id, slug in INTERFACE_TYPE_CHOICES: - InterfaceTemplate.objects.filter(type=id).update(type=slug) - - -def interface_type_to_slug(apps, schema_editor): - Interface = apps.get_model('dcim', 'Interface') - for id, slug in INTERFACE_TYPE_CHOICES: - Interface.objects.filter(type=id).update(type=slug) - - -def interface_mode_to_slug(apps, schema_editor): - Interface = apps.get_model('dcim', 'Interface') - for id, slug in INTERFACE_MODE_CHOICES: - Interface.objects.filter(mode=id).update(mode=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0081_3569_device_fields'), - ] - - operations = [ - - # InterfaceTemplate.type - migrations.AlterField( - model_name='interfacetemplate', - name='type', - field=models.CharField(max_length=50), - ), - migrations.RunPython( - code=interfacetemplate_type_to_slug - ), - - # Interface.type - migrations.AlterField( - model_name='interface', - name='type', - field=models.CharField(max_length=50), - ), - migrations.RunPython( - code=interface_type_to_slug - ), - - # Interface.mode - migrations.AlterField( - model_name='interface', - name='mode', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=interface_mode_to_slug - ), - migrations.AlterField( - model_name='interface', - name='mode', - field=models.CharField(blank=True, max_length=50), - ), - - ] diff --git a/netbox/dcim/migrations/0082_3569_port_fields.py b/netbox/dcim/migrations/0082_3569_port_fields.py deleted file mode 100644 index 6d8f50c32..000000000 --- a/netbox/dcim/migrations/0082_3569_port_fields.py +++ /dev/null @@ -1,93 +0,0 @@ -from django.db import migrations, models - - -PORT_TYPE_CHOICES = ( - (1000, '8p8c'), - (1100, '110-punch'), - (1200, 'bnc'), - (2000, 'st'), - (2100, 'sc'), - (2110, 'sc-apc'), - (2200, 'fc'), - (2300, 'lc'), - (2310, 'lc-apc'), - (2400, 'mtrj'), - (2500, 'mpo'), - (2600, 'lsh'), - (2610, 'lsh-apc'), -) - - -def frontporttemplate_type_to_slug(apps, schema_editor): - FrontPortTemplate = apps.get_model('dcim', 'FrontPortTemplate') - for id, slug in PORT_TYPE_CHOICES: - FrontPortTemplate.objects.filter(type=id).update(type=slug) - - -def rearporttemplate_type_to_slug(apps, schema_editor): - RearPortTemplate = apps.get_model('dcim', 'RearPortTemplate') - for id, slug in PORT_TYPE_CHOICES: - RearPortTemplate.objects.filter(type=id).update(type=slug) - - -def frontport_type_to_slug(apps, schema_editor): - FrontPort = apps.get_model('dcim', 'FrontPort') - for id, slug in PORT_TYPE_CHOICES: - FrontPort.objects.filter(type=id).update(type=slug) - - -def rearport_type_to_slug(apps, schema_editor): - RearPort = apps.get_model('dcim', 'RearPort') - for id, slug in PORT_TYPE_CHOICES: - RearPort.objects.filter(type=id).update(type=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0082_3569_interface_fields'), - ] - - operations = [ - - # FrontPortTemplate.type - migrations.AlterField( - model_name='frontporttemplate', - name='type', - field=models.CharField(max_length=50), - ), - migrations.RunPython( - code=frontporttemplate_type_to_slug - ), - - # RearPortTemplate.type - migrations.AlterField( - model_name='rearporttemplate', - name='type', - field=models.CharField(max_length=50), - ), - migrations.RunPython( - code=rearporttemplate_type_to_slug - ), - - # FrontPort.type - migrations.AlterField( - model_name='frontport', - name='type', - field=models.CharField(max_length=50), - ), - migrations.RunPython( - code=frontport_type_to_slug - ), - - # RearPort.type - migrations.AlterField( - model_name='rearport', - name='type', - field=models.CharField(max_length=50), - ), - migrations.RunPython( - code=rearport_type_to_slug - ), - ] diff --git a/netbox/dcim/migrations/0083_3569_cable_fields.py b/netbox/dcim/migrations/0083_3569_cable_fields.py deleted file mode 100644 index 26cf734f7..000000000 --- a/netbox/dcim/migrations/0083_3569_cable_fields.py +++ /dev/null @@ -1,106 +0,0 @@ -from django.db import migrations, models - - -CABLE_TYPE_CHOICES = ( - (1300, 'cat3'), - (1500, 'cat5'), - (1510, 'cat5e'), - (1600, 'cat6'), - (1610, 'cat6a'), - (1700, 'cat7'), - (1800, 'dac-active'), - (1810, 'dac-passive'), - (1900, 'coaxial'), - (3000, 'mmf'), - (3010, 'mmf-om1'), - (3020, 'mmf-om2'), - (3030, 'mmf-om3'), - (3040, 'mmf-om4'), - (3500, 'smf'), - (3510, 'smf-os1'), - (3520, 'smf-os2'), - (3800, 'aoc'), - (5000, 'power'), -) - -CABLE_STATUS_CHOICES = ( - ('true', 'connected'), - ('false', 'planned'), -) - -CABLE_LENGTH_UNIT_CHOICES = ( - (1200, 'm'), - (1100, 'cm'), - (2100, 'ft'), - (2000, 'in'), -) - - -def cable_type_to_slug(apps, schema_editor): - Cable = apps.get_model('dcim', 'Cable') - for id, slug in CABLE_TYPE_CHOICES: - Cable.objects.filter(type=id).update(type=slug) - - -def cable_status_to_slug(apps, schema_editor): - Cable = apps.get_model('dcim', 'Cable') - for bool_str, slug in CABLE_STATUS_CHOICES: - Cable.objects.filter(status=bool_str).update(status=slug) - - -def cable_length_unit_to_slug(apps, schema_editor): - Cable = apps.get_model('dcim', 'Cable') - for id, slug in CABLE_LENGTH_UNIT_CHOICES: - Cable.objects.filter(length_unit=id).update(length_unit=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0082_3569_port_fields'), - ] - - operations = [ - - # Cable.type - migrations.AlterField( - model_name='cable', - name='type', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=cable_type_to_slug - ), - migrations.AlterField( - model_name='cable', - name='type', - field=models.CharField(blank=True, max_length=50), - ), - - # Cable.status - migrations.AlterField( - model_name='cable', - name='status', - field=models.CharField(default='connected', max_length=50), - ), - migrations.RunPython( - code=cable_status_to_slug - ), - - # Cable.length_unit - migrations.AlterField( - model_name='cable', - name='length_unit', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=cable_length_unit_to_slug - ), - migrations.AlterField( - model_name='cable', - name='length_unit', - field=models.CharField(blank=True, max_length=50), - ), - - ] diff --git a/netbox/dcim/migrations/0084_3569_powerfeed_fields.py b/netbox/dcim/migrations/0084_3569_powerfeed_fields.py deleted file mode 100644 index 332443d0a..000000000 --- a/netbox/dcim/migrations/0084_3569_powerfeed_fields.py +++ /dev/null @@ -1,100 +0,0 @@ -from django.db import migrations, models - - -POWERFEED_STATUS_CHOICES = ( - (0, 'offline'), - (1, 'active'), - (2, 'planned'), - (4, 'failed'), -) - -POWERFEED_TYPE_CHOICES = ( - (1, 'primary'), - (2, 'redundant'), -) - -POWERFEED_SUPPLY_CHOICES = ( - (1, 'ac'), - (2, 'dc'), -) - -POWERFEED_PHASE_CHOICES = ( - (1, 'single-phase'), - (3, 'three-phase'), -) - - -def powerfeed_status_to_slug(apps, schema_editor): - PowerFeed = apps.get_model('dcim', 'PowerFeed') - for id, slug in POWERFEED_STATUS_CHOICES: - PowerFeed.objects.filter(status=id).update(status=slug) - - -def powerfeed_type_to_slug(apps, schema_editor): - PowerFeed = apps.get_model('dcim', 'PowerFeed') - for id, slug in POWERFEED_TYPE_CHOICES: - PowerFeed.objects.filter(type=id).update(type=slug) - - -def powerfeed_supply_to_slug(apps, schema_editor): - PowerFeed = apps.get_model('dcim', 'PowerFeed') - for id, slug in POWERFEED_SUPPLY_CHOICES: - PowerFeed.objects.filter(supply=id).update(supply=slug) - - -def powerfeed_phase_to_slug(apps, schema_editor): - PowerFeed = apps.get_model('dcim', 'PowerFeed') - for id, slug in POWERFEED_PHASE_CHOICES: - PowerFeed.objects.filter(phase=id).update(phase=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0083_3569_cable_fields'), - ] - - operations = [ - - # PowerFeed.status - migrations.AlterField( - model_name='powerfeed', - name='status', - field=models.CharField(default='active', max_length=50), - ), - migrations.RunPython( - code=powerfeed_status_to_slug - ), - - # PowerFeed.type - migrations.AlterField( - model_name='powerfeed', - name='type', - field=models.CharField(default='primary', max_length=50), - ), - migrations.RunPython( - code=powerfeed_type_to_slug - ), - - # PowerFeed.supply - migrations.AlterField( - model_name='powerfeed', - name='supply', - field=models.CharField(default='ac', max_length=50), - ), - migrations.RunPython( - code=powerfeed_supply_to_slug - ), - - # PowerFeed.phase - migrations.AlterField( - model_name='powerfeed', - name='phase', - field=models.CharField(default='single-phase', max_length=50), - ), - migrations.RunPython( - code=powerfeed_phase_to_slug - ), - - ] diff --git a/netbox/dcim/migrations/0085_3569_poweroutlet_fields.py b/netbox/dcim/migrations/0085_3569_poweroutlet_fields.py deleted file mode 100644 index e2c070584..000000000 --- a/netbox/dcim/migrations/0085_3569_poweroutlet_fields.py +++ /dev/null @@ -1,62 +0,0 @@ -from django.db import migrations, models - - -POWEROUTLET_FEED_LEG_CHOICES_CHOICES = ( - (1, 'A'), - (2, 'B'), - (3, 'C'), -) - - -def poweroutlettemplate_feed_leg_to_slug(apps, schema_editor): - PowerOutletTemplate = apps.get_model('dcim', 'PowerOutletTemplate') - for id, slug in POWEROUTLET_FEED_LEG_CHOICES_CHOICES: - PowerOutletTemplate.objects.filter(feed_leg=id).update(feed_leg=slug) - - -def poweroutlet_feed_leg_to_slug(apps, schema_editor): - PowerOutlet = apps.get_model('dcim', 'PowerOutlet') - for id, slug in POWEROUTLET_FEED_LEG_CHOICES_CHOICES: - PowerOutlet.objects.filter(feed_leg=id).update(feed_leg=slug) - - -class Migration(migrations.Migration): - atomic = False - - dependencies = [ - ('dcim', '0084_3569_powerfeed_fields'), - ] - - operations = [ - - # PowerOutletTemplate.feed_leg - migrations.AlterField( - model_name='poweroutlettemplate', - name='feed_leg', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=poweroutlettemplate_feed_leg_to_slug - ), - migrations.AlterField( - model_name='poweroutlettemplate', - name='feed_leg', - field=models.CharField(blank=True, max_length=50), - ), - - # PowerOutlet.feed_leg - migrations.AlterField( - model_name='poweroutlet', - name='feed_leg', - field=models.CharField(blank=True, default='', max_length=50), - ), - migrations.RunPython( - code=poweroutlet_feed_leg_to_slug - ), - migrations.AlterField( - model_name='poweroutlet', - name='feed_leg', - field=models.CharField(blank=True, max_length=50), - ), - - ] diff --git a/netbox/dcim/migrations/0086_device_name_nonunique.py b/netbox/dcim/migrations/0086_device_name_nonunique.py deleted file mode 100644 index 3666cf018..000000000 --- a/netbox/dcim/migrations/0086_device_name_nonunique.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.6 on 2019-12-09 15:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tenancy', '0006_custom_tag_models'), - ('dcim', '0085_3569_poweroutlet_fields'), - ] - - operations = [ - migrations.AlterField( - model_name='device', - name='name', - field=models.CharField(blank=True, max_length=64, null=True), - ), - migrations.AlterUniqueTogether( - name='device', - unique_together={('rack', 'position', 'face'), ('virtual_chassis', 'vc_position'), ('site', 'tenant', 'name')}, - ), - ] diff --git a/netbox/dcim/migrations/0087_role_descriptions.py b/netbox/dcim/migrations/0087_role_descriptions.py deleted file mode 100644 index 5f8fd9707..000000000 --- a/netbox/dcim/migrations/0087_role_descriptions.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.6 on 2019-12-10 17:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0086_device_name_nonunique'), - ] - - operations = [ - migrations.AddField( - model_name='devicerole', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - migrations.AddField( - model_name='rackrole', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - ] diff --git a/netbox/dcim/migrations/0088_powerfeed_available_power.py b/netbox/dcim/migrations/0088_powerfeed_available_power.py deleted file mode 100644 index af13d49c6..000000000 --- a/netbox/dcim/migrations/0088_powerfeed_available_power.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-12 02:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0087_role_descriptions'), - ] - - operations = [ - migrations.AlterField( - model_name='powerfeed', - name='available_power', - field=models.PositiveIntegerField(default=0, editable=False), - ), - ] diff --git a/netbox/dcim/migrations/0089_deterministic_ordering.py b/netbox/dcim/migrations/0089_deterministic_ordering.py deleted file mode 100644 index 77d18739e..000000000 --- a/netbox/dcim/migrations/0089_deterministic_ordering.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-15 18:10 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0088_powerfeed_available_power'), - ] - - operations = [ - migrations.AlterModelOptions( - name='device', - options={'ordering': ('name', 'pk')}, - ), - migrations.AlterModelOptions( - name='rack', - options={'ordering': ('site', 'group', 'name', 'pk')}, - ), - ] diff --git a/netbox/dcim/migrations/0090_cable_termination_models.py b/netbox/dcim/migrations/0090_cable_termination_models.py deleted file mode 100644 index b5f240f3e..000000000 --- a/netbox/dcim/migrations/0090_cable_termination_models.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.8 on 2020-01-15 20:51 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0089_deterministic_ordering'), - ] - - operations = [ - migrations.AlterField( - model_name='cable', - name='termination_a_type', - field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), - ), - migrations.AlterField( - model_name='cable', - name='termination_b_type', - field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), - ), - ] diff --git a/netbox/dcim/migrations/0091_interface_type_other.py b/netbox/dcim/migrations/0091_interface_type_other.py deleted file mode 100644 index 1ea24885f..000000000 --- a/netbox/dcim/migrations/0091_interface_type_other.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.db import migrations - - -def interface_type_to_slug(apps, schema_editor): - Interface = apps.get_model('dcim', 'Interface') - Interface.objects.filter(type=32767).update(type='other') - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0090_cable_termination_models'), - ] - - operations = [ - # Missed type "other" in the initial migration (see #3967) - migrations.RunPython( - code=interface_type_to_slug - ), - ] diff --git a/netbox/dcim/migrations/0092_fix_rack_outer_unit.py b/netbox/dcim/migrations/0092_fix_rack_outer_unit.py deleted file mode 100644 index 3d63f1cb3..000000000 --- a/netbox/dcim/migrations/0092_fix_rack_outer_unit.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.db import migrations - -RACK_DIMENSION_CHOICES = ( - (1000, 'mm'), - (2000, 'in'), -) - - -def rack_outer_unit_to_slug(apps, schema_editor): - Rack = apps.get_model('dcim', 'Rack') - for id, slug in RACK_DIMENSION_CHOICES: - Rack.objects.filter(outer_unit=str(id)).update(outer_unit=slug) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0091_interface_type_other'), - ] - - operations = [ - # Fixes a missed field migration from #3569; see bug #4056. The original migration has also been fixed. - migrations.RunPython( - code=rack_outer_unit_to_slug - ), - ] diff --git a/netbox/dcim/migrations/0093_device_component_ordering.py b/netbox/dcim/migrations/0093_device_component_ordering.py deleted file mode 100644 index 925694958..000000000 --- a/netbox/dcim/migrations/0093_device_component_ordering.py +++ /dev/null @@ -1,147 +0,0 @@ -from django.db import migrations -import utilities.fields -import utilities.ordering - - -def _update_model_names(model): - # Update each unique field value in bulk - for name in model.objects.values_list('name', flat=True).order_by('name').distinct(): - model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name, max_length=100)) - - -def naturalize_consoleports(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'ConsolePort')) - - -def naturalize_consoleserverports(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'ConsoleServerPort')) - - -def naturalize_powerports(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'PowerPort')) - - -def naturalize_poweroutlets(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'PowerOutlet')) - - -def naturalize_frontports(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'FrontPort')) - - -def naturalize_rearports(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'RearPort')) - - -def naturalize_devicebays(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'DeviceBay')) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0092_fix_rack_outer_unit'), - ] - - operations = [ - migrations.AlterModelOptions( - name='consoleport', - options={'ordering': ('device', '_name')}, - ), - migrations.AlterModelOptions( - name='consoleserverport', - options={'ordering': ('device', '_name')}, - ), - migrations.AlterModelOptions( - name='devicebay', - options={'ordering': ('device', '_name')}, - ), - migrations.AlterModelOptions( - name='frontport', - options={'ordering': ('device', '_name')}, - ), - migrations.AlterModelOptions( - name='inventoryitem', - options={'ordering': ('device__id', 'parent__id', '_name')}, - ), - migrations.AlterModelOptions( - name='poweroutlet', - options={'ordering': ('device', '_name')}, - ), - migrations.AlterModelOptions( - name='powerport', - options={'ordering': ('device', '_name')}, - ), - migrations.AlterModelOptions( - name='rearport', - options={'ordering': ('device', '_name')}, - ), - migrations.AddField( - model_name='consoleport', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='consoleserverport', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='devicebay', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='frontport', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='inventoryitem', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='poweroutlet', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='powerport', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='rearport', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.RunPython( - code=naturalize_consoleports, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_consoleserverports, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_powerports, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_poweroutlets, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_frontports, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_rearports, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_devicebays, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0094_device_component_template_ordering.py b/netbox/dcim/migrations/0094_device_component_template_ordering.py deleted file mode 100644 index 70acd3189..000000000 --- a/netbox/dcim/migrations/0094_device_component_template_ordering.py +++ /dev/null @@ -1,138 +0,0 @@ -from django.db import migrations -import utilities.fields -import utilities.ordering - - -def _update_model_names(model): - # Update each unique field value in bulk - for name in model.objects.values_list('name', flat=True).order_by('name').distinct(): - model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name, max_length=100)) - - -def naturalize_consoleporttemplates(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'ConsolePortTemplate')) - - -def naturalize_consoleserverporttemplates(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'ConsoleServerPortTemplate')) - - -def naturalize_powerporttemplates(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'PowerPortTemplate')) - - -def naturalize_poweroutlettemplates(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'PowerOutletTemplate')) - - -def naturalize_frontporttemplates(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'FrontPortTemplate')) - - -def naturalize_rearporttemplates(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'RearPortTemplate')) - - -def naturalize_devicebaytemplates(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'DeviceBayTemplate')) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0093_device_component_ordering'), - ] - - operations = [ - migrations.AlterModelOptions( - name='consoleporttemplate', - options={'ordering': ('device_type', '_name')}, - ), - migrations.AlterModelOptions( - name='consoleserverporttemplate', - options={'ordering': ('device_type', '_name')}, - ), - migrations.AlterModelOptions( - name='devicebaytemplate', - options={'ordering': ('device_type', '_name')}, - ), - migrations.AlterModelOptions( - name='frontporttemplate', - options={'ordering': ('device_type', '_name')}, - ), - migrations.AlterModelOptions( - name='poweroutlettemplate', - options={'ordering': ('device_type', '_name')}, - ), - migrations.AlterModelOptions( - name='powerporttemplate', - options={'ordering': ('device_type', '_name')}, - ), - migrations.AlterModelOptions( - name='rearporttemplate', - options={'ordering': ('device_type', '_name')}, - ), - migrations.AddField( - model_name='consoleporttemplate', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='frontporttemplate', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='powerporttemplate', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='rearporttemplate', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.RunPython( - code=naturalize_consoleporttemplates, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_consoleserverporttemplates, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_powerporttemplates, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_poweroutlettemplates, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_frontporttemplates, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_rearporttemplates, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_devicebaytemplates, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0095_primary_model_ordering.py b/netbox/dcim/migrations/0095_primary_model_ordering.py deleted file mode 100644 index 2d6be72c8..000000000 --- a/netbox/dcim/migrations/0095_primary_model_ordering.py +++ /dev/null @@ -1,70 +0,0 @@ -from django.db import migrations -import utilities.fields -import utilities.ordering - - -def _update_model_names(model): - # Update each unique field value in bulk - for name in model.objects.values_list('name', flat=True).order_by('name').distinct(): - model.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name, max_length=100)) - - -def naturalize_sites(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'Site')) - - -def naturalize_racks(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'Rack')) - - -def naturalize_devices(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'Device')) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0094_device_component_template_ordering'), - ] - - operations = [ - migrations.AlterModelOptions( - name='device', - options={'ordering': ('_name', 'pk')}, - ), - migrations.AlterModelOptions( - name='rack', - options={'ordering': ('site', 'group', '_name', 'pk')}, - ), - migrations.AlterModelOptions( - name='site', - options={'ordering': ('_name',)}, - ), - migrations.AddField( - model_name='device', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize, null=True), - ), - migrations.AddField( - model_name='rack', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.AddField( - model_name='site', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize), - ), - migrations.RunPython( - code=naturalize_sites, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_racks, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_devices, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0096_interface_ordering.py b/netbox/dcim/migrations/0096_interface_ordering.py deleted file mode 100644 index 7b2663c95..000000000 --- a/netbox/dcim/migrations/0096_interface_ordering.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.db import migrations -import utilities.fields -import utilities.ordering - - -def _update_model_names(model): - # Update each unique field value in bulk - for name in model.objects.values_list('name', flat=True).order_by('name').distinct(): - model.objects.filter(name=name).update(_name=utilities.ordering.naturalize_interface(name, max_length=100)) - - -def naturalize_interfacetemplates(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'InterfaceTemplate')) - - -def naturalize_interfaces(apps, schema_editor): - _update_model_names(apps.get_model('dcim', 'Interface')) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0095_primary_model_ordering'), - ] - - operations = [ - migrations.AlterModelOptions( - name='interface', - options={'ordering': ('device', '_name')}, - ), - migrations.AlterModelOptions( - name='interfacetemplate', - options={'ordering': ('device_type', '_name')}, - ), - migrations.AddField( - model_name='interface', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface), - ), - migrations.AddField( - model_name='interfacetemplate', - name='_name', - field=utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface), - ), - migrations.RunPython( - code=naturalize_interfacetemplates, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - code=naturalize_interfaces, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0097_interfacetemplate_type_other.py b/netbox/dcim/migrations/0097_interfacetemplate_type_other.py deleted file mode 100644 index d71b5c655..000000000 --- a/netbox/dcim/migrations/0097_interfacetemplate_type_other.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.db import migrations - - -def interfacetemplate_type_to_slug(apps, schema_editor): - InterfaceTemplate = apps.get_model('dcim', 'InterfaceTemplate') - InterfaceTemplate.objects.filter(type=32767).update(type='other') - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0096_interface_ordering'), - ] - - operations = [ - # Missed type "other" in the initial migration (see #3967) - migrations.RunPython( - code=interfacetemplate_type_to_slug - ), - ] diff --git a/netbox/dcim/migrations/0098_devicetype_images.py b/netbox/dcim/migrations/0098_devicetype_images.py deleted file mode 100644 index 837a2b73a..000000000 --- a/netbox/dcim/migrations/0098_devicetype_images.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.9 on 2020-02-20 15:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0097_interfacetemplate_type_other'), - ] - - operations = [ - migrations.AddField( - model_name='devicetype', - name='front_image', - field=models.ImageField(blank=True, upload_to='devicetype-images'), - ), - migrations.AddField( - model_name='devicetype', - name='rear_image', - field=models.ImageField(blank=True, upload_to='devicetype-images'), - ), - ] diff --git a/netbox/dcim/migrations/0099_powerfeed_negative_voltage.py b/netbox/dcim/migrations/0099_powerfeed_negative_voltage.py deleted file mode 100644 index db16fbc91..000000000 --- a/netbox/dcim/migrations/0099_powerfeed_negative_voltage.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.10 on 2020-03-03 16:59 - -from django.db import migrations, models -import utilities.validators - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0098_devicetype_images'), - ] - - operations = [ - migrations.AlterField( - model_name='powerfeed', - name='voltage', - field=models.SmallIntegerField(default=120, validators=[utilities.validators.ExclusionValidator([0])]), - ), - ] diff --git a/netbox/dcim/migrations/0100_mptt_remove_indexes.py b/netbox/dcim/migrations/0100_mptt_remove_indexes.py deleted file mode 100644 index 79d9cb597..000000000 --- a/netbox/dcim/migrations/0100_mptt_remove_indexes.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.0.3 on 2020-02-18 21:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0099_powerfeed_negative_voltage'), - ] - - operations = [ - migrations.AlterField( - model_name='region', - name='level', - field=models.PositiveIntegerField(editable=False), - ), - migrations.AlterField( - model_name='region', - name='lft', - field=models.PositiveIntegerField(editable=False), - ), - migrations.AlterField( - model_name='region', - name='rght', - field=models.PositiveIntegerField(editable=False), - ), - ] diff --git a/netbox/dcim/migrations/0101_nested_rackgroups.py b/netbox/dcim/migrations/0101_nested_rackgroups.py deleted file mode 100644 index dd5f8af26..000000000 --- a/netbox/dcim/migrations/0101_nested_rackgroups.py +++ /dev/null @@ -1,43 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion -import mptt.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0100_mptt_remove_indexes'), - ] - - operations = [ - migrations.AddField( - model_name='rackgroup', - name='parent', - field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.RackGroup'), - ), - migrations.AddField( - model_name='rackgroup', - name='level', - field=models.PositiveIntegerField(default=0, editable=False), - preserve_default=False, - ), - migrations.AddField( - model_name='rackgroup', - name='lft', - field=models.PositiveIntegerField(default=1, editable=False), - preserve_default=False, - ), - migrations.AddField( - model_name='rackgroup', - name='rght', - field=models.PositiveIntegerField(default=2, editable=False), - preserve_default=False, - ), - # tree_id will be set to a valid value during the following migration (which needs to be a separate migration) - migrations.AddField( - model_name='rackgroup', - name='tree_id', - field=models.PositiveIntegerField(db_index=True, default=0, editable=False), - preserve_default=False, - ), - ] diff --git a/netbox/dcim/migrations/0102_nested_rackgroups_rebuild.py b/netbox/dcim/migrations/0102_nested_rackgroups_rebuild.py deleted file mode 100644 index 411bb3abb..000000000 --- a/netbox/dcim/migrations/0102_nested_rackgroups_rebuild.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.db import migrations - - -def rebuild_mptt(apps, schema_editor): - RackGroup = apps.get_model('dcim', 'RackGroup') - for i, rackgroup in enumerate(RackGroup.objects.all(), start=1): - RackGroup.objects.filter(pk=rackgroup.pk).update(tree_id=i) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0101_nested_rackgroups'), - ] - - operations = [ - migrations.RunPython( - code=rebuild_mptt, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0103_standardize_description.py b/netbox/dcim/migrations/0103_standardize_description.py deleted file mode 100644 index eb4a2d760..000000000 --- a/netbox/dcim/migrations/0103_standardize_description.py +++ /dev/null @@ -1,98 +0,0 @@ -# Generated by Django 3.0.3 on 2020-03-13 20:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0102_nested_rackgroups_rebuild'), - ] - - operations = [ - migrations.AddField( - model_name='manufacturer', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='platform', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='rackgroup', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='region', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='consoleport', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='consoleserverport', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='devicebay', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='devicerole', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='frontport', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='interface', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='inventoryitem', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='poweroutlet', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='powerport', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='rackreservation', - name='description', - field=models.CharField(max_length=200), - ), - migrations.AlterField( - model_name='rackrole', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='rearport', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='site', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - ] diff --git a/netbox/dcim/migrations/0104_correct_infiniband_types.py b/netbox/dcim/migrations/0104_correct_infiniband_types.py deleted file mode 100644 index 91438e62e..000000000 --- a/netbox/dcim/migrations/0104_correct_infiniband_types.py +++ /dev/null @@ -1,34 +0,0 @@ -from django.db import migrations - - -INFINIBAND_SLUGS = ( - ('inifiband-sdr', 'infiniband-sdr'), - ('inifiband-ddr', 'infiniband-ddr'), - ('inifiband-qdr', 'infiniband-qdr'), - ('inifiband-fdr10', 'infiniband-fdr10'), - ('inifiband-fdr', 'infiniband-fdr'), - ('inifiband-edr', 'infiniband-edr'), - ('inifiband-hdr', 'infiniband-hdr'), - ('inifiband-ndr', 'infiniband-ndr'), - ('inifiband-xdr', 'infiniband-xdr'), -) - - -def correct_infiniband_types(apps, schema_editor): - Interface = apps.get_model('dcim', 'Interface') - for old, new in INFINIBAND_SLUGS: - Interface.objects.filter(type=old).update(type=new) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0103_standardize_description'), - ] - - operations = [ - migrations.RunPython( - code=correct_infiniband_types, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0105_interface_name_collation.py b/netbox/dcim/migrations/0105_interface_name_collation.py deleted file mode 100644 index 3079cf5cd..000000000 --- a/netbox/dcim/migrations/0105_interface_name_collation.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.0.5 on 2020-04-21 20:13 - -from django.db import migrations -import utilities.query_functions - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0104_correct_infiniband_types'), - ] - - operations = [ - migrations.AlterModelOptions( - name='interface', - options={'ordering': ('device', utilities.query_functions.CollateAsChar('_name'))}, - ), - ] diff --git a/netbox/dcim/migrations/0106_role_default_color.py b/netbox/dcim/migrations/0106_role_default_color.py deleted file mode 100644 index c4df1b33f..000000000 --- a/netbox/dcim/migrations/0106_role_default_color.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.0.6 on 2020-05-26 13:33 - -from django.db import migrations -import utilities.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0105_interface_name_collation'), - ] - - operations = [ - migrations.AlterField( - model_name='devicerole', - name='color', - field=utilities.fields.ColorField(default='9e9e9e', max_length=6), - ), - migrations.AlterField( - model_name='rackrole', - name='color', - field=utilities.fields.ColorField(default='9e9e9e', max_length=6), - ), - ] diff --git a/netbox/dcim/migrations/0107_component_labels.py b/netbox/dcim/migrations/0107_component_labels.py deleted file mode 100644 index c89bfc0b6..000000000 --- a/netbox/dcim/migrations/0107_component_labels.py +++ /dev/null @@ -1,96 +0,0 @@ -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0106_role_default_color'), - ] - - operations = [ - migrations.AddField( - model_name='consoleport', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='consoleporttemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='consoleserverport', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='devicebay', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='frontport', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='frontporttemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='interface', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='interfacetemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='inventoryitem', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='poweroutlet', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='powerport', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='powerporttemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='rearport', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='rearporttemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - ] diff --git a/netbox/dcim/migrations/0108_add_tags.py b/netbox/dcim/migrations/0108_add_tags.py deleted file mode 100644 index 670f1f0e9..000000000 --- a/netbox/dcim/migrations/0108_add_tags.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 3.0.6 on 2020-06-10 18:32 - -from django.db import migrations -import taggit.managers - - -class Migration(migrations.Migration): - - dependencies = [ - ('extras', '0042_customfield_manager'), - ('dcim', '0107_component_labels'), - ] - - operations = [ - migrations.AddField( - model_name='cable', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AddField( - model_name='powerpanel', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - migrations.AddField( - model_name='rackreservation', - name='tags', - field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'), - ), - ] diff --git a/netbox/dcim/migrations/0109_interface_remove_vm.py b/netbox/dcim/migrations/0109_interface_remove_vm.py deleted file mode 100644 index 6e1d727b0..000000000 --- a/netbox/dcim/migrations/0109_interface_remove_vm.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0108_add_tags'), - ('virtualization', '0016_replicate_interfaces'), - ] - - operations = [ - migrations.RemoveField( - model_name='interface', - name='virtual_machine', - ), - # device is now a required field - migrations.AlterField( - model_name='interface', - name='device', - field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.Device'), - preserve_default=False, - ), - ] diff --git a/netbox/dcim/migrations/0110_virtualchassis_name.py b/netbox/dcim/migrations/0110_virtualchassis_name.py deleted file mode 100644 index e8455d6fe..000000000 --- a/netbox/dcim/migrations/0110_virtualchassis_name.py +++ /dev/null @@ -1,46 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion - - -def copy_master_name(apps, schema_editor): - """ - Copy the master device's name to the VirtualChassis. - """ - VirtualChassis = apps.get_model('dcim', 'VirtualChassis') - - for vc in VirtualChassis.objects.prefetch_related('master'): - name = vc.master.name if vc.master.name else f'Unnamed VC #{vc.pk}' - VirtualChassis.objects.filter(pk=vc.pk).update(name=name) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0109_interface_remove_vm'), - ] - - operations = [ - migrations.AlterModelOptions( - name='virtualchassis', - options={'ordering': ['name'], 'verbose_name_plural': 'virtual chassis'}, - ), - migrations.AddField( - model_name='virtualchassis', - name='name', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AlterField( - model_name='virtualchassis', - name='master', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vc_master_for', to='dcim.Device'), - ), - migrations.RunPython( - code=copy_master_name, - reverse_code=migrations.RunPython.noop - ), - migrations.AlterField( - model_name='virtualchassis', - name='name', - field=models.CharField(max_length=64), - ), - ] diff --git a/netbox/dcim/migrations/0111_component_template_description.py b/netbox/dcim/migrations/0111_component_template_description.py deleted file mode 100644 index 3040f586c..000000000 --- a/netbox/dcim/migrations/0111_component_template_description.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 3.0.6 on 2020-06-30 18:55 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0110_virtualchassis_name'), - ] - - operations = [ - migrations.AddField( - model_name='consoleporttemplate', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='frontporttemplate', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='interfacetemplate', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='powerporttemplate', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AddField( - model_name='rearporttemplate', - name='description', - field=models.CharField(blank=True, max_length=200), - ), - ] diff --git a/netbox/dcim/migrations/0112_standardize_components.py b/netbox/dcim/migrations/0112_standardize_components.py deleted file mode 100644 index 1a3465e02..000000000 --- a/netbox/dcim/migrations/0112_standardize_components.py +++ /dev/null @@ -1,120 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0111_component_template_description'), - ] - - operations = [ - # Set max_length=64 for all name fields - migrations.AlterField( - model_name='consoleport', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='consoleporttemplate', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='consoleserverport', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='consoleserverporttemplate', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='devicebay', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='devicebaytemplate', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='inventoryitem', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='poweroutlet', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='poweroutlettemplate', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='powerport', - name='name', - field=models.CharField(max_length=64), - ), - migrations.AlterField( - model_name='powerporttemplate', - name='name', - field=models.CharField(max_length=64), - ), - - # Update related_name for necessary component and component template models - migrations.AlterField( - model_name='consoleporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleporttemplates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='consoleserverporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverporttemplates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='devicebay', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devicebays', to='dcim.Device'), - ), - migrations.AlterField( - model_name='devicebaytemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devicebaytemplates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='frontporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontporttemplates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfacetemplates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='inventoryitem', - name='device', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventoryitems', to='dcim.Device'), - ), - migrations.AlterField( - model_name='poweroutlettemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlettemplates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='powerporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='powerporttemplates', to='dcim.DeviceType'), - ), - migrations.AlterField( - model_name='rearporttemplate', - name='device_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearporttemplates', to='dcim.DeviceType'), - ), - ] diff --git a/netbox/dcim/migrations/0113_nullbooleanfield_to_booleanfield.py b/netbox/dcim/migrations/0113_nullbooleanfield_to_booleanfield.py deleted file mode 100644 index b96e2dcd4..000000000 --- a/netbox/dcim/migrations/0113_nullbooleanfield_to_booleanfield.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 3.1b1 on 2020-07-16 15:55 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0112_standardize_components'), - ] - - operations = [ - migrations.AlterField( - model_name='consoleport', - name='connection_status', - field=models.BooleanField(blank=True, null=True), - ), - migrations.AlterField( - model_name='consoleserverport', - name='connection_status', - field=models.BooleanField(blank=True, null=True), - ), - migrations.AlterField( - model_name='interface', - name='connection_status', - field=models.BooleanField(blank=True, null=True), - ), - migrations.AlterField( - model_name='powerfeed', - name='connection_status', - field=models.BooleanField(blank=True, null=True), - ), - migrations.AlterField( - model_name='poweroutlet', - name='connection_status', - field=models.BooleanField(blank=True, null=True), - ), - migrations.AlterField( - model_name='powerport', - name='connection_status', - field=models.BooleanField(blank=True, null=True), - ), - ] diff --git a/netbox/dcim/migrations/0114_update_jsonfield.py b/netbox/dcim/migrations/0114_update_jsonfield.py deleted file mode 100644 index 5a971bced..000000000 --- a/netbox/dcim/migrations/0114_update_jsonfield.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.1b1 on 2020-07-16 16:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0113_nullbooleanfield_to_booleanfield'), - ] - - operations = [ - migrations.AlterField( - model_name='device', - name='local_context_data', - field=models.JSONField(blank=True, null=True), - ), - migrations.AlterField( - model_name='platform', - name='napalm_args', - field=models.JSONField(blank=True, null=True), - ), - ] diff --git a/netbox/dcim/migrations/0115_rackreservation_order.py b/netbox/dcim/migrations/0115_rackreservation_order.py deleted file mode 100644 index 594f6b9a4..000000000 --- a/netbox/dcim/migrations/0115_rackreservation_order.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 3.1 on 2020-08-24 16:03 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0114_update_jsonfield'), - ] - - operations = [ - migrations.AlterModelOptions( - name='rackreservation', - options={'ordering': ['created', 'pk']}, - ), - ] diff --git a/netbox/dcim/migrations/0116_rearport_max_positions.py b/netbox/dcim/migrations/0116_rearport_max_positions.py deleted file mode 100644 index a03f4e3d5..000000000 --- a/netbox/dcim/migrations/0116_rearport_max_positions.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 3.1 on 2020-09-16 16:51 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0115_rackreservation_order'), - ] - - operations = [ - migrations.AlterField( - model_name='frontport', - name='rear_port_position', - field=models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)]), - ), - migrations.AlterField( - model_name='frontporttemplate', - name='rear_port_position', - field=models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)]), - ), - migrations.AlterField( - model_name='rearport', - name='positions', - field=models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)]), - ), - migrations.AlterField( - model_name='rearporttemplate', - name='positions', - field=models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)]), - ), - ] diff --git a/netbox/dcim/migrations/0117_custom_field_data.py b/netbox/dcim/migrations/0117_custom_field_data.py deleted file mode 100644 index 36933cc46..000000000 --- a/netbox/dcim/migrations/0117_custom_field_data.py +++ /dev/null @@ -1,60 +0,0 @@ -import django.core.serializers.json -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0116_rearport_max_positions'), - ] - - operations = [ - # Original CustomFieldModels - migrations.AddField( - model_name='device', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='devicetype', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='powerfeed', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rack', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='site', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - - # Added under #5146 - migrations.AddField( - model_name='cable', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='powerpanel', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rackreservation', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='virtualchassis', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - ] diff --git a/netbox/dcim/migrations/0118_inventoryitem_mptt.py b/netbox/dcim/migrations/0118_inventoryitem_mptt.py deleted file mode 100644 index 844e00136..000000000 --- a/netbox/dcim/migrations/0118_inventoryitem_mptt.py +++ /dev/null @@ -1,44 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion -import mptt.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0117_custom_field_data'), - ] - - operations = [ - # The MPTT will be rebuilt in the following migration. Using dummy values for now. - migrations.AddField( - model_name='inventoryitem', - name='level', - field=models.PositiveIntegerField(default=0, editable=False), - preserve_default=False, - ), - migrations.AddField( - model_name='inventoryitem', - name='lft', - field=models.PositiveIntegerField(default=0, editable=False), - preserve_default=False, - ), - migrations.AddField( - model_name='inventoryitem', - name='rght', - field=models.PositiveIntegerField(default=0, editable=False), - preserve_default=False, - ), - migrations.AddField( - model_name='inventoryitem', - name='tree_id', - field=models.PositiveIntegerField(db_index=True, default=0, editable=False), - preserve_default=False, - ), - # Convert ForeignKey to TreeForeignKey - migrations.AlterField( - model_name='inventoryitem', - name='parent', - field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.inventoryitem'), - ), - ] diff --git a/netbox/dcim/migrations/0119_inventoryitem_mptt_rebuild.py b/netbox/dcim/migrations/0119_inventoryitem_mptt_rebuild.py deleted file mode 100644 index d3bdb3025..000000000 --- a/netbox/dcim/migrations/0119_inventoryitem_mptt_rebuild.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.db import migrations -import mptt -import mptt.managers - - -def rebuild_mptt(apps, schema_editor): - manager = mptt.managers.TreeManager() - InventoryItem = apps.get_model('dcim', 'InventoryItem') - manager.model = InventoryItem - mptt.register(InventoryItem) - manager.contribute_to_class(InventoryItem, 'objects') - manager.rebuild() - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0118_inventoryitem_mptt'), - ] - - operations = [ - migrations.RunPython( - code=rebuild_mptt, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0120_cache_cable_peer.py b/netbox/dcim/migrations/0120_cache_cable_peer.py deleted file mode 100644 index c45d03396..000000000 --- a/netbox/dcim/migrations/0120_cache_cable_peer.py +++ /dev/null @@ -1,141 +0,0 @@ -import sys - -from django.db import migrations, models -import django.db.models.deletion - - -def cache_cable_peers(apps, schema_editor): - ContentType = apps.get_model('contenttypes', 'ContentType') - Cable = apps.get_model('dcim', 'Cable') - ConsolePort = apps.get_model('dcim', 'ConsolePort') - ConsoleServerPort = apps.get_model('dcim', 'ConsoleServerPort') - PowerPort = apps.get_model('dcim', 'PowerPort') - PowerOutlet = apps.get_model('dcim', 'PowerOutlet') - Interface = apps.get_model('dcim', 'Interface') - FrontPort = apps.get_model('dcim', 'FrontPort') - RearPort = apps.get_model('dcim', 'RearPort') - PowerFeed = apps.get_model('dcim', 'PowerFeed') - - models = ( - ConsolePort, - ConsoleServerPort, - PowerPort, - PowerOutlet, - Interface, - FrontPort, - RearPort, - PowerFeed - ) - - if 'test' not in sys.argv: - print("\n", end="") - - for model in models: - if 'test' not in sys.argv: - print(f" Updating {model._meta.verbose_name} cable peers...", flush=True) - ct = ContentType.objects.get_for_model(model) - for cable in Cable.objects.filter(termination_a_type=ct): - model.objects.filter(pk=cable.termination_a_id).update( - _cable_peer_type_id=cable.termination_b_type_id, - _cable_peer_id=cable.termination_b_id - ) - for cable in Cable.objects.filter(termination_b_type=ct): - model.objects.filter(pk=cable.termination_b_id).update( - _cable_peer_type_id=cable.termination_a_type_id, - _cable_peer_id=cable.termination_a_id - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('dcim', '0119_inventoryitem_mptt_rebuild'), - ] - - operations = [ - migrations.AddField( - model_name='consoleport', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='consoleport', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.AddField( - model_name='consoleserverport', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='consoleserverport', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.AddField( - model_name='frontport', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='frontport', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.AddField( - model_name='interface', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='interface', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.AddField( - model_name='powerfeed', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='powerfeed', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.AddField( - model_name='poweroutlet', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='poweroutlet', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.AddField( - model_name='powerport', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='powerport', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.AddField( - model_name='rearport', - name='_cable_peer_id', - field=models.PositiveIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='rearport', - name='_cable_peer_type', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), - ), - migrations.RunPython( - code=cache_cable_peers, - reverse_code=migrations.RunPython.noop - ), - ] diff --git a/netbox/dcim/migrations/0121_cablepath.py b/netbox/dcim/migrations/0121_cablepath.py deleted file mode 100644 index 69411415d..000000000 --- a/netbox/dcim/migrations/0121_cablepath.py +++ /dev/null @@ -1,108 +0,0 @@ -import dcim.fields -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('dcim', '0120_cache_cable_peer'), - ] - - operations = [ - migrations.CreateModel( - name='CablePath', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), - ('origin_id', models.PositiveIntegerField()), - ('destination_id', models.PositiveIntegerField(blank=True, null=True)), - ('path', dcim.fields.PathField(base_field=models.CharField(max_length=40), size=None)), - ('is_active', models.BooleanField(default=False)), - ('is_split', models.BooleanField(default=False)), - ('destination_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype')), - ('origin_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype')), - ], - options={ - 'unique_together': {('origin_type', 'origin_id')}, - }, - ), - migrations.AddField( - model_name='consoleport', - name='_path', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), - ), - migrations.AddField( - model_name='consoleserverport', - name='_path', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), - ), - migrations.AddField( - model_name='interface', - name='_path', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), - ), - migrations.AddField( - model_name='powerfeed', - name='_path', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), - ), - migrations.AddField( - model_name='poweroutlet', - name='_path', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), - ), - migrations.AddField( - model_name='powerport', - name='_path', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'), - ), - migrations.RemoveField( - model_name='consoleport', - name='connected_endpoint', - ), - migrations.RemoveField( - model_name='consoleport', - name='connection_status', - ), - migrations.RemoveField( - model_name='consoleserverport', - name='connection_status', - ), - migrations.RemoveField( - model_name='interface', - name='_connected_circuittermination', - ), - migrations.RemoveField( - model_name='interface', - name='_connected_interface', - ), - migrations.RemoveField( - model_name='interface', - name='connection_status', - ), - migrations.RemoveField( - model_name='powerfeed', - name='connected_endpoint', - ), - migrations.RemoveField( - model_name='powerfeed', - name='connection_status', - ), - migrations.RemoveField( - model_name='poweroutlet', - name='connection_status', - ), - migrations.RemoveField( - model_name='powerport', - name='_connected_powerfeed', - ), - migrations.RemoveField( - model_name='powerport', - name='_connected_poweroutlet', - ), - migrations.RemoveField( - model_name='powerport', - name='connection_status', - ), - ] diff --git a/netbox/dcim/migrations/0122_standardize_name_length.py b/netbox/dcim/migrations/0122_standardize_name_length.py deleted file mode 100644 index 6c805f2ee..000000000 --- a/netbox/dcim/migrations/0122_standardize_name_length.py +++ /dev/null @@ -1,98 +0,0 @@ -# Generated by Django 3.1 on 2020-10-15 19:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0121_cablepath'), - ] - - operations = [ - migrations.AlterField( - model_name='devicerole', - name='name', - field=models.CharField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='devicerole', - name='slug', - field=models.SlugField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='devicetype', - name='model', - field=models.CharField(max_length=100), - ), - migrations.AlterField( - model_name='devicetype', - name='slug', - field=models.SlugField(max_length=100), - ), - migrations.AlterField( - model_name='manufacturer', - name='name', - field=models.CharField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='manufacturer', - name='slug', - field=models.SlugField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='powerfeed', - name='name', - field=models.CharField(max_length=100), - ), - migrations.AlterField( - model_name='powerpanel', - name='name', - field=models.CharField(max_length=100), - ), - migrations.AlterField( - model_name='rack', - name='name', - field=models.CharField(max_length=100), - ), - migrations.AlterField( - model_name='rackgroup', - name='name', - field=models.CharField(max_length=100), - ), - migrations.AlterField( - model_name='rackgroup', - name='slug', - field=models.SlugField(max_length=100), - ), - migrations.AlterField( - model_name='rackrole', - name='name', - field=models.CharField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='rackrole', - name='slug', - field=models.SlugField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='region', - name='name', - field=models.CharField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='region', - name='slug', - field=models.SlugField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='site', - name='name', - field=models.CharField(max_length=100, unique=True), - ), - migrations.AlterField( - model_name='site', - name='slug', - field=models.SlugField(max_length=100, unique=True), - ), - ] diff --git a/netbox/dcim/migrations/0123_standardize_models.py b/netbox/dcim/migrations/0123_standardize_models.py deleted file mode 100644 index 48e9e6503..000000000 --- a/netbox/dcim/migrations/0123_standardize_models.py +++ /dev/null @@ -1,422 +0,0 @@ -import django.core.serializers.json -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0122_standardize_name_length'), - ] - - operations = [ - migrations.AddField( - model_name='consoleport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='consoleport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='consoleport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='consoleporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='consoleporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='consoleserverport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='consoleserverport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='consoleserverport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='devicebay', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='devicebay', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='devicebay', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='devicerole', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='frontport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='frontport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='frontport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='frontporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='frontporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='interface', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='interface', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='interface', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='interfacetemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='interfacetemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='inventoryitem', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='inventoryitem', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='inventoryitem', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='manufacturer', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='platform', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='poweroutlet', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='poweroutlet', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='poweroutlet', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='powerport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='powerport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='powerport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='powerporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='powerporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='rackgroup', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rackrole', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rearport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='rearport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rearport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='rearporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='rearporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='region', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AlterField( - model_name='cable', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='cablepath', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='consoleport', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='consoleporttemplate', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='consoleserverport', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='consoleserverporttemplate', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='device', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='devicebay', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='devicebaytemplate', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='devicerole', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='devicetype', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='frontport', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='frontporttemplate', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='interface', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='interfacetemplate', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='inventoryitem', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='manufacturer', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='platform', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='powerfeed', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='poweroutlet', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='poweroutlettemplate', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='powerpanel', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='powerport', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='powerporttemplate', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='rack', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='rackgroup', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='rackreservation', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='rackrole', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='rearport', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='rearporttemplate', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='region', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='site', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='virtualchassis', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - ] diff --git a/netbox/dcim/migrations/0124_mark_connected.py b/netbox/dcim/migrations/0124_mark_connected.py deleted file mode 100644 index d7bf40a24..000000000 --- a/netbox/dcim/migrations/0124_mark_connected.py +++ /dev/null @@ -1,51 +0,0 @@ -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0123_standardize_models'), - ] - - operations = [ - migrations.AddField( - model_name='consoleport', - name='mark_connected', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='consoleserverport', - name='mark_connected', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='frontport', - name='mark_connected', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='interface', - name='mark_connected', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='powerfeed', - name='mark_connected', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='poweroutlet', - name='mark_connected', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='powerport', - name='mark_connected', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='rearport', - name='mark_connected', - field=models.BooleanField(default=False), - ), - ] diff --git a/netbox/dcim/migrations/0125_console_port_speed.py b/netbox/dcim/migrations/0125_console_port_speed.py deleted file mode 100644 index 1a7f455d6..000000000 --- a/netbox/dcim/migrations/0125_console_port_speed.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0124_mark_connected'), - ] - - operations = [ - migrations.AddField( - model_name='consoleport', - name='speed', - field=models.PositiveSmallIntegerField(blank=True, null=True), - ), - migrations.AddField( - model_name='consoleserverport', - name='speed', - field=models.PositiveSmallIntegerField(blank=True, null=True), - ), - ] diff --git a/netbox/dcim/migrations/0126_rename_rackgroup_location.py b/netbox/dcim/migrations/0126_rename_rackgroup_location.py deleted file mode 100644 index 8755097bd..000000000 --- a/netbox/dcim/migrations/0126_rename_rackgroup_location.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0125_console_port_speed'), - ] - - operations = [ - migrations.RenameModel( - old_name='RackGroup', - new_name='Location', - ), - migrations.AlterModelOptions( - name='rack', - options={'ordering': ('site', 'location', '_name', 'pk')}, - ), - migrations.AlterField( - model_name='location', - name='site', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='dcim.site'), - ), - migrations.RenameField( - model_name='powerpanel', - old_name='rack_group', - new_name='location', - ), - migrations.RenameField( - model_name='rack', - old_name='group', - new_name='location', - ), - migrations.AlterUniqueTogether( - name='rack', - unique_together={('location', 'facility_id'), ('location', 'name')}, - ), - ] diff --git a/netbox/dcim/migrations/0127_device_location.py b/netbox/dcim/migrations/0127_device_location.py deleted file mode 100644 index 479f9cea9..000000000 --- a/netbox/dcim/migrations/0127_device_location.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0126_rename_rackgroup_location'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='location', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.location'), - ), - ] diff --git a/netbox/dcim/migrations/0128_device_location_populate.py b/netbox/dcim/migrations/0128_device_location_populate.py deleted file mode 100644 index 06a172ac3..000000000 --- a/netbox/dcim/migrations/0128_device_location_populate.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.db import migrations -from django.db.models import Subquery, OuterRef - - -def populate_device_location(apps, schema_editor): - Device = apps.get_model('dcim', 'Device') - Device.objects.filter(rack__isnull=False).update( - location_id=Subquery( - Device.objects.filter(pk=OuterRef('pk')).values('rack__location_id')[:1] - ) - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0127_device_location'), - ] - - operations = [ - migrations.RunPython( - code=populate_device_location - ), - ] diff --git a/netbox/dcim/migrations/0129_interface_parent.py b/netbox/dcim/migrations/0129_interface_parent.py deleted file mode 100644 index 37e722f0a..000000000 --- a/netbox/dcim/migrations/0129_interface_parent.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0128_device_location_populate'), - ] - - operations = [ - migrations.AddField( - model_name='interface', - name='parent', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='child_interfaces', to='dcim.interface'), - ), - ] diff --git a/netbox/dcim/migrations/0130_sitegroup.py b/netbox/dcim/migrations/0130_sitegroup.py deleted file mode 100644 index 3b3bdcf10..000000000 --- a/netbox/dcim/migrations/0130_sitegroup.py +++ /dev/null @@ -1,39 +0,0 @@ -import django.core.serializers.json -from django.db import migrations, models -import django.db.models.deletion -import mptt.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0129_interface_parent'), - ] - - operations = [ - migrations.CreateModel( - name='SiteGroup', - fields=[ - ('created', models.DateField(auto_now_add=True, null=True)), - ('last_updated', models.DateTimeField(auto_now=True, null=True)), - ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)), - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=100, unique=True)), - ('slug', models.SlugField(max_length=100, unique=True)), - ('description', models.CharField(blank=True, max_length=200)), - ('lft', models.PositiveIntegerField(editable=False)), - ('rght', models.PositiveIntegerField(editable=False)), - ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), - ('level', models.PositiveIntegerField(editable=False)), - ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.sitegroup')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='site', - name='group', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='dcim.sitegroup'), - ), - ] diff --git a/netbox/dcim/migrations/0132_cable_length.py b/netbox/dcim/migrations/0132_cable_length.py new file mode 100644 index 000000000..e20a8b8aa --- /dev/null +++ b/netbox/dcim/migrations/0132_cable_length.py @@ -0,0 +1,16 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0131_consoleport_speed'), + ] + + operations = [ + migrations.AlterField( + model_name='cable', + name='length', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True), + ), + ] diff --git a/netbox/dcim/migrations/0133_port_colors.py b/netbox/dcim/migrations/0133_port_colors.py new file mode 100644 index 000000000..8cae7ac8e --- /dev/null +++ b/netbox/dcim/migrations/0133_port_colors.py @@ -0,0 +1,32 @@ +from django.db import migrations +import utilities.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0132_cable_length'), + ] + + operations = [ + migrations.AddField( + model_name='frontport', + name='color', + field=utilities.fields.ColorField(blank=True, max_length=6), + ), + migrations.AddField( + model_name='frontporttemplate', + name='color', + field=utilities.fields.ColorField(blank=True, max_length=6), + ), + migrations.AddField( + model_name='rearport', + name='color', + field=utilities.fields.ColorField(blank=True, max_length=6), + ), + migrations.AddField( + model_name='rearporttemplate', + name='color', + field=utilities.fields.ColorField(blank=True, max_length=6), + ), + ] diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index ee19d553d..0375a9fb4 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -25,6 +25,7 @@ __all__ = ( 'Interface', 'InterfaceTemplate', 'InventoryItem', + 'Location', 'Manufacturer', 'Platform', 'PowerFeed', @@ -34,7 +35,6 @@ __all__ = ( 'PowerPort', 'PowerPortTemplate', 'Rack', - 'Location', 'RackReservation', 'RackRole', 'RearPort', diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index e7040376c..c3f8cac3f 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -74,7 +74,9 @@ class Cable(PrimaryModel): color = ColorField( blank=True ) - length = models.PositiveSmallIntegerField( + length = models.DecimalField( + max_digits=8, + decimal_places=2, blank=True, null=True ) @@ -109,11 +111,6 @@ class Cable(PrimaryModel): objects = RestrictedQuerySet.as_manager() - csv_headers = [ - 'termination_a_type', 'termination_a_id', 'termination_b_type', 'termination_b_id', 'type', 'status', 'label', - 'color', 'length', 'length_unit', - ] - class Meta: ordering = ['pk'] unique_together = ( @@ -287,20 +284,6 @@ class Cable(PrimaryModel): # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk) self._pk = self.pk - def to_csv(self): - return ( - '{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model), - self.termination_a_id, - '{}.{}'.format(self.termination_b_type.app_label, self.termination_b_type.model), - self.termination_b_id, - self.get_type_display(), - self.get_status_display(), - self.label, - self.color, - self.length, - self.length_unit, - ) - def get_status_class(self): return CableStatusChoices.CSS_CLASSES.get(self.status) diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 9eb7785bf..e704f74a7 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -6,7 +6,7 @@ from dcim.choices import * from dcim.constants import * from extras.utils import extras_features from netbox.models import ChangeLoggedModel -from utilities.fields import NaturalOrderingField +from utilities.fields import ColorField, NaturalOrderingField from utilities.querysets import RestrictedQuerySet from utilities.ordering import naturalize_interface from .device_components import ( @@ -267,6 +267,9 @@ class FrontPortTemplate(ComponentTemplateModel): max_length=50, choices=PortTypeChoices ) + color = ColorField( + blank=True + ) rear_port = models.ForeignKey( to='dcim.RearPortTemplate', on_delete=models.CASCADE, @@ -319,6 +322,7 @@ class FrontPortTemplate(ComponentTemplateModel): name=self.name, label=self.label, type=self.type, + color=self.color, rear_port=rear_port, rear_port_position=self.rear_port_position ) @@ -333,6 +337,9 @@ class RearPortTemplate(ComponentTemplateModel): max_length=50, choices=PortTypeChoices ) + color = ColorField( + blank=True + ) positions = models.PositiveSmallIntegerField( default=1, validators=[ @@ -351,6 +358,7 @@ class RearPortTemplate(ComponentTemplateModel): name=self.name, label=self.label, type=self.type, + color=self.color, positions=self.positions ) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 6b67ad302..6a81e2cf1 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -10,9 +10,10 @@ from mptt.models import MPTTModel, TreeForeignKey from dcim.choices import * from dcim.constants import * from dcim.fields import MACAddressField +from dcim.svg import CableTraceSVG from extras.utils import extras_features from netbox.models import PrimaryModel -from utilities.fields import NaturalOrderingField +from utilities.fields import ColorField, NaturalOrderingField from utilities.mptt import TreeManager from utilities.ordering import naturalize_interface from utilities.querysets import RestrictedQuerySet @@ -193,6 +194,13 @@ class PathEndpoint(models.Model): # Return the path as a list of three-tuples (A termination, cable, B termination) return list(zip(*[iter(path)] * 3)) + def get_trace_svg(self, base_url=None, width=None): + if width is not None: + trace = CableTraceSVG(self, base_url=base_url, width=width) + else: + trace = CableTraceSVG(self, base_url=base_url) + return trace.render() + @property def path(self): return self._path @@ -229,7 +237,6 @@ class ConsolePort(ComponentModel, CableTermination, PathEndpoint): help_text='Port speed in bits per second' ) - csv_headers = ['device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description'] clone_fields = ['device', 'type', 'speed'] class Meta: @@ -239,17 +246,6 @@ class ConsolePort(ComponentModel, CableTermination, PathEndpoint): def get_absolute_url(self): return reverse('dcim:consoleport', kwargs={'pk': self.pk}) - def to_csv(self): - return ( - self.device.identifier, - self.name, - self.label, - self.type, - self.speed, - self.mark_connected, - self.description, - ) - # # Console server ports @@ -273,7 +269,6 @@ class ConsoleServerPort(ComponentModel, CableTermination, PathEndpoint): help_text='Port speed in bits per second' ) - csv_headers = ['device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description'] clone_fields = ['device', 'type', 'speed'] class Meta: @@ -283,17 +278,6 @@ class ConsoleServerPort(ComponentModel, CableTermination, PathEndpoint): def get_absolute_url(self): return reverse('dcim:consoleserverport', kwargs={'pk': self.pk}) - def to_csv(self): - return ( - self.device.identifier, - self.name, - self.label, - self.type, - self.speed, - self.mark_connected, - self.description, - ) - # # Power ports @@ -323,9 +307,6 @@ class PowerPort(ComponentModel, CableTermination, PathEndpoint): help_text="Allocated power draw (watts)" ) - csv_headers = [ - 'device', 'name', 'label', 'type', 'mark_connected', 'maximum_draw', 'allocated_draw', 'description', - ] clone_fields = ['device', 'maximum_draw', 'allocated_draw'] class Meta: @@ -335,18 +316,6 @@ class PowerPort(ComponentModel, CableTermination, PathEndpoint): def get_absolute_url(self): return reverse('dcim:powerport', kwargs={'pk': self.pk}) - def to_csv(self): - return ( - self.device.identifier, - self.name, - self.label, - self.get_type_display(), - self.mark_connected, - self.maximum_draw, - self.allocated_draw, - self.description, - ) - def clean(self): super().clean() @@ -436,7 +405,6 @@ class PowerOutlet(ComponentModel, CableTermination, PathEndpoint): help_text="Phase (for three-phase feeds)" ) - csv_headers = ['device', 'name', 'label', 'type', 'mark_connected', 'power_port', 'feed_leg', 'description'] clone_fields = ['device', 'type', 'power_port', 'feed_leg'] class Meta: @@ -446,18 +414,6 @@ class PowerOutlet(ComponentModel, CableTermination, PathEndpoint): def get_absolute_url(self): return reverse('dcim:poweroutlet', kwargs={'pk': self.pk}) - def to_csv(self): - return ( - self.device.identifier, - self.name, - self.label, - self.get_type_display(), - self.mark_connected, - self.power_port.name if self.power_port else None, - self.get_feed_leg_display(), - self.description, - ) - def clean(self): super().clean() @@ -577,10 +533,6 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint): related_query_name='interface' ) - csv_headers = [ - 'device', 'name', 'label', 'parent', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', 'mtu', - 'mgmt_only', 'description', 'mode', - ] clone_fields = ['device', 'parent', 'lag', 'type', 'mgmt_only'] class Meta: @@ -590,23 +542,6 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint): def get_absolute_url(self): return reverse('dcim:interface', kwargs={'pk': self.pk}) - def to_csv(self): - return ( - self.device.identifier if self.device else None, - self.name, - self.label, - self.parent.name if self.parent else None, - self.lag.name if self.lag else None, - self.get_type_display(), - self.enabled, - self.mark_connected, - self.mac_address, - self.mtu, - self.mgmt_only, - self.description, - self.get_mode_display(), - ) - def clean(self): super().clean() @@ -700,6 +635,9 @@ class FrontPort(ComponentModel, CableTermination): max_length=50, choices=PortTypeChoices ) + color = ColorField( + blank=True + ) rear_port = models.ForeignKey( to='dcim.RearPort', on_delete=models.CASCADE, @@ -713,9 +651,6 @@ class FrontPort(ComponentModel, CableTermination): ] ) - csv_headers = [ - 'device', 'name', 'label', 'type', 'mark_connected', 'rear_port', 'rear_port_position', 'description', - ] clone_fields = ['device', 'type'] class Meta: @@ -728,18 +663,6 @@ class FrontPort(ComponentModel, CableTermination): def get_absolute_url(self): return reverse('dcim:frontport', kwargs={'pk': self.pk}) - def to_csv(self): - return ( - self.device.identifier, - self.name, - self.label, - self.get_type_display(), - self.mark_connected, - self.rear_port.name, - self.rear_port_position, - self.description, - ) - def clean(self): super().clean() @@ -766,6 +689,9 @@ class RearPort(ComponentModel, CableTermination): max_length=50, choices=PortTypeChoices ) + color = ColorField( + blank=True + ) positions = models.PositiveSmallIntegerField( default=1, validators=[ @@ -775,8 +701,6 @@ class RearPort(ComponentModel, CableTermination): ) clone_fields = ['device', 'type', 'positions'] - csv_headers = ['device', 'name', 'label', 'type', 'mark_connected', 'positions', 'description'] - class Meta: ordering = ('device', '_name') unique_together = ('device', 'name') @@ -795,17 +719,6 @@ class RearPort(ComponentModel, CableTermination): f"({frontport_count})" }) - def to_csv(self): - return ( - self.device.identifier, - self.name, - self.label, - self.get_type_display(), - self.mark_connected, - self.positions, - self.description, - ) - # # Device bays @@ -824,7 +737,6 @@ class DeviceBay(ComponentModel): null=True ) - csv_headers = ['device', 'name', 'label', 'installed_device', 'description'] clone_fields = ['device'] class Meta: @@ -834,15 +746,6 @@ class DeviceBay(ComponentModel): def get_absolute_url(self): return reverse('dcim:devicebay', kwargs={'pk': self.pk}) - def to_csv(self): - return ( - self.device.identifier, - self.name, - self.label, - self.installed_device.identifier if self.installed_device else None, - self.description, - ) - def clean(self): super().clean() @@ -918,9 +821,6 @@ class InventoryItem(MPTTModel, ComponentModel): objects = TreeManager() - csv_headers = [ - 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', - ] clone_fields = ['device', 'parent', 'manufacturer', 'part_id'] class Meta: @@ -929,16 +829,3 @@ class InventoryItem(MPTTModel, ComponentModel): def get_absolute_url(self): return reverse('dcim:inventoryitem', kwargs={'pk': self.pk}) - - def to_csv(self): - return ( - self.device.name or '{{{}}}'.format(self.device.pk), - self.name, - self.label, - self.manufacturer.name if self.manufacturer else None, - self.part_id, - self.serial, - self.asset_tag, - self.discovered, - self.description, - ) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 95c3c50db..10cd35c13 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -56,8 +56,6 @@ class Manufacturer(OrganizationalModel): objects = RestrictedQuerySet.as_manager() - csv_headers = ['name', 'slug', 'description'] - class Meta: ordering = ['name'] @@ -67,13 +65,6 @@ class Manufacturer(OrganizationalModel): def get_absolute_url(self): return reverse('dcim:manufacturer', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.description - ) - @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks') class DeviceType(PrimaryModel): @@ -336,10 +327,6 @@ class DeviceType(PrimaryModel): if self.rear_image: self.rear_image.delete(save=False) - @property - def display_name(self): - return f'{self.manufacturer.name} {self.model}' - @property def is_parent_device(self): return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT @@ -383,8 +370,6 @@ class DeviceRole(OrganizationalModel): objects = RestrictedQuerySet.as_manager() - csv_headers = ['name', 'slug', 'color', 'vm_role', 'description'] - class Meta: ordering = ['name'] @@ -394,15 +379,6 @@ class DeviceRole(OrganizationalModel): def get_absolute_url(self): return reverse('dcim:devicerole', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.color, - self.vm_role, - self.description, - ) - @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class Platform(OrganizationalModel): @@ -446,8 +422,6 @@ class Platform(OrganizationalModel): objects = RestrictedQuerySet.as_manager() - csv_headers = ['name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description'] - class Meta: ordering = ['name'] @@ -457,16 +431,6 @@ class Platform(OrganizationalModel): def get_absolute_url(self): return reverse('dcim:platform', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.manufacturer.name if self.manufacturer else None, - self.napalm_driver, - self.napalm_args, - self.description, - ) - @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks') class Device(PrimaryModel, ConfigContextModel): @@ -612,19 +576,9 @@ class Device(PrimaryModel, ConfigContextModel): images = GenericRelation( to='extras.ImageAttachment' ) - secrets = GenericRelation( - to='secrets.Secret', - content_type_field='assigned_object_type', - object_id_field='assigned_object_id', - related_query_name='device' - ) objects = ConfigContextModelQuerySet.as_manager() - csv_headers = [ - 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', - 'site', 'location', 'rack_name', 'position', 'face', 'comments', - ] clone_fields = [ 'device_type', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'status', 'cluster', ] @@ -638,7 +592,13 @@ class Device(PrimaryModel, ConfigContextModel): ) def __str__(self): - return self.display_name or super().__str__() + if self.name: + return self.name + elif self.virtual_chassis: + return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})' + elif self.device_type: + return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})' + return super().__str__() def get_absolute_url(self): return reverse('dcim:device', args=[self.pk]) @@ -820,36 +780,6 @@ class Device(PrimaryModel, ConfigContextModel): device.rack = self.rack device.save() - def to_csv(self): - return ( - self.name or '', - self.device_role.name, - self.tenant.name if self.tenant else None, - self.device_type.manufacturer.name, - self.device_type.model, - self.platform.name if self.platform else None, - self.serial, - self.asset_tag, - self.get_status_display(), - self.site.name, - self.rack.location.name if self.rack and self.rack.location else None, - self.rack.name if self.rack else None, - self.position, - self.get_face_display(), - self.comments, - ) - - @property - def display_name(self): - if self.name: - return self.name - elif self.virtual_chassis: - return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})' - elif self.device_type: - return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})' - else: - return '' # Device has not yet been created - @property def identifier(self): """ @@ -944,8 +874,6 @@ class VirtualChassis(PrimaryModel): objects = RestrictedQuerySet.as_manager() - csv_headers = ['name', 'domain', 'master'] - class Meta: ordering = ['name'] verbose_name_plural = 'virtual chassis' @@ -982,10 +910,3 @@ class VirtualChassis(PrimaryModel): ) return super().delete(*args, **kwargs) - - def to_csv(self): - return ( - self.name, - self.domain, - self.master.name if self.master else None, - ) diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py index 03e77eea9..f81abd328 100644 --- a/netbox/dcim/models/power.py +++ b/netbox/dcim/models/power.py @@ -42,8 +42,6 @@ class PowerPanel(PrimaryModel): objects = RestrictedQuerySet.as_manager() - csv_headers = ['site', 'location', 'name'] - class Meta: ordering = ['site', 'name'] unique_together = ['site', 'name'] @@ -54,13 +52,6 @@ class PowerPanel(PrimaryModel): def get_absolute_url(self): return reverse('dcim:powerpanel', args=[self.pk]) - def to_csv(self): - return ( - self.site.name, - self.location.name if self.location else None, - self.name, - ) - def clean(self): super().clean() @@ -133,10 +124,6 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination): objects = RestrictedQuerySet.as_manager() - csv_headers = [ - 'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', - 'voltage', 'amperage', 'max_utilization', 'comments', - ] clone_fields = [ 'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', 'available_power', @@ -152,24 +139,6 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination): def get_absolute_url(self): return reverse('dcim:powerfeed', args=[self.pk]) - def to_csv(self): - return ( - self.power_panel.site.name, - self.power_panel.name, - self.rack.location.name if self.rack and self.rack.location else None, - self.rack.name if self.rack else None, - self.name, - self.get_status_display(), - self.get_type_display(), - self.mark_connected, - self.get_supply_display(), - self.get_phase_display(), - self.voltage, - self.amperage, - self.max_utilization, - self.comments, - ) - def clean(self): super().clean() diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index c4416ca28..c287d7d6c 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -13,7 +13,7 @@ from django.urls import reverse from dcim.choices import * from dcim.constants import * -from dcim.elevations import RackElevationSVG +from dcim.svg import RackElevationSVG from extras.utils import extras_features from netbox.models import OrganizationalModel, PrimaryModel from utilities.choices import ColorChoices @@ -58,8 +58,6 @@ class RackRole(OrganizationalModel): objects = RestrictedQuerySet.as_manager() - csv_headers = ['name', 'slug', 'color', 'description'] - class Meta: ordering = ['name'] @@ -69,14 +67,6 @@ class RackRole(OrganizationalModel): def get_absolute_url(self): return reverse('dcim:rackrole', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.color, - self.description, - ) - @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks') class Rack(PrimaryModel): @@ -185,16 +175,18 @@ class Rack(PrimaryModel): comments = models.TextField( blank=True ) + vlan_groups = GenericRelation( + to='ipam.VLANGroup', + content_type_field='scope_type', + object_id_field='scope_id', + related_query_name='rack' + ) images = GenericRelation( to='extras.ImageAttachment' ) objects = RestrictedQuerySet.as_manager() - csv_headers = [ - 'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width', - 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments', - ] clone_fields = [ 'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', @@ -209,7 +201,9 @@ class Rack(PrimaryModel): ) def __str__(self): - return self.display_name or super().__str__() + if self.facility_id: + return f'{self.name} ({self.facility_id})' + return self.name def get_absolute_url(self): return reverse('dcim:rack', args=[self.pk]) @@ -249,27 +243,6 @@ class Rack(PrimaryModel): 'location': f"Location must be from the same site, {self.site}." }) - def to_csv(self): - return ( - self.site.name, - self.location.name if self.location else None, - self.name, - self.facility_id, - self.tenant.name if self.tenant else None, - self.get_status_display(), - self.role.name if self.role else None, - self.get_type_display() if self.type else None, - self.serial, - self.asset_tag, - self.width, - self.u_height, - self.desc_units, - self.outer_width, - self.outer_depth, - self.outer_unit, - self.comments, - ) - @property def units(self): if self.desc_units: @@ -277,12 +250,6 @@ class Rack(PrimaryModel): else: return reversed(range(1, self.u_height + 1)) - @property - def display_name(self): - if self.facility_id: - return f'{self.name} ({self.facility_id})' - return self.name - def get_status_class(self): return RackStatusChoices.CSS_CLASSES.get(self.status) @@ -497,8 +464,6 @@ class RackReservation(PrimaryModel): objects = RestrictedQuerySet.as_manager() - csv_headers = ['site', 'location', 'rack', 'units', 'tenant', 'user', 'description'] - class Meta: ordering = ['created', 'pk'] @@ -535,17 +500,6 @@ class RackReservation(PrimaryModel): ) }) - def to_csv(self): - return ( - self.rack.site.name, - self.rack.location if self.rack.location else None, - self.rack.name, - ','.join([str(u) for u in self.units]), - self.tenant.name if self.tenant else None, - self.user.username, - self.description - ) - @property def unit_list(self): return array_to_string(self.units) diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index 7ab37567a..56946642b 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -53,20 +53,16 @@ class Region(NestedGroupModel): max_length=200, blank=True ) - - csv_headers = ['name', 'slug', 'parent', 'description'] + vlan_groups = GenericRelation( + to='ipam.VLANGroup', + content_type_field='scope_type', + object_id_field='scope_id', + related_query_name='region' + ) def get_absolute_url(self): return reverse('dcim:region', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.parent.name if self.parent else None, - self.description, - ) - def get_site_count(self): return Site.objects.filter( Q(region=self) | @@ -105,20 +101,16 @@ class SiteGroup(NestedGroupModel): max_length=200, blank=True ) - - csv_headers = ['name', 'slug', 'parent', 'description'] + vlan_groups = GenericRelation( + to='ipam.VLANGroup', + content_type_field='scope_type', + object_id_field='scope_id', + related_query_name='site_group' + ) def get_absolute_url(self): return reverse('dcim:sitegroup', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.parent.name if self.parent else None, - self.description, - ) - def get_site_count(self): return Site.objects.filter( Q(group=self) | @@ -230,17 +222,18 @@ class Site(PrimaryModel): comments = models.TextField( blank=True ) + vlan_groups = GenericRelation( + to='ipam.VLANGroup', + content_type_field='scope_type', + object_id_field='scope_id', + related_query_name='site' + ) images = GenericRelation( to='extras.ImageAttachment' ) objects = RestrictedQuerySet.as_manager() - csv_headers = [ - 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'time_zone', 'description', - 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', - 'contact_email', 'comments', - ] clone_fields = [ 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', @@ -255,28 +248,6 @@ class Site(PrimaryModel): def get_absolute_url(self): return reverse('dcim:site', args=[self.pk]) - def to_csv(self): - return ( - self.name, - self.slug, - self.get_status_display(), - self.region.name if self.region else None, - self.group.name if self.group else None, - self.tenant.name if self.tenant else None, - self.facility, - self.asn, - self.time_zone, - self.description, - self.physical_address, - self.shipping_address, - self.latitude, - self.longitude, - self.contact_name, - self.contact_phone, - self.contact_email, - self.comments, - ) - def get_status_class(self): return SiteStatusChoices.CSS_CLASSES.get(self.status) @@ -314,11 +285,16 @@ class Location(NestedGroupModel): max_length=200, blank=True ) + vlan_groups = GenericRelation( + to='ipam.VLANGroup', + content_type_field='scope_type', + object_id_field='scope_id', + related_query_name='location' + ) images = GenericRelation( to='extras.ImageAttachment' ) - csv_headers = ['site', 'parent', 'name', 'slug', 'description'] clone_fields = ['site', 'parent', 'description'] class Meta: @@ -331,15 +307,6 @@ class Location(NestedGroupModel): def get_absolute_url(self): return reverse('dcim:location', args=[self.pk]) - def to_csv(self): - return ( - self.site, - self.parent.name if self.parent else '', - self.name, - self.slug, - self.description, - ) - def clean(self): super().clean() diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 33a868f2c..9fc68ee70 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -1,6 +1,5 @@ import logging -from cacheops import invalidate_obj from django.contrib.contenttypes.models import ContentType from django.db.models.signals import post_save, post_delete, pre_delete from django.db import transaction @@ -33,7 +32,6 @@ def rebuild_paths(obj): for cp in cable_paths: cp.delete() if cp.origin: - invalidate_obj(cp.origin) create_cablepath(cp.origin) diff --git a/netbox/dcim/svg.py b/netbox/dcim/svg.py new file mode 100644 index 000000000..4789dd2d6 --- /dev/null +++ b/netbox/dcim/svg.py @@ -0,0 +1,514 @@ +import svgwrite +from svgwrite.container import Group, Hyperlink +from svgwrite.shapes import Line, Rect +from svgwrite.text import Text + +from django.conf import settings +from django.urls import reverse +from django.utils.http import urlencode + +from utilities.utils import foreground_color +from .choices import DeviceFaceChoices +from .constants import RACK_ELEVATION_BORDER_WIDTH + + +__all__ = ( + 'CableTraceSVG', + 'RackElevationSVG', +) + + +class RackElevationSVG: + """ + Use this class to render a rack elevation as an SVG image. + + :param rack: A NetBox Rack instance + :param user: User instance. If specified, only devices viewable by this user will be fully displayed. + :param include_images: If true, the SVG document will embed front/rear device face images, where available + :param base_url: Base URL for links within the SVG document. If none, links will be relative. + """ + def __init__(self, rack, user=None, include_images=True, base_url=None): + self.rack = rack + self.include_images = include_images + if base_url is not None: + self.base_url = base_url.rstrip('/') + else: + self.base_url = '' + + # Determine the subset of devices within this rack that are viewable by the user, if any + permitted_devices = self.rack.devices + if user is not None: + permitted_devices = permitted_devices.restrict(user, 'view') + self.permitted_device_ids = permitted_devices.values_list('pk', flat=True) + + @staticmethod + def _get_device_description(device): + return '{} ({}) — {} {} ({}U) {} {}'.format( + device.name, + device.device_role, + device.device_type.manufacturer.name, + device.device_type.model, + device.device_type.u_height, + device.asset_tag or '', + device.serial or '' + ) + + @staticmethod + def _add_gradient(drawing, id_, color): + gradient = drawing.linearGradient( + start=(0, 0), + end=(0, 25), + spreadMethod='repeat', + id_=id_, + gradientTransform='rotate(45, 0, 0)', + gradientUnits='userSpaceOnUse' + ) + gradient.add_stop_color(offset='0%', color='#f7f7f7') + gradient.add_stop_color(offset='50%', color='#f7f7f7') + gradient.add_stop_color(offset='50%', color=color) + gradient.add_stop_color(offset='100%', color=color) + drawing.defs.add(gradient) + + @staticmethod + def _setup_drawing(width, height): + drawing = svgwrite.Drawing(size=(width, height)) + + # add the stylesheet + with open('{}/rack_elevation.css'.format(settings.STATIC_ROOT)) as css_file: + drawing.defs.add(drawing.style(css_file.read())) + + # add gradients + RackElevationSVG._add_gradient(drawing, 'reserved', '#c7c7ff') + RackElevationSVG._add_gradient(drawing, 'occupied', '#d7d7d7') + RackElevationSVG._add_gradient(drawing, 'blocked', '#ffc0c0') + + return drawing + + def _draw_device_front(self, drawing, device, start, end, text): + name = str(device) + if device.devicebay_count: + name += ' ({}/{})'.format(device.get_children().count(), device.devicebay_count) + + color = device.device_role.color + link = drawing.add( + drawing.a( + href='{}{}'.format(self.base_url, reverse('dcim:device', kwargs={'pk': device.pk})), + target='_top', + fill='black' + ) + ) + link.set_desc(self._get_device_description(device)) + link.add(drawing.rect(start, end, style='fill: #{}'.format(color), class_='slot')) + hex_color = '#{}'.format(foreground_color(color)) + link.add(drawing.text(str(name), insert=text, fill=hex_color)) + + # Embed front device type image if one exists + if self.include_images and device.device_type.front_image: + image = drawing.image( + href=device.device_type.front_image.url, + insert=start, + size=end, + class_='device-image' + ) + image.fit(scale='slice') + link.add(image) + + def _draw_device_rear(self, drawing, device, start, end, text): + rect = drawing.rect(start, end, class_="slot blocked") + rect.set_desc(self._get_device_description(device)) + drawing.add(rect) + drawing.add(drawing.text(str(device), insert=text)) + + # Embed rear device type image if one exists + if self.include_images and device.device_type.rear_image: + image = drawing.image( + href=device.device_type.rear_image.url, + insert=start, + size=end, + class_='device-image' + ) + image.fit(scale='slice') + drawing.add(image) + + @staticmethod + def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation): + link = drawing.add( + drawing.a( + href='{}?{}'.format( + reverse('dcim:device_add'), + urlencode({'rack': rack.pk, 'site': rack.site.pk, 'face': face_id, 'position': id_}) + ), + target='_top' + ) + ) + if reservation: + link.set_desc('{} — {} · {}'.format( + reservation.description, reservation.user, reservation.created + )) + link.add(drawing.rect(start, end, class_=class_)) + link.add(drawing.text("add device", insert=text, class_='add-device')) + + def merge_elevations(self, face): + elevation = self.rack.get_rack_units(face=face, expand_devices=False) + if face == DeviceFaceChoices.FACE_REAR: + other_face = DeviceFaceChoices.FACE_FRONT + else: + other_face = DeviceFaceChoices.FACE_REAR + other = self.rack.get_rack_units(face=other_face) + + unit_cursor = 0 + for u in elevation: + o = other[unit_cursor] + if not u['device'] and o['device'] and o['device'].device_type.is_full_depth: + u['device'] = o['device'] + u['height'] = 1 + unit_cursor += u.get('height', 1) + + return elevation + + def render(self, face, unit_width, unit_height, legend_width): + """ + Return an SVG document representing a rack elevation. + """ + drawing = self._setup_drawing( + unit_width + legend_width + RACK_ELEVATION_BORDER_WIDTH * 2, + unit_height * self.rack.u_height + RACK_ELEVATION_BORDER_WIDTH * 2 + ) + reserved_units = self.rack.get_reserved_units() + + unit_cursor = 0 + for ru in range(0, self.rack.u_height): + start_y = ru * unit_height + position_coordinates = (legend_width / 2, start_y + unit_height / 2 + RACK_ELEVATION_BORDER_WIDTH) + unit = ru + 1 if self.rack.desc_units else self.rack.u_height - ru + drawing.add( + drawing.text(str(unit), position_coordinates, class_="unit") + ) + + for unit in self.merge_elevations(face): + + # Loop through all units in the elevation + device = unit['device'] + height = unit.get('height', 1) + + # Setup drawing coordinates + x_offset = legend_width + RACK_ELEVATION_BORDER_WIDTH + y_offset = unit_cursor * unit_height + RACK_ELEVATION_BORDER_WIDTH + end_y = unit_height * height + start_cordinates = (x_offset, y_offset) + end_cordinates = (unit_width, end_y) + text_cordinates = (x_offset + (unit_width / 2), y_offset + end_y / 2) + + # Draw the device + if device and device.face == face and device.pk in self.permitted_device_ids: + self._draw_device_front(drawing, device, start_cordinates, end_cordinates, text_cordinates) + elif device and device.device_type.is_full_depth and device.pk in self.permitted_device_ids: + self._draw_device_rear(drawing, device, start_cordinates, end_cordinates, text_cordinates) + elif device: + # Devices which the user does not have permission to view are rendered only as unavailable space + drawing.add(drawing.rect(start_cordinates, end_cordinates, class_='blocked')) + else: + # Draw shallow devices, reservations, or empty units + class_ = 'slot' + reservation = reserved_units.get(unit["id"]) + if device: + class_ += ' occupied' + if reservation: + class_ += ' reserved' + self._draw_empty( + drawing, + self.rack, + start_cordinates, + end_cordinates, + text_cordinates, + unit["id"], + face, + class_, + reservation + ) + + unit_cursor += height + + # Wrap the drawing with a border + border_width = RACK_ELEVATION_BORDER_WIDTH + border_offset = RACK_ELEVATION_BORDER_WIDTH / 2 + frame = drawing.rect( + insert=(legend_width + border_offset, border_offset), + size=(unit_width + border_width, self.rack.u_height * unit_height + border_width), + class_='rack' + ) + drawing.add(frame) + + return drawing + + +OFFSET = 0.5 +PADDING = 10 +LINE_HEIGHT = 20 + + +class CableTraceSVG: + """ + Generate a graphical representation of a CablePath in SVG format. + + :param origin: The originating termination + :param width: Width of the generated image (in pixels) + :param base_url: Base URL for links within the SVG document. If none, links will be relative. + """ + def __init__(self, origin, width=400, base_url=None): + self.origin = origin + self.width = width + self.base_url = base_url.rstrip('/') if base_url is not None else '' + + # Establish a cursor to track position on the y axis + # Center edges on pixels to render sharp borders + self.cursor = OFFSET + + @property + def center(self): + return self.width / 2 + + @classmethod + def _get_labels(cls, instance): + """ + Return a list of text labels for the given instance based on model type. + """ + labels = [str(instance)] + if instance._meta.model_name == 'device': + labels.append(f'{instance.device_type.manufacturer} {instance.device_type}') + location_label = f'{instance.site}' + if instance.location: + location_label += f' / {instance.location}' + if instance.rack: + location_label += f' / {instance.rack}' + labels.append(location_label) + elif instance._meta.model_name == 'circuit': + labels[0] = f'Circuit {instance}' + labels.append(instance.provider) + elif instance._meta.model_name == 'circuittermination': + if instance.xconnect_id: + labels.append(f'{instance.xconnect_id}') + elif instance._meta.model_name == 'providernetwork': + labels.append(instance.provider) + + return labels + + @classmethod + def _get_color(cls, instance): + """ + Return the appropriate fill color for an object within a cable path. + """ + if hasattr(instance, 'parent_object'): + # Termination + return 'f0f0f0' + if hasattr(instance, 'device_role'): + # Device + return instance.device_role.color + else: + # Other parent object + return 'e0e0e0' + + def _draw_box(self, width, color, url, labels, y_indent=0, padding_multiplier=1, radius=10): + """ + Return an SVG Link element containing a Rect and one or more text labels representing a + parent object or cable termination point. + + :param width: Box width + :param color: Box fill color + :param url: Hyperlink URL + :param labels: Iterable of text labels + :param y_indent: Vertical indent (for overlapping other boxes) (default: 0) + :param padding_multiplier: Add extra vertical padding (default: 1) + :param radius: Box corner radius (default: 10) + """ + self.cursor -= y_indent + + # Create a hyperlink + link = Hyperlink(href=f'{self.base_url}{url}', target='_blank') + + # Add the box + position = ( + OFFSET + (self.width - width) / 2, + self.cursor + ) + height = PADDING * padding_multiplier \ + + LINE_HEIGHT * len(labels) \ + + PADDING * padding_multiplier + box = Rect(position, (width - 2, height), rx=radius, class_='parent-object', style=f'fill: #{color}') + link.add(box) + self.cursor += PADDING * padding_multiplier + + # Add text label(s) + for i, label in enumerate(labels): + self.cursor += LINE_HEIGHT + text_coords = (self.center, self.cursor - LINE_HEIGHT / 2) + text_color = f'#{foreground_color(color, dark="303030")}' + text = Text(label, insert=text_coords, fill=text_color, class_='bold' if not i else []) + link.add(text) + + self.cursor += PADDING * padding_multiplier + + return link + + def _draw_cable(self, color, url, labels): + """ + Return an SVG group containing a line element and text labels representing a Cable. + + :param color: Cable (line) color + :param url: Hyperlink URL + :param labels: Iterable of text labels + """ + group = Group(class_='connector') + + # Draw a "shadow" line to give the cable a border + start = (OFFSET + self.center, self.cursor) + height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2 + end = (start[0], start[1] + height) + cable_shadow = Line(start=start, end=end, class_='cable-shadow') + group.add(cable_shadow) + + # Draw the cable + cable = Line(start=start, end=end, style=f'stroke: #{color}') + group.add(cable) + + self.cursor += PADDING * 2 + + # Add link + link = Hyperlink(href=f'{self.base_url}{url}', target='_blank') + + # Add text label(s) + for i, label in enumerate(labels): + self.cursor += LINE_HEIGHT + text_coords = (self.center + PADDING * 2, self.cursor - LINE_HEIGHT / 2) + text = Text(label, insert=text_coords, class_='bold' if not i else []) + link.add(text) + + group.add(link) + self.cursor += PADDING * 2 + + return group + + def _draw_attachment(self): + """ + Return an SVG group containing a line element and "Attachment" label. + """ + group = Group(class_='connector') + + # Draw attachment (line) + start = (OFFSET + self.center, OFFSET + self.cursor) + height = PADDING * 2 + LINE_HEIGHT + PADDING * 2 + end = (start[0], start[1] + height) + line = Line(start=start, end=end, class_='attachment') + group.add(line) + self.cursor += PADDING * 4 + + return group + + def render(self): + """ + Return an SVG document representing a cable trace. + """ + traced_path = self.origin.trace() + + # Prep elements list + parent_objects = [] + terminations = [] + connectors = [] + + # Iterate through each (term, cable, term) segment in the path + for i, segment in enumerate(traced_path): + near_end, connector, far_end = segment + + # Near end parent + if i == 0: + # If this is the first segment, draw the originating termination's parent object + parent_object = self._draw_box( + width=self.width, + color=self._get_color(near_end.parent_object), + url=near_end.parent_object.get_absolute_url(), + labels=self._get_labels(near_end.parent_object), + padding_multiplier=2 + ) + parent_objects.append(parent_object) + + # Near end termination + termination = self._draw_box( + width=self.width * .8, + color=self._get_color(near_end), + url=near_end.get_absolute_url(), + labels=self._get_labels(near_end), + y_indent=PADDING, + radius=5 + ) + terminations.append(termination) + + # Connector (either a Cable or attachment to a ProviderNetwork) + if connector is not None: + + # Cable + cable_labels = [ + f'Cable {connector}', + connector.get_status_display() + ] + if connector.type: + cable_labels.append(connector.get_type_display()) + if connector.length and connector.length_unit: + cable_labels.append(f'{connector.length} {connector.get_length_unit_display()}') + cable = self._draw_cable( + color=connector.color or '000000', + url=connector.get_absolute_url(), + labels=cable_labels + ) + connectors.append(cable) + + # Far end termination + termination = self._draw_box( + width=self.width * .8, + color=self._get_color(far_end), + url=far_end.get_absolute_url(), + labels=self._get_labels(far_end), + radius=5 + ) + terminations.append(termination) + + # Far end parent + parent_object = self._draw_box( + width=self.width, + color=self._get_color(far_end.parent_object), + url=far_end.parent_object.get_absolute_url(), + labels=self._get_labels(far_end.parent_object), + y_indent=PADDING, + padding_multiplier=2 + ) + parent_objects.append(parent_object) + + else: + + # Attachment + attachment = self._draw_attachment() + connectors.append(attachment) + + # ProviderNetwork + parent_object = self._draw_box( + width=self.width, + color=self._get_color(far_end), + url=far_end.get_absolute_url(), + labels=self._get_labels(far_end), + padding_multiplier=2 + ) + parent_objects.append(parent_object) + + # Determine drawing size + self.drawing = svgwrite.Drawing( + size=(self.width, self.cursor + 2) + ) + + # Attach CSS stylesheet + with open(f'{settings.STATIC_ROOT}/cable_trace.css') as css_file: + self.drawing.defs.add(self.drawing.style(css_file.read())) + + # Add elements to the drawing in order of depth (Z axis) + for element in connectors + parent_objects + terminations: + self.drawing.add(element) + + return self.drawing diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index b5d1b64ed..352ac7d5c 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -52,10 +52,20 @@ def get_cabletermination_row_class(record): return '' +def get_interface_state_attribute(record): + """ + Get interface enabled state as string to attach to DOM element. + """ + if record.enabled: + return "enabled" + else: + return "disabled" + # # Device roles # + class DeviceRoleTable(BaseTable): pk = ToggleColumn() name = tables.Column( @@ -278,10 +288,10 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', + 'pk', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', ) - default_columns = ('pk', 'device', 'name', 'label', 'type', 'speed', 'description') + default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description') class DeviceConsolePortTable(ConsolePortTable): @@ -322,10 +332,10 @@ class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', + 'pk', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', ) - default_columns = ('pk', 'device', 'name', 'label', 'type', 'speed', 'description') + default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description') class DeviceConsoleServerPortTable(ConsoleServerPortTable): @@ -367,10 +377,10 @@ class PowerPortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = PowerPort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'mark_connected', 'maximum_draw', 'allocated_draw', + 'pk', 'name', 'device', 'label', 'type', 'description', 'mark_connected', 'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', ) - default_columns = ('pk', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description') + default_columns = ('pk', 'name', 'device', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description') class DevicePowerPortTable(PowerPortTable): @@ -418,10 +428,10 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = PowerOutlet fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'mark_connected', 'cable', + 'pk', 'name', 'device', 'label', 'type', 'description', 'power_port', 'feed_leg', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', ) - default_columns = ('pk', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description') + default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description') class DevicePowerOutletTable(PowerOutletTable): @@ -480,11 +490,11 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable class Meta(DeviceComponentTable.Meta): model = Interface fields = ( - 'pk', 'device', 'name', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', + 'pk', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', ) - default_columns = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description') + default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description') class DeviceInterfaceTable(InterfaceTable): @@ -524,6 +534,7 @@ class DeviceInterfaceTable(InterfaceTable): row_attrs = { 'class': get_cabletermination_row_class, 'data-name': lambda record: record.name, + 'data-enabled': get_interface_state_attribute, } @@ -534,6 +545,7 @@ class FrontPortTable(DeviceComponentTable, CableTerminationTable): 'args': [Accessor('device_id')], } ) + color = ColorColumn() rear_port_position = tables.Column( verbose_name='Position' ) @@ -547,10 +559,12 @@ class FrontPortTable(DeviceComponentTable, CableTerminationTable): class Meta(DeviceComponentTable.Meta): model = FrontPort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'mark_connected', - 'cable', 'cable_color', 'cable_peer', 'tags', + 'pk', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', + 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'tags', + ) + default_columns = ( + 'pk', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', ) - default_columns = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description') class DeviceFrontPortTable(FrontPortTable): @@ -588,6 +602,7 @@ class RearPortTable(DeviceComponentTable, CableTerminationTable): 'args': [Accessor('device_id')], } ) + color = ColorColumn() tags = TagColumn( url_name='dcim:rearport_list' ) @@ -595,10 +610,10 @@ class RearPortTable(DeviceComponentTable, CableTerminationTable): class Meta(DeviceComponentTable.Meta): model = RearPort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', + 'pk', 'name', 'device', 'label', 'type', 'color', 'positions', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'tags', ) - default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') + default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description') class DeviceRearPortTable(RearPortTable): @@ -647,8 +662,8 @@ class DeviceBayTable(DeviceComponentTable): class Meta(DeviceComponentTable.Meta): model = DeviceBay - fields = ('pk', 'device', 'name', 'label', 'status', 'installed_device', 'description', 'tags') - default_columns = ('pk', 'device', 'name', 'label', 'status', 'installed_device', 'description') + fields = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description', 'tags') + default_columns = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description') class DeviceDeviceBayTable(DeviceBayTable): @@ -693,10 +708,10 @@ class InventoryItemTable(DeviceComponentTable): class Meta(BaseTable.Meta): model = InventoryItem fields = ( - 'pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', + 'pk', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered', 'tags', ) - default_columns = ('pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag') + default_columns = ('pk', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag') class DeviceInventoryItemTable(InventoryItemTable): diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 0a445171d..6720baf66 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -4,7 +4,9 @@ from dcim.models import ( ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceType, FrontPortTemplate, InterfaceTemplate, Manufacturer, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate, ) -from utilities.tables import BaseTable, BooleanColumn, ButtonsColumn, LinkedCountColumn, TagColumn, ToggleColumn +from utilities.tables import ( + BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, LinkedCountColumn, TagColumn, ToggleColumn, +) __all__ = ( 'ConsolePortTemplateTable', @@ -164,6 +166,7 @@ class FrontPortTemplateTable(ComponentTemplateTable): rear_port_position = tables.Column( verbose_name='Position' ) + color = ColorColumn() actions = ButtonsColumn( model=FrontPortTemplate, buttons=('edit', 'delete'), @@ -172,11 +175,12 @@ class FrontPortTemplateTable(ComponentTemplateTable): class Meta(BaseTable.Meta): model = FrontPortTemplate - fields = ('pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'actions') + fields = ('pk', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'actions') empty_text = "None" class RearPortTemplateTable(ComponentTemplateTable): + color = ColorColumn() actions = ButtonsColumn( model=RearPortTemplate, buttons=('edit', 'delete'), @@ -185,7 +189,7 @@ class RearPortTemplateTable(ComponentTemplateTable): class Meta(BaseTable.Meta): model = RearPortTemplate - fields = ('pk', 'name', 'label', 'type', 'positions', 'description', 'actions') + fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'description', 'actions') empty_text = "None" diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index 2582a7117..65a30ac61 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -26,24 +26,34 @@ CABLE_TERMINATION_PARENT = """ DEVICE_LINK = """ - {{ record.name|default:'Unnamed device' }} + {{ record.name|default:'Unnamed device' }} """ DEVICEBAY_STATUS = """ {% if record.installed_device_id %} - + {{ record.installed_device.get_status_display }} {% else %} - Vacant + Vacant {% endif %} """ INTERFACE_IPADDRESSES = """ -{% for ip in record.ip_addresses.all %} - {{ ip }}
-{% endfor %} +
+ {% for ip in record.ip_addresses.all %} + + {{ ip }} + + {% endfor %} +
""" INTERFACE_TAGGED_VLANS = """ @@ -60,7 +70,7 @@ INTERFACE_TAGGED_VLANS = """ POWERFEED_CABLE = """ {{ value }} - + """ @@ -72,7 +82,7 @@ POWERFEED_CABLETERMINATION = """ """ LOCATION_ELEVATIONS = """ - + """ @@ -83,205 +93,199 @@ LOCATION_ELEVATIONS = """ CONSOLEPORT_BUTTONS = """ {% if record.cable %} - + {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %} {% if perms.dcim.delete_cable %} - + {% endif %} {% elif perms.dcim.add_cable %} - - - {% if not record.mark_connected %} - - - - - {% else %} - - {% endif %} + + + + + + +{% else %} + {% endif %} """ CONSOLESERVERPORT_BUTTONS = """ {% if record.cable %} - + {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %} {% if perms.dcim.delete_cable %} - + {% endif %} {% elif perms.dcim.add_cable %} - - - {% if not record.mark_connected %} - - - - - {% else %} - - {% endif %} + + + + + + +{% else %} + {% endif %} """ POWERPORT_BUTTONS = """ {% if record.cable %} - + {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %} {% if perms.dcim.delete_cable %} - + {% endif %} {% elif perms.dcim.add_cable %} - - - {% if not record.mark_connected %} - - - - - {% else %} - - {% endif %} + + + + + + +{% else %} + {% endif %} """ POWEROUTLET_BUTTONS = """ {% if record.cable %} - + {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %} {% if perms.dcim.delete_cable %} - + {% endif %} {% elif perms.dcim.add_cable %} - - + + {% if not record.mark_connected %} - + {% else %} - + {% endif %} {% endif %} """ INTERFACE_BUTTONS = """ {% if perms.ipam.add_ipaddress %} - + {% endif %} {% if record.cable %} - + {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %} {% if perms.dcim.delete_cable %} - + {% endif %} {% elif record.is_connectable and perms.dcim.add_cable %} - - + + {% if not record.mark_connected %} - - - - + + + + {% else %} - + {% endif %} {% endif %} """ FRONTPORT_BUTTONS = """ {% if record.cable %} - + {% include 'dcim/inc/cable_toggle_buttons.html' with cable=record.cable %} {% if perms.dcim.delete_cable %} - + {% endif %} {% elif perms.dcim.add_cable %} - - + + {% if not record.mark_connected %} - -