nautobot 3.0.0rc1__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (103) hide show
  1. nautobot/apps/forms.py +8 -0
  2. nautobot/apps/templatetags.py +231 -0
  3. nautobot/apps/testing.py +11 -1
  4. nautobot/apps/ui.py +21 -1
  5. nautobot/apps/utils.py +26 -1
  6. nautobot/core/celery/__init__.py +46 -1
  7. nautobot/core/cli/bootstrap_v3_to_v5.py +185 -44
  8. nautobot/core/cli/bootstrap_v3_to_v5_changes.yaml +314 -0
  9. nautobot/core/graphql/generators.py +2 -2
  10. nautobot/core/jobs/bulk_actions.py +12 -6
  11. nautobot/core/jobs/cleanup.py +13 -1
  12. nautobot/core/settings.py +13 -0
  13. nautobot/core/settings.yaml +22 -0
  14. nautobot/core/settings_funcs.py +11 -1
  15. nautobot/core/tables.py +19 -1
  16. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  17. nautobot/core/templates/components/panel/header_extra_content_table.html +9 -3
  18. nautobot/core/templates/generic/object_create.html +1 -1
  19. nautobot/core/templates/inc/header.html +9 -10
  20. nautobot/core/templates/login.html +16 -1
  21. nautobot/core/templates/nautobot_config.py.j2 +14 -1
  22. nautobot/core/templates/redoc_ui.html +3 -0
  23. nautobot/core/templatetags/helpers.py +3 -3
  24. nautobot/core/testing/views.py +3 -1
  25. nautobot/core/tests/test_graphql.py +13 -0
  26. nautobot/core/tests/test_jobs.py +118 -0
  27. nautobot/core/tests/test_views.py +24 -0
  28. nautobot/core/ui/bulk_buttons.py +2 -3
  29. nautobot/core/utils/lookup.py +2 -3
  30. nautobot/core/utils/permissions.py +1 -1
  31. nautobot/core/views/generic.py +1 -0
  32. nautobot/core/views/mixins.py +37 -10
  33. nautobot/core/views/renderers.py +1 -0
  34. nautobot/core/views/utils.py +3 -3
  35. nautobot/data_validation/views.py +1 -9
  36. nautobot/dcim/forms.py +9 -9
  37. nautobot/dcim/models/devices.py +3 -3
  38. nautobot/dcim/tables/power.py +3 -0
  39. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +1 -1
  40. nautobot/dcim/views.py +30 -44
  41. nautobot/extras/api/views.py +14 -3
  42. nautobot/extras/choices.py +3 -0
  43. nautobot/extras/jobs.py +48 -2
  44. nautobot/extras/migrations/0132_approval_workflow_seed_data.py +127 -0
  45. nautobot/extras/models/approvals.py +11 -1
  46. nautobot/extras/models/models.py +19 -0
  47. nautobot/extras/models/relationships.py +3 -1
  48. nautobot/extras/tables.py +35 -18
  49. nautobot/extras/templates/extras/approval_workflow/approve.html +9 -2
  50. nautobot/extras/templates/extras/approval_workflow/deny.html +9 -3
  51. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +1 -1
  52. nautobot/extras/templates/extras/customfield_update.html +1 -1
  53. nautobot/extras/templates/extras/dynamicgroup_update.html +2 -2
  54. nautobot/extras/templates/extras/inc/approval_buttons_column.html +10 -2
  55. nautobot/extras/templates/extras/inc/job_tiles.html +2 -2
  56. nautobot/extras/templates/extras/inc/jobresult.html +1 -1
  57. nautobot/extras/templates/extras/metadatatype_create.html +1 -1
  58. nautobot/extras/templates/extras/object_approvalworkflow.html +2 -3
  59. nautobot/extras/templates/extras/secretsgroup_update.html +1 -1
  60. nautobot/extras/tests/test_api.py +57 -3
  61. nautobot/extras/tests/test_customfields_filters.py +84 -4
  62. nautobot/extras/tests/test_views.py +323 -6
  63. nautobot/extras/views.py +114 -39
  64. nautobot/ipam/constants.py +2 -2
  65. nautobot/ipam/tables.py +7 -6
  66. nautobot/load_balancers/constants.py +6 -0
  67. nautobot/load_balancers/migrations/0001_initial.py +14 -3
  68. nautobot/load_balancers/models.py +5 -4
  69. nautobot/load_balancers/tables.py +5 -0
  70. nautobot/project-static/dist/css/nautobot.css +1 -1
  71. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  72. nautobot/project-static/dist/js/graphql-libraries.js +1 -1
  73. nautobot/project-static/dist/js/graphql-libraries.js.map +1 -1
  74. nautobot/project-static/dist/js/libraries.js +1 -1
  75. nautobot/project-static/dist/js/libraries.js.LICENSE.txt +38 -2
  76. nautobot/project-static/dist/js/libraries.js.map +1 -1
  77. nautobot/project-static/dist/js/nautobot-graphiql.js +1 -1
  78. nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -1
  79. nautobot/project-static/dist/js/nautobot.js +1 -1
  80. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  81. nautobot/project-static/img/dark-theme.png +0 -0
  82. nautobot/project-static/img/light-theme.png +0 -0
  83. nautobot/project-static/img/system-theme.png +0 -0
  84. nautobot/project-static/js/forms.js +1 -85
  85. nautobot/tenancy/tables.py +3 -2
  86. nautobot/tenancy/views.py +3 -2
  87. nautobot/ui/package-lock.json +553 -569
  88. nautobot/ui/package.json +10 -10
  89. nautobot/ui/src/js/checkbox.js +132 -0
  90. nautobot/ui/src/js/nautobot.js +6 -0
  91. nautobot/ui/src/js/select2.js +69 -73
  92. nautobot/ui/src/js/theme.js +129 -39
  93. nautobot/ui/src/scss/nautobot.scss +11 -1
  94. nautobot/vpn/templates/vpn/vpnprofile_create.html +2 -2
  95. nautobot/wireless/filters.py +15 -1
  96. nautobot/wireless/tables.py +18 -14
  97. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +1 -1
  98. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/METADATA +2 -2
  99. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/RECORD +103 -98
  100. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/LICENSE.txt +0 -0
  101. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/NOTICE +0 -0
  102. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/WHEEL +0 -0
  103. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,8 @@ import logging
