diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md index 38d521de6..bac2b4dca 100644 --- a/docs/development/getting-started.md +++ b/docs/development/getting-started.md @@ -54,6 +54,12 @@ NetBox ships with a [git pre-commit hook](https://githooks.com/) script that aut cd .git/hooks/ ln -s ../../scripts/git-hooks/pre-commit ``` +For the pre-commit hooks to work, you will also need to install the pycodestyle package: + +```no-highlight +python -m pip install pycodestyle +``` +...and setup the yarn packages as shown in the [Web UI Development Guide](web-ui.md) ### 3. Create a Python Virtual Environment @@ -118,6 +124,10 @@ This ensures that your development environment is now complete and operational. !!! tip "IDE Integration" Some IDEs, such as the highly-recommended [PyCharm](https://www.jetbrains.com/pycharm/), will integrate with Django's development server and allow you to run it directly within the IDE. This is strongly encouraged as it makes for a much more convenient development environment. +## UI Development + +For UI development you will need to review the [Web UI Development Guide](web-ui.md) + ## Populating Demo Data Once you have your development environment up and running, it might be helpful to populate some "dummy" data to make interacting with the UI and APIs more convenient. Check out the [netbox-demo-data](https://github.com/netbox-community/netbox-demo-data) repo on GitHub, which houses a collection of sample data that can be easily imported to any new NetBox deployment. (This sample data is used to populate the public demo instance at .) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 1421bb2c7..7b23e242b 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -15,17 +15,21 @@ ### Bug Fixes +* [#9663](https://github.com/netbox-community/netbox/issues/9663) - Omit available IP annotations when filtering prefix child IPs list * [#10040](https://github.com/netbox-community/netbox/issues/10040) - Fix exception when ordering prefixes by flat representation * [#10053](https://github.com/netbox-community/netbox/issues/10053) - Custom fields header should not be displayed when editing circuit terminations with no custom fields * [#10055](https://github.com/netbox-community/netbox/issues/10055) - Fix extraneous NAT indicator by device primary IP * [#10057](https://github.com/netbox-community/netbox/issues/10057) - Fix AttributeError exception when global search results include rack reservations * [#10059](https://github.com/netbox-community/netbox/issues/10059) - Add identifier column to L2VPN table +* [#10070](https://github.com/netbox-community/netbox/issues/10070) - Add unique constraint for L2VPN slug +* [#10087](https://github.com/netbox-community/netbox/issues/10087) - Correct display of far end in console/power/interface connections tables * [#10089](https://github.com/netbox-community/netbox/issues/10089) - `linkify` template filter should escape object representation * [#10094](https://github.com/netbox-community/netbox/issues/10094) - Fix 404 when using "create and add another" to add contact assignments * [#10108](https://github.com/netbox-community/netbox/issues/10108) - Linkify inside NAT IPs for primary device IPs in UI * [#10109](https://github.com/netbox-community/netbox/issues/10109) - Fix available prefixes calculation for container prefixes in the global table * [#10111](https://github.com/netbox-community/netbox/issues/10111) - Wrap search QS to catch ValueError on identifier field * [#10134](https://github.com/netbox-community/netbox/issues/10134) - Custom fields data serializer should return a 400 response for invalid data +* [#10147](https://github.com/netbox-community/netbox/issues/10147) - Permit the creation of 0U device types via REST API --- diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 249a3f167..af806acb8 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -310,7 +310,7 @@ class DeviceTypeSerializer(NetBoxModelSerializer): max_digits=4, decimal_places=1, label='Position (U)', - min_value=decimal.Decimal(0.5), + min_value=0, default=1.0 ) subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False) diff --git a/netbox/dcim/tables/__init__.py b/netbox/dcim/tables/__init__.py index e3b2a42ba..843b612b1 100644 --- a/netbox/dcim/tables/__init__.py +++ b/netbox/dcim/tables/__init__.py @@ -1,109 +1,8 @@ -import django_tables2 as tables -from django_tables2.utils import Accessor - -from netbox.tables import BaseTable, columns -from dcim.models import ConsolePort, Interface, PowerPort from .cables import * +from .connections import * from .devices import * from .devicetypes import * from .modules import * from .power import * from .racks import * from .sites import * - - -# -# Device connections -# - -class ConsoleConnectionTable(BaseTable): - console_server = tables.Column( - accessor=Accessor('_path__destination__device'), - orderable=False, - linkify=True, - verbose_name='Console Server' - ) - console_server_port = tables.Column( - accessor=Accessor('_path__destination'), - orderable=False, - linkify=True, - verbose_name='Port' - ) - device = tables.Column( - linkify=True - ) - name = tables.Column( - linkify=True, - verbose_name='Console Port' - ) - reachable = columns.BooleanColumn( - accessor=Accessor('_path__is_active'), - verbose_name='Reachable' - ) - - class Meta(BaseTable.Meta): - model = ConsolePort - fields = ('device', 'name', 'console_server', 'console_server_port', 'reachable') - - -class PowerConnectionTable(BaseTable): - pdu = tables.Column( - accessor=Accessor('_path__destination__device'), - orderable=False, - linkify=True, - verbose_name='PDU' - ) - outlet = tables.Column( - accessor=Accessor('_path__destination'), - orderable=False, - linkify=True, - verbose_name='Outlet' - ) - device = tables.Column( - linkify=True - ) - name = tables.Column( - linkify=True, - verbose_name='Power Port' - ) - reachable = columns.BooleanColumn( - accessor=Accessor('_path__is_active'), - verbose_name='Reachable' - ) - - class Meta(BaseTable.Meta): - model = PowerPort - fields = ('device', 'name', 'pdu', 'outlet', 'reachable') - - -class InterfaceConnectionTable(BaseTable): - device_a = tables.Column( - accessor=Accessor('device'), - linkify=True, - verbose_name='Device A' - ) - interface_a = tables.Column( - accessor=Accessor('name'), - linkify=True, - verbose_name='Interface A' - ) - device_b = tables.Column( - accessor=Accessor('_path__destination__device'), - orderable=False, - linkify=True, - verbose_name='Device B' - ) - interface_b = tables.Column( - accessor=Accessor('_path__destination'), - orderable=False, - linkify=True, - verbose_name='Interface B' - ) - reachable = columns.BooleanColumn( - accessor=Accessor('_path__is_active'), - verbose_name='Reachable' - ) - - class Meta(BaseTable.Meta): - model = Interface - fields = ('device_a', 'interface_a', 'device_b', 'interface_b', 'reachable') diff --git a/netbox/dcim/tables/connections.py b/netbox/dcim/tables/connections.py new file mode 100644 index 000000000..f9f78f3a6 --- /dev/null +++ b/netbox/dcim/tables/connections.py @@ -0,0 +1,71 @@ +import django_tables2 as tables +from django_tables2.utils import Accessor + +from netbox.tables import BaseTable, columns +from dcim.models import ConsolePort, Interface, PowerPort +from .devices import PathEndpointTable + +__all__ = ( + 'ConsoleConnectionTable', + 'InterfaceConnectionTable', + 'PowerConnectionTable', +) + + +# +# Device connections +# + +class ConsoleConnectionTable(PathEndpointTable): + device = tables.Column( + linkify=True + ) + name = tables.Column( + linkify=True, + verbose_name='Console Port' + ) + reachable = columns.BooleanColumn( + accessor=Accessor('_path__is_active'), + verbose_name='Reachable' + ) + + class Meta(BaseTable.Meta): + model = ConsolePort + fields = ('device', 'name', 'connection', 'reachable') + + +class PowerConnectionTable(PathEndpointTable): + device = tables.Column( + linkify=True + ) + name = tables.Column( + linkify=True, + verbose_name='Power Port' + ) + reachable = columns.BooleanColumn( + accessor=Accessor('_path__is_active'), + verbose_name='Reachable' + ) + + class Meta(BaseTable.Meta): + model = PowerPort + fields = ('device', 'name', 'connection', 'reachable') + + +class InterfaceConnectionTable(PathEndpointTable): + device = tables.Column( + accessor=Accessor('device'), + linkify=True + ) + interface = tables.Column( + accessor=Accessor('name'), + linkify=True + ) + reachable = columns.BooleanColumn( + accessor=Accessor('_path__is_active'), + verbose_name='Reachable' + ) + + class Meta(BaseTable.Meta): + model = Interface + fields = ('device', 'interface', 'connection', 'reachable') diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index d972f3890..4b358a433 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -244,6 +244,9 @@ INTERFACE_BUTTONS = """ {% if perms.ipam.add_l2vpntermination %}
  • L2VPN Termination
  • {% endif %} + {% if perms.ipam.add_fhrpgroupassignment %} +
  • Assign FHRP Group
  • + {% endif %} {% endif %} diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index a78a98ae5..acd52178d 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -461,16 +461,19 @@ class DeviceTypeTest(APIViewTestCases.APIViewTestCase): 'manufacturer': manufacturers[1].pk, 'model': 'Device Type 4', 'slug': 'device-type-4', + 'u_height': 0, }, { 'manufacturer': manufacturers[1].pk, 'model': 'Device Type 5', 'slug': 'device-type-5', + 'u_height': 0.5, }, { 'manufacturer': manufacturers[1].pk, 'model': 'Device Type 6', 'slug': 'device-type-6', + 'u_height': 1, }, ] diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index a31eabc5e..39b2340e1 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2893,7 +2893,7 @@ class CableBulkDeleteView(generic.BulkDeleteView): # class ConsoleConnectionsListView(generic.ObjectListView): - queryset = ConsolePort.objects.filter(_path__isnull=False).order_by('device') + queryset = ConsolePort.objects.filter(_path__is_complete=True) filterset = filtersets.ConsoleConnectionFilterSet filterset_form = forms.ConsoleConnectionFilterForm table = tables.ConsoleConnectionTable @@ -2907,7 +2907,7 @@ class ConsoleConnectionsListView(generic.ObjectListView): class PowerConnectionsListView(generic.ObjectListView): - queryset = PowerPort.objects.filter(_path__isnull=False).order_by('device') + queryset = PowerPort.objects.filter(_path__is_complete=True) filterset = filtersets.PowerConnectionFilterSet filterset_form = forms.PowerConnectionFilterForm table = tables.PowerConnectionTable @@ -2921,7 +2921,7 @@ class PowerConnectionsListView(generic.ObjectListView): class InterfaceConnectionsListView(generic.ObjectListView): - queryset = Interface.objects.filter(_path__isnull=False).order_by('device') + queryset = Interface.objects.filter(_path__is_complete=True) filterset = filtersets.InterfaceConnectionFilterSet filterset_form = forms.InterfaceConnectionFilterForm table = tables.InterfaceConnectionTable diff --git a/netbox/ipam/migrations/0060_alter_l2vpn_slug.py b/netbox/ipam/migrations/0060_alter_l2vpn_slug.py new file mode 100644 index 000000000..9e70c2063 --- /dev/null +++ b/netbox/ipam/migrations/0060_alter_l2vpn_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.7 on 2022-08-22 15:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0059_l2vpn'), + ] + + operations = [ + migrations.AlterField( + model_name='l2vpn', + name='slug', + field=models.SlugField(max_length=100, unique=True), + ), + ] diff --git a/netbox/ipam/models/l2vpn.py b/netbox/ipam/models/l2vpn.py index 9718e9cab..a457f334b 100644 --- a/netbox/ipam/models/l2vpn.py +++ b/netbox/ipam/models/l2vpn.py @@ -21,7 +21,10 @@ class L2VPN(NetBoxModel): max_length=100, unique=True ) - slug = models.SlugField() + slug = models.SlugField( + max_length=100, + unique=True + ) type = models.CharField( max_length=50, choices=L2VPNTypeChoices diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index a086ab66d..185154ffb 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -526,8 +526,7 @@ class PrefixIPAddressesView(generic.ObjectChildrenView): return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group') def prep_table_data(self, request, queryset, parent): - show_available = bool(request.GET.get('show_available', 'true') == 'true') - if show_available: + if not request.GET.get('q'): return add_available_ipaddresses(parent.prefix, queryset, parent.is_pool) return queryset