Skip to content

Commit

Permalink
Djhoward12 naaps 131 form grid conversion (#134)
Browse files Browse the repository at this point in the history
* This is a work in progress. It is working for steps of -1 and -2

* changing label functions to start at ZZ on a wrap around

* updating svg for wrapping

* Modified _label_text to use proper number of arguments.

* Updated label_text function to correctly wrap

* adding .

* Fixed Y Axis label bleed over with small seed and large steps. Refactored svg.py to reduce redundant code.

* Update changes/131.fixed

Co-authored-by: Joe Wesch <10467633+joewesch@users.noreply.github.com>

* Update changes/136.fixed

Co-authored-by: Joe Wesch <10467633+joewesch@users.noreply.github.com>

* Resolving comment on forms line 200 and svg line 91.

* updated bool var nam in utils for letters

---------

Co-authored-by: “DJ <“dj.howard@networktocode.com>
Co-authored-by: Joe Wesch <10467633+joewesch@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 3, 2024
1 parent 146f0dd commit 626f9c1
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 83 deletions.
1 change: 1 addition & 0 deletions changes/131.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed wrap-around for letters going negative from A to ZZZ and updated display of labels in form.
1 change: 1 addition & 0 deletions changes/134.housekeeping
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactored svg.py to reduce redundant code and local variables.
1 change: 1 addition & 0 deletions changes/136.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed grid label spacing on Y-Axis by checking the length of all labels to determine correct offset.
60 changes: 46 additions & 14 deletions nautobot_floor_plan/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def _clean_origin_seed(self, field_name, axis):
return 0
return utils.grid_letter_to_number(value)

if not str(value).isdigit():
if not str(value).replace("-", "").isnumeric():
self.add_error(field_name, f"{axis} origin start should use numbers.")
return 0
return int(value)
Expand Down Expand Up @@ -196,10 +196,25 @@ def __init__(self, *args, **kwargs):
self.y_letters = fp_obj.y_axis_labels == choices.AxisLabelsChoices.LETTERS

if self.instance.x_origin or self.instance.y_origin:
if self.x_letters:
self.initial["x_origin"] = utils.grid_number_to_letter(self.instance.x_origin)
if self.y_letters:
self.initial["y_origin"] = utils.grid_number_to_letter(self.instance.y_origin)
self.initial["x_origin"] = utils.axis_init_label_conversion(
fp_obj.x_origin_seed,
utils.grid_number_to_letter(self.instance.x_origin) if self.x_letters else self.initial.get("x_origin"),
fp_obj.x_axis_step,
self.x_letters,
)
self.initial["y_origin"] = utils.axis_init_label_conversion(
fp_obj.y_origin_seed,
utils.grid_number_to_letter(self.instance.y_origin) if self.y_letters else self.initial.get("y_origin"),
fp_obj.y_axis_step,
self.y_letters,
)
elif self.initial.get("x_origin") and self.initial.get("y_origin"):
self.initial["x_origin"] = utils.axis_init_label_conversion(
fp_obj.x_origin_seed, self.initial.get("x_origin"), fp_obj.x_axis_step, self.x_letters
)
self.initial["y_origin"] = utils.axis_init_label_conversion(
fp_obj.y_origin_seed, self.initial.get("y_origin"), fp_obj.y_axis_step, self.y_letters
)

def letter_validator(self, field, value, axis):
"""Validate that origin uses combination of letters."""
Expand All @@ -209,22 +224,39 @@ def letter_validator(self, field, value, axis):
return True

def number_validator(self, field, value, axis):
"""Validate that origin uses combination of numbers."""
if not str(value).isdigit():
"""Validate that origin uses combination of positive or negative numbers."""
if not str(value).replace("-", "").isnumeric():
self.add_error(field, f"{axis} origin should use numbers.")
return False
return True

def _clean_origin(self, field_name, axis):
"""Common clean method for origin fields."""
# Retrieve floor plan object if available
fp_id = self.initial.get("floor_plan") or self.data.get("floor_plan")
if not fp_id:
return 0

fp_obj = self.fields["floor_plan"].queryset.get(id=fp_id)
value = self.cleaned_data.get(field_name)
if self.x_letters and field_name == "x_origin" or self.y_letters and field_name == "y_origin":
if self.letter_validator(field_name, value, axis) is not True:
return 0 # required to pass model clean() method
return utils.grid_letter_to_number(value)
if self.number_validator(field_name, value, axis) is not True:
return 0 # required to pass model clean() method
return int(value)

# Determine if letters are being used for x or y axis labels
using_letters = (field_name == "x_origin" and self.x_letters) or (field_name == "y_origin" and self.y_letters)

# Perform validation based on the type (letters or numbers)
validator = self.letter_validator if using_letters else self.number_validator
if not validator(field_name, value, axis):
return 0 # Required to pass model clean() method

# Select the appropriate axis seed and step
if field_name == "x_origin":
origin_seed, step, use_letters = fp_obj.x_origin_seed, fp_obj.x_axis_step, self.x_letters
else:
origin_seed, step, use_letters = fp_obj.y_origin_seed, fp_obj.y_axis_step, self.y_letters

# Convert and return the label position using the specified conversion function
cleaned_value = utils.axis_clean_label_conversion(origin_seed, value, step, use_letters)
return int(cleaned_value) if not using_letters else cleaned_value

def clean_x_origin(self):
"""Validate input and convert x_origin to an integer."""
Expand Down
140 changes: 71 additions & 69 deletions nautobot_floor_plan/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,77 @@ def _setup_drawing(self, width, depth):

return drawing

def _label_text(self, label_text_out, step, seed, label_text_in):
def _label_text(self, label_text_out, floor_plan, label_text_in, is_letters):
"""Change label based off defined increment or decrement step."""
if label_text_out == seed:
if label_text_out == floor_plan["seed"]:
return label_text_out
label_text_out = label_text_in + step
label_text_out = label_text_in + floor_plan["step"]

# Handle negative values and wrapping
if is_letters and label_text_out <= 0:
label_text_out = 18278 if label_text_in == 0 else 18278 + (label_text_in + floor_plan["step"])

return label_text_out

def _draw_tile_link(self, drawing, axis, x_letters, y_letters):
query_params = urlencode(
{
"floor_plan": self.floor_plan.pk,
"x_origin": grid_number_to_letter(axis["x"]) if x_letters else axis["x"],
"y_origin": grid_number_to_letter(axis["y"]) if y_letters else axis["y"],
"return_url": self.return_url,
}
)
add_url = f"{self.add_url}?{query_params}"
add_link = drawing.add(drawing.a(href=add_url, target="_top"))

add_link.add(
drawing.rect(
(
(axis["x"] - self.floor_plan.x_origin_seed + 0.5) * self.GRID_SIZE_X
+ self.GRID_OFFSET
- (self.TEXT_LINE_HEIGHT / 2),
(axis["y"] - self.floor_plan.y_origin_seed + 0.5) * self.GRID_SIZE_Y
+ self.GRID_OFFSET
- (self.TEXT_LINE_HEIGHT / 2),
),
(self.TEXT_LINE_HEIGHT, self.TEXT_LINE_HEIGHT),
class_="add-tile-button",
rx=self.CORNER_RADIUS,
)
)
add_link.add(
drawing.text(
"+",
insert=(
(axis["x"] - self.floor_plan.x_origin_seed + 0.5) * self.GRID_SIZE_X + self.GRID_OFFSET,
(axis["y"] - self.floor_plan.y_origin_seed + 0.5) * self.GRID_SIZE_Y + self.GRID_OFFSET,
),
class_="button-text",
)
)

def _draw_grid(self, drawing):
"""Render the grid underlying all tiles."""
# Set inital values for x and y axis label location
x_letters = self.floor_plan.x_axis_labels == AxisLabelsChoices.LETTERS
y_letters = self.floor_plan.y_axis_labels == AxisLabelsChoices.LETTERS
x_floor_plan = {"seed": self.floor_plan.x_origin_seed, "step": self.floor_plan.x_axis_step}
y_floor_plan = {"seed": self.floor_plan.y_origin_seed, "step": self.floor_plan.y_axis_step}
# Initial states for labels
x_label_text = 0
y_label_text = 0
# Setting intial value for y axis label text to 0
y_label_text_offset = 0
# Vertical lines
max_y_length = max(
len(str(self._label_text(y, y_floor_plan, 0, y_letters)))
for y in range(self.floor_plan.y_origin_seed, self.floor_plan.y_size + self.floor_plan.y_origin_seed)
)
y_label_text_offset = (
self.Y_LABEL_TEXT_OFFSET - (6 - len(str(self.floor_plan.y_origin_seed))) if max_y_length > 1 else 0
)
if max_y_length >= 4:
y_label_text_offset = self.Y_LABEL_TEXT_OFFSET + 4

# Draw grid lines
for x in range(0, self.floor_plan.x_size + 1):
drawing.add(
drawing.line(
Expand All @@ -114,7 +170,6 @@ def _draw_grid(self, drawing):
class_="grid",
)
)
# Horizontal lines
for y in range(0, self.floor_plan.y_size + 1):
drawing.add(
drawing.line(
Expand All @@ -126,16 +181,11 @@ def _draw_grid(self, drawing):
class_="grid",
)
)
# Axis labels

# Draw axis labels and links
for x in range(self.floor_plan.x_origin_seed, self.floor_plan.x_size + self.floor_plan.x_origin_seed):
x_label_text = self._label_text(x, self.floor_plan.x_axis_step, self.floor_plan.x_origin_seed, x_label_text)
if self.floor_plan.x_axis_labels == AxisLabelsChoices.LETTERS and x_label_text == 0:
x_label_text = 26
label = (
grid_number_to_letter(x_label_text)
if self.floor_plan.x_axis_labels == AxisLabelsChoices.LETTERS
else str(x_label_text)
)
x_label_text = self._label_text(x, x_floor_plan, x_label_text, x_letters)
label = grid_number_to_letter(x_label_text) if x_letters else str(x_label_text)
drawing.add(
drawing.text(
label,
Expand All @@ -146,20 +196,10 @@ def _draw_grid(self, drawing):
class_="grid-label",
)
)

for y in range(self.floor_plan.y_origin_seed, self.floor_plan.y_size + self.floor_plan.y_origin_seed):
y_label_text = self._label_text(y, self.floor_plan.y_axis_step, self.floor_plan.y_origin_seed, y_label_text)
if self.floor_plan.y_axis_labels == AxisLabelsChoices.LETTERS and y_label_text == 0:
y_label_text = 26
label = (
grid_number_to_letter(y_label_text)
if self.floor_plan.y_axis_labels == AxisLabelsChoices.LETTERS
else str(y_label_text)
)
# Adjust the starting position of the y_axis_label text if the length of the inital SEED value is greater than 1
if len(str(self.floor_plan.y_origin_seed)) > 1:
y_label_text_offset = self.Y_LABEL_TEXT_OFFSET - (6 - len(str(self.floor_plan.y_origin_seed)))
if len(str(self.floor_plan.y_origin_seed)) > 4:
y_label_text_offset = self.Y_LABEL_TEXT_OFFSET + 4
y_label_text = self._label_text(y, y_floor_plan, y_label_text, y_letters)
label = grid_number_to_letter(y_label_text) if y_letters else str(y_label_text)
drawing.add(
drawing.text(
label,
Expand All @@ -171,48 +211,10 @@ def _draw_grid(self, drawing):
)
)

# Links to populate tiles
y_letters = self.floor_plan.y_axis_labels == AxisLabelsChoices.LETTERS
x_letters = self.floor_plan.x_axis_labels == AxisLabelsChoices.LETTERS
for y in range(self.floor_plan.y_origin_seed, self.floor_plan.y_size + self.floor_plan.y_origin_seed):
for x in range(self.floor_plan.x_origin_seed, self.floor_plan.x_size + self.floor_plan.x_origin_seed):
query_params = urlencode(
{
"floor_plan": self.floor_plan.pk,
"x_origin": grid_number_to_letter(x) if x_letters else x,
"y_origin": grid_number_to_letter(y) if y_letters else y,
"return_url": self.return_url,
}
)
add_url = f"{self.add_url}?{query_params}"
add_link = drawing.add(drawing.a(href=add_url, target="_top"))
# "add" button
add_link.add(
drawing.rect(
(
(x - self.floor_plan.x_origin_seed + 0.5) * self.GRID_SIZE_X
+ self.GRID_OFFSET
- (self.TEXT_LINE_HEIGHT / 2),
(y - self.floor_plan.y_origin_seed + 0.5) * self.GRID_SIZE_Y
+ self.GRID_OFFSET
- (self.TEXT_LINE_HEIGHT / 2),
),
(self.TEXT_LINE_HEIGHT, self.TEXT_LINE_HEIGHT),
class_="add-tile-button",
rx=self.CORNER_RADIUS,
)
)
# "+" inside the add button
add_link.add(
drawing.text(
"+",
insert=(
(x - self.floor_plan.x_origin_seed + 0.5) * self.GRID_SIZE_X + self.GRID_OFFSET,
(y - self.floor_plan.y_origin_seed + 0.5) * self.GRID_SIZE_Y + self.GRID_OFFSET,
),
class_="button-text",
)
)
axis = {"x": x, "y": y}
self._draw_tile_link(drawing, axis, x_letters, y_letters)

def _draw_edit_delete_button(self, drawing, tile, button_offset, grid_offset):
if tile.allocation_type == AllocationTypeChoices.RACK:
Expand Down
50 changes: 50 additions & 0 deletions nautobot_floor_plan/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,56 @@ def grid_letter_to_number(letter):
return number


def axis_init_label_conversion(axis_origin, axis_location, step, is_letters):
"""Returns the correct label position, converting to letters if `letters` is True."""
if is_letters:
axis_location = grid_letter_to_number(axis_location)
# Calculate the converted location based on origin, step, and location
converted_location = axis_origin + (int(axis_location) - int(axis_origin)) * step
# Check if we need wrap around due to letters being chosen
if is_letters:
# Set wrap around value of ZZZ
total_cells = 18278
# Adjust for wrap-around when working with letters A or ZZZ
if converted_location < 1:
converted_location = total_cells + converted_location
elif converted_location > total_cells:
converted_location -= total_cells
result_label = grid_number_to_letter(converted_location)
return result_label
return converted_location


def axis_clean_label_conversion(axis_origin, axis_label, step, is_letters):
"""Returns the correct database label position."""
total_cells = 18278
# Convert letters to numbers if needed
if is_letters:
axis_label = grid_letter_to_number(axis_label)

# Reverse the init conversion logic to determine the numeric position
position_difference = int(axis_label) - int(axis_origin)

if step < 0:
# Adjust for wrap-around when working with letters A or ZZZ
if int(axis_label) > int(axis_origin):
position_difference -= total_cells
else:
if int(axis_label) < int(axis_origin):
position_difference += total_cells

# Calculate the original location using the step
original_location = axis_origin + (position_difference // step)

# Ensure original location stays within bounds for letters
if is_letters:
if original_location < 1:
original_location += total_cells
elif original_location > total_cells:
original_location -= total_cells
return str(original_location)


def validate_not_zero(value):
"""Prevent the usage of 0 as a value in the step form field or model attribute."""
if value == 0:
Expand Down

0 comments on commit 626f9c1

Please sign in to comment.