@@ -1704,6 +1704,9 @@ def mktemplate(request, fid):
17041704 )
17051705 template .save ()
17061706 template .tags = finding .tags .all ()
1707+ # Ensure template tags exist in Finding's tag model
1708+ # (They should already exist since they come from a finding, but ensure for consistency)
1709+ ensure_template_tags_in_finding_model (template )
17071710
17081711 for vulnerability_id in finding .vulnerability_ids :
17091712 Vulnerability_Id_Template (
@@ -1782,12 +1785,52 @@ def find_template_to_apply(request, fid):
17821785def choose_finding_template_options (request , tid , fid ):
17831786 finding = get_object_or_404 (Finding , id = fid )
17841787 template = get_object_or_404 (Finding_Template , id = tid )
1785- data = finding .__dict__
1786- # Not sure what's going on here, just leave same as with django-tagging
1787- data ["tags" ] = [tag .name for tag in template .tags .all ()]
1788+ data = finding .__dict__ .copy ()
1789+ # Remove tags and other non-serializable fields
1790+ data .pop ("tags" , None )
1791+ data .pop ("_state" , None )
1792+ data .pop ("_tags_tagulous" , None )
1793+ template_tag_names = [tag .name for tag in template .tags .all ()]
1794+ # Add tags as comma-separated string for TagField
1795+ if template_tag_names :
1796+ data ["tags" ] = ", " .join (template_tag_names )
17881797 data ["vulnerability_ids" ] = "\n " .join (finding .vulnerability_ids )
17891798
17901799 form = ApplyFindingTemplateForm (data = data , template = template )
1800+ # Combine tags from both Finding_Template and Finding tag models
1801+ # This ensures we don't lose tags that exist on templates but may have been removed from findings
1802+ if "tags" in form .fields :
1803+ finding_tag_model = Finding .tags .tag_model
1804+ template_tag_model = Finding_Template .tags .tag_model
1805+
1806+ # Get all tags from Finding_Template model
1807+ template_tags = set (template_tag_model .objects .values_list ("name" , flat = True ))
1808+ # Get all tags from Finding model
1809+ finding_tags = set (finding_tag_model .objects .values_list ("name" , flat = True ))
1810+ # Combine both sets to get all unique tag names
1811+ all_tag_names = template_tags | finding_tags
1812+
1813+ # Ensure all tags from both models exist in Finding's tag model (where they'll be applied)
1814+ # Strictly speaking, creating tags here isn't necessary since TagField can create them on save,
1815+ # but it's the safest option to avoid tags getting lost or not getting rendered properly.
1816+ # This prevents tagulous from removing tags that only exist on templates and ensures
1817+ # TagField can display them correctly during form rendering.
1818+ # Store tag objects in a dict for reuse
1819+ tag_objects = {}
1820+ for tag_name in all_tag_names :
1821+ tag , _ = finding_tag_model .objects .get_or_create (
1822+ name = tag_name ,
1823+ defaults = {"name" : tag_name , "protected" : False },
1824+ )
1825+ tag_objects [tag_name ] = tag
1826+
1827+ # Update autocomplete_tags to include tags from both models
1828+ form .fields ["tags" ].autocomplete_tags = finding_tag_model .objects .all ().order_by ("name" )
1829+
1830+ # Set initial value using template tags (already created above)
1831+ if template_tag_names :
1832+ template_finding_tags = [tag_objects [tag_name ] for tag_name in template_tag_names ]
1833+ form .fields ["tags" ].initial = template_finding_tags
17911834 product_tab = Product_Tab (
17921835 finding .test .engagement .product ,
17931836 title = "Finding Template Options" ,
@@ -2109,6 +2152,28 @@ def export_templates_to_json(request):
21092152 return HttpResponse (leads_as_json , content_type = "json" )
21102153
21112154
2155+ def ensure_template_tags_in_finding_model (template ):
2156+ """
2157+ Ensure all tags on a Finding_Template also exist in Finding's tag model.
2158+ This prevents tags from being lost when tagulous cleans up unused tags and ensures
2159+ tags can be properly applied when templates are used.
2160+ """
2161+ if not template or not template .pk :
2162+ return
2163+
2164+ finding_tag_model = Finding .tags .tag_model
2165+
2166+ # Get all tag names from the template
2167+ template_tag_names = [tag .name for tag in template .tags .all ()]
2168+
2169+ # Ensure each tag exists in Finding's tag model
2170+ for tag_name in template_tag_names :
2171+ finding_tag_model .objects .get_or_create (
2172+ name = tag_name ,
2173+ defaults = {"name" : tag_name , "protected" : False },
2174+ )
2175+
2176+
21122177def apply_cwe_mitigation (apply_to_findings , template , * , update = True ):
21132178 count = 0
21142179 if apply_to_findings and template .template_match and template .cwe is not None :
@@ -2191,6 +2256,8 @@ def add_template(request):
21912256 template , form .cleaned_data ["vulnerability_ids" ].split (),
21922257 )
21932258 form .save_m2m ()
2259+ # Ensure template tags exist in Finding's tag model
2260+ ensure_template_tags_in_finding_model (template )
21942261 count = apply_cwe_mitigation (
21952262 form .cleaned_data ["apply_to_findings" ], template ,
21962263 )
@@ -2238,6 +2305,8 @@ def edit_template(request, tid):
22382305 )
22392306 template .save ()
22402307 form .save_m2m ()
2308+ # Ensure template tags exist in Finding's tag model
2309+ ensure_template_tags_in_finding_model (template )
22412310
22422311 count = apply_cwe_mitigation (
22432312 form .cleaned_data ["apply_to_findings" ], template ,
0 commit comments