Skip to content

Commit

Permalink
Merge branch 'develop' into 15496-circuit-termination
Browse files Browse the repository at this point in the history
  • Loading branch information
arthanson committed May 9, 2024
2 parents f809733 + 2a06e19 commit 6b8a6da
Show file tree
Hide file tree
Showing 40 changed files with 166 additions and 131 deletions.
3 changes: 1 addition & 2 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Expand Up @@ -34,10 +34,9 @@ body:
label: Python Version
description: What version of Python are you currently running?
options:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
validations:
required: true
- type: textarea
Expand Down
16 changes: 16 additions & 0 deletions contrib/uwsgi.ini
Expand Up @@ -11,8 +11,24 @@ master = true
; clear environment on exit
vacuum = true

; make SIGTERM stop the app (instead of reload)
die-on-term = true

; exit if no app can be loaded
need-app = true

; do not use multiple interpreters
single-interpreter = true

; change to the project directory
chdir = netbox

; specify the WSGI module to load
module = netbox.wsgi

; workaround to make uWSGI reloads work with pyuwsgi (not to be used if using uwsgi package instead)
binary-path = venv/bin/python

; only log internal messages and errors (reverse proxy already logs the requests)
disable-logging = true
log-5xx = true
2 changes: 1 addition & 1 deletion docs/development/adding-models.md
Expand Up @@ -77,7 +77,7 @@ Create the following for each model:

## 13. GraphQL API components

Create a Graphene object type for the model in `graphql/types.py` by subclassing the appropriate class from `netbox.graphql.types`.
Create a GraphQL object type for the model in `graphql/types.py` by subclassing the appropriate class from `netbox.graphql.types`.

Also extend the schema class defined in `graphql/schema.py` with the individual object and object list fields per the established convention.

Expand Down
2 changes: 1 addition & 1 deletion docs/installation/4b-uwsgi.md
Expand Up @@ -17,7 +17,7 @@ pip3 install pyuwsgi
Once installed, add the package to `local_requirements.txt` to ensure it is re-installed during future rebuilds of the virtual environment:

```no-highlight
sudo sh -c "echo 'pyuwgsi' >> /opt/netbox/local_requirements.txt"
sudo sh -c "echo 'pyuwsgi' >> /opt/netbox/local_requirements.txt"
```

## Configuration
Expand Down
4 changes: 2 additions & 2 deletions docs/integrations/graphql-api.md
@@ -1,6 +1,6 @@
# 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/).
NetBox provides a read-only [GraphQL](https://graphql.org/) API to complement its REST API. This API is powered by [Strawberry Django](https://strawberry-graphql.github.io/strawberry-django/).

## Queries

Expand Down Expand Up @@ -47,7 +47,7 @@ NetBox provides both a singular and plural query field for each object type:

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 filters) to fetch all devices.

