Compare commits

...

2 Commits

Author SHA1 Message Date
Jeremy Stretch
6022433a40
Closes #19134: Allow negative values for interface TX power (#19847)
Some checks are pending
CI / build (20.x, 3.10) (push) Waiting to run
CI / build (20.x, 3.11) (push) Waiting to run
CI / build (20.x, 3.12) (push) Waiting to run
2025-07-09 10:17:41 -07:00
Jeremy Stretch
878c624eaf
Closes #19722: Extend the object types REST API endpoint (#19826) 2025-07-09 08:43:24 -07:00
5 changed files with 90 additions and 4 deletions

View File

@ -1,7 +1,9 @@
from django.contrib.contenttypes.models import ContentType, ContentTypeManager
from django.db.models import Q
from netbox.plugins import PluginConfig
from netbox.registry import registry
from utilities.string import title
__all__ = (
'ObjectType',
@ -48,3 +50,29 @@ class ObjectType(ContentType):
class Meta:
proxy = True
@property
def app_labeled_name(self):
# Override ContentType's "app | model" representation style.
return f"{self.app_verbose_name} > {title(self.model_verbose_name)}"
@property
def app_verbose_name(self):
if model := self.model_class():
return model._meta.app_config.verbose_name
@property
def model_verbose_name(self):
if model := self.model_class():
return model._meta.verbose_name
@property
def model_verbose_name_plural(self):
if model := self.model_class():
return model._meta.verbose_name_plural
@property
def is_plugin_model(self):
if not (model := self.model_class()):
return # Return null if model class is invalid
return isinstance(model._meta.app_config, PluginConfig)

View File

@ -1507,7 +1507,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
tx_power = forms.IntegerField(
required=False,
label=_('Transmit power (dBm)'),
min_value=0,
min_value=-40,
max_value=127
)
vrf_id = DynamicModelMultipleChoiceField(

View File

@ -0,0 +1,24 @@
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0208_platform_manufacturer_uniqueness'),
]
operations = [
migrations.AlterField(
model_name='interface',
name='tx_power',
field=models.SmallIntegerField(
blank=True,
null=True,
validators=[
django.core.validators.MinValueValidator(-40),
django.core.validators.MaxValueValidator(127)
]
),
),
]

View File

@ -719,10 +719,13 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
verbose_name=('channel width (MHz)'),
help_text=_("Populated by selected channel (if set)")
)
tx_power = models.PositiveSmallIntegerField(
tx_power = models.SmallIntegerField(
blank=True,
null=True,
validators=(MaxValueValidator(127),),
validators=(
MinValueValidator(-40),
MaxValueValidator(127),
),
verbose_name=_('transmit power (dBm)')
)
poe_mode = models.CharField(

View File

@ -1,7 +1,13 @@
import inspect
from django.urls import NoReverseMatch, reverse
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from core.models import ObjectType
from netbox.api.serializers import BaseModelSerializer
from utilities.views import get_viewname
__all__ = (
'ObjectTypeSerializer',
@ -10,7 +16,32 @@ __all__ = (
class ObjectTypeSerializer(BaseModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:objecttype-detail')
app_name = serializers.CharField(source='app_verbose_name', read_only=True)
model_name = serializers.CharField(source='model_verbose_name', read_only=True)
model_name_plural = serializers.CharField(source='model_verbose_name_plural', read_only=True)
is_plugin_model = serializers.BooleanField(read_only=True)
rest_api_endpoint = serializers.SerializerMethodField()
description = serializers.SerializerMethodField()
class Meta:
model = ObjectType
fields = ['id', 'url', 'display', 'app_label', 'model']
fields = [
'id', 'url', 'display', 'app_label', 'app_name', 'model', 'model_name', 'model_name_plural',
'is_plugin_model', 'rest_api_endpoint', 'description',
]
@extend_schema_field(OpenApiTypes.STR)
def get_rest_api_endpoint(self, obj):
if not (model := obj.model_class()):
return
if viewname := get_viewname(model, action='list', rest_api=True):
try:
return reverse(viewname)
except NoReverseMatch:
return
@extend_schema_field(OpenApiTypes.STR)
def get_description(self, obj):
if not (model := obj.model_class()):
return
return inspect.getdoc(model)