diff --git a/changes/132.fixed b/changes/132.fixed new file mode 100644 index 0000000..b91815a --- /dev/null +++ b/changes/132.fixed @@ -0,0 +1 @@ +Changed Floor Plans to disable being resized if a tile has been placed to prevent phantom tiles outside of the original size. \ No newline at end of file diff --git a/changes/137.housekeeping b/changes/137.housekeeping new file mode 100644 index 0000000..36cf179 --- /dev/null +++ b/changes/137.housekeeping @@ -0,0 +1 @@ +Fixed spelling errors and formatting on documentation. \ No newline at end of file diff --git a/docs/user/app_getting_started.md b/docs/user/app_getting_started.md index f05bd17..7ddb8cd 100644 --- a/docs/user/app_getting_started.md +++ b/docs/user/app_getting_started.md @@ -40,7 +40,7 @@ After clicking "Create", you will be presented with a new floor plan render: You can click the "+" icon in the corner of any rectangle in the grid to define information about this Tile in the Floor Plan. (If you've defined a large floor plan, or have a small display, you may find it useful to use your mouse wheel to zoom in first. You can also click and drag when zoomed in to pan around the grid.) This will bring you to a simple create/edit form for describing the Tile. -You can assign a Status to each tile, and optionally assign a Rack or RackGroup as well as specifying the orientation of the Rack relative to the Floor Plan. You can also specify the size of a Tile if you want it to cover multiple "spaces" in the Floor Plan - this can be useful to document larger-than-usual Racks, or to mark entire sections of the Floor Plan as "Reserved" or "Unavailable". +You can assign a Status to each tile, and optionally assign a Rack or RackGroup as well as specifying the orientation of the Rack relative to the Floor Plan. You can also specify the size of a Tile if you want it to cover multiple "spaces" in the Floor Plan - this can be useful to document larger-than-usual Racks, or to mark entire sections of the Floor Plan as "Reserved" or "Unavailable". You can place racks within the Status or RackGroup tiles that are covering multiple spaces. When placing a Rack onto a RackGroup tile, the rack must be added to the appropriate RackGroup. RackGroup and Status tiles that cover multiple "spaces" can be increased or reduced in size as long as they don't overlap with other Status or RackGroup tiles. @@ -51,3 +51,5 @@ When a Rack is assigned to a tile, the display of the tile will additionally inc By repeating this process as many times as desired, you can populate the Floor Plan in detail to indicate the Status of each Tile as well as the position, Status, and space usage of your Racks: ![Populated floor plan](../images/floor-plan-populated.png) + +Once tiles have been added to a Floor Plan, the Floor Plan can no longer be resized. This is to prevent the resizing of a Floor Plan that could leave a Tile outside the bounds of the new dimensions. All the tiles would need to be removed or the Floor Plan would need to be deleted and recreated to change the dimensions. diff --git a/docs/user/app_overview.md b/docs/user/app_overview.md index cc33195..819b956 100644 --- a/docs/user/app_overview.md +++ b/docs/user/app_overview.md @@ -19,23 +19,24 @@ This App is primarily developed and maintained by Network to Code, LLC. ## App Capabilities -Included is a non-exhaustive list of capabilites beyond a standard MVC (model view controller) paradigm. +Included is a non-exhaustive list of capabilities beyond a standard MVC (model view controller) paradigm. - Provides visualization of racks on a floor map. - Provides visualization of racks being assigned to a rack group on a floor map. - Provides visualization of tenant and tenant groups for racks on a floor map. - Provides easy navigation from floor map to rack and subsequently device from Rack. - Provides the ability to assign Racks to coordinates / tiles. - - From the Floor Plan UI. - - From the Rack Object UI. - - From the API. + - From the Floor Plan UI + - From the Rack Object UI. + - From the API. - Provides ability to map status to color for many use cases. - - Leveraging this you can depict hot / cold aisle. + - Leveraging this you can depict hot / cold aisle. - Provides the ability to set the direction of the Rack and show up. - Provides the ability to span multiple adjacent tiles by a single rack. - Provides the ability to place racks in a group that spans multiple tiles. - Provides custom layout size in any rectangular shape using X & Y axis. -- Provides the abililty to choose Numbers or Letters for grid labels. +- Provides the ability to resize the Floor Plan until Tiles have been placed. Once a Tile has been placed the Floor Plan cannot be resized until the Tiles have been removed. +- Provides the ability to choose Numbers or Letters for grid labels. - Provides the ability for a user to define a specific number or letter as a starting point for grid labels. - Provides the ability for a user to define a positive or negative integer to allow for the skipping of letters or numbers for grid labels. - Provides the ability to save the generated SVG from a click of a "Save SVG" link. @@ -51,4 +52,4 @@ This App: ### Extras -This App presently auto-defines Nautobot extras/extensibility status features. This app automatically assigns the following default statuses for use with Floor plan Tiles. `Active, Reserved, Decommissioning, Unavailable and Planned`. \ No newline at end of file +This App presently auto-defines Nautobot extras/extensibility status features. This app automatically assigns the following default statuses for use with Floor plan Tiles. `Active, Reserved, Decommissioning, Unavailable and Planned`. diff --git a/nautobot_floor_plan/models.py b/nautobot_floor_plan/models.py index 9ef98aa..22e29f7 100644 --- a/nautobot_floor_plan/models.py +++ b/nautobot_floor_plan/models.py @@ -92,6 +92,11 @@ def get_svg(self, *, user, base_url): """Get SVG representation of this FloorPlan.""" return FloorPlanSVG(floor_plan=self, user=user, base_url=base_url).render() + def clean(self): + """Validate the floor plan dimensions and other constraints.""" + super().clean() + self.validate_no_resizing_with_tiles() + def save(self, *args, **kwargs): """Override save in order to update any existing tiles.""" if self.present_in_database: @@ -128,6 +133,19 @@ def update_tile_origins(self, x_initial, x_updated, y_initial, y_updated): return tiles + def validate_no_resizing_with_tiles(self): + """Prevent resizing the floor plan dimensions if tiles have been placed.""" + if self.tiles.exists(): + # Check for original instance + original = self.__class__.objects.filter(pk=self.pk).first() + if original: + # Don't allow resize if tile is placed + if self.x_size != original.x_size or self.y_size != original.y_size: + raise ValidationError( + "Cannot resize a FloorPlan after tiles have been placed. " + f"FloorPlan must maintain original size: ({original.x_size}, {original.y_size}), " + ) + @extras_features( "custom_fields", diff --git a/nautobot_floor_plan/tests/test_models.py b/nautobot_floor_plan/tests/test_models.py index 311c581..9fa0e67 100644 --- a/nautobot_floor_plan/tests/test_models.py +++ b/nautobot_floor_plan/tests/test_models.py @@ -113,6 +113,32 @@ def test_create_floor_plan_invalid_step(self): location=self.floors[1], x_size=100, y_size=100, x_axis_step=0, y_axis_step=2 ).validated_save() + def test_resize_x_floor_plan_with_tiles(self): + """Test that a FloorPlan cannot be resized after tiles are placed.""" + floor_plan = models.FloorPlan.objects.create( + location=self.floors[0], x_size=3, y_size=3, x_origin_seed=1, y_origin_seed=1 + ) + tile = models.FloorPlanTile(floor_plan=floor_plan, x_origin=1, y_origin=1, status=self.status) + tile.validated_save() + + # Attempt to resize the FloorPlan + floor_plan.x_size = 5 + with self.assertRaises(ValidationError): + floor_plan.validated_save() + + def test_resize_y_floor_plan_with_tiles(self): + """Test that a FloorPlan cannot be resized after tiles are placed.""" + floor_plan = models.FloorPlan.objects.create( + location=self.floors[0], x_size=3, y_size=3, x_origin_seed=1, y_origin_seed=1 + ) + tile = models.FloorPlanTile(floor_plan=floor_plan, x_origin=1, y_origin=1, status=self.status) + tile.validated_save() + + # Attempt to resize the FloorPlan + floor_plan.y_size = 4 + with self.assertRaises(ValidationError): + floor_plan.validated_save() + class TestFloorPlanTile(TestCase): """Test FloorPlanTile model."""