3
3
  import os
4
4
  import re
5
5
 
6
+ import yaml
7
+
6
8
  from .migrate_deprecated_templates import replace_deprecated_templates
7
9
 
8
10
  logger = logging.getLogger(__name__)
@@ -547,6 +549,97 @@ def _fix_breadcrumbs_block(html_string: str, stats: dict) -> str:
547
549
  return block_pattern.sub(process_match, html_string)
548
550
 
549
551
 
552
+ # --- Grid Breakpoints Resize Function ---
553
+
554
+
555
+ def _resize_grid_breakpoints(html_string: str, class_combinations: list[str], stats: dict, file_path: str) -> str:
556
+ """
557
+ Resizes grid breakpoints in `col-*` and `offset-*` classes one step up. Uses given `class_combinations` for known
558
+ class pattern replacements and otherwise does generic xs → sm and md → lg breakpoint resize. In case class list
559
+ contains grid breakpoints other than xs and md, flags it for manual review.
560
+ """
561
+ # Define the breakpoint mapping
562
+ breakpoint_map = {"xs": "sm", "sm": "md", "md": "lg", "lg": "xl", "xl": "xxl"}
563
+ breakpoint_map_keys = list(breakpoint_map.keys())
564
+
565
+ if "manual_grid_template_lines" not in stats:
566
+ stats["manual_grid_template_lines"] = []
567
+
568
+ def create_grid_class_regex(breakpoints=breakpoint_map_keys): # pylint: disable=dangerous-default-value
569
+ # Create regex matching Bootstrap grid classes, i.e. `col-*` and `offset-*`, within given breakpoints.
570
+ return re.compile(rf"\b(col|offset)-({'|'.join(breakpoints)})([a-zA-Z0-9-]*)")
571
+
572
+ # Resize all given grid `breakpoints` in `string` according to defined `breakpoint_map`
573
+ def resize_breakpoints(string, breakpoints=breakpoint_map_keys, count_stats=False): # pylint: disable=dangerous-default-value
574
+ def regex_repl(match):
575
+ new_breakpoint = breakpoint_map[match.group(2)]
576
+ if count_stats:
577
+ stats["grid_breakpoints"] += 1
578
+ return f"{match.group(1)}-{new_breakpoint}{match.group(3)}"
579
+
580
+ # Replace with regex, e.g., col-xs-12 → col-sm-12
581
+ regex = create_grid_class_regex(breakpoints)
582
+ return regex.sub(regex_repl, string)
583
+
584
+ # Resize given `class_combinations` and create an additional joint array from the two. This is required to determine
585
+ # whether a known class combination is present in certain element class list and handle one of the following cases:
586
+ # 1. No, but identified grid breakpoints other than xs and md: flag for manual review.
587
+ # 2. No, and only xs and md grid breakpoints found: generic xs → sm and md → lg replacement.
588
+ # 3. Yes, but has not been resized yet: resize with proper combination.
589
+ # 4. Yes, and has already been resized: do nothing.
590
+ resized_class_combinations = [resize_breakpoints(class_combination) for class_combination in class_combinations]
591
+ known_class_combinations = [*class_combinations, *resized_class_combinations]
592
+
593
+ def grid_breakpoints_replacer(match):
594
+ classes = match.group(1)
595
+ # Remove Django template tag blocks, variables and comments and split individual classes into separate strings.
596
+ raw_classes = re.compile(r"{{?((?!{|}).)*}}?").sub(" ", classes).split()
597
+ # Filter out all non-grid classes, keep only `col-*` and `offset-*`.
598
+ grid_class_regex = create_grid_class_regex()
599
+ grid_classes = [cls for cls in raw_classes if grid_class_regex.search(cls)]
600
+
601
+ # Check whether given class list consists of any of the known class combinations.
602
+ known_class_combination = None
603
+ for class_combination in known_class_combinations:
604
+ # Look for an exact match, when all classes from given combination are included in element classes and vice versa.
605
+ if all(cls in classes for cls in class_combination.split()) and all(
606
+ grid_class in class_combination for grid_class in grid_classes
607
+ ):
608
+ known_class_combination = class_combination
609
+ break
610
+
611
+ if known_class_combination is None:
612
+ # Class combination has not been found.
613
+ if any("xs" not in grid_class and "md" not in grid_class for grid_class in grid_classes):
614
+ # Class list contains grid breakpoints other than xs and md, require manual review.
615
+ linenum = match.string.count("\n", 0, match.start()) + 1
616
+ stats["manual_grid_template_lines"].append(
617
+ f"{file_path}:{linenum} - Please review manually '{match.group(0)}'"
618
+ )
619
+ else:
620
+ # Class list contains only xs and md grid breakpoints, do generic xs → sm and md → lg replacement
621
+ return f'class="{resize_breakpoints(classes, breakpoints=["xs", "md"], count_stats=True)}"'
622
+
623
+ elif known_class_combination not in resized_class_combinations:
624
+ # Class combination has been found, but has not been resized yet: resize with proper combination.
625
+ resized_classes = resized_class_combinations[class_combinations.index(known_class_combination)].split()
626
+
627
+ def class_replacer(m):
628
+ current_class = m.group(0)
629
+ stats["grid_breakpoints"] += 1
630
+ return resized_classes[known_class_combination.split().index(current_class)]
631
+
632
+ # Replace all classes from given combination by mapping them individually to their resized equivalents.
633
+ return f'class="{re.compile("|".join(known_class_combination.split())).sub(class_replacer, classes)}"'
634
+
635
+ # Return unchanged string if conditions above are not satisfied, i.e. do nothing.
636
+ return match.group(0)
637
+
638
+ # Find all `class="..."` matches and execute grid breakpoint replacement on them.
639
+ pattern = re.compile(r'class="([^"]*)"')
640
+ return pattern.sub(grid_breakpoints_replacer, html_string)
641
+
642
+
550
643
  # --- Main Conversion Function ---
