1
- from copy import deepcopy
2
-
3
1
from django .core .exceptions import ValidationError
4
2
from django .db import transaction
5
3
from django .db .models import Q
6
4
from django .utils .translation import gettext_lazy as _
7
5
from rest_framework import serializers
8
6
from swapper import load_model
9
7
10
- from openwisp_users .api .mixins import FilterSerializerByOrgManaged
11
8
from openwisp_utils .api .serializers import ValidatedModelSerializer
12
9
10
+ from ...serializers import BaseSerializer
13
11
from .. import settings as app_settings
14
12
15
13
Template = load_model ('config' , 'Template' )
@@ -24,10 +22,6 @@ class BaseMeta:
24
22
read_only_fields = ['created' , 'modified' ]
25
23
26
24
27
- class BaseSerializer (FilterSerializerByOrgManaged , ValidatedModelSerializer ):
28
- pass
29
-
30
-
31
25
class TemplateSerializer (BaseSerializer ):
32
26
config = serializers .JSONField (initial = {}, required = False )
33
27
tags = serializers .StringRelatedField (many = True , read_only = True )
@@ -110,7 +104,12 @@ def get_queryset(self):
110
104
return queryset
111
105
112
106
113
- class BaseConfigSerializer (serializers .ModelSerializer ):
107
+ class BaseConfigSerializer (ValidatedModelSerializer ):
108
+ # The device object is excluded from validation
109
+ # because this serializer focuses on validating
110
+ # config objects.
111
+ exclude_validation = ['device' ]
112
+
114
113
class Meta :
115
114
model = Config
116
115
fields = ['status' , 'error_reason' , 'backend' , 'templates' , 'context' , 'config' ]
@@ -119,8 +118,26 @@ class Meta:
119
118
'error_reason' : {'read_only' : True },
120
119
}
121
120
121
+ def validate (self , data ):
122
+ """
123
+ The validation here is a bit tricky:
124
+
125
+ For existing devices, we have to perform the
126
+ model validation of the existing config object,
127
+ because if we simulate the validation on a new
128
+ config object pointing to an existing device,
129
+ the validation will fail because a config object
130
+ for this device already exists (due to one-to-one relationship).
131
+ """
132
+ device = self .context .get ('device' )
133
+ if not self .instance and device :
134
+ # Existing device with existing config
135
+ if device ._has_config ():
136
+ self .instance = device .config
137
+ return super ().validate (data )
138
+
122
139
123
- class DeviceConfigMixin ( object ):
140
+ class DeviceConfigSerializer ( BaseSerializer ):
124
141
def _get_config_templates (self , config_data ):
125
142
return [template .pk for template in config_data .pop ('templates' , [])]
126
143
@@ -131,11 +148,29 @@ def _prepare_config(self, device, config_data):
131
148
config .full_clean ()
132
149
return config
133
150
151
+ def _is_config_data_relevant (self , config_data ):
152
+ """
153
+ Returns True if ``config_data`` does not equal
154
+ the default values and hence the config is useful.
155
+ """
156
+ return not (
157
+ config_data .get ('backend' ) == app_settings .DEFAULT_BACKEND
158
+ and not config_data .get ('templates' )
159
+ and not config_data .get ('context' )
160
+ and not config_data .get ('config' )
161
+ )
162
+
134
163
@transaction .atomic
135
164
def _create_config (self , device , config_data ):
136
165
config_templates = self ._get_config_templates (config_data )
137
166
try :
138
167
if not device ._has_config ():
168
+ # if the user hasn't set any useful config data, skip
169
+ if (
170
+ not self ._is_config_data_relevant (config_data )
171
+ and not config_templates
172
+ ):
173
+ return
139
174
config = Config (device = device , ** config_data )
140
175
config .full_clean ()
141
176
config .save ()
@@ -151,15 +186,10 @@ def _create_config(self, device, config_data):
151
186
raise serializers .ValidationError ({'config' : error .messages })
152
187
153
188
def _update_config (self , device , config_data ):
154
- if (
155
- config_data .get ('backend' ) == app_settings .DEFAULT_BACKEND
156
- and not config_data .get ('templates' )
157
- and not config_data .get ('context' )
158
- and not config_data .get ('config' )
159
- ):
160
- # Do not create Config object if config_data only
161
- # contains the default value.
162
- # See https://github.com/openwisp/openwisp-controller/issues/699
189
+ # Do not create Config object if config_data only
190
+ # contains the default values.
191
+ # See https://github.com/openwisp/openwisp-controller/issues/699
192
+ if not self ._is_config_data_relevant (config_data ):
163
193
return
164
194
if not device ._has_config ():
165
195
return self ._create_config (device , config_data )
@@ -190,9 +220,7 @@ class DeviceListConfigSerializer(BaseConfigSerializer):
190
220
templates = FilterTemplatesByOrganization (many = True , write_only = True )
191
221
192
222
193
- class DeviceListSerializer (
194
- DeviceConfigMixin , FilterSerializerByOrgManaged , serializers .ModelSerializer
195
- ):
223
+ class DeviceListSerializer (DeviceConfigSerializer ):
196
224
config = DeviceListConfigSerializer (required = False )
197
225
198
226
class Meta (BaseMeta ):
@@ -219,14 +247,13 @@ class Meta(BaseMeta):
219
247
'management_ip' : {'allow_blank' : True },
220
248
}
221
249
222
- def validate (self , attrs ):
223
- device_data = deepcopy (attrs )
250
+ def validate (self , data ):
224
251
# Validation of "config" is performed after
225
252
# device object is created in the "create" method.
226
- device_data .pop ('config' , None )
227
- device = self . instance or self . Meta . model ( ** device_data )
228
- device . full_clean ()
229
- return attrs
253
+ config_data = data .pop ('config' , None )
254
+ data = super (). validate ( data )
255
+ data [ 'config' ] = config_data
256
+ return data
230
257
231
258
def create (self , validated_data ):
232
259
config_data = validated_data .pop ('config' , None )
@@ -247,7 +274,7 @@ class DeviceDetailConfigSerializer(BaseConfigSerializer):
247
274
templates = FilterTemplatesByOrganization (many = True )
248
275
249
276
250
- class DeviceDetailSerializer (DeviceConfigMixin , BaseSerializer ):
277
+ class DeviceDetailSerializer (DeviceConfigSerializer ):
251
278
config = DeviceDetailConfigSerializer (allow_null = True )
252
279
is_deactivated = serializers .BooleanField (read_only = True )
253
280
@@ -280,7 +307,7 @@ def update(self, instance, validated_data):
280
307
if config_data :
281
308
self ._update_config (instance , config_data )
282
309
283
- elif hasattr ( instance , 'config' ) and validated_data .get ('organization' ):
310
+ elif instance . _has_config ( ) and validated_data .get ('organization' ):
284
311
if instance .organization != validated_data .get ('organization' ):
285
312
# config.device.organization is used for validating
286
313
# the organization of templates. It is also used for adding
0 commit comments