11import json
2+ import logging
23from collections import OrderedDict
34from copy import copy
45
78from django .utils .translation import gettext_lazy as _
89from jsonfield import JSONField
910from netjsonconfig .exceptions import ValidationError as NetjsonconfigValidationError
10- from swapper import get_model_name
11+ from swapper import get_model_name , load_model
1112from taggit .managers import TaggableManager
1213
1314from ...base import ShareableOrgMixinUniqueName
1415from ..settings import DEFAULT_AUTO_CERT
15- from ..tasks import update_template_related_config_status
16+ from ..tasks import (
17+ auto_add_template_to_existing_configs ,
18+ update_template_related_config_status ,
19+ )
1620from .base import BaseConfig
1721
22+ logger = logging .getLogger (__name__ )
23+
1824TYPE_CHOICES = (("generic" , _ ("Generic" )), ("vpn" , _ ("VPN-client" )))
1925
2026
@@ -61,7 +67,8 @@ class AbstractTemplate(ShareableOrgMixinUniqueName, BaseConfig):
6167 default = False ,
6268 db_index = True ,
6369 help_text = _ (
64- "whether new configurations will have this template enabled by default"
70+ "whether this template is applied to all current and future devices"
71+ " by default (can be unassigned manually)"
6572 ),
6673 )
6774 required = models .BooleanField (
@@ -124,14 +131,31 @@ def pre_save_handler(cls, instance, *args, **kwargs):
124131 # the old configuration may become invalid, raising an exception.
125132 # Setting the checksum to None forces related configurations to update.
126133 current_checksum = None
127- instance ._update_related_config_status = instance .checksum != current_checksum
134+ instance ._should_update_related_config_status = (
135+ instance .checksum != current_checksum
136+ )
137+
138+ # Check if template is becoming default or required
139+ if (instance .default and not current .default ) or (
140+ instance .required and not current .required
141+ ):
142+ instance ._should_add_to_existing_configs = True
128143
129144 @classmethod
130145 def post_save_handler (cls , instance , created , * args , ** kwargs ):
131- if not created and getattr (instance , "_update_related_config_status" , False ):
146+ if not created and getattr (
147+ instance , "_should_update_related_config_status" , False
148+ ):
132149 transaction .on_commit (
133150 lambda : update_template_related_config_status .delay (instance .pk )
134151 )
152+ # Auto-add template to existing configs if it's new or became default/required
153+ if getattr (instance , "_should_add_to_existing_configs" , False ) or (
154+ created and (instance .default or instance .required )
155+ ):
156+ transaction .on_commit (
157+ lambda : auto_add_template_to_existing_configs .delay (str (instance .pk ))
158+ )
135159
136160 def _update_related_config_status (self ):
137161 # use atomic to ensure any code bound to
@@ -153,6 +177,44 @@ def _update_related_config_status(self):
153177 if config .pk in changing_status :
154178 config ._send_config_status_changed_signal ()
155179
180+ def _auto_add_to_existing_configs (self ):
181+ """
182+ When a template is ``default`` or ``required``, adds the template
183+ to each relevant ``Config`` object
184+ """
185+ Config = load_model ("config" , "Config" )
186+
187+ # Only proceed if template is default or required
188+ if not (self .default or self .required ):
189+ return
190+
191+ # use atomic to ensure any code bound to
192+ # be executed via transaction.on_commit
193+ # is executed after the whole block
194+ with transaction .atomic ():
195+ # Exclude deactivating or deactivated configs
196+ configs = (
197+ Config .objects .select_related ("device" )
198+ .filter (
199+ backend = self .backend ,
200+ )
201+ .exclude (
202+ models .Q (status__in = ["deactivating" , "deactivated" ])
203+ | models .Q (templates__id = self .pk )
204+ )
205+ )
206+ if self .organization_id :
207+ configs = configs .filter (device__organization_id = self .organization_id )
208+ for config in configs .iterator ():
209+ try :
210+ config .templates .add (self )
211+ except Exception as e :
212+ # Log error but continue with other configs
213+ logger .exception (
214+ f"Failed to add template { self .pk } to "
215+ f"config { config .pk } : { e } "
216+ )
217+
156218 def clean (self , * args , ** kwargs ):
157219 """
158220 * validates org relationship of VPN if present
0 commit comments