551
644
 
552
645
 
@@ -564,7 +657,9 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
564
657
  "nav_items": 0,
565
658
  "dropdown_items": 0,
566
659
  "panel_classes": 0,
660
+ "grid_breakpoints": 0,
567
661
  "manual_nav_template_lines": [],
662
+ "manual_grid_template_lines": [],
568
663
  }
569
664
 
570
665
  # --- Stage 1: Apply rules that work directly on the HTML string (simple string/regex replacements) ---
@@ -628,6 +723,26 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
628
723
  "data-backdrop": "data-bs-backdrop", # Bootstrap 5 uses data-bs-* attributes
629
724
  }
630
725
 
726
+ standard_grid_breakpoint_combinations = [
727
+ "col-md-6 col-sm-6 col-xs-12", # Rack elevation container: nautobot/dcim/templates/dcim/rack_elevation.html:2
728
+ "col-sm-3 col-md-2 col-md-offset-1", # Legacy user page nav pills container: nautobot/users/templates/users/base.html:10
729
+ "col-sm-3 col-md-2 offset-md-1",
730
+ "col-sm-4 col-sm-offset-4", # Selected centered panel containers, e.g. on 404 and 500 error pages and legacy login page: nautobot/core/templates/login.html:54
731
+ "col-sm-4 offset-sm-4",
732
+ "col-sm-4 col-md-3", # Legacy header search form container: nautobot/core/templates/generic/object_list.html:32
733
+ "col-sm-8 col-md-9 col-sm-12 col-md-12", # Legacy breadcrumbs container variation on change list page: nautobot/core/templates/admin/change_list.html:33
734
+ "col-sm-8 col-md-9 col-md-12", # Legacy breadcrumbs container variation on generic object list view page: nautobot/core/templates/generic/object_list.html:13
735
+ "col-sm-8 col-md-9", # Legacy breadcrumbs container: nautobot/core/templates/generic/object_retrieve.html:16
736
+ "col-sm-9 col-md-8", # Legacy user page content container: nautobot/users/templates/users/base.html:31
737
+ "col-md-5 col-sm-12", # Cable trace form left-hand side container: nautobot/dcim/templates/dcim/cable_trace.html:10
738
+ "col-md-7 col-sm-12", # Cable trace form right-hand side container: nautobot/dcim/templates/dcim/cable_trace.html:86
739
+ "col-lg-6 col-md-6", # Jinja template/rendered template panel containers: nautobot/core/templates/utilities/render_jinja2.html:29
740
+ "col-md-4 col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1", # Standard centered form container variation on generic object bulk update page: nautobot/core/templates/generic/object_bulk_update.html:39
741
+ "col-md-4 col-lg-8 col-lg-offset-2 col-md-10 offset-md-1",
742
+ "col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1", # Standard centered form container, e.g. on generic object create page: nautobot/core/templates/generic/object_create.html:12
743
+ "col-lg-8 offset-lg-2 col-md-10 offset-md-1",
744
+ ]
745
+
631
746
  current_html = _replace_attributes(current_html, attribute_replacements, stats)
632
747
  current_html = _replace_classes(current_html, class_replacements, stats, file_path=file_path)
633
748
  current_html = _fix_extra_breadcrumbs_block(current_html, stats)
@@ -642,6 +757,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
642
757
  current_html = _convert_hover_copy_buttons(current_html, stats)
643
758
  current_html = _fix_nav_tabs_items(current_html, stats, file_path=file_path)
644
759
  current_html = _fix_dropdown_items(current_html, stats, file_path=file_path)
760
+ current_html = _resize_grid_breakpoints(current_html, standard_grid_breakpoint_combinations, stats, file_path)
645
761
 
646
762
  return current_html, stats
647
763
 
@@ -649,11 +765,10 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
649
765
  # --- File Processing ---