For more detail on constructing GraphQL queries, see the [Graphene documentation](https://docs.graphene-python.org/en/latest/) as well as the [GraphQL queries documentation](https://graphql.org/learn/queries/).
For more detail on constructing GraphQL queries, see the [GraphQL queries documentation](https://graphql.org/learn/queries/). For filtering and lookup syntax, please refer to the [Strawberry Django documentation](https://strawberry-graphql.github.io/strawberry-django/guide/filters/).

## Filtering

Expand Down
2 changes: 1 addition & 1 deletion docs/plugins/development/graphql-api.md
Expand Up @@ -2,7 +2,7 @@

## Defining the Schema Class

A plugin can extend NetBox's GraphQL API by registering its own schema class. By default, NetBox will attempt to import `graphql.schema` from the plugin, if it exists. This path can be overridden by defining `graphql_schema` on the PluginConfig instance as the dotted path to the desired Python class. This class must be a subclass of `graphene.ObjectType`.
A plugin can extend NetBox's GraphQL API by registering its own schema class. By default, NetBox will attempt to import `graphql.schema` from the plugin, if it exists. This path can be overridden by defining `graphql_schema` on the PluginConfig instance as the dotted path to the desired Python class.

### Example

Expand Down
2 changes: 1 addition & 1 deletion netbox/circuits/api/serializers_/circuits.py
Expand Up @@ -48,7 +48,7 @@ class Meta:
class CircuitSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
provider = ProviderSerializer(nested=True)
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True)
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
status = ChoiceField(choices=CircuitStatusChoices, required=False)
type = CircuitTypeSerializer(nested=True)
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
Expand Down
1 change: 1 addition & 0 deletions netbox/circuits/api/serializers_/providers.py
Expand Up @@ -45,6 +45,7 @@ class Meta:
class ProviderAccountSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail')
provider = ProviderSerializer(nested=True)
name = serializers.CharField(allow_blank=True, max_length=100, required=False, default='')

class Meta:
model = ProviderAccount
Expand Down
4 changes: 2 additions & 2 deletions netbox/circuits/tests/test_api.py
Expand Up @@ -141,7 +141,7 @@ def setUpTestData(cls):
{
'cid': 'Circuit 6',
'provider': providers[1].pk,
'provider_account': provider_accounts[1].pk,
# Omit provider account to test uniqueness constraint
'type': circuit_types[1].pk,
},
]
Expand Down Expand Up @@ -237,7 +237,7 @@ def setUpTestData(cls):
'account': '5678',
},
{
'name': 'Provider Account 6',
# Omit name to test uniqueness constraint
'provider': providers[0].pk,
'account': '6789',
},
Expand Down
1 change: 1 addition & 0 deletions netbox/dcim/api/serializers_/devices.py
Expand Up @@ -122,6 +122,7 @@ def get_config_context(self, obj):
class VirtualDeviceContextSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualdevicecontext-detail')
device = DeviceSerializer(nested=True)
identifier = serializers.IntegerField(allow_null=True, max_value=32767, min_value=0, required=False, default=None)
tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True)
primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True)
Expand Down
4 changes: 2 additions & 2 deletions netbox/dcim/api/serializers_/sites.py
Expand Up @@ -51,7 +51,7 @@ class SiteSerializer(NetBoxModelSerializer):
status = ChoiceField(choices=SiteStatusChoices, required=False)
region = RegionSerializer(nested=True, required=False, allow_null=True)
group = SiteGroupSerializer(nested=True, required=False, allow_null=True)
tenant = TenantSerializer(required=False, allow_null=True)
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
time_zone = TimeZoneSerializerField(required=False, allow_null=True)
asns = SerializedPKRelatedField(
queryset=ASN.objects.all(),
Expand Down Expand Up @@ -83,7 +83,7 @@ class Meta:
class LocationSerializer(NestedGroupModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
site = SiteSerializer(nested=True)
parent = NestedLocationSerializer(required=False, allow_null=True)
parent = NestedLocationSerializer(required=False, allow_null=True, default=None)
status = ChoiceField(choices=LocationStatusChoices, required=False)
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
rack_count = serializers.IntegerField(read_only=True)
Expand Down
7 changes: 5 additions & 2 deletions netbox/dcim/tests/test_api.py
Expand Up @@ -10,6 +10,7 @@
from extras.models import ConfigTemplate
from ipam.models import ASN, RIR, VLAN, VRF
from netbox.api.serializers import GenericObjectSerializer
from tenancy.models import Tenant
from utilities.testing import APITestCase, APIViewTestCases, create_test_device
from virtualization.models import Cluster, ClusterType
from wireless.choices import WirelessChannelChoices
Expand Down Expand Up @@ -152,6 +153,7 @@ def setUpTestData(cls):
Site.objects.bulk_create(sites)

rir = RIR.objects.create(name='RFC 6996', is_private=True)
tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')

asns = [
ASN(asn=65000 + i, rir=rir) for i in range(8)
Expand All @@ -166,6 +168,7 @@ def setUpTestData(cls):
'group': groups[1].pk,
'status': SiteStatusChoices.STATUS_ACTIVE,
'asns': [asns[0].pk, asns[1].pk],
'tenant': tenant.pk,
},
{
'name': 'Site 5',
Expand Down Expand Up @@ -230,7 +233,7 @@ def setUpTestData(cls):
'name': 'Test Location 6',
'slug': 'test-location-6',
'site': sites[1].pk,
'parent': parent_locations[1].pk,
# Omit parent to test uniqueness constraint
'status': LocationStatusChoices.STATUS_PLANNED,
},
]
Expand Down Expand Up @@ -2307,6 +2310,6 @@ def setUpTestData(cls):
'device': devices[1].pk,
'status': 'active',
'name': 'VDC 3',
'identifier': 3,
# Omit identifier to test uniqueness constraint
},
]
1 change: 1 addition & 0 deletions netbox/extras/management/commands/runscript.py
Expand Up @@ -85,6 +85,7 @@ def _run_script():

module_name, script_name = script.split('.', 1)
module, script = get_module_and_script(module_name, script_name)
script = script.python_class

# Take user from command line if provided and exists, other
if options['user']:
Expand Down
5 changes: 4 additions & 1 deletion netbox/extras/migrations/0109_script_model.py
Expand Up @@ -60,7 +60,10 @@ def get_name(cls):
return cls.full_name.split(".", maxsplit=1)[1]

loader = SourceFileLoader(get_python_name(scriptmodule), get_full_path(scriptmodule))
module = loader.load_module()
try:
module = loader.load_module()
except FileNotFoundError:
return {}

