mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-26 01:06:11 -06:00
Merge branch 'feature' into 10945-recurring-jobs
This commit is contained in:
commit
f4842ad8db
@ -141,6 +141,22 @@ When determining the primary IP address for a device, IPv6 is preferred over IPv
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## QUEUE_MAPPINGS
|
||||||
|
|
||||||
|
Allows changing which queues are used internally for background tasks.
|
||||||
|
|
||||||
|
```python
|
||||||
|
QUEUE_MAPPINGS = {
|
||||||
|
'webhook': 'low',
|
||||||
|
'report': 'high',
|
||||||
|
'script': 'high',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If no queue is defined the queue named `default` will be used.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## RELEASE_CHECK_URL
|
## RELEASE_CHECK_URL
|
||||||
|
|
||||||
Default: None (disabled)
|
Default: None (disabled)
|
||||||
|
@ -225,6 +225,9 @@ Once NetBox has been configured, we're ready to proceed with the actual installa
|
|||||||
* Builds the documentation locally (for offline use)
|
* Builds the documentation locally (for offline use)
|
||||||
* Aggregate static resource files on disk
|
* Aggregate static resource files on disk
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
If you still have a Python virtual environment active from a previous installation step, disable it now by running the `deactivate` command. This will avoid errors on systems where `sudo` has been configured to preserve the user's current environment.
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
sudo /opt/netbox/upgrade.sh
|
sudo /opt/netbox/upgrade.sh
|
||||||
```
|
```
|
||||||
|
@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
## v3.3.10 (FUTURE)
|
## v3.3.10 (FUTURE)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#10748](https://github.com/netbox-community/netbox/issues/10748) - Add provider selection field for provider networks to circuit termination edit view
|
||||||
|
* [#11119](https://github.com/netbox-community/netbox/issues/11119) - Enable filtering L2VPNs by slug
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#11041](https://github.com/netbox-community/netbox/issues/11041) - Correct power utilization percentage precision
|
||||||
|
* [#11087](https://github.com/netbox-community/netbox/issues/11087) - Fix background color of bottom banner content
|
||||||
|
* [#11101](https://github.com/netbox-community/netbox/issues/11101) - Correct circuits count under site view
|
||||||
|
* [#11128](https://github.com/netbox-community/netbox/issues/11128) - Disable ordering changelog table by object to avoid exception
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v3.3.9 (2022-11-30)
|
## v3.3.9 (2022-11-30)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
* [#815](https://github.com/netbox-community/netbox/issues/815) - Enable specifying terminations when bulk importing circuits
|
* [#815](https://github.com/netbox-community/netbox/issues/815) - Enable specifying terminations when bulk importing circuits
|
||||||
* [#11090](https://github.com/netbox-community/netbox/issues/11090) - Add regular expression support to global search engine
|
* [#11090](https://github.com/netbox-community/netbox/issues/11090) - Add regular expression support to global search engine
|
||||||
|
* [#11022](https://github.com/netbox-community/netbox/issues/11022) - Introduce `QUEUE_MAPPINGS` configuration parameter to allow customization of background task prioritization
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -145,16 +145,28 @@ class CircuitTerminationForm(NetBoxModelForm):
|
|||||||
},
|
},
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
provider_network_provider = DynamicModelChoiceField(
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label='Provider',
|
||||||
|
initial_params={
|
||||||
|
'networks': 'provider_network'
|
||||||
|
}
|
||||||
|
)
|
||||||
provider_network = DynamicModelChoiceField(
|
provider_network = DynamicModelChoiceField(
|
||||||
queryset=ProviderNetwork.objects.all(),
|
queryset=ProviderNetwork.objects.all(),
|
||||||
|
query_params={
|
||||||
|
'provider_id': '$provider_network_provider',
|
||||||
|
},
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
fields = [
|
fields = [
|
||||||
'provider', 'circuit', 'term_side', 'region', 'site_group', 'site', 'provider_network', 'mark_connected',
|
'provider', 'circuit', 'term_side', 'region', 'site_group', 'site', 'provider_network_provider',
|
||||||
'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'tags',
|
'provider_network', 'mark_connected', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
||||||
|
'description', 'tags',
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'port_speed': _("Physical circuit speed"),
|
'port_speed': _("Physical circuit speed"),
|
||||||
|
@ -391,7 +391,7 @@ class SiteView(generic.ObjectView):
|
|||||||
scope_id=instance.pk
|
scope_id=instance.pk
|
||||||
).count(),
|
).count(),
|
||||||
'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
||||||
'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).count(),
|
'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct().count(),
|
||||||
'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(),
|
'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(),
|
||||||
}
|
}
|
||||||
locations = Location.objects.add_related_count(
|
locations = Location.objects.add_related_count(
|
||||||
|
@ -21,6 +21,8 @@ from extras.choices import *
|
|||||||
from extras.constants import *
|
from extras.constants import *
|
||||||
from extras.conditions import ConditionSet
|
from extras.conditions import ConditionSet
|
||||||
from extras.utils import FeatureQuery, image_upload
|
from extras.utils import FeatureQuery, image_upload
|
||||||
|
from netbox.config import get_config
|
||||||
|
from netbox.constants import RQ_QUEUE_DEFAULT
|
||||||
from netbox.models import ChangeLoggedModel
|
from netbox.models import ChangeLoggedModel
|
||||||
from netbox.models.features import (
|
from netbox.models.features import (
|
||||||
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, TagsMixin, WebhooksMixin,
|
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, TagsMixin, WebhooksMixin,
|
||||||
@ -679,7 +681,8 @@ class JobResult(models.Model):
|
|||||||
schedule_at: Schedule the job to be executed at the passed date and time
|
schedule_at: Schedule the job to be executed at the passed date and time
|
||||||
interval: Recurrence interval (in minutes)
|
interval: Recurrence interval (in minutes)
|
||||||
"""
|
"""
|
||||||
queue = django_rq.get_queue('default')
|
rq_queue_name = get_config().QUEUE_MAPPINGS.get(obj_type.name, RQ_QUEUE_DEFAULT)
|
||||||
|
queue = django_rq.get_queue(rq_queue_name)
|
||||||
status = JobResultStatusChoices.STATUS_SCHEDULED if schedule_at else JobResultStatusChoices.STATUS_PENDING
|
status = JobResultStatusChoices.STATUS_SCHEDULED if schedule_at else JobResultStatusChoices.STATUS_PENDING
|
||||||
job_result: JobResult = JobResult.objects.create(
|
job_result: JobResult = JobResult.objects.create(
|
||||||
name=name,
|
name=name,
|
||||||
|
@ -216,7 +216,8 @@ class ObjectChangeTable(NetBoxTable):
|
|||||||
object_repr = tables.TemplateColumn(
|
object_repr = tables.TemplateColumn(
|
||||||
accessor=tables.A('changed_object'),
|
accessor=tables.A('changed_object'),
|
||||||
template_code=OBJECTCHANGE_OBJECT,
|
template_code=OBJECTCHANGE_OBJECT,
|
||||||
verbose_name='Object'
|
verbose_name='Object',
|
||||||
|
orderable=False
|
||||||
)
|
)
|
||||||
request_id = tables.TemplateColumn(
|
request_id = tables.TemplateColumn(
|
||||||
template_code=OBJECTCHANGE_REQUEST_ID,
|
template_code=OBJECTCHANGE_REQUEST_ID,
|
||||||
|
@ -5,6 +5,8 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django_rq import get_queue
|
from django_rq import get_queue
|
||||||
|
|
||||||
|
from netbox.config import get_config
|
||||||
|
from netbox.constants import RQ_QUEUE_DEFAULT
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
from utilities.utils import serialize_object
|
from utilities.utils import serialize_object
|
||||||
@ -78,7 +80,8 @@ def flush_webhooks(queue):
|
|||||||
"""
|
"""
|
||||||
Flush a list of object representation to RQ for webhook processing.
|
Flush a list of object representation to RQ for webhook processing.
|
||||||
"""
|
"""
|
||||||
rq_queue = get_queue('default')
|
rq_queue_name = get_config().QUEUE_MAPPINGS.get('webhook', RQ_QUEUE_DEFAULT)
|
||||||
|
rq_queue = get_queue(rq_queue_name)
|
||||||
webhooks_cache = {
|
webhooks_cache = {
|
||||||
'type_create': {},
|
'type_create': {},
|
||||||
'type_update': {},
|
'type_update': {},
|
||||||
|
@ -962,7 +962,7 @@ class L2VPNFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = L2VPN
|
model = L2VPN
|
||||||
fields = ['id', 'identifier', 'name', 'type', 'description']
|
fields = ['id', 'identifier', 'name', 'slug', 'type', 'description']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -1505,6 +1505,10 @@ class L2VPNTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'name': ['L2VPN 1', 'L2VPN 2']}
|
params = {'name': ['L2VPN 1', 'L2VPN 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_slug(self):
|
||||||
|
params = {'slug': ['l2vpn-1', 'l2vpn-2']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_identifier(self):
|
def test_identifier(self):
|
||||||
params = {'identifier': ['65001', '65002']}
|
params = {'identifier': ['65001', '65002']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -1,2 +1,7 @@
|
|||||||
# Prefix for nested serializers
|
# Prefix for nested serializers
|
||||||
NESTED_SERIALIZER_PREFIX = 'Nested'
|
NESTED_SERIALIZER_PREFIX = 'Nested'
|
||||||
|
|
||||||
|
# RQ queue names
|
||||||
|
RQ_QUEUE_DEFAULT = 'default'
|
||||||
|
RQ_QUEUE_HIGH = 'high'
|
||||||
|
RQ_QUEUE_LOW = 'low'
|
||||||
|
@ -17,6 +17,7 @@ from extras.plugins import PluginConfig
|
|||||||
from sentry_sdk.integrations.django import DjangoIntegration
|
from sentry_sdk.integrations.django import DjangoIntegration
|
||||||
|
|
||||||
from netbox.config import PARAMS
|
from netbox.config import PARAMS
|
||||||
|
from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -101,6 +102,7 @@ MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media'
|
|||||||
METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False)
|
METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False)
|
||||||
PLUGINS = getattr(configuration, 'PLUGINS', [])
|
PLUGINS = getattr(configuration, 'PLUGINS', [])
|
||||||
PLUGINS_CONFIG = getattr(configuration, 'PLUGINS_CONFIG', {})
|
PLUGINS_CONFIG = getattr(configuration, 'PLUGINS_CONFIG', {})
|
||||||
|
QUEUE_MAPPINGS = getattr(configuration, 'QUEUE_MAPPINGS', {})
|
||||||
RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None)
|
RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None)
|
||||||
REMOTE_AUTH_AUTO_CREATE_USER = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_USER', False)
|
REMOTE_AUTH_AUTO_CREATE_USER = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_USER', False)
|
||||||
REMOTE_AUTH_BACKEND = getattr(configuration, 'REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
|
REMOTE_AUTH_BACKEND = getattr(configuration, 'REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
|
||||||
@ -639,11 +641,16 @@ else:
|
|||||||
}
|
}
|
||||||
|
|
||||||
RQ_QUEUES = {
|
RQ_QUEUES = {
|
||||||
'high': RQ_PARAMS,
|
RQ_QUEUE_HIGH: RQ_PARAMS,
|
||||||
'default': RQ_PARAMS,
|
RQ_QUEUE_DEFAULT: RQ_PARAMS,
|
||||||
'low': RQ_PARAMS,
|
RQ_QUEUE_LOW: RQ_PARAMS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add any queues defined in QUEUE_MAPPINGS
|
||||||
|
RQ_QUEUES.update({
|
||||||
|
queue: RQ_PARAMS for queue in set(QUEUE_MAPPINGS.values()) if queue not in RQ_QUEUES
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Plugins
|
# Plugins
|
||||||
|
@ -102,14 +102,14 @@ Blocks:
|
|||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{# Bottom banner #}
|
||||||
|
{% if config.BANNER_BOTTOM %}
|
||||||
|
<div class="text-center mx-3">
|
||||||
|
{{ config.BANNER_BOTTOM|safe }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if config.BANNER_BOTTOM %}
|
|
||||||
<div class="text-center mx-3">
|
|
||||||
{{ config.BANNER_BOTTOM|safe }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# BS5 pop-up modals #}
|
{# BS5 pop-up modals #}
|
||||||
{% block modals %}{% endblock %}
|
{% block modals %}{% endblock %}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
{% render_field form.site %}
|
{% render_field form.site %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane{% if providernetwork_tab_active %} active{% endif %}" id="providernetwork">
|
<div class="tab-pane{% if providernetwork_tab_active %} active{% endif %}" id="providernetwork">
|
||||||
|
{% render_field form.provider_network_provider %}
|
||||||
{% render_field form.provider_network %}
|
{% render_field form.provider_network %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Provider Network</td>
|
<td>Provider Network</td>
|
||||||
<td>{{ termination.provider_network|linkify }}</td>
|
<td>{{ termination.provider_network.provider|linkify }} / {{ termination.provider_network|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -140,7 +140,8 @@ def percentage(x, y):
|
|||||||
"""
|
"""
|
||||||
if x is None or y is None:
|
if x is None or y is None:
|
||||||
return None
|
return None
|
||||||
return round(x / y * 100)
|
|
||||||
|
return round(x / y * 100, 1)
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
@register.filter()
|
||||||
|
Loading…
Reference in New Issue
Block a user