650
766
 
651
767
 
652
- def fix_html_files_in_directory(directory: str, resize=False, dry_run=False, skip_templates=False) -> None:
768
+ def fix_html_files_in_directory(directory: str, dry_run=False, skip_templates=False) -> None:
653
769
  """
654
770
  Recursively finds all .html files in the given directory, applies convert_bootstrap_classes,
655
- and overwrites each file with the fixed content. If resize is True, it will only change the
656
- breakpoints (This should only be done once.).
771
+ and overwrites each file with the fixed content.
657
772
  """
658
773
 
659
774
  totals = {
@@ -665,11 +780,9 @@ def fix_html_files_in_directory(directory: str, resize=False, dry_run=False, ski
665
780
  "nav_items",
666
781
  "dropdown_items",
667
782
  "panel_classes",
668
- "resizing_xs",
783
+ "grid_breakpoints",
669
784
  ]
670
785
  }
671
- # Breakpoints that are not xs do not count as failures in djlint, so we keep a separate counter
672
- resizing_other = 0
673
786
 
674
787
  if not os.path.exists(directory):
675
788
  raise FileNotFoundError(directory)
@@ -680,9 +793,6 @@ def fix_html_files_in_directory(directory: str, resize=False, dry_run=False, ski
680
793
  else:
681
794
  only_filename = None
682
795
 
683
- # Define the breakpoint mapping
684
- breakpoint_map = {"xs": "sm", "sm": "md", "md": "lg", "lg": "xl", "xl": "xxl"}
685
-
686
796
  for root, _, files in os.walk(directory):
687
797
  for filename in files:
688
798
  if only_filename and only_filename != filename:
@@ -695,29 +805,6 @@ def fix_html_files_in_directory(directory: str, resize=False, dry_run=False, ski
695
805
 
696
806
  content = original_content
697
807
 
698
- if resize:
699
- # If resize is True, we only change the breakpoints
700
- # This is a one-time operation to adjust the breakpoints.
701
- logger.info("Resizing Breakpoints: %s", file_path)
702
-
703
- resizing_other = 0
704
-
705
- # Iterate from the highest breakpoint to the lowest
706
- for bkpt in ["xl", "lg", "md", "sm", "xs"]:
707
- # Replace with regex, e.g., col-xs-12 → col-sm-12
708
- regex = re.compile(rf"(\bcol-{bkpt})([a-zA-Z0-9-]*)")
709
-
710
- def regex_repl(m, captured_bkpt=bkpt):
711
- nonlocal resizing_other
712
- if captured_bkpt == "xs":
713
- totals["resizing_xs"] += 1
714
- else:
715
- resizing_other += 1
716
- new_bkpt = breakpoint_map[captured_bkpt]
717
- return f"col-{new_bkpt}{m.group(2)}"
718
-
719
- content = regex.sub(regex_repl, content)
720
-
721
808
  fixed_content, stats = convert_bootstrap_classes(content, file_path=file_path)
722
809
 
723
810
  if dry_run:
@@ -741,12 +828,18 @@ def fix_html_files_in_directory(directory: str, resize=False, dry_run=False, ski
741
828
  print(f"{stats['dropdown_items']} dropdown-items, ", end="")
742
829
  if stats["panel_classes"]:
743
830
  print(f"{stats['panel_classes']} panel replacements, ", end="")
831
+ if stats["grid_breakpoints"]:
832
+ print(f"{stats['grid_breakpoints']} grid breakpoint replacements, ", end="")
744
833
  print()
745
834
 
746
835
  if stats.get("manual_nav_template_lines"):
747
836
  print(" !!! Manual review needed for nav-item fixes at:")
748
837
  for line in stats["manual_nav_template_lines"]:
749
838
  print(f" - {line}")
839
+ if stats.get("manual_grid_template_lines"):
840
+ print(" !!! Manual review needed for non-standard grid breakpoints at:")
841
+ for line in stats["manual_grid_template_lines"]:
842
+ print(f" - {line}")
750
843
  for k, v in stats.items():
751
844
  if k in totals:
752
845
  totals[k] += v
@@ -763,21 +856,63 @@ def fix_html_files_in_directory(directory: str, resize=False, dry_run=False, ski
763
856
  print(f"- <li> in <ul.nav-tabs>: {totals['nav_items']}")
764
857
  print(f"- <a> in <ul.dropdown-menu>: {totals['dropdown_items']}")
765
858
  print(f"- Panel class replacements: {totals['panel_classes']}")
766
- print(f"- Resizing breakpoint xs: {totals['resizing_xs']}")
767
- print("-------------------------------------")
768
- print(f"- Resizing other breakpoints: {resizing_other}")
859
+ print(f"- Grid breakpoint resizes: {totals['grid_breakpoints']}")
769
860
  print("-------------------------------------")
770
861
  print(f"- Deprecated templates replaced: {templates_replaced}")
771
862
 
772
863
 
864
+ def check_python_files_for_legacy_html(directory: str):
865
+ exclude_dirs = [
866
+ "__pycache__",
867
+ "node_modules",
868
+ ]
869
+ exclude_files = [
870
+ "bootstrap_v3_to_v5.py",
871
+ ]
872
+
873
+ with open(os.path.join(os.path.dirname(__file__), "bootstrap_v3_to_v5_changes.yaml")) as yaml_file:
874
+ try:
875
+ bootstrap_v3_to_v5_changes = yaml.safe_load(yaml_file)
876
+ except yaml.YAMLError:
877
+ print("`bootstrap_v3_to_v5_changes.yaml` file is corrupted.")
878
+ return 1
879
+
880
+ def has_multiline_pattern(change):
881
+ return "(?s)" in change["Search Regex"][1:-2]
882
+
883
+ multiline_pattern_changes = [change for change in bootstrap_v3_to_v5_changes if has_multiline_pattern(change)]
884
+ standard_pattern_changes = [change for change in bootstrap_v3_to_v5_changes if not has_multiline_pattern(change)]
885
+
886
+ matches = 0
887
+ for dirpath, dirnames, filenames in os.walk(directory):
888
+ for filename in filenames:
889
+ if filename in exclude_files or not filename.endswith(".py"):
890
+ continue
891
+ with open(os.path.join(dirpath, filename), "rt") as fh:
892
+ contents = fh.readlines()
893
+ full_contents = "".join(contents)
894
+
895
+ for linenum, line in enumerate(contents, start=1):
896
+ for change in standard_pattern_changes:
897
+ if re.search(change["Search Regex"][1:-2], line):
898
+ print(f"{os.path.join(dirpath, filename)}({linenum}):\t{line}\t:\t{change['Bootstrap v5']}")
899
+ matches += 1
900
+ for change in multiline_pattern_changes:
901
+ multiline_matches = re.finditer(change["Search Regex"][1:-2], full_contents)
902
+ for multiline_match in multiline_matches:
903
+ linenum = multiline_match.string.count("\n", 0, multiline_match.start()) + 1
904
+ substring = multiline_match.string[multiline_match.start() : multiline_match.end()]
905
+ print(f"{os.path.join(dirpath, filename)}({linenum}):\t{substring}\n\t:\t{change['Bootstrap v5']}")
906
+ matches += 1
907
+ for exclude_dir in exclude_dirs:
908
+ if exclude_dir in dirnames:
909
+ dirnames.remove(exclude_dir)
910
+
911
+ return matches
912
+
913
+
773
914
  def main():
774
915
  parser = argparse.ArgumentParser(description="Bootstrap 3 to 5 HTML fixer.")
775
- parser.add_argument(
776
- "-r",
777
- "--resize",
778
- action="store_true",
779
- help="Change column breakpoints to be one level higher, such as 'col-xs-*' to 'col-sm-*'",
780
- )
781
916
  parser.add_argument(
782
917
  "-d",
783
918
  "--dry-run",
@@ -788,11 +923,17 @@ def main():
788
923
  parser.add_argument(
789
924
  "-st", "--skip-template-replacement", action="store_true", help="Skip replacing deprecated templates."
790
925
  )
926
+ parser.add_argument("-p", "--check-python-files", action="store_true", help="Check Python files for legacy HTML.")
927
+ parser.add_argument("--no-fix-html-templates", action="store_true", help="Do not fix HTML template files.")
791
928
  args = parser.parse_args()
792
929
 
793
- fix_html_files_in_directory(
794
- args.path, resize=args.resize, dry_run=args.dry_run, skip_templates=args.skip_template_replacement
795
- )
930
+ exit_code = 0
931
+ if args.check_python_files:
932
+ exit_code = check_python_files_for_legacy_html(args.path)
933
+ if not args.no_fix_html_templates:
934
+ fix_html_files_in_directory(args.path, dry_run=args.dry_run, skip_templates=args.skip_template_replacement)
935
+
936
+ return exit_code
796
937
 
797
938
 
798
939
  if __name__ == "__main__":
@@ -0,0 +1,314 @@
1
+ - "Bootstrap v3": btn-default
2
+ "Bootstrap v5": btn-secondary
3
+ "Search Regex": |
4
+ `("|')([^"']*)(?<=\s|"|')btn-default(?=\s|"|')([^"']*)("|')`
5
+
6
+ - "Bootstrap v3": btn-lg
7
+ "Bootstrap v5": btn
8
+ "Search Regex": |
9
+ `("|')([^"']*)(?<=\s|"|')btn-lg(?=\s|"|')([^"']*)("|')`
10
+
11
+ - "Bootstrap v3": btn-xs
12
+ "Bootstrap v5": btn-sm
13
+ "Search Regex": |
14
+ `("|')([^"']*)(?<=\s|"|')btn-xs(?=\s|"|')([^"']*)("|')`
15
+
16
+ - "Bootstrap v3": caret
17
+ "Bootstrap v5": mdi mdi-chevron-down
18
+ "Search Regex": |
19
+ `("|')([^"']*)(?<=\s|"|')caret(?=\s|"|')([^"']*)("|')`
20
+
21
+ - "Bootstrap v3": center-block
22
+ "Bootstrap v5": d-block mx-auto
23
+ "Search Regex": |
24
+ `("|')([^"']*)(?<=\s|"|')center-block(?=\s|"|')([^"']*)("|')`
25
+
26
+ - "Bootstrap v3": checkbox
27
+ "Bootstrap v5": form-check
28
+ "Search Regex": |
29
+ `(?<!type=)("|')([^"']*)(?<=\s|"|')checkbox(?=\s|"|')([^"']*)("|')`
30
+
31
+ - "Bootstrap v3": checkbox-inline
32
+ "Bootstrap v5": form-check-input
33
+ "Search Regex": |
34
+ `("|')([^"']*)(?<=\s|"|')checkbox-inline(?=\s|"|')([^"']*)("|')`
35
+
36
+ - "Bootstrap v3": close
37
+ "Bootstrap v5": btn-close
38
+ "Search Regex": |
39
+ `(?<=class=)("|')([^"']*)(?<=\s|"|')close(?=\s|"|')([^"']*)("|')`
40
+
41
+ - "Bootstrap v3": col-*-offset-*
42
+ "Bootstrap v5": offset-*-*
43
+ "Search Regex": |
44
+ `("|')([^"']*)(?<=\s|"|')col-(xs|sm|md|lg|x?xl)-offset-\d{1,2}(?=\s|"|')([^"']*)("|')`
45
+
46
+ - "Bootstrap v3": control-label
47
+ "Bootstrap v5": col-form-label
48
+ "Search Regex": |
49
+ `("|')([^"']*)(?<=\s|"|')control-label(?=\s|"|')([^"']*)("|')`
50
+
51
+ - "Bootstrap v3": dropdown-menu-left
52
+ "Bootstrap v5": dropdown-menu-start
53
+ "Search Regex": |
54
+ `("|')([^"']*)(?<=\s|"|')dropdown-menu-left(?=\s|"|')([^"']*)("|')`
55
+
56
+ - "Bootstrap v3": dropdown-menu-right
57
+ "Bootstrap v5": dropdown-menu-end
58
+ "Search Regex": |
59
+ `("|')([^"']*)(?<=\s|"|')dropdown-menu-right(?=\s|"|')([^"']*)("|')`
60
+
61
+ - "Bootstrap v3": form-control-static
62
+ "Bootstrap v5": form-control-plaintext
63
+ "Search Regex": |
64
+ `("|')([^"']*)(?<=\s|"|')form-control-static(?=\s|"|')([^"']*)("|')`
65
+
66
+ - "Bootstrap v3": form-group
67
+ "Bootstrap v5": mb-10 d-flex justify-content-center
68
+ "Search Regex": |
69
+ `("|')([^"']*)(?<=\s|"|')form-group(?=\s|"|')([^"']*)("|')`
70
+
71
+ - "Bootstrap v3": help-block
72
+ "Bootstrap v5": form-text
73
+ "Search Regex": |
74
+ `("|')([^"']*)(?<=\s|"|')help-block(?=\s|"|')([^"']*)("|')`
75
+
76
+ - "Bootstrap v3": hidden
77
+ "Bootstrap v5": d-none
78
+ "Search Regex": |
79
+ `(?<!type=)("|')([^"']*)(?<=\s|"|')hidden(?=\s|"|')([^"']*)("|')`
80
+
81
+ - "Bootstrap v3": label
82
+ "Bootstrap v5": badge
83
+ "Search Regex": |
84
+ `(?<=class=)("|')([^"']*)(?<=\s|"|')label(?=\s|"|')([^"']*)("|')`
85
+
86
+ - "Bootstrap v3": label-default
87
+ "Bootstrap v5": bg-default
88
+ "Search Regex": |
89
+ `("|')([^"']*)(?<=\s|"|')label-default(?=\s|"|')([^"']*)("|')`
90
+
91
+ - "Bootstrap v3": label-danger
92
+ "Bootstrap v5": bg-danger
93
+ "Search Regex": |
94
+ `("|')([^"']*)(?<=\s|"|')label-danger(?=\s|"|')([^"']*)("|')`
95
+
96
+ - "Bootstrap v3": label-info
97
+ "Bootstrap v5": bg-info
98
+ "Search Regex": |
99
+ `("|')([^"']*)(?<=\s|"|')label-info(?=\s|"|')([^"']*)("|')`
100
+
101
+ - "Bootstrap v3": label-primary
102
+ "Bootstrap v5": bg-primary
103
+ "Search Regex": |
104
+ `("|')([^"']*)(?<=\s|"|')label-primary(?=\s|"|')([^"']*)("|')`
105
+
106
+ - "Bootstrap v3": label-success
107
+ "Bootstrap v5": bg-success
108
+ "Search Regex": |
109
+ `("|')([^"']*)(?<=\s|"|')label-success(?=\s|"|')([^"']*)("|')`
110
+
111
+ - "Bootstrap v3": label-transparent
112
+ "Bootstrap v5": bg-transparent
113
+ "Search Regex": |
114
+ `("|')([^"']*)(?<=\s|"|')label-transparent(?=\s|"|')([^"']*)("|')`
115
+
116
+ - "Bootstrap v3": label-warning
117
+ "Bootstrap v5": bg-warning
118
+ "Search Regex": |
119
+ `("|')([^"']*)(?<=\s|"|')label-warning(?=\s|"|')([^"']*)("|')`
120
+
121
+ - "Bootstrap v3": noprint
122
+ "Bootstrap v5": d-print-none
123
+ "Search Regex": |
124
+ `("|')([^"']*)(?<=\s|"|')noprint(?=\s|"|')([^"']*)("|')`
125
+
126
+ - "Bootstrap v3": panel
127
+ "Bootstrap v5": card
128
+ "Search Regex": |
129
+ `("|')([^"']*)(?<=\s|"|')panel(?=\s|"|')([^"']*)("|')`
130
+
131
+ - "Bootstrap v3": panel-body
132
+ "Bootstrap v5": card-body
133
+ "Search Regex": |
134
+ `("|')([^"']*)(?<=\s|"|')panel-body(?=\s|"|')([^"']*)("|')`
135
+
136
+ - "Bootstrap v3": panel-footer
137
+ "Bootstrap v5": card-footer
138
+ "Search Regex": |
139
+ `("|')([^"']*)(?<=\s|"|')panel-footer(?=\s|"|')([^"']*)("|')`
140
+
141
+ - "Bootstrap v3": panel-heading
142
+ "Bootstrap v5": card-header
143
+ "Search Regex": |
144
+ `("|')([^"']*)(?<=\s|"|')panel-heading(?=\s|"|')([^"']*)("|')`
145
+
146
+ - "Bootstrap v3": pull-left
147
+ "Bootstrap v5": float-start
148
+ "Search Regex": |
149
+ `("|')([^"']*)(?<=\s|"|')pull-left(?=\s|"|')([^"']*)("|')`
150
+
151
+ - "Bootstrap v3": pull-right
152
+ "Bootstrap v5": float-end
153
+ "Search Regex": |
154
+ `("|')([^"']*)(?<=\s|"|')pull-right(?=\s|"|')([^"']*)("|')`
155
+
156
+ - "Bootstrap v3": sr-only
157
+ "Bootstrap v5": visually-hidden
158
+ "Search Regex": |
159
+ `("|')([^"']*)(?<=\s|"|')sr-only(?=\s|"|')([^"']*)("|')`
160
+
161
+ - "Bootstrap v3": sr-only-focusable
162
+ "Bootstrap v5": visually-hidden-focusable
163
+ "Search Regex": |
164
+ `("|')([^"']*)(?<=\s|"|')sr-only-focusable(?=\s|"|')([^"']*)("|')`
165
+
166
+ - "Bootstrap v3": text-left
167
+ "Bootstrap v5": text-start
168
+ "Search Regex": |
169
+ `("|')([^"']*)(?<=\s|"|')text-left(?=\s|"|')([^"']*)("|')`
170
+
171
+ - "Bootstrap v3": text-muted
172
+ "Bootstrap v5": text-secondary
173
+ "Search Regex": |
174
+ `("|')([^"']*)(?<=\s|"|')text-muted(?=\s|"|')([^"']*)("|')`
175
+
176
+ - "Bootstrap v3": text-right
177
+ "Bootstrap v5": text-end
178
+ "Search Regex": |
179
+ `("|')([^"']*)(?<=\s|"|')text-right(?=\s|"|')([^"']*)("|')`
180
+
181
+ - "Bootstrap v3": accordion-toggle
182
+ "Bootstrap v5": nb-collapse-toggle
183
+ "Search Regex": |
184
+ `("|')([^"']*)(?<=\s|"|')accordion-toggle(?=\s|"|')([^"']*)("|')`
185
+
186
+ - "Bootstrap v3": accordion-toggle-all
187
+ "Bootstrap v5": data-nb-toggle
188
+ "Search Regex": |
189
+ `("|')([^"']*)(?<=\s|"|')accordion-toggle-all(?=\s|"|')([^"']*)("|')`
190
+
191
+ - "Bootstrap v3": banner-bottom
192
+ "Bootstrap v5": nb-banner-bottom
193
+ "Search Regex": |
194
+ `("|')([^"']*)(?<=\s|"|')banner-bottom(?=\s|"|')([^"']*)("|')`
195
+
196
+ - "Bootstrap v3": btn-inline
197
+ "Bootstrap v5": nb-btn-inline-hover
198
+ "Search Regex": |
199
+ `("|')([^"']*)(?<=\s|"|')btn-inline(?=\s|"|')([^"']*)("|')`
200
+
201
+ - "Bootstrap v3": color-block
202
+ "Bootstrap v5": nb-color-block
203
+ "Search Regex": |
204
+ `("|')([^"']*)(?<=\s|"|')color-block(?=\s|"|')([^"']*)("|')`
205
+
206
+ - "Bootstrap v3": description
207
+ "Bootstrap v5": nb-description
208
+ "Search Regex": |
209
+ `(?<=class=)("|')([^"']*)(?<=\s|"|')description(?=\s|"|')([^"']*)("|')`
210
+
211
+ - "Bootstrap v3": editor-container
212
+ "Bootstrap v5": nb-editor-container
213
+ "Search Regex": |
214
+ `("|')([^"']*)(?<=\s|"|')editor-container(?=\s|"|')([^"']*)("|')`
215
+
216
+ - "Bootstrap v3": loading
217
+ "Bootstrap v5": nb-loading
218
+ "Search Regex": |
219
+ `("|')([^"']*)(?<=\s|"|')loading(?=\s|"|')([^"']*)("|')`
220
+
221
+ - "Bootstrap v3": report-stats
222
+ "Bootstrap v5": nb-report-stats
223
+ "Search Regex": |
224
+ `("|')([^"']*)(?<=\s|"|')report-stats(?=\s|"|')([^"']*)("|')`
225
+
226
+ - "Bootstrap v3": required
227
+ "Bootstrap v5": nb-required
228
+ "Search Regex": |
229
+ `("|')([^"']*)(?<=\s|"|')required(?=\s|"|')([^"']*)("|')`
230
+
231
+ - "Bootstrap v3": right-side-panel
232
+ "Bootstrap v5": nb-right-side-panel
233
+ "Search Regex": |
234
+ `("|')([^"']*)(?<=\s|"|')right-side-panel(?=\s|"|')([^"']*)("|')`
235
+
236
+ - "Bootstrap v3": software-image-hierarchy
237
+ "Bootstrap v5": nb-software-image-hierarchy
238
+ "Search Regex": |
239
+ `("|')([^"']*)(?<=\s|"|')software-image-hierarchy(?=\s|"|')([^"']*)("|')`
240
+
241
+ - "Bootstrap v3": style-line
242
+ "Bootstrap v5": nb-style-line
243
+ "Search Regex": |
244
+ `("|')([^"']*)(?<=\s|"|')style-line(?=\s|"|')([^"']*)("|')`
245
+
246
+ - "Bootstrap v3": table-headings
247
+ "Bootstrap v5": nb-table-headings
248
+ "Search Regex": |
249
+ `("|')([^"']*)(?<=\s|"|')table-headings(?=\s|"|')([^"']*)("|')`
250
+
251
+ - "Bootstrap v3": tile
252
+ "Bootstrap v5": nb-tile
253
+ "Search Regex": |
254
+ `("|')([^"']*)(?<=\s|"|')tile(?=\s|"|')([^"']*)("|')`
255
+
256
+ - "Bootstrap v3": tile-description
257
+ "Bootstrap v5": nb-tile-description
258
+ "Search Regex": |
259
+ `("|')([^"']*)(?<=\s|"|')tile-description(?=\s|"|')([^"']*)("|')`
260
+
261
+ - "Bootstrap v3": tile-footer
262
+ "Bootstrap v5": nb-tile-footer
263
+ "Search Regex": |
264
+ `("|')([^"']*)(?<=\s|"|')tile-footer(?=\s|"|')([^"']*)("|')`
265
+
266
+ - "Bootstrap v3": tile-header
267
+ "Bootstrap v5": nb-tile-header
268
+ "Search Regex": |
269
+ `("|')([^"']*)(?<=\s|"|')tile-header(?=\s|"|')([^"']*)("|')`
270
+
271
+ - "Bootstrap v3": tiles
272
+ "Bootstrap v5": nb-tiles
273
+ "Search Regex": |
274
+ `("|')([^"']*)(?<=\s|"|')tiles(?=\s|"|')([^"']*)("|')`
275
+
276
+ - "Bootstrap v3": tree-hierarchy
277
+ "Bootstrap v5": nb-tree-hierarchy
278
+ "Search Regex": |
279
+ `("|')([^"']*)(?<=\s|"|')tree-hierarchy(?=\s|"|')([^"']*)("|')`
280
+
281
+ - "Bootstrap v3": filter-container
282
+ "Bootstrap v5": removed
283
+ "Search Regex": |
284
+ `("|')([^"']*)(?<=\s|"|')filter-container(?=\s|"|')([^"']*)("|')`
285
+
286
+ - "Bootstrap v3": data-backdrop
287
+ "Bootstrap v5": data-bs-backdrop
288
+ "Search Regex": |
289
+ `data-backdrop`
290
+
291
+ - "Bootstrap v3": data-dismiss
292
+ "Bootstrap v5": data-bs-dismiss
293
+ "Search Regex": |
294
+ `data-dismiss`
295
+
296
+ - "Bootstrap v3": data-target
297
+ "Bootstrap v5": data-bs-target
298
+ "Search Regex": |
299
+ `data-target`
300
+
301
+ - "Bootstrap v3": data-title
302
+ "Bootstrap v5": data-bs-title
303
+ "Search Regex": |
304
+ `data-title`
305
+
306
+ - "Bootstrap v3": data-toggle
307
+ "Bootstrap v5": data-bs-toggle
308
+ "Search Regex": |
309
+ `data-toggle`
310
+
311
+ - "Bootstrap v3": Breadcrumbs, Dropdowns, Tabs
312
+ "Bootstrap v5": breadcrumb-item, dropdown-item, nav-item, nav-link
313
+ "Search Regex": |
314
+ `(?s)<li((?!<li|</li>|breadcrumb-item).)*(?=<a|<button)((?!<li|</li>|breadcrumb-item|dropdown-item|nav-item|nav-link).)*</li>`
@@ -3,7 +3,6 @@
3
3
  import logging
4
4
 
5
5
  import graphene
6
- from graphene_django.fields import DjangoListField
7
6
  import graphene_django_optimizer as gql_optimizer
8
7
  from graphql import GraphQLError
9
8
 
@@ -375,7 +374,8 @@ def generate_attrs_for_schema_type(schema_type):
375
374
  # Define Attributes for single item and list with their search parameters
376
375
  search_params = generate_list_search_parameters(schema_type)
377
376
  attrs[single_item_name] = graphene.Field(schema_type, id=graphene.ID())
378
- attrs[list_name] = DjangoListField(schema_type, **search_params)
377
+ # TODO: refactor to use DjangoListField instead of graphene.List (https://github.com/nautobot/nautobot/issues/4690)
378
+ attrs[list_name] = graphene.List(schema_type, **search_params)
379
379
 
380
380
  # Define Resolvers for both single item and list
381
381
  single_item_resolver_name = f"{RESOLVER_PREFIX}{single_item_name}"