scripts = {}
ordered = getattr(module, 'script_order', [])
Expand Down
9 changes: 2 additions & 7 deletions netbox/extras/tables/tables.py
Expand Up @@ -545,7 +545,7 @@ class ScriptResultsTable(BaseTable):
template_code="""{% load log_levels %}{% log_level record.status %}""",
verbose_name=_('Level')
)
message = tables.Column(
message = columns.MarkdownColumn(
verbose_name=_('Message')
)

Expand All @@ -566,22 +566,17 @@ class ReportResultsTable(BaseTable):
time = tables.Column(
verbose_name=_('Time')
)
status = tables.Column(
empty_values=(),
verbose_name=_('Level')
)
status = tables.TemplateColumn(
template_code="""{% load log_levels %}{% log_level record.status %}""",
verbose_name=_('Level')
)

object = tables.Column(
verbose_name=_('Object')
)
url = tables.Column(
verbose_name=_('URL')
)
message = tables.Column(
message = columns.MarkdownColumn(
verbose_name=_('Message')
)

Expand Down
2 changes: 1 addition & 1 deletion netbox/ipam/models/ip.py
Expand Up @@ -574,7 +574,7 @@ def clean(self):
if not self.end_address > self.start_address:
raise ValidationError({
'end_address': _(
"Ending address must be lower than the starting address ({start_address})"
"Ending address must be greater than the starting address ({start_address})"
).format(start_address=self.start_address)
})

Expand Down
1 change: 1 addition & 0 deletions netbox/ipam/views.py
Expand Up @@ -781,6 +781,7 @@ def get_extra_context(self, request, instance):
class IPAddressEditView(generic.ObjectEditView):
queryset = IPAddress.objects.all()
form = forms.IPAddressForm
template_name = 'ipam/ipaddress_edit.html'

def alter_object(self, obj, request, url_args, url_kwargs):

Expand Down
1 change: 1 addition & 0 deletions netbox/netbox/navigation/__init__.py
Expand Up @@ -32,6 +32,7 @@ class MenuItem:
link: str
link_text: str
permissions: Optional[Sequence[str]] = ()
auth_required: Optional[bool] = False
staff_only: Optional[bool] = False
buttons: Optional[Sequence[MenuItemButton]] = ()

Expand Down
11 changes: 9 additions & 2 deletions netbox/netbox/navigation/menu.py
Expand Up @@ -372,6 +372,7 @@
MenuItem(
link=f'users:user_list',
link_text=_('Users'),
auth_required=True,
permissions=[f'auth.view_user'],
buttons=(
MenuItemButton(
Expand All @@ -391,6 +392,7 @@
MenuItem(
link=f'users:group_list',
link_text=_('Groups'),
auth_required=True,
permissions=[f'auth.view_group'],
buttons=(
MenuItemButton(
Expand All @@ -410,12 +412,14 @@
MenuItem(
link=f'users:token_list',
link_text=_('API Tokens'),
auth_required=True,
permissions=[f'users.view_token'],
buttons=get_model_buttons('users', 'token')
),
MenuItem(
link=f'users:objectpermission_list',
link_text=_('Permissions'),
auth_required=True,
permissions=[f'users.view_objectpermission'],
buttons=get_model_buttons('users', 'objectpermission', actions=['add'])
),
Expand All @@ -426,16 +430,19 @@
items=(
MenuItem(
link='core:system',
link_text=_('System')
link_text=_('System'),
auth_required=True
),
MenuItem(
link='core:configrevision_list',
link_text=_('Configuration History'),
auth_required=True,
permissions=['core.view_configrevision']
),
MenuItem(
link='core:background_queue_list',
link_text=_('Background Tasks')
link_text=_('Background Tasks'),
auth_required=True
),
),
),
Expand Down
10 changes: 7 additions & 3 deletions netbox/netbox/settings.py
Expand Up @@ -372,7 +372,6 @@ def _setting(name, default=None):
# Middleware
MIDDLEWARE = [
"strawberry_django.middlewares.debug_toolbar.DebugToolbarMiddleware",
'django_prometheus.middleware.PrometheusBeforeMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
Expand All @@ -386,8 +385,14 @@ def _setting(name, default=None):
'netbox.middleware.RemoteUserMiddleware',
'netbox.middleware.CoreMiddleware',
'netbox.middleware.MaintenanceModeMiddleware',
'django_prometheus.middleware.PrometheusAfterMiddleware',
]
if METRICS_ENABLED:
# If metrics are enabled, add the before & after Prometheus middleware
MIDDLEWARE = [
'django_prometheus.middleware.PrometheusBeforeMiddleware',
*MIDDLEWARE,
'django_prometheus.middleware.PrometheusAfterMiddleware',
]

# URLs
ROOT_URLCONF = 'netbox.urls'
Expand Down Expand Up @@ -522,7 +527,6 @@ def _setting(name, default=None):
sentry_sdk.init(
dsn=SENTRY_DSN,
release=VERSION,
integrations=[sentry_sdk.integrations.django.DjangoIntegration()],
sample_rate=SENTRY_SAMPLE_RATE,
traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
send_default_pii=True,
Expand Down
2 changes: 1 addition & 1 deletion netbox/netbox/tables/tables.py
Expand Up @@ -52,7 +52,7 @@ def __init__(self, *args, user=None, **kwargs):

# Set default empty_text if none was provided
if self.empty_text is None:
self.empty_text = f"No {self._meta.model._meta.verbose_name_plural} found"
self.empty_text = _("No {model_name} found").format(model_name=self._meta.model._meta.verbose_name_plural)

# Determine the table columns to display by checking the following:
# 1. User's configuration for the table
Expand Down
2 changes: 1 addition & 1 deletion netbox/project-static/dist/netbox.js

Large diffs are not rendered by default.

0 comments on commit 6b8a6da

Please sign in to comment.