class ComposeTreeSerializer(StrictSerializerMixin, DynamicFieldsSerializerMixin, serializers.ModelSerializer): compose = serializers.SlugRelatedField(slug_field='compose_id', queryset=Compose.objects.all()) variant = ComposeTreeVariantField() arch = serializers.SlugRelatedField(slug_field='name', queryset=Arch.objects.all()) location = ChoiceSlugField(slug_field='short', queryset=Location.objects.all()) scheme = ChoiceSlugField(slug_field='name', queryset=Scheme.objects.all()) url = serializers.CharField() synced_content = ChoiceSlugField(slug_field='name', many=True, queryset=ContentCategory.objects.all()) class Meta: model = ComposeTree fields = ('compose', 'variant', 'arch', 'location', 'scheme', 'url', 'synced_content') def validate(self, attrs): super(ComposeTreeSerializer, self).validate(attrs) compose = attrs.get('compose', None) variant = attrs.get('variant', None) arch = attrs.get('arch', None) if compose == variant.compose and arch in variant.arches: return attrs elif compose == variant.compose and arch not in variant.arches: raise serializers.ValidationError('Arch %s does not exist in given compose/variant branch' % arch) else: raise serializers.ValidationError('The combination with compose %s, variant %s, arch %s does not exist' % (compose, variant, arch))
class ReleaseGroupSerializer(StrictSerializerMixin, serializers.ModelSerializer): description = serializers.CharField(required=True) type = ChoiceSlugField(slug_field='name', queryset=ReleaseGroupType.objects.all()) releases = ChoiceSlugField(slug_field='release_id', many=True, queryset=Release.objects.all(), allow_null=True, required=False, default=[]) active = serializers.BooleanField(default=True) class Meta: model = ReleaseGroup fields = ('name', 'description', 'type', 'releases', 'active') def to_internal_value(self, data): releases = data.get('releases', []) for release in releases: try: Release.objects.get(release_id=release) except Release.DoesNotExist: raise serializers.ValidationError( {'detail': 'release %s does not exist' % release}) return super(ReleaseGroupSerializer, self).to_internal_value(data)
class RepoSerializer(StrictSerializerMixin, serializers.ModelSerializer): release_id = serializers.CharField(source='variant_arch.variant.release.release_id') variant_uid = serializers.CharField(source='variant_arch.variant.variant_uid') arch = serializers.CharField(source='variant_arch.arch.name') service = ChoiceSlugField(slug_field='name', queryset=models.Service.objects.all()) repo_family = ChoiceSlugField(slug_field='name', queryset=models.RepoFamily.objects.all()) content_format = ChoiceSlugField(slug_field='name', queryset=models.ContentFormat.objects.all()) content_category = ChoiceSlugField(slug_field='name', queryset=models.ContentCategory.objects.all()) name = serializers.CharField() shadow = serializers.BooleanField(required=False) class Meta: model = models.Repo fields = ('id', 'release_id', 'variant_uid', 'arch', 'service', 'repo_family', 'content_format', 'content_category', 'name', 'shadow', 'product_id') def validate(self, attrs): try: variant_arch = attrs.get('variant_arch', {}) release_id = variant_arch.get('variant', {}).get('release', {}).get('release_id', '') variant_uid = variant_arch.get('variant', {}).get('variant_uid', '') arch = variant_arch.get('arch', {}).get('name', '') if self.instance and self.partial: variantarch = self.instance.variant_arch release_id = release_id or variantarch.variant.release.release_id variant_uid = variant_uid or variantarch.variant.variant_uid arch = arch or variantarch.arch.name attrs['variant_arch'] = release_models.VariantArch.objects.get( variant__release__release_id=release_id, variant__variant_uid=variant_uid, arch__name=arch ) except ObjectDoesNotExist: raise serializers.ValidationError( 'No VariantArch for release_id=%s, variant_uid=%s, arch=%s' % (release_id, variant_uid, arch) ) if not self.instance: # Validate repo name. instance = models.Repo(**attrs) instance.clean() # Validate repo is unique. try: models.Repo.objects.get(**attrs) raise serializers.ValidationError( 'Repo with this Variant arch, Service, Repo family, Content format, ' 'Content category, Name and Shadow already exists.' ) except models.Repo.DoesNotExist: pass return super(RepoSerializer, self).validate(attrs)
class ComposeSerializer(StrictSerializerMixin, serializers.ModelSerializer): compose_type = serializers.CharField() release = serializers.CharField() sigkeys = serializers.SerializerMethodField() rpm_mapping_template = serializers.SerializerMethodField() acceptance_testing = ChoiceSlugField( slug_field='name', queryset=ComposeAcceptanceTestingState.objects.all()) linked_releases = LinkedReleasesField(slug_field='release_id', many=True, queryset=Release.objects.all()) rtt_tested_architectures = serializers.SerializerMethodField() class Meta: model = Compose fields = ( 'compose_id', 'compose_date', 'compose_type', 'compose_respin', 'release', 'compose_label', 'deleted', 'rpm_mapping_template', 'sigkeys', 'acceptance_testing', 'linked_releases', 'rtt_tested_architectures', ) def get_rpm_mapping_template(self, obj): """url""" return urldecode( reverse('composerpmmapping-detail', args=[obj.compose_id, '{{package}}'], request=self.context['request'])) def get_rtt_tested_architectures(self, obj): """{"variant": {"arch": "testing status"}}""" return obj.get_arch_testing_status() def get_sigkeys(self, obj): """["string"]""" compose_id_to_key_id_cache = self.context.get( "compose_id_to_key_id_cache") if compose_id_to_key_id_cache: result = sorted(list(compose_id_to_key_id_cache.get(obj.id, []))) if obj.exist_unsigned: result.append(None) return result return obj.sigkeys def validate(self, attrs): release = attrs.get('release') or getattr( getattr(self, 'object', None), 'release') if release and release in attrs.get('linked_releases', []): raise serializers.ValidationError( 'Can not link to the release for which the compose was built.') return attrs
class BaseProductSerializer(StrictSerializerMixin, serializers.ModelSerializer): base_product_id = serializers.CharField(read_only=True) release_type = ChoiceSlugField(slug_field='short', queryset=ReleaseType.objects.all()) class Meta: model = BaseProduct fields = ('base_product_id', 'short', 'version', 'name', 'release_type')
class ReleaseScheduleSerializer(StrictSerializerMixin, serializers.HyperlinkedModelSerializer): """ ReleaseSchedule Serializer """ release = serializers.SlugRelatedField(slug_field='release_id', read_only=False, queryset=Release.objects.all()) sla = ChoiceSlugField(slug_field='name', queryset=SLA.objects.all()) date = serializers.DateField() active = serializers.BooleanField(read_only=True) release_url = serializers.HyperlinkedRelatedField( source="release", read_only=True, view_name='release-detail', lookup_field='release_id', ) sla_url = serializers.HyperlinkedRelatedField( source="sla", read_only=True, view_name='sla-detail', ) class Meta: model = ReleaseSchedule fields = ('id', 'release', 'sla', 'date', 'active', 'release_url', 'sla_url')
class ReleaseVariantSerializer(StrictSerializerMixin, serializers.ModelSerializer): type = ChoiceSlugField(source='variant_type', slug_field='name', queryset=VariantType.objects.all()) release = serializers.SlugRelatedField(slug_field='release_id', queryset=Release.objects.all()) id = serializers.CharField(source='variant_id') uid = serializers.CharField(source='variant_uid') name = serializers.CharField(source='variant_name') arches = VariantArchNestedSerializer(source='variantarch_set', many=True) key_combination_error = 'add_arches/remove_arches can not be combined with arches.' extra_fields = ['add_arches', 'remove_arches'] class Meta: model = Variant fields = ('release', 'id', 'uid', 'name', 'type', 'arches') def to_internal_value(self, data): # Save value of attributes not directly corresponding to serializer # fields. We can't rely on data dict to be mutable, so the values can # not be removed from it. self.add_arches = data.get('add_arches', None) self.remove_arches = data.get('remove_arches', None) return super(ReleaseVariantSerializer, self).to_internal_value(data) def update(self, instance, validated_data): arches = validated_data.pop('variantarch_set', []) instance = super(ReleaseVariantSerializer, self).update(instance, validated_data) if arches: if self.add_arches or self.remove_arches: raise FieldError(self.key_combination_error) # If arches were completely specified, try first to remove unwanted # arches, then create new ones. requested = dict([(x.arch.name, x) for x in arches]) for variant in instance.variantarch_set.all(): if variant.arch.name in requested: del requested[variant.arch.name] else: variant.delete() for arch in requested.values(): arch.variant = instance arch.save() # These loops can only do something on partial update: when doing PUT, # "arches" is required and if any of the other arch modifications were # specified, an exception would be raised above. for arch_name in self.add_arches or []: arch = common_models.Arch.objects.get(name=arch_name) vararch = VariantArch(arch=arch, variant=instance) vararch.save() for arch_name in self.remove_arches or []: instance.variantarch_set.filter(arch__name=arch_name).delete() return instance
class BuildImageRTTTestsSerializer(StrictSerializerMixin, serializers.ModelSerializer): format = serializers.CharField(source='image_format.name', read_only=True) test_result = ChoiceSlugField(slug_field='name', queryset=ComposeAcceptanceTestingState.objects.all()) build_nvr = serializers.CharField(source='image_id', read_only=True) class Meta: model = models.BuildImage fields = ('id', 'build_nvr', 'format', 'test_result')
class PushTargetSerializer(StrictSerializerMixin, serializers.ModelSerializer): name = serializers.CharField() description = serializers.CharField(allow_blank=True) host = serializers.CharField(allow_blank=True, required=False) service = ChoiceSlugField(slug_field='name', queryset=models.Service.objects.all()) class Meta: model = models.PushTarget fields = ('id', 'name', 'description', 'service', 'host')
class SLAToComponentBranchSerializerForComponentBranch( serializers.ModelSerializer): """ A serializer for the SLAToComponentBranch model to be used in the ComponentBranch serializer """ sla = ChoiceSlugField(slug_field='name', read_only=True) eol = serializers.DateField(read_only=True) class Meta: model = SLAToComponentBranch fields = ('id', 'sla', 'eol')
class ReleaseComponentSerializer(DynamicFieldsSerializerMixin, StrictSerializerMixin, serializers.HyperlinkedModelSerializer): """ ReleaseComponent Serializer """ release = ReleaseField(read_only=False) global_component = serializers.SlugRelatedField(slug_field='name', read_only=False, queryset=GlobalComponent.objects.all()) dist_git_branch = serializers.CharField(source='inherited_dist_git_branch', required=False) dist_git_web_url = serializers.URLField(required=False, max_length=200, read_only=True) bugzilla_component = TreeForeignKeyField(read_only=False, required=False, allow_null=True) brew_package = serializers.CharField(required=False) active = serializers.BooleanField(required=False, default=True) type = ChoiceSlugField(slug_field='name', queryset=ReleaseComponentType.objects.all(), required=False) def update(self, instance, validated_data): signals.releasecomponent_serializer_extract_data.send(sender=self, validated_data=validated_data) instance = super(ReleaseComponentSerializer, self).update(instance, validated_data) signals.releasecomponent_serializer_post_update.send(sender=self, release_component=instance) if hasattr(instance, 'pk'): # reload to make sure changes in mapping are reflected instance = ReleaseComponent.objects.get(pk=instance.pk) # from view's doc, for ReleaseComponent, # PUT and PATCH update works the same as each other except `name` is required when PUT update, # so there will be not setattr here. return instance def create(self, validated_data): signals.releasecomponent_serializer_extract_data.send(sender=self, validated_data=validated_data) instance = super(ReleaseComponentSerializer, self).create(validated_data) signals.releasecomponent_serializer_post_create.send(sender=self, release_component=instance) return instance def to_internal_value(self, data): # Raise error explictly when release are given. if self.instance: allowed_keys = self.get_allowed_keys() - set(['release']) extra_fields = set(data.keys()) - allowed_keys self.maybe_raise_error(extra_fields) data['release'] = self.instance.release data['global_component'] = data.get('global_component', self.instance.global_component) return super(ReleaseComponentSerializer, self).to_internal_value(data) def validate_release(self, value): if not value.is_active(): raise serializers.ValidationError('Can not create a release component with an inactive release.') return value class Meta: model = ReleaseComponent fields = ('id', 'release', 'bugzilla_component', 'brew_package', 'global_component', 'name', 'dist_git_branch', 'dist_git_web_url', 'active', 'type')
class ComposeTreeRTTTestSerializer(StrictSerializerMixin, DynamicFieldsSerializerMixin, serializers.ModelSerializer): compose = serializers.CharField(source='variant.compose.compose_id', read_only=True) variant = ComposeTreeVariantField(read_only=True) arch = serializers.CharField(source='arch.name', read_only=True) test_result = ChoiceSlugField(source='rtt_testing_status', slug_field='name', queryset=ComposeAcceptanceTestingState.objects.all()) class Meta: model = VariantArch fields = ('compose', 'variant', 'arch', 'test_result')
class MultiDestinationSerializer(StrictSerializerMixin, serializers.ModelSerializer): global_component = ChoiceSlugField(slug_field='name', queryset=GlobalComponent.objects.all()) origin_repo = RepoField() destination_repo = RepoField() subscribers = ChoiceSlugField(slug_field='username', many=True, queryset=Person.objects.filter(active=True), default=[]) active = serializers.BooleanField(default=True) class Meta: model = models.MultiDestination fields = ( 'id', 'global_component', 'origin_repo', 'destination_repo', 'subscribers', 'active', )
class ComposeImageRTTTestSerializer(StrictSerializerMixin, DynamicFieldsSerializerMixin, serializers.ModelSerializer): compose = serializers.CharField(source='variant_arch.variant.compose.compose_id', read_only=True) variant = serializers.CharField(source='variant_arch.variant', read_only=True) arch = serializers.CharField(source='variant_arch.arch', read_only=True) file_name = serializers.CharField(source='image.file_name', read_only=True) test_result = ChoiceSlugField(source='rtt_test_result', slug_field='name', queryset=ComposeAcceptanceTestingState.objects.all()) class Meta: model = ComposeImage fields = ('compose', 'variant', 'arch', 'file_name', 'test_result')
class ReleaseComponentRelationshipSerializer(StrictSerializerMixin, serializers.ModelSerializer): type = ChoiceSlugField( queryset=ReleaseComponentRelationshipType.objects.all(), slug_field='name', required=True, source='relation_type') from_component = ReleaseComponentField( required=True, queryset=ReleaseComponent.objects.all()) to_component = ReleaseComponentField( required=True, queryset=ReleaseComponent.objects.all()) class Meta: model = ReleaseComponentRelationship fields = ('id', 'type', 'from_component', 'to_component')
class ComponentBranchSerializer(StrictSerializerMixin, DynamicFieldsSerializerMixin, serializers.ModelSerializer): """ A serializer for the ComponentBranch model """ name = BranchNameField() global_component = serializers.SlugRelatedField( slug_field='name', queryset=GlobalComponent.objects.all()) type = ChoiceSlugField(slug_field='name', queryset=ReleaseComponentType.objects.all()) critical_path = serializers.BooleanField(default=False) slas = SLAToComponentBranchSerializerForComponentBranch(many=True, read_only=True) active = serializers.SerializerMethodField('is_active') def is_active(self, branch): """ Calls the is_branch_active function to determine if the branch is still active :param branch: a ComponentBranch object :return: a boolean """ return is_branch_active(branch) class Meta: model = ComponentBranch fields = ('id', 'global_component', 'name', 'slas', 'type', 'active', 'critical_path') def update(self, instance, validated_data): """ Override the update function to not allow a user to modify the branch name """ if 'name' in validated_data and instance.name != validated_data['name']: raise serializers.ValidationError({ 'name': ['You may not modify the branch\'s name due to policy'] }) return super(ComponentBranchSerializer, self).update(instance, validated_data)
class ComponentBranchSerializerWithoutSLA(serializers.Serializer): """ A serializer for the ComponentBranch model to be used in the SLAToComponentBranch serializer """ id = serializers.IntegerField(read_only=True) name = BranchNameField() global_component = serializers.SlugRelatedField( slug_field='name', queryset=GlobalComponent.objects.all()) type = ChoiceSlugField(slug_field='name', queryset=ReleaseComponentType.objects.all()) critical_path = serializers.BooleanField(required=False) active = serializers.SerializerMethodField('is_active') def is_active(self, branch): """ Calls the is_branch_active function to determine if the branch is still active :param branch: a ComponentBranch object :return: a boolean """ return is_branch_active(branch)
class ReleaseSerializer(StrictSerializerMixin, serializers.ModelSerializer): release_type = ChoiceSlugField(slug_field='short', queryset=ReleaseType.objects.all()) release_id = serializers.CharField(read_only=True) compose_set = serializers.SerializerMethodField() base_product = serializers.SlugRelatedField(slug_field='base_product_id', queryset=BaseProduct.objects.all(), required=False, default=None, allow_null=True) product_version = serializers.SlugRelatedField(slug_field='product_version_id', queryset=ProductVersion.objects.all(), required=False, allow_null=True, default=None) active = serializers.BooleanField(default=True) integrated_with = serializers.SlugRelatedField(slug_field='release_id', queryset=Release.objects.all(), required=False, allow_null=True, default=None) sigkey = serializers.SlugRelatedField(slug_field='key_id', queryset=SigKey.objects.all(), required=False, allow_null=True, default=None) allow_buildroot_push = serializers.BooleanField(default=False) allowed_debuginfo_services = ChoiceSlugField(slug_field='name', many=True, queryset=Service.objects.all(), required=False, default=[]) allowed_push_targets = serializers.SlugRelatedField( many=True, slug_field='name', queryset=PushTarget.objects.all(), default=[]) class Meta: model = Release fields = ('release_id', 'short', 'version', 'name', 'base_product', 'active', 'product_version', 'release_type', 'compose_set', 'integrated_with', 'sigkey', 'allow_buildroot_push', 'allowed_debuginfo_services', 'allowed_push_targets') def get_compose_set(self, obj): """[Compose.compose_id]""" return [compose.compose_id for compose in sorted(obj.get_all_composes())] def create(self, validated_data): signals.release_serializer_extract_data.send(sender=self, validated_data=validated_data) obj = super(ReleaseSerializer, self).create(validated_data) signals.release_serializer_post_create.send(sender=self, release=obj) return obj def update(self, instance, validated_data): signals.release_serializer_extract_data.send(sender=self, validated_data=validated_data) obj = super(ReleaseSerializer, self).update(instance, validated_data) signals.release_serializer_post_update.send(sender=self, release=obj) if hasattr(instance, 'pk'): # reload to make sure changes in mapping are reflected obj = Release.objects.get(pk=obj.pk) # By default, PUT does not erase optional field if not specified. This # loops over all optional fields and resets them manually. if not self.partial: for field_name, field in self.fields.iteritems(): if not field.read_only and field_name not in validated_data: attr = field.source or field_name try: if hasattr(obj, attr): setattr(obj, attr, None) except ValueError: pass obj.save() return obj def validate(self, data): convert_push_targets_to_mask(data, self.instance, 'product_version') return data
class ReleaseSerializer(StrictSerializerMixin, serializers.ModelSerializer): release_type = ChoiceSlugField(slug_field='short', queryset=ReleaseType.objects.all()) release_id = serializers.CharField(read_only=True) compose_set = serializers.SerializerMethodField() base_product = serializers.SlugRelatedField( slug_field='base_product_id', queryset=BaseProduct.objects.all(), required=False, default=None, allow_null=True) product_version = serializers.SlugRelatedField( slug_field='product_version_id', queryset=ProductVersion.objects.all(), required=False, allow_null=True, default=None) active = serializers.BooleanField(default=True) integrated_with = serializers.SlugRelatedField( slug_field='release_id', queryset=Release.objects.all(), required=False, allow_null=True, default=None) sigkey = serializers.SlugRelatedField(slug_field='key_id', queryset=SigKey.objects.all(), required=False, allow_null=True, default=None) allow_buildroot_push = serializers.BooleanField(default=False) allowed_debuginfo_services = ChoiceSlugField( slug_field='name', many=True, queryset=Service.objects.all(), required=False, default=[]) allowed_push_targets = allowed_push_targets_field( parent_key='product version') class Meta: model = Release fields = ('release_id', 'short', 'version', 'name', 'base_product', 'active', 'product_version', 'release_type', 'compose_set', 'integrated_with', 'sigkey', 'allow_buildroot_push', 'allowed_debuginfo_services', 'allowed_push_targets') def set_default_sigkey(self, validated_data): if hasattr(settings, 'RELEASE_DEFAULT_SIGKEY'): if "sigkey" in validated_data and not validated_data["sigkey"]: validated_data["sigkey"], _ = SigKey.objects.get_or_create( key_id=settings.RELEASE_DEFAULT_SIGKEY) return validated_data def get_compose_set(self, obj): """["Compose.compose_id"]""" return [ compose.compose_id for compose in sorted(obj.get_all_composes()) ] def create(self, validated_data): signals.release_serializer_extract_data.send( sender=self, validated_data=validated_data) validated_data = self.set_default_sigkey(validated_data) obj = super(ReleaseSerializer, self).create(validated_data) signals.release_serializer_post_create.send(sender=self, release=obj) return obj def update(self, instance, validated_data): signals.release_serializer_extract_data.send( sender=self, validated_data=validated_data) validated_data = self.set_default_sigkey(validated_data) obj = super(ReleaseSerializer, self).update(instance, validated_data) signals.release_serializer_post_update.send(sender=self, release=obj) if hasattr(instance, 'pk'): # reload to make sure changes in mapping are reflected obj = Release.objects.get(pk=obj.pk) obj.save() return obj def validate(self, data): convert_push_targets_to_mask(data, self.instance, 'product_version') return data
class SLAToComponentBranchSerializer(StrictSerializerMixin, DynamicFieldsSerializerMixin, serializers.Serializer): """ A serializer for the SLAToComponentBranch model that allows branch creation """ id = serializers.IntegerField(read_only=True) sla = ChoiceSlugField(slug_field='name', queryset=SLA.objects.all()) branch = ComponentBranchSerializerWithoutSLA() eol = serializers.DateField() def create(self, validated_data): """ Creates the SLAToComponentBranch entry based on the serialized data """ branch_component_type_name = validated_data['branch']['type'] component_type = ReleaseComponentType.objects.filter( name=branch_component_type_name).first() if not component_type: error_msg = ( 'The specified ReleaseComponentType "{0}" does not exist'. format(branch_component_type_name)) raise serializers.ValidationError({'branch.type': [error_msg]}) branch_global_component_name = \ validated_data['branch']['global_component'] branch_global_component = GlobalComponent.objects.filter( name=branch_global_component_name).first() if not branch_global_component: error_msg = ('The specified GlobalComponent "{0}" does not exist'. format(branch_global_component_name)) raise serializers.ValidationError( {'branch.global_component': [error_msg]}) branch_name = validated_data['branch']['name'] branch_critical_path = validated_data['branch'].get('critical_path') branch = ComponentBranch.objects.filter( name=branch_name, type=component_type.id, global_component=branch_global_component.id).first() if branch: # The critical_path field is optional, but if it was supplied and it # doesn't match the found branch's critical_path field, raise an # error if branch_critical_path is not None and \ branch.critical_path != branch_critical_path: error_msg = ('The found branch\'s critical_path field did not ' 'match the supplied value') raise serializers.ValidationError( {'branch.critical_path': [error_msg]}) else: # Set the default for this optional value when creating if branch_critical_path is None: branch_critical_path = False branch = ComponentBranch( name=branch_name, type=component_type, global_component=branch_global_component, critical_path=branch_critical_path, ) sla_name = validated_data['sla'] sla = SLA.objects.filter(name=sla_name).first() if not sla: error_msg = 'The specified SLA "{0}" does not exist'.format( sla_name) raise serializers.ValidationError({'sla': [error_msg]}) if SLAToComponentBranch.objects.filter(sla=sla.id, branch=branch.id).exists(): error_msg = ( 'The SLA "{0}" tied to the component "{1}" and branch "{2}" ' 'already exists').format(sla.name, branch.global_component.name, branch.name) raise serializers.ValidationError({'branch': [error_msg]}) # This tells us if the branch object was created or not if branch._state.adding: branch.save() eol = validated_data['eol'] return SLAToComponentBranch.objects.create(sla=sla, branch=branch, eol=eol) def update(self, instance, validated_data): """ Updates the SLAToComponentBranch entry based on the serialized data """ branch = validated_data.get('branch', {}) branch_name = branch.get('name') component_type = branch.get('type') global_component = branch.get('global_component') critical_path = branch.get('critical_path', None) if branch: if instance.branch.name != branch_name \ or instance.branch.type != component_type \ or instance.branch.global_component != global_component \ or (critical_path is not None and instance.branch.critical_path is not critical_path): raise serializers.ValidationError({ 'branch': ['The branch cannot be modified using this API'] }) # TODO: Should we not allow this value to change? instance.sla = validated_data.get('sla', instance.sla) instance.eol = validated_data.get('eol', instance.eol) instance.save() return instance